# Hearthforge CI for VidArchive. # Steps share one container and run in file order, so what "setup" installs # stays available to every later step. image = "docker.io/golang:1.26" work_dir = "/ci/build" clone_project_to = "/ci/build/project" shell_setup = "set -euo pipefail" timeout = 1800 cpu_limit = 2.0 memory_limit = "2g" # Go's build and module caches. Both live outside clone_project_to. cache = [ { path = "/root/.cache/go-build", max_size = "2g" }, { path = "/go/pkg/mod", max_size = "2g" }, ] [on] push = ["*"] tag = true [variables] [variables.ONLINE_TESTS] default = "1" description = "Set to 1 to also run the live yt-dlp tests against YouTube (needs network)" # ffmpeg/ffprobe unlock the media tests; yt-dlp is the standalone Linux build, # which needs no Python. podman-remote is for the image step. [[steps]] name = "setup" timeout = 600 run_sh = """ apt-get update -qq && apt-get install -y -qq --no-install-recommends ffmpeg podman-remote > /dev/null curl -fsSL https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_linux -o /usr/local/bin/yt-dlp chmod +x /usr/local/bin/yt-dlp yt-dlp --version """ # gofmt has no failure exit code, so an empty report is the pass condition. [[steps]] name = "vet" run_sh = "cd project && gofmt -l . | tee /ci/build/gofmt.txt && test ! -s /ci/build/gofmt.txt && go vet ./..." [[steps]] name = "test" run_sh = "cd project && go test ./..." # The live tests hit YouTube, which may block CI IPs. A failure must stay # visible but must not fail the run. [[steps]] name = "test-online" run_if = 'test "$ONLINE_TESTS" = "1"' run_sh = "cd project && VIDARCHIVE_ONLINE_TESTS=1 go test ./internal/service -run Live -v" warn_on_fail = true [[steps]] name = "build" run_sh = "cd project && CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o /ci/build/dist/vidarchive ./cmd/vidarchive" publish_file = ["/ci/build/dist/vidarchive"] # ── image ──────────────────────────────────────────────────────────────────── # Packages the binary the build step made, so the image ships exactly what was # tested. The Containerfile's build stage is skipped via BIN_STAGE. # engine_socket hands this step the host engine, which is Podman on this # server (needs CI_ENGINE_SOCKET=1). REGISTRY_PASSWORD is a CI secret with the # admin password. CI_REGISTRY is "/" on the built-in registry. # Tags: every run pushes the short sha and "edge"; a tag run also pushes the # tag and "latest". [[steps]] name = "image" engine_socket = true timeout = 900 run_sh = """ cd project mkdir -p ci-bin cp /ci/build/dist/vidarchive ci-bin/vidarchive echo "$REGISTRY_PASSWORD" | podman-remote login "${CI_REGISTRY%%/*}" -u admin --password-stdin # A remote build sends a seccomp profile path that the server opens. Ask the # server for its own path. An empty answer means no profile can be named, so # the build runs unconfined rather than failing. prof=$(podman-remote info --format '{{.Host.Security.SECCOMPProfilePath}}' 2>/dev/null || true) if [ -n "$prof" ]; then seccomp="seccomp=$prof" else seccomp="seccomp=unconfined" fi img="$CI_REGISTRY:$CI_COMMIT_SHORT_SHA" podman-remote build --security-opt "$seccomp" \ -f Containerfile -t "$img" --build-arg BIN_STAGE=prebuilt . podman-remote push "$img" if [ -n "${CI_COMMIT_TAG:-}" ]; then tags="$CI_COMMIT_TAG latest" else tags="edge" fi for t in $tags; do podman-remote tag "$img" "$CI_REGISTRY:$t" podman-remote push "$CI_REGISTRY:$t" done """