commit 23196d688407238b8b237b3b288dbd99e58352c6 Author: Amirhossein Khalili Date: Tue Sep 15 14:36:19 2026 +0330 feat: add dedicated portfolio Gitea runner 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"