feat: add application versioning support to backend and UI
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m38s

- Introduce APP_VERSION build argument and environment variable in Dockerfile.
- Load AppVersion from environment variables in the configuration loader.
- Pass the application version to the HTML renderer and expose it to templates via PageData.
- Update tests to verify the version is correctly rendered in the footer.
This commit is contained in:
2026-05-31 20:21:37 +03:00
parent 1513030c2a
commit 42449b3322
8 changed files with 29 additions and 11 deletions

View File

@@ -16,12 +16,15 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
FROM alpine:3.22 FROM alpine:3.22
ARG APP_VERSION=dev
RUN apk add --no-cache ca-certificates ffmpeg wget RUN apk add --no-cache ca-certificates ffmpeg wget
ENV WARPBOX_ADDR=:8080 \ ENV WARPBOX_ADDR=:8080 \
WARPBOX_DATA_DIR=/data \ WARPBOX_DATA_DIR=/data \
WARPBOX_STATIC_DIR=/app/static \ WARPBOX_STATIC_DIR=/app/static \
WARPBOX_TEMPLATE_DIR=/app/templates WARPBOX_TEMPLATE_DIR=/app/templates \
APP_VERSION=${APP_VERSION}
WORKDIR /app WORKDIR /app

View File

@@ -12,6 +12,7 @@ import (
type Config struct { type Config struct {
AppName string AppName string
AppVersion string
Environment string Environment string
Addr string Addr string
BaseURL string BaseURL string
@@ -54,6 +55,7 @@ type SettingsDefaults struct {
func Load() (Config, error) { func Load() (Config, error) {
cfg := Config{ cfg := Config{
AppName: envString("WARPBOX_APP_NAME", "warpbox.dev"), AppName: envString("WARPBOX_APP_NAME", "warpbox.dev"),
AppVersion: envString("APP_VERSION", "dev"),
Environment: envString("WARPBOX_ENV", "development"), Environment: envString("WARPBOX_ENV", "development"),
Addr: envString("WARPBOX_ADDR", ":8080"), Addr: envString("WARPBOX_ADDR", ":8080"),
BaseURL: strings.TrimRight(envString("WARPBOX_BASE_URL", "http://localhost:8080"), "/"), BaseURL: strings.TrimRight(envString("WARPBOX_BASE_URL", "http://localhost:8080"), "/"),

View File

@@ -569,6 +569,9 @@ func TestHomeReflectsUploadPolicySettings(t *testing.T) {
if !strings.Contains(body, "Max file size: 123 MB") || !strings.Contains(body, "456 MB") { if !strings.Contains(body, "Max file size: 123 MB") || !strings.Contains(body, "456 MB") {
t.Fatalf("home did not reflect policy settings: %s", body) t.Fatalf("home did not reflect policy settings: %s", body)
} }
if !strings.Contains(body, "warpbox.dev · test ·") {
t.Fatalf("home footer did not include app version: %s", body)
}
} }
func TestAPIDocsHeaderReflectsLoggedInUser(t *testing.T) { func TestAPIDocsHeaderReflectsLoggedInUser(t *testing.T) {

View File

@@ -179,6 +179,7 @@ func newTestApp(t *testing.T) (*App, func()) {
logger := slog.New(slog.NewTextHandler(io.Discard, nil)) logger := slog.New(slog.NewTextHandler(io.Discard, nil))
cfg := config.Config{ cfg := config.Config{
AppName: "warpbox.dev", AppName: "warpbox.dev",
AppVersion: "test",
BaseURL: "http://example.test", BaseURL: "http://example.test",
DataDir: filepath.Join(root, "data"), DataDir: filepath.Join(root, "data"),
StaticDir: staticDir, StaticDir: staticDir,
@@ -197,7 +198,7 @@ func newTestApp(t *testing.T) (*App, func()) {
if err != nil { if err != nil {
t.Fatalf("NewUploadService returned error: %v", err) t.Fatalf("NewUploadService returned error: %v", err)
} }
renderer, err := web.NewRenderer(cfg.TemplateDir, cfg.AppName, cfg.BaseURL) renderer, err := web.NewRenderer(cfg.TemplateDir, cfg.AppName, cfg.AppVersion, cfg.BaseURL)
if err != nil { if err != nil {
service.Close() service.Close()
t.Fatalf("NewRenderer returned error: %v", err) t.Fatalf("NewRenderer returned error: %v", err)

View File

@@ -13,7 +13,7 @@ import (
) )
func New(cfg config.Config, logger *slog.Logger) (*http.Server, error) { func New(cfg config.Config, logger *slog.Logger) (*http.Server, error) {
renderer, err := web.NewRenderer(cfg.TemplateDir, cfg.AppName, cfg.BaseURL) renderer, err := web.NewRenderer(cfg.TemplateDir, cfg.AppName, cfg.AppVersion, cfg.BaseURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -8,13 +8,15 @@ import (
) )
type Renderer struct { type Renderer struct {
templates map[string]*template.Template templates map[string]*template.Template
appName string appName string
baseURL string appVersion string
baseURL string
} }
type PageData struct { type PageData struct {
AppName string AppName string
AppVersion string
BaseURL string BaseURL string
Title string Title string
Description string Description string
@@ -25,7 +27,7 @@ type PageData struct {
Data any Data any
} }
func NewRenderer(templateDir, appName, baseURL string) (*Renderer, error) { func NewRenderer(templateDir, appName, appVersion, baseURL string) (*Renderer, error) {
layouts, err := filepath.Glob(filepath.Join(templateDir, "layouts", "*.html")) layouts, err := filepath.Glob(filepath.Join(templateDir, "layouts", "*.html"))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -56,14 +58,16 @@ func NewRenderer(templateDir, appName, baseURL string) (*Renderer, error) {
} }
return &Renderer{ return &Renderer{
templates: templates, templates: templates,
appName: appName, appName: appName,
baseURL: baseURL, appVersion: appVersion,
baseURL: baseURL,
}, nil }, nil
} }
func (r *Renderer) Render(w http.ResponseWriter, status int, page string, data PageData) { func (r *Renderer) Render(w http.ResponseWriter, status int, page string, data PageData) {
data.AppName = r.appName data.AppName = r.appName
data.AppVersion = r.appVersion
data.BaseURL = r.baseURL data.BaseURL = r.baseURL
data.CurrentYear = time.Now().Year() data.CurrentYear = time.Now().Year()

View File

@@ -60,7 +60,7 @@
</main> </main>
<footer class="site-footer"> <footer class="site-footer">
<span>{{.AppName}} · {{.CurrentYear}} · self-hosted</span> <span>{{.AppName}} · {{.AppVersion}} · {{.CurrentYear}} · self-hosted</span>
<label class="theme-picker"> <label class="theme-picker">
<span>Theme</span> <span>Theme</span>
<select data-theme-select aria-label="Site theme"> <select data-theme-select aria-label="Site theme">

View File

@@ -14,6 +14,11 @@ set -a
source "${ENV_FILE}" source "${ENV_FILE}"
set +a set +a
if [[ -z "${APP_VERSION:-}" ]]; then
APP_VERSION="$(git -C "${ROOT_DIR}" describe --tags --abbrev=0 2>/dev/null || printf 'dev')"
export APP_VERSION
fi
if [[ "${WARPBOX_DATA_DIR:-}" != /* ]]; then if [[ "${WARPBOX_DATA_DIR:-}" != /* ]]; then
export WARPBOX_DATA_DIR="${ROOT_DIR}/${WARPBOX_DATA_DIR:-data}" export WARPBOX_DATA_DIR="${ROOT_DIR}/${WARPBOX_DATA_DIR:-data}"
fi fi