.hearthforge-ci.toml
⎇
Raw
1# Hearthforge CI for VidArchive.
2# Steps share one container and run in file order, so what "setup" installs
3# stays available to every later step.
4
5image = "docker.io/golang:1.26"
6work_dir = "/ci/build"
7clone_project_to = "/ci/build/project"
8shell_setup = "set -euo pipefail"
9timeout = 1800
10cpu_limit = 2.0
11memory_limit = "2g"
12
13# Go's build and module caches. Both live outside clone_project_to.
14cache = [
15 { path = "/root/.cache/go-build", max_size = "2g" },
16 { path = "/go/pkg/mod", max_size = "2g" },
17]
18
19[on]
20push = ["*"]
21tag = true
22
23[variables]
24 [variables.ONLINE_TESTS]
25 default = "1"
26 description = "Set to 1 to also run the live yt-dlp tests against YouTube (needs network)"
27
28# ffmpeg/ffprobe unlock the media tests; yt-dlp is the standalone Linux build,
29# which needs no Python. podman-remote is for the image step.
30[[steps]]
31name = "setup"
32timeout = 600
33run_sh = """
34apt-get update -qq && apt-get install -y -qq --no-install-recommends ffmpeg podman-remote > /dev/null
35curl -fsSL https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux -o /usr/local/bin/yt-dlp
36chmod +x /usr/local/bin/yt-dlp
37yt-dlp --version
38"""
39
40# gofmt has no failure exit code, so an empty report is the pass condition.
41[[steps]]
42name = "vet"
43run_sh = "cd project && gofmt -l . | tee /ci/build/gofmt.txt && test ! -s /ci/build/gofmt.txt && go vet ./..."
44
45[[steps]]
46name = "test"
47run_sh = "cd project && go test ./..."
48
49# The live tests hit YouTube, which may block CI IPs. A failure must stay
50# visible but must not fail the run.
51[[steps]]
52name = "test-online"
53run_if = 'test "$ONLINE_TESTS" = "1"'
54run_sh = "cd project && VIDARCHIVE_ONLINE_TESTS=1 go test ./internal/service -run Live -v"
55warn_on_fail = true
56
57[[steps]]
58name = "build"
59run_sh = "cd project && CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /ci/build/dist/vidarchive ./cmd/vidarchive"
60publish_file = ["/ci/build/dist/vidarchive"]
61
62# ── image ────────────────────────────────────────────────────────────────────
63# Packages the binary the build step made, so the image ships exactly what was
64# tested. The Containerfile's build stage is skipped via BIN_STAGE.
65# engine_socket hands this step the host engine, which is Podman on this
66# server (needs CI_ENGINE_SOCKET=1). REGISTRY_PASSWORD is a CI secret with the
67# admin password. CI_REGISTRY is "<host>/<repo>" on the built-in registry.
68# Tags: every run pushes the short sha and "edge"; a tag run also pushes the
69# tag and "latest".
70[[steps]]
71name = "image"
72engine_socket = true
73timeout = 900
74run_sh = """
75cd project
76mkdir -p ci-bin
77cp /ci/build/dist/vidarchive ci-bin/vidarchive
78
79echo "$REGISTRY_PASSWORD" | podman-remote login "${CI_REGISTRY%%/*}" -u admin --password-stdin
80
81# A remote build sends a seccomp profile path that the server opens. Ask the
82# server for its own path. An empty answer means no profile can be named, so
83# the build runs unconfined rather than failing.
84prof=$(podman-remote info --format '{{.Host.Security.SECCOMPProfilePath}}' 2>/dev/null || true)
85if [ -n "$prof" ]; then
86 seccomp="seccomp=$prof"
87else
88 seccomp="seccomp=unconfined"
89fi
90
91img="$CI_REGISTRY:$CI_COMMIT_SHORT_SHA"
92podman-remote build --security-opt "$seccomp" \
93 -f Containerfile -t "$img" --build-arg BIN_STAGE=prebuilt .
94podman-remote push "$img"
95
96if [ -n "${CI_COMMIT_TAG:-}" ]; then
97 tags="$CI_COMMIT_TAG latest"
98else
99 tags="edge"
100fi
101for t in $tags; do
102 podman-remote tag "$img" "$CI_REGISTRY:$t"
103 podman-remote push "$CI_REGISTRY:$t"
104done
105"""
106