package daemon import ( "context" "fmt" "os" "path/filepath" "strings" "banger/internal/guest" "banger/internal/model" "banger/internal/system" ) func (d *Daemon) seedAuthorizedKeyOnExt4Image(ctx context.Context, imagePath string) (string, error) { if strings.TrimSpace(d.config.SSHKeyPath) == "" { return "", nil } fingerprint, err := guest.AuthorizedPublicKeyFingerprint(d.config.SSHKeyPath) if err != nil { return "", fmt.Errorf("derive authorized ssh key fingerprint: %w", err) } publicKey, err := guest.AuthorizedPublicKey(d.config.SSHKeyPath) if err != nil { return "", fmt.Errorf("derive authorized ssh key: %w", err) } mountDir, cleanup, err := system.MountTempDir(ctx, d.runner, imagePath, false) if err != nil { return "", err } defer cleanup() if err := d.flattenNestedWorkHome(ctx, mountDir); err != nil { return "", err } // Same rationale as in ensureAuthorizedKeyOnWorkDisk — the seed's // filesystem root becomes /root inside the guest, and sshd's // StrictModes check walks its ownership and mode. if err := normaliseHomeDirPerms(ctx, d.runner, mountDir); err != nil { return "", err } sshDir := filepath.Join(mountDir, ".ssh") if _, err := d.runner.RunSudo(ctx, "mkdir", "-p", sshDir); err != nil { return "", err } if _, err := d.runner.RunSudo(ctx, "chmod", "700", sshDir); err != nil { return "", err } if _, err := d.runner.RunSudo(ctx, "chown", "0:0", sshDir); err != nil { return "", err } authorizedKeysPath := filepath.Join(sshDir, "authorized_keys") existing, err := d.runner.RunSudo(ctx, "cat", authorizedKeysPath) if err != nil { existing = nil } merged := mergeAuthorizedKey(existing, publicKey) tmpFile, err := os.CreateTemp("", "banger-image-authorized-keys-*") if err != nil { return "", err } tmpPath := tmpFile.Name() if _, err := tmpFile.Write(merged); err != nil { _ = tmpFile.Close() _ = os.Remove(tmpPath) return "", err } if err := tmpFile.Close(); err != nil { _ = os.Remove(tmpPath) return "", err } defer os.Remove(tmpPath) if _, err := d.runner.RunSudo(ctx, "install", "-m", "600", tmpPath, authorizedKeysPath); err != nil { return "", err } return fingerprint, nil } func (d *Daemon) refreshManagedWorkSeedFingerprint(ctx context.Context, image model.Image, fingerprint string) error { if !image.Managed || strings.TrimSpace(image.WorkSeedPath) == "" || strings.TrimSpace(fingerprint) == "" { return nil } seededFingerprint, err := d.seedAuthorizedKeyOnExt4Image(ctx, image.WorkSeedPath) if err != nil { return err } if seededFingerprint == "" || seededFingerprint == image.SeededSSHPublicKeyFingerprint { return nil } image.SeededSSHPublicKeyFingerprint = seededFingerprint image.UpdatedAt = model.Now() return d.store.UpsertImage(ctx, image) }