✨feature: Add Gitea docs
This commit is contained in:
@@ -19,9 +19,13 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
|
||||
h1 { font-size: clamp(2rem, 3vw, 2.5rem); }
|
||||
|
||||
h2 { font-size: clamp(1.625rem, 2.4vw, 2rem); }
|
||||
|
||||
h3 { font-size: clamp(1.375rem, 2vw, 1.625rem); }
|
||||
.card h3 { text-transform: capitalize; }
|
||||
summary h3 { display: inline-block; }
|
||||
|
||||
h4 { font-size: clamp(1.25rem, 1.8vw, 1.375rem); }
|
||||
|
||||
p { margin: 0 0 0.875rem; color: var(--text); }
|
||||
|
||||
223
assets/files/gitea/forge
Normal file
223
assets/files/gitea/forge
Normal file
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ===========================
|
||||
# SoloForge Toolkit (forge)
|
||||
# ===========================
|
||||
#
|
||||
# Handy commands for managing:
|
||||
# - Gitea (SoloForge)
|
||||
# - Gitea Actions runner
|
||||
# - Backups
|
||||
#
|
||||
# Paths: adjust if you move things.
|
||||
GITEA_DIR="/gitea"
|
||||
RUNNER_DIR="/gitea/gitea-runner"
|
||||
BACKUP_DIR="/gitea/backups"
|
||||
|
||||
GITEA_COMPOSE="$GITEA_DIR/docker-compose.yml"
|
||||
RUNNER_COMPOSE="$RUNNER_DIR/docker-compose.yml"
|
||||
|
||||
GITEA_CONTAINER_NAME="Gitea"
|
||||
RUNNER_CONTAINER_NAME="gitea-act-runner"
|
||||
|
||||
# Prefer `docker compose`, fall back to `docker-compose` if needed
|
||||
dc() {
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
docker compose "$@"
|
||||
elif command -v docker-compose >/dev/null 2>&1; then
|
||||
docker-compose "$@"
|
||||
else
|
||||
echo "Error: neither 'docker compose' nor 'docker-compose' found." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: docker not found in PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
SoloForge toolkit
|
||||
|
||||
Usage:
|
||||
forge status Show status of Gitea and runner containers
|
||||
forge ps Alias for status
|
||||
forge gitea-logs Tail logs from Gitea container
|
||||
forge runner-logs Tail logs from Actions runner container
|
||||
forge backup Run a Gitea dump and move it into BACKUP_DIR
|
||||
forge restart-gitea Restart Gitea stack
|
||||
forge restart-runner Restart Actions runner stack
|
||||
forge runner-reset Re-register runner with current labels (destroys .runner)
|
||||
forge diag Quick diagnostic summary
|
||||
|
||||
Config:
|
||||
GITEA_DIR = $GITEA_DIR
|
||||
RUNNER_DIR = $RUNNER_DIR
|
||||
BACKUP_DIR = $BACKUP_DIR
|
||||
EOF
|
||||
}
|
||||
|
||||
ensure_dirs() {
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
echo "== Docker containers =="
|
||||
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' \
|
||||
| grep -E "$GITEA_CONTAINER_NAME|$RUNNER_CONTAINER_NAME" || true
|
||||
echo
|
||||
echo "== Gitea compose =="
|
||||
echo " $GITEA_COMPOSE"
|
||||
echo "== Runner compose =="
|
||||
echo " $RUNNER_COMPOSE"
|
||||
}
|
||||
|
||||
cmd_gitea_logs() {
|
||||
echo "Tailing logs for container: $GITEA_CONTAINER_NAME"
|
||||
docker logs -f "$GITEA_CONTAINER_NAME"
|
||||
}
|
||||
|
||||
cmd_runner_logs() {
|
||||
echo "Tailing logs for container: $RUNNER_CONTAINER_NAME"
|
||||
docker logs -f "$RUNNER_CONTAINER_NAME"
|
||||
}
|
||||
|
||||
cmd_backup() {
|
||||
ensure_dirs
|
||||
|
||||
echo "Running Gitea dump inside container: $GITEA_CONTAINER_NAME"
|
||||
# Gitea dump will write gitea-dump-*.zip under /data which is mounted to $GITEA_DIR/gitea
|
||||
docker exec -u 1000 "$GITEA_CONTAINER_NAME" gitea dump -c /data/gitea/conf/app.ini
|
||||
|
||||
echo "Locating latest dump..."
|
||||
local dump
|
||||
dump=$(ls -1t "$GITEA_DIR"/gitea/gitea-dump-*.zip 2>/dev/null | head -n1 || true)
|
||||
|
||||
if [[ -z "$dump" ]]; then
|
||||
echo "Error: no gitea-dump-*.zip found under $GITEA_DIR/gitea" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local base
|
||||
base=$(basename "$dump")
|
||||
local dest="$BACKUP_DIR/$base"
|
||||
|
||||
echo "Moving $dump -> $dest"
|
||||
mv "$dump" "$dest"
|
||||
|
||||
echo "Backup complete: $dest"
|
||||
}
|
||||
|
||||
cmd_restart_gitea() {
|
||||
echo "Restarting Gitea stack (compose: $GITEA_COMPOSE)"
|
||||
(cd "$GITEA_DIR" && dc -f "$GITEA_COMPOSE" down)
|
||||
(cd "$GITEA_DIR" && dc -f "$GITEA_COMPOSE" up -d)
|
||||
echo "Gitea stack restarted."
|
||||
}
|
||||
|
||||
cmd_restart_runner() {
|
||||
echo "Restarting runner stack (compose: $RUNNER_COMPOSE)"
|
||||
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" down || true)
|
||||
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" up -d)
|
||||
echo "Runner stack restarted."
|
||||
}
|
||||
|
||||
cmd_runner_reset() {
|
||||
echo "Resetting runner registration..."
|
||||
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" down || true)
|
||||
|
||||
local runner_state="$RUNNER_DIR/data/.runner"
|
||||
if [[ -f "$runner_state" ]]; then
|
||||
echo "Removing $runner_state"
|
||||
rm -f "$runner_state"
|
||||
else
|
||||
echo "No existing .runner file found (nothing to delete)."
|
||||
fi
|
||||
|
||||
echo "Bringing runner stack up (will auto-register with current env)..."
|
||||
(cd "$RUNNER_DIR" && dc -f "$RUNNER_COMPOSE" up -d)
|
||||
|
||||
echo "Runner reset requested. Check runner logs via: forge runner-logs"
|
||||
}
|
||||
|
||||
cmd_diag() {
|
||||
echo "== SoloForge diagnostic =="
|
||||
echo
|
||||
echo "-- Paths --"
|
||||
echo "GITEA_DIR = $GITEA_DIR"
|
||||
echo "RUNNER_DIR = $RUNNER_DIR"
|
||||
echo "BACKUP_DIR = $BACKUP_DIR"
|
||||
echo
|
||||
|
||||
echo "-- Containers --"
|
||||
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' \
|
||||
| grep -E "$GITEA_CONTAINER_NAME|$RUNNER_CONTAINER_NAME" || echo " (no Gitea/runner containers found)"
|
||||
echo
|
||||
|
||||
echo "-- Runner .runner file --"
|
||||
if [[ -f "$RUNNER_DIR/data/.runner" ]]; then
|
||||
echo "Found: $RUNNER_DIR/data/.runner"
|
||||
else
|
||||
echo "No .runner file present (runner may not be registered)."
|
||||
fi
|
||||
echo
|
||||
|
||||
echo "-- Latest backup --"
|
||||
if [[ -d "$BACKUP_DIR" ]]; then
|
||||
local latest
|
||||
latest=$(ls -1t "$BACKUP_DIR"/gitea-dump-*.zip 2>/dev/null | head -n1 || true)
|
||||
if [[ -n "$latest" ]]; then
|
||||
echo " $latest"
|
||||
else
|
||||
echo " No backups found in $BACKUP_DIR"
|
||||
fi
|
||||
else
|
||||
echo " Backup dir does not exist: $BACKUP_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
# ===========================
|
||||
# Dispatch
|
||||
# ===========================
|
||||
|
||||
cmd="${1:-help}"
|
||||
shift || true
|
||||
|
||||
case "$cmd" in
|
||||
status|ps)
|
||||
cmd_status
|
||||
;;
|
||||
gitea-logs)
|
||||
cmd_gitea_logs
|
||||
;;
|
||||
runner-logs)
|
||||
cmd_runner_logs
|
||||
;;
|
||||
backup)
|
||||
cmd_backup
|
||||
;;
|
||||
restart-gitea)
|
||||
cmd_restart_gitea
|
||||
;;
|
||||
restart-runner)
|
||||
cmd_restart_runner
|
||||
;;
|
||||
runner-reset)
|
||||
cmd_runner_reset
|
||||
;;
|
||||
diag)
|
||||
cmd_diag
|
||||
;;
|
||||
help|--help|-h)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $cmd" >&2
|
||||
echo
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
92
assets/files/gitea/git-bulk
Normal file
92
assets/files/gitea/git-bulk
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ---- Config: set these at the top ----
|
||||
GITEA_BASE="https://git.keithsolomon.net"
|
||||
GITEA_USER="keith"
|
||||
GITEA_TOKEN="f4b01fa50f56271f8ba002221fb68404de955077"
|
||||
REMOTE_NAME="origin" # which remote to use/push
|
||||
SEARCH_ROOT="${1:-$PWD}" # default to current dir if not provided
|
||||
|
||||
# sanity checks
|
||||
command -v jq >/dev/null 2>&1 || { echo "jq is required. Install with: sudo apt install jq"; exit 1; }
|
||||
|
||||
: "${GITEA_BASE:?Need GITEA_BASE}"
|
||||
: "${GITEA_USER:?Need GITEA_USER}"
|
||||
: "${GITEA_TOKEN:?Need GITEA_TOKEN}"
|
||||
|
||||
SEARCH_ROOT="$(realpath "$SEARCH_ROOT")"
|
||||
|
||||
echo "Scanning for git repos under: $SEARCH_ROOT"
|
||||
echo
|
||||
|
||||
# Use -print0 to safely handle spaces/newlines in paths
|
||||
find "$SEARCH_ROOT" -type d -name ".git" -print0 | while IFS= read -r -d '' gitdir; do
|
||||
# Absolute repo dir
|
||||
repo_dir="$(dirname "$gitdir")"
|
||||
repo_dir="$(realpath "$repo_dir")"
|
||||
|
||||
echo "=== $repo_dir ==="
|
||||
|
||||
# Run everything for this repo in a subshell so cd doesn't affect the outer loop
|
||||
(
|
||||
cd "$repo_dir" || exit 0
|
||||
|
||||
# Get origin URL (or skip if none)
|
||||
if ! url=$(git remote get-url "$REMOTE_NAME" 2>/dev/null); then
|
||||
echo " -> Skipping (no '$REMOTE_NAME' remote)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only migrate repos that point (or pointed) at git.keithsolomon.net
|
||||
if [[ "$url" != *"git.keithsolomon.net"* ]]; then
|
||||
echo " -> Skipping (origin is not git.keithsolomon.net)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Derive repo name: strip .git and take basename
|
||||
clean_url="${url%.git}"
|
||||
repo_name="$(basename "$clean_url")"
|
||||
|
||||
echo " -> Repo name on Gitea: $repo_name"
|
||||
|
||||
# Create repo via Gitea API (idempotent: 201 = created, 409 = already exists)
|
||||
create_payload=$(jq -n \
|
||||
--arg name "$repo_name" \
|
||||
--arg desc "Imported from local clone at $repo_dir" \
|
||||
'{name: $name, private: true, description: $desc}')
|
||||
|
||||
http_status=$(
|
||||
curl -sS -o /tmp/gitea-create-repo.out \
|
||||
-w "%{http_code}" \
|
||||
-X POST "$GITEA_BASE/api/v1/user/repos" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-d "$create_payload" || echo "000"
|
||||
)
|
||||
|
||||
if [[ "$http_status" == "201" ]]; then
|
||||
echo " -> Created repo on Gitea"
|
||||
elif [[ "$http_status" == "409" ]]; then
|
||||
echo " -> Repo already exists on Gitea, continuing"
|
||||
else
|
||||
echo " !! Unexpected HTTP status $http_status creating repo, output:"
|
||||
cat /tmp/gitea-create-repo.out
|
||||
echo " !! Skipping push for $repo_dir"
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Make sure origin URL points at the new Gitea HTTPS remote
|
||||
new_url="$GITEA_BASE/$GITEA_USER/$repo_name.git"
|
||||
git remote set-url "$REMOTE_NAME" "$new_url" 2>/dev/null || git remote add "$REMOTE_NAME" "$new_url"
|
||||
|
||||
echo " -> Pushing all branches..."
|
||||
git push --all "$REMOTE_NAME" || echo " !! Failed pushing branches"
|
||||
echo " -> Pushing tags..."
|
||||
git push --tags "$REMOTE_NAME" || echo " !! Failed pushing tags"
|
||||
echo
|
||||
)
|
||||
done
|
||||
|
||||
echo "Done."
|
||||
26
assets/files/gitea/git-flip
Normal file
26
assets/files/gitea/git-flip
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_BASE="https://git.keithsolomon.net"
|
||||
GITEA_USER="keith" # your username
|
||||
GITEA_TOKEN="f4b01fa50f56271f8ba002221fb68404de955077"
|
||||
|
||||
echo "Fetching repos for user: $GITEA_USER"
|
||||
repos=$(curl -sS \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
"$GITEA_BASE/api/v1/users/$GITEA_USER/repos" | jq -r '.[].name')
|
||||
|
||||
for repo in $repos; do
|
||||
echo "Setting $repo → public..."
|
||||
|
||||
curl -sS -X PATCH \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"private":false}' \
|
||||
"$GITEA_BASE/api/v1/repos/$GITEA_USER/$repo" \
|
||||
>/dev/null
|
||||
|
||||
echo "✓ $repo is now public"
|
||||
done
|
||||
|
||||
echo "Done."
|
||||
65
assets/files/gitea/gitea-docker-compose.yml
Normal file
65
assets/files/gitea/gitea-docker-compose.yml
Normal file
@@ -0,0 +1,65 @@
|
||||
services:
|
||||
gitea-db:
|
||||
container_name: Gitea-db
|
||||
restart: unless-stopped
|
||||
image: postgres:16
|
||||
|
||||
environment:
|
||||
POSTGRES_USER: gitea
|
||||
POSTGRES_PASSWORD: <change-me>
|
||||
POSTGRES_DB: gitea
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
volumes:
|
||||
- /gitea/postgres:/var/lib/postgresql/data
|
||||
|
||||
gitea:
|
||||
container_name: Gitea
|
||||
restart: unless-stopped
|
||||
image: gitea/gitea:latest
|
||||
|
||||
depends_on:
|
||||
gitea-db:
|
||||
condition: service_healthy
|
||||
|
||||
environment:
|
||||
USER_UID: 1000
|
||||
USER_GID: 1000
|
||||
GITEA__database__DB_TYPE: postgres
|
||||
GITEA__database__HOST: gitea-db:5432
|
||||
GITEA__database__NAME: gitea
|
||||
GITEA__database__USER: gitea
|
||||
GITEA__database__PASSWD: <change-me>
|
||||
GITEA__server__DOMAIN: git.keithsolomon.net
|
||||
GITEA__server__ROOT_URL: https://git.keithsolomon.net/
|
||||
GITEA__server__SSH_PORT: 222
|
||||
GITEA__log__MODE: console
|
||||
|
||||
volumes:
|
||||
- /gitea/gitea:/data
|
||||
|
||||
networks:
|
||||
- frontend
|
||||
|
||||
ports:
|
||||
- "3000:3000" # HTTP web UI
|
||||
- "222:22" # SSH for git over SSH
|
||||
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=frontend"
|
||||
- "traefik.http.routers.gitea.rule=Host(`git.keithsolomon.net`)"
|
||||
- "traefik.http.services.gitea.loadbalancer.server.port=3000" # Gitea's default web port
|
||||
|
||||
networks:
|
||||
frontend:
|
||||
external: true
|
||||
name: frontend
|
||||
20
assets/files/gitea/gitea-runner-docker-compose.yml
Normal file
20
assets/files/gitea/gitea-runner-docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
runner:
|
||||
container_name: gitea-act-runner
|
||||
restart: unless-stopped
|
||||
image: gitea/act_runner:latest
|
||||
|
||||
working_dir: /data
|
||||
|
||||
environment:
|
||||
GITEA_INSTANCE_URL: "https://git.keithsolomon.net"
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: "vmdpAbiuPoaX8pEmzZkJjUJpIlI4iIIlXGGEBU5n"
|
||||
GITEA_RUNNER_NAME: "hetzner-runner-1"
|
||||
GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bullseye,self-hosted,linux,x86_64,docker"
|
||||
TZ: "America/Winnipeg"
|
||||
|
||||
command: ["act_runner", "daemon"]
|
||||
|
||||
volumes:
|
||||
- /gitea/gitea-runner/data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
254
content/soloforge-docs.md
Normal file
254
content/soloforge-docs.md
Normal file
@@ -0,0 +1,254 @@
|
||||
---
|
||||
title: SoloForge Documentation
|
||||
section: docs
|
||||
summary: SoloForge Infrastructure Documentation for Self-Hosted Gitea with Actions Runner on Hetzner
|
||||
tags: [servers, infrasctructure, gitea, ci/cd, hetzner, docker]
|
||||
nav: 1
|
||||
---
|
||||
|
||||
## Self-Hosted Gitea + Actions Runner (Hetzner Deployment)
|
||||
|
||||
This document describes the current SoloForge setup — Gitea, Traefik routing, the Gitea Actions runner, backup routines, directory layout, and operational notes.
|
||||
|
||||
This repo exists so future-me (and actual-me) don’t need to reverse-engineer anything when something eventually explodes.
|
||||
|
||||
## 1. Overview
|
||||
|
||||
SoloForge is a self-hosted Gitea instance running on a Hetzner VM.
|
||||
|
||||
It provides:
|
||||
|
||||
- Private (but login-protected) Git hosting
|
||||
- Public-visible repositories (instance auth is required anyway)
|
||||
- Gitea Actions, backed by a Docker-based self-hosted runner
|
||||
- Automatic CI for TODO-to-issue sync and other workflows
|
||||
- Reverse-proxy via Traefik
|
||||
- Automated Gitea backups
|
||||
|
||||
The system replaces the original Proxmox-hosted Gitea instance lost due to disk failure.
|
||||
|
||||
---
|
||||
|
||||
## 2. System Layout
|
||||
|
||||
### Hetzner VM
|
||||
|
||||
- Debian 13
|
||||
- Docker + Docker Compose installed
|
||||
- Traefik reverse proxy (existing before SoloForge migration)
|
||||
- HTTPS termination handled by Traefik via Let’s Encrypt
|
||||
|
||||
### Directory structure
|
||||
|
||||
```bash
|
||||
/gitea/
|
||||
├── gitea/ # Gitea app + persistent data
|
||||
├── postgres/ # PostgreSQL data directory (if using Postgres)
|
||||
└── docker-compose.yml # Main Gitea stack
|
||||
```
|
||||
|
||||
### Runner lives separately
|
||||
|
||||
```bash
|
||||
/gitea/gitea-runner/
|
||||
├── docker-compose.yml # Actions runner stack
|
||||
└── data/ # Contains .runner registration + job cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Gitea Deployment
|
||||
|
||||
### 3.1 Gitea Compose Service
|
||||
|
||||
Gitea is launched via Docker Compose and reverse-proxied through Traefik.
|
||||
Data lives under `/gitea/gitea` to ensure persistence.
|
||||
|
||||
### 3.2 Traefik Routing
|
||||
|
||||
Traefik handles:
|
||||
|
||||
- HTTPS certificate generation
|
||||
- Routing git.keithsolomon.net → Gitea web UI
|
||||
- Exposing SSH port (222) for git-over-SSH
|
||||
|
||||
No YAML generator required anymore — everything is stable and hand-maintained.
|
||||
|
||||
---
|
||||
|
||||
## 4. Gitea Actions Runner
|
||||
|
||||
SoloForge uses a self-hosted Gitea Actions runner, running via Docker and capable of executing JavaScript (Node-based) GitHub-style actions.
|
||||
|
||||
### 4.1 Runner compose file
|
||||
|
||||
Located at:
|
||||
|
||||
`/gitea/gitea-runner/docker-compose.yml`
|
||||
|
||||
Core configuration:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
GITEA_INSTANCE_URL: "https://git.keithsolomon.net"
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN: "<token>"
|
||||
GITEA_RUNNER_NAME: "hetzner-runner-1"
|
||||
GITEA_RUNNER_LABELS: "ubuntu-latest:docker://node:20-bullseye,self-hosted,linux,x86_64,docker"
|
||||
```
|
||||
|
||||
By default, GitHub runners provide Node.js preinstalled.
|
||||
Self-hosted runners do NOT.
|
||||
|
||||
Mapping:
|
||||
|
||||
`ubuntu-latest:docker://node:20-bullseye`
|
||||
|
||||
ensures any workflow using:
|
||||
|
||||
```yaml
|
||||
runs-on: ubuntu-latest
|
||||
```
|
||||
|
||||
runs inside a Node-enabled container, fixing "node: command not found" errors.
|
||||
|
||||
### 4.3 Re-registering the runner (important!)
|
||||
|
||||
If labels change or the runner breaks:
|
||||
|
||||
```bash
|
||||
cd /gitea/gitea-runner
|
||||
docker compose down
|
||||
rm -f data/.runner # Forces new registration
|
||||
docker compose up -d # Registers with current labels
|
||||
```
|
||||
|
||||
Check runner status in Gitea:
|
||||
|
||||
**Site Admin → Actions → Runners**
|
||||
|
||||
---
|
||||
|
||||
## 5. Workflows
|
||||
|
||||
### 5.1 TODO-to-Issue Sync
|
||||
|
||||
Certain repos use a custom JavaScript action to:
|
||||
|
||||
- Parse TODO comments
|
||||
- Generate/close GitHub-style issues inside Gitea
|
||||
|
||||
These workflows run cleanly now because:
|
||||
|
||||
- The runner supports Node (ubuntu-latest → node:20 container)
|
||||
- Repository permissions allow issue writing
|
||||
|
||||
### 5.2 Secret tokens
|
||||
|
||||
Unlike GitHub, Gitea does not auto-inject GITHUB_TOKEN.
|
||||
Workflows requiring an auth token need one defined manually in:
|
||||
|
||||
**Repo → Settings → Secrets**
|
||||
|
||||
Example:
|
||||
|
||||
`GITHUB_TOKEN = <personal access token>`
|
||||
|
||||
(Or rename to something more Gitea-themed.)
|
||||
|
||||
---
|
||||
|
||||
## 6. Repository Management
|
||||
|
||||
### 6.1 Bulk import
|
||||
|
||||
All repos were migrated using a [custom bulk-mirror script](/assets/files/gitea/git-bulk) that:
|
||||
|
||||
- Created missing repos via the Gitea API
|
||||
- Pushed full history via git push --all and --tags
|
||||
|
||||
### 6.2 Public visibility
|
||||
|
||||
All repos are public (since Gitea login protects everything).
|
||||
A [bulk-update script](/assets/files/gitea/git-flip) is available to flip visibility via API if needed.
|
||||
|
||||
---
|
||||
|
||||
## 7. Backups
|
||||
|
||||
Gitea supports built-in dumps via:
|
||||
|
||||
`gitea dump`
|
||||
|
||||
A cronjob is installed to dump nightly at 3am:
|
||||
|
||||
```bash
|
||||
/gitea-backups/
|
||||
└── gitea-dump-YYYYMMDD.zip
|
||||
```
|
||||
|
||||
Recommended: sync this folder offsite or back to home lab.
|
||||
|
||||
---
|
||||
|
||||
## 8. Restore Notes
|
||||
|
||||
If Gitea must be restored from dump:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
rm -rf gitea/* postgres/*
|
||||
unzip gitea-dump.zip into /gitea/gitea
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
If the runner needs re-registration, follow section 4.3.
|
||||
|
||||
---
|
||||
|
||||
## 9. Future Improvements (Optional)
|
||||
|
||||
- Mirror “source of truth” repos between GitHub ↔ Gitea
|
||||
- Add automated org-level secrets
|
||||
- Configure multiple runners (home lab, Hetzner, etc.)
|
||||
- Add Prometheus metrics + Grafana board for CI activity
|
||||
- Set up Gitea’s dependency listing or vulnerability scanning
|
||||
|
||||
---
|
||||
|
||||
## 10. `forge` CLI Tool
|
||||
|
||||
A custom CLI tool [`forge`](/assets/files/gitea/forge) exists to help manage common tasks:
|
||||
|
||||
| Command | Description |
|
||||
| --------------------------- | ---------------------------------------------------------- |
|
||||
| `forge status` | Show status of Gitea and runner containers |
|
||||
| `forge ps` | Alias for status |
|
||||
| `forge gitea-logs` | Tail logs from Gitea container |
|
||||
| `forge runner-logs` | Tail logs from Actions runner container |
|
||||
| `forge backup` | Run a Gitea dump and move it into BACKUP_DIR |
|
||||
| `forge restart-gitea` | Restart Gitea stack |
|
||||
| `forge restart-runner` | Restart Actions runner stack |
|
||||
| `forge runner-reset` | Re-register runner with current labels (destroys .runner) |
|
||||
| `forge diag` | Quick diagnostic summary |
|
||||
|
||||
## TL;DR Cheat Sheet
|
||||
|
||||
### Runner broke?
|
||||
|
||||
`→ delete data/.runner, docker compose up -d`
|
||||
|
||||
### `node` not found?
|
||||
|
||||
`→ ensure ubuntu-latest label is mapped to node:20-bullseye`
|
||||
|
||||
### Release workflows failing?
|
||||
|
||||
`→ they're GitHub-only; they run on GitHub mirrors`
|
||||
|
||||
### Backup?
|
||||
|
||||
`→ see /gitea-backups, nightly gitea dump`
|
||||
|
||||
### Repo not found?
|
||||
|
||||
`→ bulk import script: auto-create + push mirror`
|
||||
Reference in New Issue
Block a user