testsAndMisc/linux_configuration/features/setup_gitea.sh
Krzysztof kuhy Rudnicki 57a070e7d9 Remove SyncYomi and TachiyomiSY traces
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:37:04 +01:00

199 lines
6.3 KiB
Bash
Executable File

#!/bin/bash
# Self-hosted Gitea git server, mirroring GitHub repos, exposed publicly via
# DuckDNS + Caddy automatic HTTPS.
#
# Companion to setup_wireguard_ssh.sh, which owns /etc/nftables.conf and
# provides the 'allow-web' firewall rule this script depends on.
#
# DNS note: kuhy.duckdns.org is kept updated by an existing cron job installed
# by install_joplin.sh (~/.joplin-server/duckdns-update.sh) -- this script
# does not manage DuckDNS itself.
#
# Networking note: Gitea and Caddy run with `network_mode: host`, not a
# Docker bridge network. This host's custom nftables ruleset has a
# default-drop FORWARD chain with no rules; Docker's own forwarding rules
# (separate iptables-legacy tables) don't override it, since both hook the
# same netfilter point and a DROP verdict from either is terminal. That
# silently blocks all bridge-networked container egress -- breaking both
# Caddy's ACME renewal and Gitea's GitHub pull-mirroring. Host networking
# sidesteps the FORWARD chain entirely (uses INPUT/OUTPUT only, both already
# permissive) without touching the shared firewall script. Gitea binds to
# 127.0.0.1:3000 only -- never exposed directly, only reachable via Caddy.
#
# Usage:
# ./setup_gitea.sh setup - full first-time setup (idempotent, re-runnable)
# ./setup_gitea.sh status - show container + certificate status
# ./setup_gitea.sh help
set -euo pipefail
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
readonly GITEA_DOMAIN="gitea.kuhy.duckdns.org"
readonly GITEA_ADMIN_USER="kuhyx"
readonly GITEA_ADMIN_EMAIL="krzysztofrudnicki0@gmail.com"
readonly GITEA_DATA_DIR="${HOME}/services/gitea"
readonly COMPOSE_FILE="${GITEA_DATA_DIR}/docker-compose.yml"
readonly CADDYFILE="${GITEA_DATA_DIR}/Caddyfile"
# Per-service Caddy site blocks live here as individual *.caddy snippets so
# adding/removing a service never clobbers another's block (see the root
# Caddyfile's `import` below). Every service on this edge owns one file.
readonly SITES_DIR="${GITEA_DATA_DIR}/sites"
readonly ADMIN_PASSWORD_FILE="${GITEA_DATA_DIR}/.admin_password"
readonly ADMIN_TOKEN_FILE="${GITEA_DATA_DIR}/.admin_token"
readonly WIREGUARD_SCRIPT="${SCRIPT_DIR}/setup_wireguard_ssh.sh"
die() {
log_error "$1"
exit 1
}
write_compose_files() {
ensure_dir "$GITEA_DATA_DIR"
cat >"$COMPOSE_FILE" <<EOF
services:
gitea:
image: gitea/gitea:1.22.3
container_name: gitea
restart: unless-stopped
network_mode: host
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__server__DOMAIN=${GITEA_DOMAIN}
- GITEA__server__ROOT_URL=https://${GITEA_DOMAIN}/
- GITEA__server__HTTP_ADDR=127.0.0.1
- GITEA__server__HTTP_PORT=3000
- GITEA__server__DISABLE_SSH=true
- GITEA__service__DISABLE_REGISTRATION=true
- GITEA__service__REGISTER_EMAIL_CONFIRM=false
- GITEA__security__INSTALL_LOCK=true
- GITEA__mirror__ENABLED=true
- GITEA__mirror__DEFAULT_INTERVAL=10m
- GITEA__mirror__MIN_INTERVAL=10m
volumes:
- ./gitea-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
caddy:
image: caddy:2.8
container_name: gitea-caddy
restart: unless-stopped
network_mode: host
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./sites:/etc/caddy/sites:ro
- caddy-data:/data
- caddy-config:/config
volumes:
caddy-data:
caddy-config:
EOF
# The root Caddyfile only imports per-service snippets; every service
# (gitea, dufs, the personal website, ...) owns its own file in
# sites/ so re-running any one setup script never drops another's block.
ensure_dir "$SITES_DIR"
cat >"$CADDYFILE" <<'EOF'
# Managed by setup_gitea.sh — do not edit by hand.
# Each service on this edge owns one snippet in sites/*.caddy.
import /etc/caddy/sites/*.caddy
EOF
cat >"${SITES_DIR}/gitea.caddy" <<EOF
# Managed by setup_gitea.sh — do not edit by hand.
${GITEA_DOMAIN} {
reverse_proxy 127.0.0.1:3000
}
EOF
log_ok "Wrote ${COMPOSE_FILE}, ${CADDYFILE}, and ${SITES_DIR}/gitea.caddy."
}
start_containers() {
docker compose -f "$COMPOSE_FILE" up -d
}
wait_for_gitea() {
log_info "Waiting for Gitea to become ready..."
local attempt logs
for ((attempt = 1; attempt <= 60; attempt++)); do
# Capture into a variable rather than piping straight into
# `grep -q`: grep quits at the first match, SIGPIPE-ing `docker
# logs` before it finishes writing, and under `pipefail` that
# upstream non-zero exit clobbers grep's own success -- the `if`
# never sees a match even though one is right there in the log.
logs=$(docker logs gitea 2>&1)
if grep -q "Listen: http" <<<"$logs"; then
log_ok "Gitea is ready."
return 0
fi
sleep 2
done
die "Gitea did not become ready within 2 minutes -- check 'docker logs gitea'."
}
bootstrap_admin() {
if docker exec -u git gitea gitea admin user list 2>/dev/null | grep -q "$GITEA_ADMIN_USER"; then
log_info "Admin user '${GITEA_ADMIN_USER}' already exists -- not recreating."
return 0
fi
umask 077
openssl rand -base64 24 >"$ADMIN_PASSWORD_FILE"
chmod 600 "$ADMIN_PASSWORD_FILE"
docker exec -u git gitea gitea admin user create \
--username "$GITEA_ADMIN_USER" --password "$(cat "$ADMIN_PASSWORD_FILE")" \
--email "$GITEA_ADMIN_EMAIL" --admin --must-change-password=false
log_ok "Created admin user '${GITEA_ADMIN_USER}'. Password saved to ${ADMIN_PASSWORD_FILE} (chmod 600)."
}
mint_api_token() {
if [[ -f $ADMIN_TOKEN_FILE ]]; then
log_info "API token already exists at ${ADMIN_TOKEN_FILE} -- not regenerating."
return 0
fi
umask 077
docker exec -u git gitea gitea admin user generate-access-token \
--username "$GITEA_ADMIN_USER" --token-name automation --scopes all --raw >"$ADMIN_TOKEN_FILE"
chmod 600 "$ADMIN_TOKEN_FILE"
log_ok "API token saved to ${ADMIN_TOKEN_FILE} (chmod 600)."
}
SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
# shellcheck source=lib/gitea_network.sh
source "$SCRIPT_DIR/lib/gitea_network.sh"
main() {
local cmd="${1:-help}"
case "$cmd" in
setup)
write_compose_files
start_containers
wait_for_gitea
bootstrap_admin
mint_api_token
open_firewall
attempt_upnp
print_router_instructions
log_ok "Gitea setup complete."
;;
status)
status_cmd
;;
help | -h | --help)
usage
;;
*)
log_error "Unknown command: $cmd"
usage
exit 1
;;
esac
}
main "$@"