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