#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