package main
import (
"context"
"os"
"regexp"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
var imageLineRe = regexp.MustCompile(`image:\s*gitfed:\S+`)
// bumpDeploymentImageTag rewrites every "image: gitfed:..." line in
// deploy/k8s/deployment.yaml (both containers carry the same tag — see
// deploy/k8s/deployment.yaml) to point at newVersion. Never committed —
// matches deploy/update.sh's "current" mode, which leaves this as a
// local-only edit too.
func bumpDeploymentImageTag(sourceDir, newVersion string) error {
path := sourceDir + "/deploy/k8s/deployment.yaml"
data, err := os.ReadFile(path)
if err != nil {
return err
}
updated := imageLineRe.ReplaceAllString(string(data), "image: gitfed:"+newVersion)
return os.WriteFile(path, []byte(updated), 0644)
}
// versionResolvedMsg reports the version actually deployed, read from
// VERSION right after the pull — this is what the running/success screens
// display, since the target shown at the "check" screen was read from the
// remote and the pull is the point it becomes locally real.
type versionResolvedMsg struct{ version string }
// runUpdatePipeline is the local (no ssh/rsync) re-implementation of
// deploy/update.sh's shared build/deploy block, run directly on the VPS
// gitfed-ctl already lives on. Each step reports exactly one
// stepResultMsg tagged with its own stream name, and the sequence bails
// out after the first failure — same "don't plough on into commands that
// can only fail more confusingly" rule as cmd/gitfed-install/actions.go.
func runUpdatePipeline(ctx context.Context, ch chan<- tea.Msg, sourceDir string, backupFirst bool) {
if backupFirst {
_, err := runBackupNow(ctx, sourceDir)
ch <- stepResultMsg{stream: "backup", err: err}
if err != nil {
return
}
}
if err := runStreamed(ctx, ch, "pull", sourceDir, "git", "pull", "--ff-only"); err != nil {
return
}
versionBytes, err := os.ReadFile(sourceDir + "/VERSION")
if err != nil {
ch <- stepResultMsg{stream: "version", err: err}
return
}
newVersion := strings.TrimSpace(string(versionBytes))
ch <- versionResolvedMsg{version: newVersion}
if err := bumpDeploymentImageTag(sourceDir, newVersion); err != nil {
ch <- stepResultMsg{stream: "version", err: err}
return
}
ch <- stepResultMsg{stream: "version"}
if err := runStreamed(ctx, ch, "build", sourceDir, "docker", "build",
"-f", "deploy/docker/Dockerfile", "-t", "gitfed:"+newVersion, "-t", "gitfed:latest", "."); err != nil {
return
}
if err := runPiped(ctx,
[]string{"docker", "save", "gitfed:" + newVersion},
[]string{"k3s", "ctr", "images", "import", "-"},
); err != nil {
ch <- stepResultMsg{stream: "import", err: err}
return
}
ch <- stepResultMsg{stream: "import"}
if err := runStreamed(ctx, ch, "apply", "", "kubectl", "-n", "gitfed", "apply",
"-f", sourceDir+"/deploy/k8s/deployment.yaml"); err != nil {
return
}
runStreamed(ctx, ch, "rollout", "", "kubectl", "-n", "gitfed", "rollout", "status",
"deployment/gitfed", "--timeout=90s")
}