c-forge/forgejo-runner-bootstrap
3
0
Fork
You've already forked forgejo-runner-bootstrap
0
A simple bootstrapping script to stand up a Forgejo Runner instance
  • Shell 100%
2026年02月09日 14:25:54 -07:00
bootstrap.bash feat: ensure ssh vs sshd detected properly for systemctl 2026年02月09日 14:25:54 -07:00
README.md generate bootstrapping script for vps + readme 2026年02月09日 12:42:23 -07:00

VPS Bootstrap Script

One-shot provisioning script for a fresh Ubuntu 24.04 VPS with Docker pre-installed. Hardens the OS, locks down SSH to key-only auth, sets up a firewall, and deploys a modular Docker Compose stack for CI and authentication services.

What it does

Stage Description
System update apt upgrade, installs ufw, fail2ban, unattended-upgrades, curl, git, jq
Auto-updates Configures unattended-upgrades for security patches only
User creation Creates a sudo + docker user, fetches SSH keys from Codeberg, optionally appends a manual key
SSH hardening Drops /etc/ssh/sshd_config.d/hardened.conf: root login off, passwords off, pubkey only, MaxAuthTries 3. Validates with sshd -t before restart (rolls back on failure)
UFW firewall Default deny inbound, allow outbound. Opens ports based on selected services
fail2ban SSH jail: 5 retries, 1h ban, 10m find window, ufw ban action
Secret generation openssl rand for all service secrets at bootstrap time
Docker Compose Generates docker-compose.yml, Caddyfile, .env, .env.example under ~/services/

Services

The stack is modular. Caddy and the Forgejo Runner are always included. The authentication services can be toggled independently.

Service Image Role Ports Flag
Caddy caddy:2 Reverse proxy, automatic TLS via Let's Encrypt 80, 443 always
Forgejo Runner data.forgejo.org/forgejo/runner:11 CI runner for Codeberg / Forgejo Actions none (outbound only) always
Docker-in-Docker docker:dind Isolated Docker daemon for CI jobs internal TCP 2375 always
lldap lldap/lldap:stable Lightweight LDAP server 389, 636 (host) / 17170 (Caddy) --skip-lldap to exclude
Pocket ID ghcr.io/pocket-id/pocket-id:v2 OIDC provider (passkey auth) 1411 (Caddy) --skip-pocket-id to exclude

Network isolation

Network Services
frontend Caddy, lldap, Pocket ID
backend lldap (internal LDAP queries between containers)
ci Docker-in-Docker, Forgejo Runner

Prerequisites

  • Fresh Ubuntu 24.04 server
  • Docker and Docker Compose already installed
  • Root or sudo access
  • A domain with DNS you control (for automatic TLS)
  • SSH public key(s) on your Codeberg profile

Usage

sudo ./bootstrap.sh \
 --username <vps-user> \
 --codeberg-user <codeberg-username> \
 --domain <base-domain> \
 [--ssh-pubkey "<public-key-string>"] \
 [--skip-lldap] \
 [--skip-pocket-id]

Arguments

Argument Required Description
--username yes Username for the new sudo account
--codeberg-user yes Codeberg username (SSH keys fetched from codeberg.org/<user>.keys)
--domain yes Base domain for services (e.g. example.com)
--ssh-pubkey no Additional SSH public key to authorize
--skip-lldap no Exclude lldap from the stack
--skip-pocket-id no Exclude Pocket ID from the stack

Examples

Full stack w/ all services:

sudo ./bootstrap.sh \
 --username deploy \
 --codeberg-user myuser \
 --domain example.com

Runner only (no auth services):

sudo ./bootstrap.sh \
 --username deploy \
 --codeberg-user myuser \
 --domain example.com \
 --skip-lldap \
 --skip-pocket-id

Pocket ID but no lldap:

sudo ./bootstrap.sh \
 --username deploy \
 --codeberg-user myuser \
 --domain example.com \
 --skip-lldap

W/ an additional SSH key:

sudo ./bootstrap.sh \
 --username deploy \
 --codeberg-user myuser \
 --domain example.com \
 --ssh-pubkey "ssh-ed25519 AAAA... user@host"

Firewall rules

Rule Port Condition
SSH 22/tcp always
HTTP 80/tcp always
HTTPS 443/tcp always
LDAP 389/tcp only with lldap
LDAPS 636/tcp only with lldap
Default inbound * deny
Default outbound * allow

Generated files

After running, ~/services/ contains:

~/services/
├── docker-compose.yml # Service stack (conditional on flags)
├── Caddyfile # Reverse proxy routes (conditional on flags)
├── .env # Generated secrets — DO NOT COMMIT
├── .env.example # Safe-to-commit template with placeholder values
├── caddy/
│ ├── data/ # TLS certificates (managed by Caddy)
│ └── config/
├── runner/
│ └── data/ # Runner state, config, cache
├── lldap/ # (only if lldap enabled)
│ └── data/
└── pocket-id/ # (only if Pocket ID enabled)
 └── data/

Post-Bootstrap

  1. Review secrets in ~/services/.env

  2. Point DNS A records to your server IP:

    • auth.<domain> -- Pocket ID (if enabled)
    • ldap.<domain> -- lldap web UI (if enabled)
  3. Start the stack:

    cd ~/services && docker compose up -d
    
  4. Register the Forgejo Runner (one-time setup):

    # Switch to the sleep command in docker-compose.yml, then:
    docker compose up -d forgejo-runner
    docker exec -it forgejo-runner /bin/sh
    # Inside the container:
    forgejo-runner generate-config > config.yml
    forgejo-runner register \
     --instance https://codeberg.org \
     --token <YOUR_TOKEN> \
     --name <RUNNER_NAME> \
     --labels "docker:docker://node:20-bookworm,ubuntu-latest:docker://ubuntu:24.04"
    exit
    # Switch back to the daemon command, then:
    docker compose up -d forgejo-runner
    
  5. Change the lldap admin password (if enabled) — default user is admin, log in at https://ldap.<domain>.

  6. Set up Pocket ID (if enabled) — the first user to register at https://auth.<domain> becomes the admin.

  7. Verify SSH access before closing your current session:

    ssh <username>@<server-ip>
    

    Password auth is now disabled. If you cannot connect with your key, your current session is still active and you can fix it.

SSH hardening details

The script writes /etc/ssh/sshd_config.d/hardened.conf with:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
X11Forwarding no
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 2
AllowAgentForwarding no
AllowTcpForwarding no

The config is validated with sshd -t before sshd is restarted. If validation fails, the hardened config is removed and the script exits.

Design notes

  • Docker-in-Docker for the Forgejo Runner avoids exposing the host Docker socket to CI workloads. The runner talks to an isolated DinD sidecar over internal TCP.
  • Caddy handles TLS automatically via Let's Encrypt with zero manual cert management.
  • lldap maps its internal ports (3890/6360) to standard LDAP ports (389/636) on the host so other servers can authenticate directly.
  • The script is idempotent where possible — it checks for an existing user before creating one, and validates sshd config before restarting.
  • Secrets are never logged — generated via openssl rand and written directly to .env.