Gitfed
bastien-mrq/gitfed / cmd / gitfed-ctl / main.go
// gitfed-ctl is an update utility for an already-running gitfed instance:
// it checks for new released versions, makes any breaking change flagged in
// CHANGELOG.md something you have to individually acknowledge before
// continuing, optionally backs up the data volume, then rebuilds and
// redeploys — all running locally on the VPS itself, so there's no SSH hop
// back to the same machine and no repeated password prompt (the problem
// with `deploy/update.sh current` this replaces for anyone who isn't
// cutting their own releases; see FEDERATION.md).
//
// Deliberately a separate binary from the four shipped in the gitfed
// container image (see deploy/docker/Dockerfile): it shells out to
// docker/k3s/kubectl/git on the host, none of which exist in the minimal
// runtime image. It also never touches gitfed's own internal packages —
// no admin socket, no store — so it has the same "zero internal gitfed
// dependency" profile as cmd/gitfed-renew-cert and installs the same way:
//
//	GOPRIVATE=git.neuromancer.ovh/* go install git.neuromancer.ovh/bastien-mrq/gitfed/cmd/gitfed-ctl@latest
//	sudo gitfed-ctl
//
// Account and federation-trust management stay on the web UI (Admin →
// Utilisateurs / Admin → Trust store) — this tool is purely about updates.
package main

import (
	"fmt"
	"os"

	tea "github.com/charmbracelet/bubbletea"
)

func main() {
	if os.Geteuid() != 0 {
		fmt.Fprintln(os.Stderr, "gitfed-ctl: must be run as root (sudo gitfed-ctl) — it needs direct access to docker/k3s/kubectl")
		os.Exit(1)
	}

	sourceDir, err := os.Getwd()
	if err != nil {
		fmt.Fprintf(os.Stderr, "gitfed-ctl: %v\n", err)
		os.Exit(1)
	}
	if err := validateSourceDir(sourceDir); err != nil {
		fmt.Fprintf(os.Stderr, "gitfed-ctl: %v\n", err)
		os.Exit(1)
	}

	m := initialModel(sourceDir)
	p := tea.NewProgram(m)
	finalModel, err := p.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "gitfed-ctl: %v\n", err)
		os.Exit(1)
	}
	if fm, ok := finalModel.(model); ok && fm.fatalErr != nil {
		fmt.Fprintf(os.Stderr, "gitfed-ctl: %v\n", fm.fatalErr)
		os.Exit(1)
	}
}

// validateSourceDir fails fast with a clear message if the current
// directory isn't a gitfed checkout, rather than letting the first git/file
// operation deep in the update wizard produce a confusing error.
func validateSourceDir(dir string) error {
	if _, err := os.Stat(dir + "/VERSION"); err != nil {
		return fmt.Errorf("no VERSION file in %s — run gitfed-ctl from inside a gitfed git checkout", dir)
	}
	if _, err := os.Stat(dir + "/.git"); err != nil {
		return fmt.Errorf("%s isn't a git checkout — run gitfed-ctl from inside a gitfed git checkout", dir)
	}
	return nil
}