feat(updater): add specific messages for Flatpak/pacman installs
- commit
- 57043dbba343504a766b797bfb735eb22c6d1fc1
- author
- bnema · · 2025-12-30 21:30 UTC
- parent
- 283a119
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +54 -1
M internal/application/port/updater.go +17 -0
| @@ -25,12 +25,29 @@ type UpdateDownloader interface { | |||
| 25 | 25 | Extract(ctx context.Context, archivePath string, destDir string) (binaryPath string, err error) | |
| 26 | 26 | } | |
| 27 | 27 | ||
| 28 | + | // SelfUpdateBlockedReason indicates why self-update is not available. | |
| 29 | + | type SelfUpdateBlockedReason string | |
| 30 | + | ||
| 31 | + | const ( | |
| 32 | + | // SelfUpdateAllowed means self-update is possible. | |
| 33 | + | SelfUpdateAllowed SelfUpdateBlockedReason = "" | |
| 34 | + | // SelfUpdateBlockedFlatpak means updates are managed by Flatpak. | |
| 35 | + | SelfUpdateBlockedFlatpak SelfUpdateBlockedReason = "flatpak" | |
| 36 | + | // SelfUpdateBlockedPacman means updates are managed by pacman/AUR. | |
| 37 | + | SelfUpdateBlockedPacman SelfUpdateBlockedReason = "pacman" | |
| 38 | + | // SelfUpdateBlockedNotWritable means the binary is not writable. | |
| 39 | + | SelfUpdateBlockedNotWritable SelfUpdateBlockedReason = "not_writable" | |
| 40 | + | ) | |
| 41 | + | ||
| 28 | 42 | // UpdateApplier stages and applies updates. | |
| 29 | 43 | type UpdateApplier interface { | |
| 30 | 44 | // CanSelfUpdate checks if the current binary is writable by the current user. | |
| 31 | 45 | // Returns true if auto-update is possible, false if user lacks write permission. | |
| 32 | 46 | CanSelfUpdate(ctx context.Context) bool | |
| 33 | 47 | ||
| 48 | + | // SelfUpdateBlockedReason returns why self-update is blocked, or empty if allowed. | |
| 49 | + | SelfUpdateBlockedReason(ctx context.Context) SelfUpdateBlockedReason | |
| 50 | + | ||
| 34 | 51 | // GetBinaryPath returns the path to the currently running binary. | |
| 35 | 52 | GetBinaryPath() (string, error) | |
| 36 | 53 | ||
| @@ -25,12 +25,29 @@ type UpdateDownloader interface { | |||||
| 25 | Extract(ctx context.Context, archivePath string, destDir string) (binaryPath string, err error) | 25 | Extract(ctx context.Context, archivePath string, destDir string) (binaryPath string, err error) | ||
| 26 | } | 26 | } | ||
| 27 | 27 | ||||
| 28 | + | // SelfUpdateBlockedReason indicates why self-update is not available. | |||
| 29 | + | type SelfUpdateBlockedReason string | |||
| 30 | + | ||||
| 31 | + | const ( | |||
| 32 | + | // SelfUpdateAllowed means self-update is possible. | |||
| 33 | + | SelfUpdateAllowed SelfUpdateBlockedReason = "" | |||
| 34 | + | // SelfUpdateBlockedFlatpak means updates are managed by Flatpak. | |||
| 35 | + | SelfUpdateBlockedFlatpak SelfUpdateBlockedReason = "flatpak" | |||
| 36 | + | // SelfUpdateBlockedPacman means updates are managed by pacman/AUR. | |||
| 37 | + | SelfUpdateBlockedPacman SelfUpdateBlockedReason = "pacman" | |||
| 38 | + | // SelfUpdateBlockedNotWritable means the binary is not writable. | |||
| 39 | + | SelfUpdateBlockedNotWritable SelfUpdateBlockedReason = "not_writable" | |||
| 40 | + | ) | |||
| 41 | + | ||||
| 28 | // UpdateApplier stages and applies updates. | 42 | // UpdateApplier stages and applies updates. | ||
| 29 | type UpdateApplier interface { | 43 | type UpdateApplier interface { | ||
| 30 | // CanSelfUpdate checks if the current binary is writable by the current user. | 44 | // CanSelfUpdate checks if the current binary is writable by the current user. | ||
| 31 | // Returns true if auto-update is possible, false if user lacks write permission. | 45 | // Returns true if auto-update is possible, false if user lacks write permission. | ||
| 32 | CanSelfUpdate(ctx context.Context) bool | 46 | CanSelfUpdate(ctx context.Context) bool | ||
| 33 | 47 | ||||
| 48 | + | // SelfUpdateBlockedReason returns why self-update is blocked, or empty if allowed. | |||
| 49 | + | SelfUpdateBlockedReason(ctx context.Context) SelfUpdateBlockedReason | |||
| 50 | + | ||||
| 34 | // GetBinaryPath returns the path to the currently running binary. | 51 | // GetBinaryPath returns the path to the currently running binary. | ||
| 35 | GetBinaryPath() (string, error) | 52 | GetBinaryPath() (string, error) | ||
| 36 | 53 | ||||
M internal/application/usecase/apply_update.go +11 -1
| @@ -58,9 +58,19 @@ func (uc *ApplyUpdateUseCase) Execute(ctx context.Context, input ApplyUpdateInpu | |||
| 58 | 58 | ||
| 59 | 59 | // Check if we can self-update. | |
| 60 | 60 | if !uc.applier.CanSelfUpdate(ctx) { | |
| 61 | + | reason := uc.applier.SelfUpdateBlockedReason(ctx) | |
| 62 | + | var message string | |
| 63 | + | switch reason { | |
| 64 | + | case port.SelfUpdateBlockedFlatpak: | |
| 65 | + | message = "Cannot auto-update: installed via Flatpak (use 'flatpak update' instead)" | |
| 66 | + | case port.SelfUpdateBlockedPacman: | |
| 67 | + | message = "Cannot auto-update: installed via pacman/AUR (use your package manager instead)" | |
| 68 | + | default: | |
| 69 | + | message = "Cannot auto-update: binary is not writable" | |
| 70 | + | } | |
| 61 | 71 | return &ApplyUpdateOutput{ | |
| 62 | 72 | Status: entity.UpdateStatusFailed, | |
| 63 | - | Message: "Cannot auto-update: binary is not writable", | |
| 73 | + | Message: message, | |
| 64 | 74 | }, nil | |
| 65 | 75 | } | |
| 66 | 76 | ||
| @@ -58,9 +58,19 @@ func (uc *ApplyUpdateUseCase) Execute(ctx context.Context, input ApplyUpdateInpu | |||||
| 58 | 58 | ||||
| 59 | // Check if we can self-update. | 59 | // Check if we can self-update. | ||
| 60 | if !uc.applier.CanSelfUpdate(ctx) { | 60 | if !uc.applier.CanSelfUpdate(ctx) { | ||
| 61 | + | reason := uc.applier.SelfUpdateBlockedReason(ctx) | |||
| 62 | + | var message string | |||
| 63 | + | switch reason { | |||
| 64 | + | case port.SelfUpdateBlockedFlatpak: | |||
| 65 | + | message = "Cannot auto-update: installed via Flatpak (use 'flatpak update' instead)" | |||
| 66 | + | case port.SelfUpdateBlockedPacman: | |||
| 67 | + | message = "Cannot auto-update: installed via pacman/AUR (use your package manager instead)" | |||
| 68 | + | default: | |||
| 69 | + | message = "Cannot auto-update: binary is not writable" | |||
| 70 | + | } | |||
| 61 | return &ApplyUpdateOutput{ | 71 | return &ApplyUpdateOutput{ | ||
| 62 | Status: entity.UpdateStatusFailed, | 72 | Status: entity.UpdateStatusFailed, | ||
| 63 | - | Message: "Cannot auto-update: binary is not writable", | 73 | + | Message: message, |
| 64 | }, nil | 74 | }, nil | ||
| 65 | } | 75 | } | ||
| 66 | 76 | ||||
M internal/infrastructure/updater/applier.go +26 -0
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | ||
| 9 | 9 | "golang.org/x/sys/unix" | |
| 10 | 10 | ||
| 11 | + | "github.com/bnema/dumber/internal/application/port" | |
| 11 | 12 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 12 | 13 | "github.com/bnema/dumber/internal/infrastructure/env" | |
| 13 | 14 | "github.com/bnema/dumber/internal/logging" | |
| @@ -96,6 +97,31 @@ func (a *Applier) CanSelfUpdate(ctx context.Context) bool { | |||
| 96 | 97 | return true | |
| 97 | 98 | } | |
| 98 | 99 | ||
| 100 | + | // SelfUpdateBlockedReason returns why self-update is blocked, or empty if allowed. | |
| 101 | + | func (a *Applier) SelfUpdateBlockedReason(ctx context.Context) port.SelfUpdateBlockedReason { | |
| 102 | + | // Flatpak sandboxed apps should not self-update; Flatpak handles updates. | |
| 103 | + | if env.IsFlatpak() { | |
| 104 | + | return port.SelfUpdateBlockedFlatpak | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | // Pacman/AUR packages should not self-update; use pacman or AUR helper. | |
| 108 | + | if env.IsPacman() { | |
| 109 | + | return port.SelfUpdateBlockedPacman | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | binaryPath, err := a.GetBinaryPath() | |
| 113 | + | if err != nil { | |
| 114 | + | return port.SelfUpdateBlockedNotWritable | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // Check if we have write permission on the binary file. | |
| 118 | + | if err := unix.Access(binaryPath, unix.W_OK); err != nil { | |
| 119 | + | return port.SelfUpdateBlockedNotWritable | |
| 120 | + | } | |
| 121 | + | ||
| 122 | + | return port.SelfUpdateAllowed | |
| 123 | + | } | |
| 124 | + | ||
| 99 | 125 | // GetBinaryPath returns the path to the currently running binary. | |
| 100 | 126 | func (*Applier) GetBinaryPath() (string, error) { | |
| 101 | 127 | path, err := os.Executable() | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | 8 | ||||
| 9 | "golang.org/x/sys/unix" | 9 | "golang.org/x/sys/unix" | ||
| 10 | 10 | ||||
| 11 | + | "github.com/bnema/dumber/internal/application/port" | |||
| 11 | "github.com/bnema/dumber/internal/infrastructure/config" | 12 | "github.com/bnema/dumber/internal/infrastructure/config" | ||
| 12 | "github.com/bnema/dumber/internal/infrastructure/env" | 13 | "github.com/bnema/dumber/internal/infrastructure/env" | ||
| 13 | "github.com/bnema/dumber/internal/logging" | 14 | "github.com/bnema/dumber/internal/logging" | ||
| @@ -96,6 +97,31 @@ func (a *Applier) CanSelfUpdate(ctx context.Context) bool { | |||||
| 96 | return true | 97 | return true | ||
| 97 | } | 98 | } | ||
| 98 | 99 | ||||
| 100 | + | // SelfUpdateBlockedReason returns why self-update is blocked, or empty if allowed. | |||
| 101 | + | func (a *Applier) SelfUpdateBlockedReason(ctx context.Context) port.SelfUpdateBlockedReason { | |||
| 102 | + | // Flatpak sandboxed apps should not self-update; Flatpak handles updates. | |||
| 103 | + | if env.IsFlatpak() { | |||
| 104 | + | return port.SelfUpdateBlockedFlatpak | |||
| 105 | + | } | |||
| 106 | + | ||||
| 107 | + | // Pacman/AUR packages should not self-update; use pacman or AUR helper. | |||
| 108 | + | if env.IsPacman() { | |||
| 109 | + | return port.SelfUpdateBlockedPacman | |||
| 110 | + | } | |||
| 111 | + | ||||
| 112 | + | binaryPath, err := a.GetBinaryPath() | |||
| 113 | + | if err != nil { | |||
| 114 | + | return port.SelfUpdateBlockedNotWritable | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | // Check if we have write permission on the binary file. | |||
| 118 | + | if err := unix.Access(binaryPath, unix.W_OK); err != nil { | |||
| 119 | + | return port.SelfUpdateBlockedNotWritable | |||
| 120 | + | } | |||
| 121 | + | ||||
| 122 | + | return port.SelfUpdateAllowed | |||
| 123 | + | } | |||
| 124 | + | ||||
| 99 | // GetBinaryPath returns the path to the currently running binary. | 125 | // GetBinaryPath returns the path to the currently running binary. | ||
| 100 | func (*Applier) GetBinaryPath() (string, error) { | 126 | func (*Applier) GetBinaryPath() (string, error) { | ||
| 101 | path, err := os.Executable() | 127 | path, err := os.Executable() | ||