Files
foxhunt/docs/infra/cloud-init-git-server.yaml
jgrusewski 30a96db895 infra: update cloud-init with lessons from Gitea deployment
Fixes discovered during live provisioning: app.ini permissions
(root:gitea + 660→640 for token generation), HTTPS via acme.sh
DNS-01, setcap for port 443, admin user creation, INSTALL_LOCK
lifecycle. Secrets replaced with placeholders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:23:30 +01:00

179 lines
5.5 KiB
YAML

#cloud-config
#
# Foxhunt Dev Git Server (Scaleway DEV1-S, nl-ams-1)
#
# Prerequisites:
# - Scaleway security group: drop all inbound (Tailscale-only access)
# - Public IP attached (Tailscale needs outbound internet for WireGuard tunnel)
# - DNS: git.fxhnt.ai A record pointing to Tailscale IP
# - Scaleway DNS API token for Let's Encrypt DNS-01 challenge
#
# Lessons learned:
# - Tailscale REQUIRES outbound internet — cannot remove public IP entirely
# - Gitea needs write access to app.ini on first start (generates internal token)
# - setcap needed for non-root port 443 binding
# - Config file must be owned root:gitea with 660 on first boot, then 640 after
#
package_update: true
package_upgrade: true
packages:
- git
- sqlite3
- curl
- wget
- socat
write_files:
- path: /etc/gitea/app.ini
# 660 on first boot so Gitea can write INTERNAL_TOKEN and SECRET_KEY
# A post-start step tightens this to 640 after tokens are generated
permissions: '0660'
content: |
[server]
PROTOCOL = https
DOMAIN = git.fxhnt.ai
ROOT_URL = https://git.fxhnt.ai/
HTTP_PORT = 443
SSH_DOMAIN = git.fxhnt.ai
START_SSH_SERVER = true
SSH_PORT = 2222
SSH_LISTEN_PORT = 2222
DISABLE_SSH = false
LFS_START_SERVER = true
[database]
DB_TYPE = sqlite3
PATH = /var/lib/gitea/data/gitea.db
[repository]
ROOT = /var/lib/gitea/repositories
[lfs]
PATH = /var/lib/gitea/data/lfs
[log]
ROOT_PATH = /var/lib/gitea/log
MODE = file
LEVEL = Info
[service]
DISABLE_REGISTRATION = true
[security]
INSTALL_LOCK = false
runcmd:
# --- Tailscale ---
- curl -fsSL https://tailscale.com/install.sh | sh
- tailscale up --authkey=TAILSCALE_AUTH_KEY_PLACEHOLDER --hostname=vm-fxhnt-git --ssh
# Wait for Tailscale to get an IP (up to 60s)
- |
for i in $(seq 1 30); do
TS_IP=$(tailscale ip -4 2>/dev/null)
if [ -n "$TS_IP" ]; then
echo "Tailscale IP: $TS_IP"
break
fi
sleep 2
done
# Bind Gitea to Tailscale interface only and set TLS cert paths
- |
TS_IP=$(tailscale ip -4)
sed -i "/^HTTP_PORT/i HTTP_ADDR = ${TS_IP}" /etc/gitea/app.ini
# Add cert paths (populated later by acme.sh)
sed -i "/^LFS_START_SERVER/a CERT_FILE = /etc/gitea/ssl/fullchain.pem\nKEY_FILE = /etc/gitea/ssl/key.pem" /etc/gitea/app.ini
# --- Gitea user and directories ---
- adduser --system --shell /bin/bash --gecos 'Gitea' --group --disabled-password --home /home/gitea gitea
- mkdir -p /var/lib/gitea/{custom,data,log,repositories}
- chown -R gitea:gitea /var/lib/gitea
- chmod -R 750 /var/lib/gitea
- mkdir -p /etc/gitea
- chown root:gitea /etc/gitea
- chmod 770 /etc/gitea
# Fix app.ini ownership (write_files creates as root:root)
- chown root:gitea /etc/gitea/app.ini
# --- Download and install Gitea ---
- |
GITEA_VERSION=$(curl -s https://api.github.com/repos/go-gitea/gitea/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//')
wget -O /usr/local/bin/gitea "https://dl.gitea.com/gitea/${GITEA_VERSION}/gitea-${GITEA_VERSION}-linux-amd64"
chmod +x /usr/local/bin/gitea
# Allow Gitea to bind to port 443 without root
- setcap 'cap_net_bind_service=+ep' /usr/local/bin/gitea
# --- TLS cert via acme.sh + Scaleway DNS-01 ---
- |
mkdir -p /etc/gitea/ssl
curl https://get.acme.sh | sh -s email=admin@fxhnt.ai
export SCW_ACCESS_KEY=SCW_ACCESS_KEY_PLACEHOLDER
export SCW_SECRET_KEY=SCW_SECRET_KEY_PLACEHOLDER
/root/.acme.sh/acme.sh --issue --dns dns_scaleway -d git.fxhnt.ai \
--keylength ec-256 --server letsencrypt
/root/.acme.sh/acme.sh --install-cert -d git.fxhnt.ai --ecc \
--cert-file /etc/gitea/ssl/cert.pem \
--key-file /etc/gitea/ssl/key.pem \
--fullchain-file /etc/gitea/ssl/fullchain.pem \
--reloadcmd "systemctl restart gitea"
chown -R gitea:gitea /etc/gitea/ssl
chmod 600 /etc/gitea/ssl/key.pem
# --- Systemd service ---
- |
cat > /etc/systemd/system/gitea.service << 'UNIT'
[Unit]
Description=Gitea (git.fxhnt.ai)
After=syslog.target network.target tailscaled.service
[Service]
RestartSec=2s
Type=simple
User=gitea
Group=gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=gitea HOME=/home/gitea GITEA_WORK_DIR=/var/lib/gitea
[Install]
WantedBy=multi-user.target
UNIT
- systemctl daemon-reload
- systemctl enable gitea
# --- First start: let Gitea generate INTERNAL_TOKEN and SECRET_KEY ---
- systemctl start gitea
- |
# Wait for Gitea to write tokens (up to 30s)
for i in $(seq 1 15); do
if grep -q 'INTERNAL_TOKEN' /etc/gitea/app.ini 2>/dev/null; then
echo "Gitea tokens generated"
break
fi
sleep 2
done
# Tighten config permissions after token generation
- chmod 640 /etc/gitea/app.ini
# Set INSTALL_LOCK now that first boot is done
- sed -i 's/INSTALL_LOCK = false/INSTALL_LOCK = true/' /etc/gitea/app.ini
- systemctl restart gitea
# --- Create admin user ---
- |
sleep 3
su -c 'GITEA_WORK_DIR=/var/lib/gitea /usr/local/bin/gitea admin user create \
--config /etc/gitea/app.ini \
--username foxhunt-admin \
--password "ADMIN_PASSWORD_PLACEHOLDER" \
--email admin@fxhnt.ai \
--admin' - gitea
# Signal completion
- echo "PROVISIONING_COMPLETE" > /var/log/cloud-init-done