package incus import ( "encoding/json" "fmt" "os/exec" "strings" ) type IncusDriver struct{} func NewIncusDriver() *IncusDriver { return &IncusDriver{} } func (d *IncusDriver) GetInstances(target string) ([]Instance, error) { // Command: incus list -f json var cmd *exec.Cmd if target == "" { cmd = exec.Command("incus", "list", "--format", "json") } else { cmd = exec.Command("incus", "list", target+":", "--format", "json") } output, err := cmd.Output() if err != nil { return nil, fmt.Errorf("failed to execute incus list: %w", err) } var instances []Instance err = json.Unmarshal(output, &instances) if err != nil { return nil, err } return instances, nil } func (d *IncusDriver) Sync(sourceInstance string, targetInstance string, targetHost string, targetPool string) error { // incus copy edge0 racker1:edge0-cold -s pool0 --mode push -p default -p net_edge0 --stateless --refresh // incus copy edge0 racker1:edge0-cold -s pool0 --mode push -p default -p net_edge0 --stateless instances, err := d.GetInstances(targetHost) if err != nil { return err } var cmd *exec.Cmd if len(instances) == 0 { cmd = exec.Command("incus", "copy", sourceInstance, targetHost+":"+targetInstance, "-s", targetPool, "--mode", "push", "--stateless", "-c", "user.backup=false", "-c", "user.sync=false") } else { cmd = exec.Command("incus", "copy", sourceInstance, targetHost+":"+targetInstance, "-s", targetPool, "--mode", "push", "--stateless", "-c", "user.backup=false", "-c", "user.sync=false", "--refresh") } out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to execute incus copy: %w (%s)", err, string(out)) } return nil } func (d *IncusDriver) Backup(instance string, tags []string) error { // incus export ups - -q --compression=zstd --instance-only --optimized-storage | restic backup --stdin --stdin-filename ups.btrfs.zstd --tag instance // Create the incus export command incusCmd := exec.Command("incus", "export", instance, "-", "-q", "--compression=zstd", "--instance-only", "--optimized-storage") // Create the restic backup command resticCmd := exec.Command("restic", "backup", "--host", instance, "--stdin", "--stdin-filename", fmt.Sprintf("%s.btrfs.zstd", instance), "--tag", strings.Join(tags, ",")) // Connect the output of incusCmd to the input of resticCmd pipe, err := incusCmd.StdoutPipe() if err != nil { return fmt.Errorf("failed to create pipe: %w", err) } resticCmd.Stdin = pipe // Start the incus export command if err := incusCmd.Start(); err != nil { return fmt.Errorf("failed to start incus export: %w", err) } // Start the restic backup command if err := resticCmd.Start(); err != nil { return fmt.Errorf("failed to start restic backup: %w", err) } // Wait for the incus export command to finish if err := incusCmd.Wait(); err != nil { return fmt.Errorf("incus export command failed: %w", err) } // Wait for the restic backup command to finish if err := resticCmd.Wait(); err != nil { return fmt.Errorf("restic backup command failed: %w", err) } return nil }