diff --git a/bin/dbbackup_linux_amd64 b/bin/dbbackup_linux_amd64 index d99138a..0252d33 100755 Binary files a/bin/dbbackup_linux_amd64 and b/bin/dbbackup_linux_amd64 differ diff --git a/cmd/install.go b/cmd/install.go index 58b5839..eaf2caf 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "os/exec" "os/signal" "strings" "syscall" @@ -198,13 +199,14 @@ func runInstallStatus(ctx context.Context) error { // Check for exporter if _, err := os.Stat("/etc/systemd/system/dbbackup-exporter.service"); err == nil { - exporterStatus, err := inst.Status(ctx, "exporter") fmt.Println() fmt.Println("🔹 Metrics Exporter:") - if err == nil && exporterStatus != nil { - fmt.Printf(" Service: %s\n", formatStatus(true, exporterStatus.Active)) + // Check if exporter is active using systemctl + cmd := exec.CommandContext(ctx, "systemctl", "is-active", "dbbackup-exporter") + if err := cmd.Run(); err == nil { + fmt.Printf(" Service: ✅ active\n") } else { - fmt.Printf(" Service: installed (status unknown)\n") + fmt.Printf(" Service: ⚪ inactive\n") } } diff --git a/internal/installer/installer.go b/internal/installer/installer.go index f76580e..e05b95c 100644 --- a/internal/installer/installer.go +++ b/internal/installer/installer.go @@ -4,6 +4,7 @@ package installer import ( "context" "fmt" + "io" "os" "os/exec" "os/user" @@ -105,6 +106,11 @@ func (i *Installer) Install(ctx context.Context, opts InstallOptions) error { return err } + // Copy binary to /usr/local/bin (required for ProtectHome=yes) + if err := i.copyBinary(&opts); err != nil { + return err + } + // Write service and timer files if err := i.writeUnitFiles(opts); err != nil { return err @@ -428,6 +434,46 @@ func (i *Installer) createDirectories(opts InstallOptions) error { return nil } +// copyBinary copies the binary to /usr/local/bin for systemd access +// This is required because ProtectHome=yes blocks access to home directories +func (i *Installer) copyBinary(opts *InstallOptions) error { + const installPath = "/usr/local/bin/dbbackup" + + // Check if binary is already in a system path + if opts.BinaryPath == installPath { + return nil + } + + if i.dryRun { + i.log.Info("Would copy binary", "from", opts.BinaryPath, "to", installPath) + opts.BinaryPath = installPath + return nil + } + + // Read source binary + src, err := os.Open(opts.BinaryPath) + if err != nil { + return fmt.Errorf("failed to open source binary: %w", err) + } + defer src.Close() + + // Create destination + dst, err := os.OpenFile(installPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) + if err != nil { + return fmt.Errorf("failed to create %s: %w", installPath, err) + } + defer dst.Close() + + // Copy + if _, err := io.Copy(dst, src); err != nil { + return fmt.Errorf("failed to copy binary: %w", err) + } + + i.log.Info("Copied binary", "from", opts.BinaryPath, "to", installPath) + opts.BinaryPath = installPath + return nil +} + // writeUnitFiles renders and writes the systemd unit files func (i *Installer) writeUnitFiles(opts InstallOptions) error { // Prepare template data diff --git a/internal/installer/templates/dbbackup-exporter.service b/internal/installer/templates/dbbackup-exporter.service index e4b2a1f..403f46c 100644 --- a/internal/installer/templates/dbbackup-exporter.service +++ b/internal/installer/templates/dbbackup-exporter.service @@ -21,14 +21,14 @@ RestrictRealtime=yes LockPersonality=yes RemoveIPC=yes -# Read-only access to catalog and backups -ReadOnlyPaths=/var/lib/dbbackup +# Read-write access to catalog for metrics collection +ReadWritePaths=/var/lib/dbbackup # Network for HTTP server RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 # Execution -ExecStart={{.BinaryPath}} metrics serve --port {{.MetricsPort}} --config {{.ConfigPath}} +ExecStart={{.BinaryPath}} metrics serve --port {{.MetricsPort}} ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure RestartSec=5