From 23196d688407238b8b237b3b288dbd99e58352c6 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Tue, 15 Sep 2026 14:36:19 +0330 Subject: [PATCH] feat: add dedicated portfolio Gitea runner --- .env.example | 3 + .gitattributes | 5 ++ .gitignore | 4 ++ Dockerfile | 12 ++++ README.md | 140 ++++++++++++++++++++++++++++++++++++++ compose.yaml | 20 ++++++ config.yaml | 23 +++++++ gitea.caddy.yaml | 18 +++++ scripts/check-runner.sh | 16 +++++ scripts/gitea-compose.sh | 24 +++++++ scripts/inspect-runner.sh | 30 ++++++++ scripts/setup-runner.sh | 18 +++++ 12 files changed, 313 insertions(+) create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 compose.yaml create mode 100644 config.yaml create mode 100644 gitea.caddy.yaml create mode 100755 scripts/check-runner.sh create mode 100755 scripts/gitea-compose.sh create mode 100755 scripts/inspect-runner.sh create mode 100755 scripts/setup-runner.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b3b2526 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +GITEA_INSTANCE_URL=https://git.example.com +GITEA_RUNNER_REGISTRATION_TOKEN= +RUNNER_DATA_PATH=./data diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e24fb68 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto +*.sh text eol=lf +*.yaml text eol=lf +Dockerfile text eol=lf +.env.example text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca2c2b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +data/ +server-state/ +*.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..85bb62a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:22-alpine@sha256:c610fcdfb1d5b4740dd70c284ed3cb16bb857e0f7166196e36a5501df7a3aa32 + +RUN apk add --no-cache \ + bash \ + ca-certificates \ + coreutils \ + docker-cli \ + docker-cli-buildx \ + docker-cli-compose \ + git \ + openssh-client \ + tar diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e43ddb --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# Portfolio Gitea Runner + +Machine-specific Gitea Actions runner and Caddy integration for the portfolio +host. The reusable `gitea-deployment` repository remains unchanged. + +The runner is intentionally repository-scoped and has access to the host Docker +daemon. Only trusted workflows may use its `portfolio-ci` label. + +## Server layout + +```text +~/gitea-deployment/ Generic reusable Gitea checkout +~/caddy-deployment/ Shared Caddy deployment +~/portfolio-gitea-runner/ This repository +``` + +`scripts/gitea-compose.sh` combines the generic Compose file with +`gitea.caddy.yaml`. The override connects Gitea to `caddy_proxy` and puts the +generic Nginx and Certbot services behind the inactive `bundled-proxy` profile. + +## Migrate the existing server checkout + +Clone this repository first, then validate and activate the external override +before restoring files in the generic checkout: + +```bash +cd ~ +git clone https://git.amiirkhl.ir/amiirkhl/portfolio-gitea-runner.git +cd ~/portfolio-gitea-runner +bash scripts/gitea-compose.sh config --quiet +bash scripts/gitea-compose.sh up -d --remove-orphans +curl -fsS https://git.amiirkhl.ir/api/healthz +``` + +After Gitea is healthy through Caddy, preserve the obsolete generated Nginx +directory outside the generic checkout and restore only the known server-local +paths: + +```bash +cd ~/gitea-deployment +git status --short +git diff -- docker-compose.yml +git diff --cached + +git restore --staged -- nginx/conf.d/gitea.conf 2>/dev/null || true +mkdir -p ~/portfolio-gitea-runner/server-state +if [[ -d nginx ]]; then + mv nginx ~/portfolio-gitea-runner/server-state/legacy-nginx +fi +git restore --source=HEAD --staged --worktree -- docker-compose.yml + +test -z "$(git status --porcelain)" +``` + +The last command must succeed. From this point onward, use +`scripts/gitea-compose.sh` for this server instead of running Compose directly +inside the generic checkout. + +## Install the runner + +Create a repository runner token under the portfolio repository's +**Settings > Actions > Runners**, then configure the server: + +```bash +git clone https://git.amiirkhl.ir/amiirkhl/portfolio-gitea-runner.git +cd portfolio-gitea-runner +cp .env.example .env +chmod 600 .env +nano .env +bash scripts/setup-runner.sh +docker compose logs --follow runner +``` + +Use these values in `.env`: + +```dotenv +GITEA_INSTANCE_URL=https://git.amiirkhl.ir +GITEA_RUNNER_REGISTRATION_TOKEN=the-repository-runner-token +RUNNER_DATA_PATH=./data +``` + +After `/data/.runner` is created, clear the reusable token in `.env` and +recreate the runner: + +```bash +sed -i 's/^GITEA_RUNNER_REGISTRATION_TOKEN=.*/GITEA_RUNNER_REGISTRATION_TOKEN=/' .env +docker compose up -d --force-recreate runner +bash scripts/check-runner.sh +``` + +The `portfolio-runner-docker-config` volume retains the Buildx builder +definition between job containers. The BuildKit container retains npm and +Next.js cache mounts. Capacity is limited to one job for the shared 2-core, +4-GB host. + +## Manage the Caddy-backed Gitea instance + +Always invoke the generic deployment through the wrapper on this server: + +```bash +bash scripts/gitea-compose.sh config --quiet +bash scripts/gitea-compose.sh up -d --remove-orphans +bash scripts/gitea-compose.sh ps +bash scripts/gitea-compose.sh logs --tail=100 gitea +``` + +Set `GITEA_DEPLOYMENT_PATH` only if the generic checkout is not located at +`~/gitea-deployment`. + +## Update and back up Gitea + +Pull repository changes while Gitea is still running. Before changing the Gitea +image, stop the application and back up PostgreSQL, `/data`, and the generic +deployment's `.env`. Use the wrapper for every subsequent Compose operation so +the Caddy override is always active. + +```bash +bash scripts/gitea-compose.sh pull gitea +bash scripts/gitea-compose.sh stop gitea +bash scripts/gitea-compose.sh up -d --remove-orphans +bash scripts/gitea-compose.sh exec -u git gitea gitea doctor check +curl -fsS https://git.amiirkhl.ir/api/healthz +``` + +Restoring an older image after a minor-version database migration may require +restoring the database and data backup as well. + +## Inspect and troubleshoot + +```bash +bash scripts/inspect-runner.sh | tee runner-inspection.log +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v portfolio-runner-docker-config:/root/.docker \ + portfolio-runner-job:node22 \ + docker buildx inspect portfolio-builder +``` + +The first portfolio pipeline run creates `portfolio-builder`. Later runs must +reuse the same builder and cached npm/Next.js layers. diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..dd0c7cf --- /dev/null +++ b/compose.yaml @@ -0,0 +1,20 @@ +services: + runner: + image: docker.io/gitea/runner:3.0.0@sha256:e4b1a4b99272b78f2f06ac60feefc8de176c49ab4be845960bb2956eaac1be04 + container_name: gitea-runner + restart: unless-stopped + environment: + CONFIG_FILE: /config.yaml + GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL:?set GITEA_INSTANCE_URL in runner/.env} + GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN:-} + GITEA_RUNNER_LABELS: portfolio-ci:docker://portfolio-runner-job:node22 + GITEA_RUNNER_NAME: portfolio-runner + volumes: + - ${RUNNER_DATA_PATH:-./data}:/data + - ./config.yaml:/config.yaml:ro + - /var/run/docker.sock:/var/run/docker.sock + - portfolio-runner-docker-config:/runner-docker-config + +volumes: + portfolio-runner-docker-config: + name: portfolio-runner-docker-config diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..3fcd18b --- /dev/null +++ b/config.yaml @@ -0,0 +1,23 @@ +runner: + file: /data/.runner + capacity: 1 + timeout: 20m + shutdown_timeout: 2m + action_shallow_clone: true + tool_cache_mode: none + +cache: + enabled: false + offline_mode: true + +container: + network: bridge + privileged: false + options: --volume portfolio-runner-docker-config:/root/.docker + valid_volumes: + - portfolio-runner-docker-config + docker_host: unix:///var/run/docker.sock + force_pull: false + force_rebuild: false + require_docker: true + docker_timeout: 10s diff --git a/gitea.caddy.yaml b/gitea.caddy.yaml new file mode 100644 index 0000000..7c27a7e --- /dev/null +++ b/gitea.caddy.yaml @@ -0,0 +1,18 @@ +services: + gitea: + networks: + - gitea_net + - caddy_proxy + + nginx: + profiles: + - bundled-proxy + + certbot: + profiles: + - bundled-proxy + +networks: + caddy_proxy: + external: true + name: caddy_proxy diff --git a/scripts/check-runner.sh b/scripts/check-runner.sh new file mode 100755 index 0000000..3974423 --- /dev/null +++ b/scripts/check-runner.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +docker compose config --quiet +docker compose ps +docker compose exec runner test -s /data/.runner + +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + portfolio-runner-job:node22 \ + sh -lc 'node --version && npm --version && git --version && docker version && docker buildx version && docker compose version' + +echo "Runner registration, Docker access, and job-image tools are ready." diff --git a/scripts/gitea-compose.sh b/scripts/gitea-compose.sh new file mode 100755 index 0000000..2d29a22 --- /dev/null +++ b/scripts/gitea-compose.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +deployment_root="${GITEA_DEPLOYMENT_PATH:-$HOME/gitea-deployment}" +base_compose="$deployment_root/docker-compose.yml" +base_env="$deployment_root/.env" + +if [[ ! -f "$base_compose" ]]; then + echo "Generic Gitea Compose file not found: $base_compose" >&2 + exit 2 +fi +if [[ ! -f "$base_env" ]]; then + echo "Generic Gitea environment file not found: $base_env" >&2 + exit 2 +fi + +exec docker compose \ + --project-name gitea-deployment \ + --project-directory "$deployment_root" \ + --env-file "$base_env" \ + --file "$base_compose" \ + --file "$repo_root/gitea.caddy.yaml" \ + "$@" diff --git a/scripts/inspect-runner.sh b/scripts/inspect-runner.sh new file mode 100755 index 0000000..af746d3 --- /dev/null +++ b/scripts/inspect-runner.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +echo "Runner image" +docker inspect --format '{{.Config.Image}}' gitea-runner 2>/dev/null || true + +echo "Runner version" +docker compose exec runner gitea-runner --version 2>/dev/null || \ + docker compose exec runner act_runner --version 2>/dev/null || true + +echo "Runner configuration" +docker compose exec runner sh -c 'cat /config.yaml' 2>/dev/null || true + +echo "Host resources" +nproc +free -h +df -h / /var/lib/docker 2>/dev/null || df -h / + +echo "Docker builders" +docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v portfolio-runner-docker-config:/root/.docker \ + portfolio-runner-job:node22 \ + docker buildx ls 2>/dev/null || true + +echo "Recent runner logs" +docker compose logs --tail=200 runner 2>/dev/null || true diff --git a/scripts/setup-runner.sh b/scripts/setup-runner.sh new file mode 100755 index 0000000..43d07eb --- /dev/null +++ b/scripts/setup-runner.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +if [[ ! -f .env ]]; then + cp .env.example .env + chmod 600 .env + echo "Created $repo_root/.env. Add the repository runner token, then rerun this script." >&2 + exit 1 +fi + +docker build --pull --tag portfolio-runner-job:node22 . +docker compose config --quiet +docker compose up -d + +echo "Runner started. Follow logs with: docker compose logs --follow runner"