#!/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" <"$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" <&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 "$@"