Gitfed
bastien-mrq/gitfed/ Commits/ 5528a28

Add version tracking, an in-app changelog, and a one-command update script

- internal/version: single source of truth, injected at build time via -ldflags from the VERSION file (Dockerfile does this automatically). - CHANGELOG.md, embedded into the binary (a root-level package, since //go:embed can't reach outside its own directory) and served at /changelog. Footer links to it with the running version. - gitfed-server -version flag; well-known doc's "software" field now reflects the real version instead of a hardcoded "0.1.0". - deploy/update.sh: bumps VERSION (patch/minor/major/explicit), refuses to run without a matching CHANGELOG.md entry, builds+imports the image tagged with that version (not just :latest, so rollout history means something), points deployment.yaml at it, applies, and commits both the release and the deploy as separate commits. - deployment.yaml now pins gitfed:0.3.0 explicitly instead of :latest.

bastien-mrq 2026-07-28 14:14 commit 5528a28f81e27e384a65fa0d8f1be5c312035a33 parent 3187941959e01e621a58b404c43044f7c6aa0538
12 files changed +174 −12
A CHANGELOG.md +21 −0
A VERSION +1 −0
A changelog.go +10 −0
M cmd/gitfed-server/main.go +9 −3
A cmd/gitfed-web/handlers_changelog.go +18 −0
M cmd/gitfed-web/render.go +12 −4
M cmd/gitfed-web/routes.go +1 −0
M deploy/docker/Dockerfile +5 −3
M deploy/k8s/README.md +11 −0
M deploy/k8s/deployment.yaml +2 −2
A deploy/update.sh +71 −0
A internal/version/version.go +13 −0
CHANGELOG.md
diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d588b81 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog + +## 0.3.0 + +- File browser (`browse files` on any repo): breadcrumbed directory listing, markdown rendered inline, anything else shown as plain text, binary files detected and not dumped. +- Rendered README/LICENSE pages got real styling — tables, blockquotes, task-list checkboxes, code blocks — instead of bare unstyled HTML. +- Fixed: repos pushed under a branch name that didn't match the bare repo's default `HEAD` (e.g. `main` vs `master`) silently looked empty everywhere — browsing, README rendering, tags. Every freshly pushed repo hit this. Bare repos now default `HEAD` to `main` at creation, and reads fall back to a repo's sole branch when `HEAD` doesn't resolve, so already-affected repos self-heal with no migration. +- Fixed: errors crossing the admin RPC socket lost their identity (a fresh `errors.New` never equals a sentinel like "not found," even with identical text), which 500'd the repo settings page for any repo with no collaborators yet. +- Logo: the "Branch Blocks" mark (a git fork drawn as three square-cornered rectangles) in the header and as a light/dark-aware favicon. +- Version tracking and this changelog. + +## 0.2.0 + +- Real username/password login for the web UI, with server-side sessions (not just an admin tool behind `kubectl port-forward` anymore). +- Self-service: manage your own SSH keys, create and configure your own repos (public/private, topics, collaborators) without needing an instance admin. +- Admin section (user management, trust store, audit log) gated by an admin role on your account instead of being the only thing the web UI could do. +- Merged the public repo browser into the same app as the admin UI — one login, scoped by what each route actually needs. + +## 0.1.0 + +- Initial implementation: SSH server with a local CA, certificate-based cross-instance identity, ACL model with public/private repos, federation trust store with rate limiting, audit log, admin TUI, admin web UI, public read-only repo browser, and Kubernetes deployment manifests.
VERSION
diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0d91a54 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.0
changelog.go
diff --git a/changelog.go b/changelog.go new file mode 100644 index 0000000..d2103c3 --- /dev/null +++ b/changelog.go @@ -0,0 +1,10 @@ +// Package gitfed holds nothing but the embedded changelog — Go's //go:embed +// can't reach outside the directory containing the directive, so this lives +// at the module root next to CHANGELOG.md itself rather than duplicating +// the file under cmd/gitfed-web. +package gitfed + +import _ "embed" + +//go:embed CHANGELOG.md +var Changelog string
cmd/gitfed-server/main.go
diff --git a/cmd/gitfed-server/main.go b/cmd/gitfed-server/main.go index 2d41e58..19df0ac 100644 --- a/cmd/gitfed-server/main.go +++ b/cmd/gitfed-server/main.go @@ -17,16 +17,21 @@ import ( "gitfed/internal/federation" "gitfed/internal/ssh" "gitfed/internal/store" + "gitfed/internal/version" ) -const version = "0.1.0" - func main() { configPath := flag.String("config", "gitfed.json", "path to instance config file") initDomain := flag.String("init", "", "initialize a new instance for this domain and exit") dataDir := flag.String("data-dir", "data", "data directory to use with -init") + showVersion := flag.Bool("version", false, "print the version and exit") flag.Parse() + if *showVersion { + fmt.Println("gitfed-server " + version.Version) + return + } + if *initDomain != "" { if err := initInstance(*configPath, *initDomain, *dataDir); err != nil { log.Fatalf("init: %v", err) @@ -74,6 +79,7 @@ func initInstance(configPath, domain, dataDir string) error { } func run(configPath string) error { + log.Printf("gitfed-server %s starting", version.Version) cfg, err := config.Load(configPath) if err != nil { return fmt.Errorf("load config (did you run -init first?): %w", err) @@ -114,7 +120,7 @@ func run(configPath string) error { if cfg.ListenHTTP != "" { go func() { - handler := federation.Handler(cfg.Domain, cfg.Contact, version, localCA) + handler := federation.Handler(cfg.Domain, cfg.Contact, version.Version, localCA) mux := http.NewServeMux() mux.Handle("/.well-known/gitfed.json", handler) log.Printf("gitfed well-known endpoint listening on %s", cfg.ListenHTTP)
cmd/gitfed-web/handlers_changelog.go
diff --git a/cmd/gitfed-web/handlers_changelog.go b/cmd/gitfed-web/handlers_changelog.go new file mode 100644 index 0000000..81bb92d --- /dev/null +++ b/cmd/gitfed-web/handlers_changelog.go @@ -0,0 +1,18 @@ +package main + +import ( + "html/template" + "net/http" + + "gitfed" +) + +func (s *server) handleChangelog(w http.ResponseWriter, r *http.Request) { + rendered, err := renderMarkdown(gitfed.Changelog) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + body := `<div class="markdown-body">` + string(rendered) + `</div>` + s.render(w, r, "Changelog", "", template.HTML(body)) +}
cmd/gitfed-web/render.go
diff --git a/cmd/gitfed-web/render.go b/cmd/gitfed-web/render.go index 9755e56..ec5a7fb 100644 --- a/cmd/gitfed-web/render.go +++ b/cmd/gitfed-web/render.go @@ -8,6 +8,8 @@ import ( "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" + + "gitfed/internal/version" ) var markdown = goldmark.New(goldmark.WithExtensions(extension.GFM)) @@ -94,6 +96,9 @@ const shellSrc = `<!doctype html> .markdown-body th, .markdown-body td { border: 1px solid #2a2f3a; padding: 0.4rem 0.8rem; text-align: left; } .markdown-body th { background: #171a21; } .markdown-body input[type="checkbox"] { width: auto; margin: 0 0.4em 0 0; } + footer { max-width: 960px; margin: 2rem auto 1.5rem; padding: 0 1.5rem; } + footer a { color: #6b7078; font-size: 0.78rem; text-decoration: none; } + footer a:hover { color: #9aa1ac; } </style> </head> <body> @@ -117,6 +122,9 @@ const shellSrc = `<!doctype html> <main> {{.Body}} </main> +<footer> + <a href="/changelog">gitfed {{.Version}}</a> +</footer> </body> </html>` @@ -126,10 +134,10 @@ func (s *server) render(w http.ResponseWriter, r *http.Request, title, active st sess, loggedIn := s.currentSession(r) w.Header().Set("Content-Type", "text/html; charset=utf-8") _ = shellTpl.Execute(w, struct { - Title, Domain, Active, Username string - LoggedIn, IsAdmin bool - Body, BrandMark template.HTML - }{title, s.domain, active, sess.Username, loggedIn, sess.IsAdmin, body, template.HTML(brandMark)}) + Title, Domain, Active, Username, Version string + LoggedIn, IsAdmin bool + Body, BrandMark template.HTML + }{title, s.domain, active, sess.Username, version.Version, loggedIn, sess.IsAdmin, body, template.HTML(brandMark)}) } // flash renders the ?msg=&err= query params (set by handlers that redirect
cmd/gitfed-web/routes.go
diff --git a/cmd/gitfed-web/routes.go b/cmd/gitfed-web/routes.go index 46da898..bb01302 100644 --- a/cmd/gitfed-web/routes.go +++ b/cmd/gitfed-web/routes.go @@ -11,6 +11,7 @@ func (s *server) routes(mux *http.ServeMux) { mux.HandleFunc("GET /login", s.handleLoginForm) mux.HandleFunc("POST /login", s.handleLogin) mux.HandleFunc("POST /logout", s.handleLogout) + mux.HandleFunc("GET /changelog", s.handleChangelog) // Self-service — any logged-in user, scoped to their own stuff via // CheckAccess inside the handlers.
deploy/docker/Dockerfile
diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 0d9d0c4..9332989 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -11,9 +11,11 @@ WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . -RUN CGO_ENABLED=0 go build -o /out/gitfed-server ./cmd/gitfed-server && \ - CGO_ENABLED=0 go build -o /out/gitfed-web ./cmd/gitfed-web && \ - CGO_ENABLED=0 go build -o /out/gitfed-tui ./cmd/gitfed-tui +RUN VERSION=$(cat VERSION) && \ + LDFLAGS="-X gitfed/internal/version.Version=$VERSION" && \ + CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o /out/gitfed-server ./cmd/gitfed-server && \ + CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o /out/gitfed-web ./cmd/gitfed-web && \ + CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o /out/gitfed-tui ./cmd/gitfed-tui FROM debian:bookworm-slim RUN apt-get update && \
deploy/k8s/README.md
diff --git a/deploy/k8s/README.md b/deploy/k8s/README.md index d66a00b..86fd9ab 100644 --- a/deploy/k8s/README.md +++ b/deploy/k8s/README.md @@ -51,6 +51,17 @@ pull this from a registry — it only ever uses what you've imported. `kubectl rollout restart deployment/gitfed -n gitfed` — importing a new image under the same tag does not restart pods that are already running. +**For every update after the first**, use `deploy/update.sh` instead of the +steps above — it does the same build/import/rollout, but tags the image +with the actual version (`gitfed:0.3.1`, not just `:latest`) so +`kubectl rollout history` means something, bumps `VERSION`, and commits. +Add the new version's `## X.Y.Z` section to `CHANGELOG.md` first, then: + +```sh +deploy/update.sh # patch bump +deploy/update.sh minor # or minor/major/an explicit X.Y.Z +``` + ## 2. DNS Add one more A/AAAA record to the same zone ess-helm's subdomains live in:
deploy/k8s/deployment.yaml
diff --git a/deploy/k8s/deployment.yaml b/deploy/k8s/deployment.yaml index e4ee6ca..f5a38c1 100644 --- a/deploy/k8s/deployment.yaml +++ b/deploy/k8s/deployment.yaml @@ -44,7 +44,7 @@ spec: fsGroup: 1000 containers: - name: server - image: gitfed:latest + image: gitfed:0.3.0 imagePullPolicy: Never command: ["/usr/local/bin/gitfed-server", "-config", "/etc/gitfed/gitfed.json"] ports: @@ -75,7 +75,7 @@ spec: periodSeconds: 20 - name: web - image: gitfed:latest + image: gitfed:0.3.0 imagePullPolicy: Never command: - sh
deploy/update.sh
diff --git a/deploy/update.sh b/deploy/update.sh new file mode 100755 index 0000000..080bb97 --- /dev/null +++ b/deploy/update.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# One-command release: bump VERSION, build+import the image on the VPS +# tagged with that version (not just :latest, so `kubectl rollout history` +# and `get pods -o yaml` actually show what's running), point +# deploy/k8s/deployment.yaml at it, apply, and wait for the rollout. +# +# Usage: +# deploy/update.sh # bump patch (0.3.0 -> 0.3.1) +# deploy/update.sh minor # 0.3.0 -> 0.4.0 +# deploy/update.sh major # 0.3.0 -> 1.0.0 +# deploy/update.sh 0.5.0 # explicit version +# +# Add the new version's entry to CHANGELOG.md yourself before running this +# — the script won't guess what changed. It commits the VERSION bump (and +# CHANGELOG.md, if you've staged it) as "Release vX.Y.Z". + +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +VPS_HOST="${GITFED_VPS_HOST:-ubuntu@51.77.215.92}" +VPS_SRC_DIR="${GITFED_VPS_SRC_DIR:-~/gitfed-src}" +NAMESPACE="gitfed" + +current="$(cat VERSION)" + +bump_patch() { IFS=. read -r maj min pat <<<"$1"; echo "$maj.$min.$((pat + 1))"; } +bump_minor() { IFS=. read -r maj min _ <<<"$1"; echo "$maj.$((min + 1)).0"; } +bump_major() { IFS=. read -r maj _ _ <<<"$1"; echo "$((maj + 1)).0.0"; } + +case "${1:-patch}" in + patch) new_version="$(bump_patch "$current")" ;; + minor) new_version="$(bump_minor "$current")" ;; + major) new_version="$(bump_major "$current")" ;; + [0-9]*.[0-9]*.[0-9]*) new_version="$1" ;; + *) echo "usage: $0 [patch|minor|major|X.Y.Z]" >&2; exit 1 ;; +esac + +if ! grep -q "^## $new_version\$" CHANGELOG.md; then + echo "error: CHANGELOG.md has no '## $new_version' entry yet — add one first." >&2 + exit 1 +fi + +echo "==> $current -> $new_version" +echo "$new_version" > VERSION + +git add VERSION CHANGELOG.md +git commit -m "Release v$new_version" + +echo "==> syncing source to $VPS_HOST:$VPS_SRC_DIR" +rsync -az --delete \ + --exclude='.git' --exclude='bin' --exclude='demo' --exclude='.claude' --exclude='*.db' \ + ./ "$VPS_HOST:$VPS_SRC_DIR/" + +echo "==> building gitfed:$new_version on the VPS" +ssh "$VPS_HOST" "cd $VPS_SRC_DIR && docker build -f deploy/docker/Dockerfile -t gitfed:$new_version -t gitfed:latest ." + +echo "==> importing into k3s containerd" +ssh "$VPS_HOST" "docker save gitfed:$new_version | sudo k3s ctr images import -" + +echo "==> pointing deployment.yaml at gitfed:$new_version" +sed -i.bak "s|image: gitfed:.*|image: gitfed:$new_version|" deploy/k8s/deployment.yaml +rm -f deploy/k8s/deployment.yaml.bak +rsync -az deploy/k8s/deployment.yaml "$VPS_HOST:$VPS_SRC_DIR/deploy/k8s/deployment.yaml" + +echo "==> applying and rolling out" +ssh "$VPS_HOST" "kubectl -n $NAMESPACE apply -f $VPS_SRC_DIR/deploy/k8s/deployment.yaml && kubectl -n $NAMESPACE rollout status deployment/gitfed --timeout=90s" + +git add deploy/k8s/deployment.yaml +git commit -m "Deploy v$new_version" + +echo "==> done: v$new_version is live"
internal/version/version.go
diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..722d0ed --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,13 @@ +// Package version holds gitfed's version string, the single source of +// truth other packages (the well-known federation doc, the web UI footer) +// display it from. +package version + +// Version is overridden at build time via +// +// go build -ldflags "-X gitfed/internal/version.Version=X.Y.Z" +// +// See deploy/docker/Dockerfile (reads VERSION at the repo root) and +// deploy/update.sh (bumps VERSION). Binaries built without that flag (e.g. +// `go build ./...` during development) report "dev". +var Version = "dev"