fix(bootstrap): clone repo with explicit branch to prevent asset drift
- commit
- ef2845791726bb10bbfe11f3ed9836067050f4b2
- author
- brice · · 2026-02-27 05:25 UTC
- parent
- e868eab
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +18 -5
M cmd/install.go +1 -1
| @@ -68,7 +68,7 @@ func runInstall(dryRun bool) error { | |||
| 68 | 68 | return fmt.Errorf("create repository adapter: %w", err) | |
| 69 | 69 | } | |
| 70 | 70 | ||
| 71 | - | bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL) | |
| 71 | + | bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL, cfg.Branch()) | |
| 72 | 72 | preflightHandler := apphandlers.NewPreflightHandler(fsAdapter, shellExec, slogAdapter) | |
| 73 | 73 | partitionHandler := apphandlers.NewPartitionHandler(shellExec, slogAdapter) | |
| 74 | 74 | baseHandler := apphandlers.NewInstallBaseHandler(fsAdapter, shellExec, chrootExec, slogAdapter) | |
| @@ -68,7 +68,7 @@ func runInstall(dryRun bool) error { | |||||
| 68 | return fmt.Errorf("create repository adapter: %w", err) | 68 | return fmt.Errorf("create repository adapter: %w", err) | ||
| 69 | } | 69 | } | ||
| 70 | 70 | ||||
| 71 | - | bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL) | 71 | + | bootstrapHandler := apphandlers.NewBootstrapHandler(fsAdapter, httpClient, slogAdapter, cfg.RepoURL, cfg.RawURL, cfg.Branch()) |
| 72 | preflightHandler := apphandlers.NewPreflightHandler(fsAdapter, shellExec, slogAdapter) | 72 | preflightHandler := apphandlers.NewPreflightHandler(fsAdapter, shellExec, slogAdapter) | ||
| 73 | partitionHandler := apphandlers.NewPartitionHandler(shellExec, slogAdapter) | 73 | partitionHandler := apphandlers.NewPartitionHandler(shellExec, slogAdapter) | ||
| 74 | baseHandler := apphandlers.NewInstallBaseHandler(fsAdapter, shellExec, chrootExec, slogAdapter) | 74 | baseHandler := apphandlers.NewInstallBaseHandler(fsAdapter, shellExec, chrootExec, slogAdapter) | ||
M internal/application/handlers/bootstrap_handler.go +4 -2
| @@ -18,16 +18,18 @@ type BootstrapHandler struct { | |||
| 18 | 18 | logger ports.Logger | |
| 19 | 19 | repoURL string | |
| 20 | 20 | rawURL string | |
| 21 | + | branch string | |
| 21 | 22 | } | |
| 22 | 23 | ||
| 23 | 24 | // NewBootstrapHandler creates a new bootstrap handler | |
| 24 | - | func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL string) *BootstrapHandler { | |
| 25 | + | func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL, branch string) *BootstrapHandler { | |
| 25 | 26 | return &BootstrapHandler{ | |
| 26 | 27 | fs: fs, | |
| 27 | 28 | httpClient: httpClient, | |
| 28 | 29 | logger: logger, | |
| 29 | 30 | repoURL: repoURL, | |
| 30 | 31 | rawURL: rawURL, | |
| 32 | + | branch: branch, | |
| 31 | 33 | } | |
| 32 | 34 | } | |
| 33 | 35 | ||
| @@ -86,7 +88,7 @@ func (h *BootstrapHandler) cloneRepo(ctx context.Context) error { | |||
| 86 | 88 | ||
| 87 | 89 | h.logger.Info("Cloning repository", "url", h.repoURL) | |
| 88 | 90 | ||
| 89 | - | cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", h.repoURL, repoDir) | |
| 91 | + | cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", "--branch", h.branch, h.repoURL, repoDir) | |
| 90 | 92 | if output, err := cmd.CombinedOutput(); err != nil { | |
| 91 | 93 | return fmt.Errorf("git clone failed: %w (output: %s)", err, string(output)) | |
| 92 | 94 | } | |
| @@ -18,16 +18,18 @@ type BootstrapHandler struct { | |||||
| 18 | logger ports.Logger | 18 | logger ports.Logger | ||
| 19 | repoURL string | 19 | repoURL string | ||
| 20 | rawURL string | 20 | rawURL string | ||
| 21 | + | branch string | |||
| 21 | } | 22 | } | ||
| 22 | 23 | ||||
| 23 | // NewBootstrapHandler creates a new bootstrap handler | 24 | // NewBootstrapHandler creates a new bootstrap handler | ||
| 24 | - | func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL string) *BootstrapHandler { | 25 | + | func NewBootstrapHandler(fs ports.FileSystem, httpClient ports.HTTPClient, logger ports.Logger, repoURL, rawURL, branch string) *BootstrapHandler { |
| 25 | return &BootstrapHandler{ | 26 | return &BootstrapHandler{ | ||
| 26 | fs: fs, | 27 | fs: fs, | ||
| 27 | httpClient: httpClient, | 28 | httpClient: httpClient, | ||
| 28 | logger: logger, | 29 | logger: logger, | ||
| 29 | repoURL: repoURL, | 30 | repoURL: repoURL, | ||
| 30 | rawURL: rawURL, | 31 | rawURL: rawURL, | ||
| 32 | + | branch: branch, | |||
| 31 | } | 33 | } | ||
| 32 | } | 34 | } | ||
| 33 | 35 | ||||
| @@ -86,7 +88,7 @@ func (h *BootstrapHandler) cloneRepo(ctx context.Context) error { | |||||
| 86 | 88 | ||||
| 87 | h.logger.Info("Cloning repository", "url", h.repoURL) | 89 | h.logger.Info("Cloning repository", "url", h.repoURL) | ||
| 88 | 90 | ||||
| 89 | - | cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", h.repoURL, repoDir) | 91 | + | cmd := exec.CommandContext(ctx, "git", "clone", "--depth", "1", "--branch", h.branch, h.repoURL, repoDir) |
| 90 | if output, err := cmd.CombinedOutput(); err != nil { | 92 | if output, err := cmd.CombinedOutput(); err != nil { | ||
| 91 | return fmt.Errorf("git clone failed: %w (output: %s)", err, string(output)) | 93 | return fmt.Errorf("git clone failed: %w (output: %s)", err, string(output)) | ||
| 92 | } | 94 | } | ||
M internal/application/services/installation_service_test.go +2 -2
| @@ -56,7 +56,7 @@ func createTestService(ctrl *gomock.Controller) *InstallationService { | |||
| 56 | 56 | mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 57 | 57 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 58 | 58 | ||
| 59 | - | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev") | |
| 59 | + | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev") | |
| 60 | 60 | preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger) | |
| 61 | 61 | partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger) | |
| 62 | 62 | baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger) | |
| @@ -141,7 +141,7 @@ func TestInstallationService_Start_InvalidHostname(t *testing.T) { | |||
| 141 | 141 | mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 142 | 142 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 143 | 143 | ||
| 144 | - | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev") | |
| 144 | + | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev") | |
| 145 | 145 | preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger) | |
| 146 | 146 | partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger) | |
| 147 | 147 | baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger) | |
| @@ -56,7 +56,7 @@ func createTestService(ctrl *gomock.Controller) *InstallationService { | |||||
| 56 | mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 56 | mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 57 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 57 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 58 | 58 | ||||
| 59 | - | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev") | 59 | + | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev") |
| 60 | preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger) | 60 | preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger) | ||
| 61 | partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger) | 61 | partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger) | ||
| 62 | baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger) | 62 | baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger) | ||
| @@ -141,7 +141,7 @@ func TestInstallationService_Start_InvalidHostname(t *testing.T) { | |||||
| 141 | mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 141 | mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 142 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 142 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 143 | 143 | ||||
| 144 | - | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev") | 144 | + | bootstrapHandler := handlers.NewBootstrapHandler(mockFS, mockHTTP, mockLogger, "https://github.com/bnema/archup", "https://raw.githubusercontent.com/bnema/archup/dev", "dev") |
| 145 | preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger) | 145 | preflightHandler := handlers.NewPreflightHandler(mockFS, mockExec, mockLogger) | ||
| 146 | partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger) | 146 | partitionHandler := handlers.NewPartitionHandler(mockExec, mockLogger) | ||
| 147 | baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger) | 147 | baseHandler := handlers.NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger) | ||
M internal/config/config.go +11 -0
| @@ -290,6 +290,17 @@ func NewConfig(version string) *Config { | |||
| 290 | 290 | } | |
| 291 | 291 | } | |
| 292 | 292 | ||
| 293 | + | // Branch returns the git branch to use for asset downloads and repo cloning. | |
| 294 | + | func (c *Config) Branch() string { | |
| 295 | + | rawURL := c.RawURL | |
| 296 | + | // Extract branch from RawURL: "https://raw.githubusercontent.com/bnema/archup/<branch>" | |
| 297 | + | parts := strings.Split(rawURL, "/") | |
| 298 | + | if len(parts) > 0 { | |
| 299 | + | return parts[len(parts)-1] | |
| 300 | + | } | |
| 301 | + | return "main" | |
| 302 | + | } | |
| 303 | + | ||
| 293 | 304 | // Load reads configuration from a shell-compatible config file | |
| 294 | 305 | func Load(path string, version string) (*Config, error) { | |
| 295 | 306 | cfg := NewConfig(version) | |
| @@ -290,6 +290,17 @@ func NewConfig(version string) *Config { | |||||
| 290 | } | 290 | } | ||
| 291 | } | 291 | } | ||
| 292 | 292 | ||||
| 293 | + | // Branch returns the git branch to use for asset downloads and repo cloning. | |||
| 294 | + | func (c *Config) Branch() string { | |||
| 295 | + | rawURL := c.RawURL | |||
| 296 | + | // Extract branch from RawURL: "https://raw.githubusercontent.com/bnema/archup/<branch>" | |||
| 297 | + | parts := strings.Split(rawURL, "/") | |||
| 298 | + | if len(parts) > 0 { | |||
| 299 | + | return parts[len(parts)-1] | |||
| 300 | + | } | |||
| 301 | + | return "main" | |||
| 302 | + | } | |||
| 303 | + | ||||
| 293 | // Load reads configuration from a shell-compatible config file | 304 | // Load reads configuration from a shell-compatible config file | ||
| 294 | func Load(path string, version string) (*Config, error) { | 305 | func Load(path string, version string) (*Config, error) { | ||
| 295 | cfg := NewConfig(version) | 306 | cfg := NewConfig(version) | ||