611 lines
20 KiB
Smarty
611 lines
20 KiB
Smarty
#cloud-config
|
|
|
|
# =============================================================================
|
|
# Forgejo Server Cloud-Init Configuration
|
|
# Git server with Docker for Forgejo 11.0.8 LTS - FULLY AUTOMATED
|
|
# =============================================================================
|
|
|
|
# NOTE: We install packages via runcmd instead of packages: section
|
|
# because DigitalOcean's base image cloud-init can interfere with the packages module
|
|
|
|
# bootcmd runs before write_files - create directories needed for write_files
|
|
bootcmd:
|
|
- mkdir -p /opt/forgejo/caddy
|
|
|
|
write_files:
|
|
# SSH daemon configuration (non-standard port, VPC only access expected)
|
|
- path: /etc/ssh/sshd_config.d/99-hardening.conf
|
|
content: |
|
|
# Non-standard port (VPC access only via jump host)
|
|
Port ${ssh_port}
|
|
|
|
# Authentication hardening
|
|
PermitRootLogin prohibit-password
|
|
PasswordAuthentication no
|
|
PubkeyAuthentication yes
|
|
AuthenticationMethods publickey
|
|
|
|
# Security settings
|
|
MaxAuthTries 3
|
|
LoginGraceTime 30
|
|
PermitEmptyPasswords no
|
|
X11Forwarding no
|
|
AllowTcpForwarding no
|
|
AllowAgentForwarding no
|
|
PermitUserEnvironment no
|
|
MaxSessions 3
|
|
|
|
# Session settings
|
|
ClientAliveInterval 300
|
|
ClientAliveCountMax 2
|
|
|
|
# Logging
|
|
LogLevel VERBOSE
|
|
permissions: '0644'
|
|
|
|
# NOTE: authorized_keys is managed by DigitalOcean's native SSH key provisioning
|
|
# The internal key is added via digitalocean_ssh_key.internal resource
|
|
|
|
# Fail2ban configuration for system SSH
|
|
- path: /etc/fail2ban/jail.d/sshd.local
|
|
content: |
|
|
[sshd]
|
|
enabled = true
|
|
port = ${ssh_port}
|
|
filter = sshd
|
|
logpath = /var/log/auth.log
|
|
backend = systemd
|
|
bantime = 1h
|
|
findtime = 10m
|
|
maxretry = 3
|
|
permissions: '0644'
|
|
|
|
# Fail2ban for Forgejo Git SSH
|
|
- path: /etc/fail2ban/jail.d/forgejo-ssh.local
|
|
content: |
|
|
[forgejo-ssh]
|
|
enabled = true
|
|
port = ${git_ssh_port}
|
|
filter = sshd
|
|
logpath = /var/lib/docker/containers/*forgejo*/*-json.log
|
|
backend = auto
|
|
bantime = 1h
|
|
findtime = 10m
|
|
maxretry = 5
|
|
permissions: '0644'
|
|
|
|
# Fail2ban filter for Forgejo HTTP auth failures
|
|
- path: /etc/fail2ban/filter.d/forgejo.conf
|
|
content: |
|
|
[Definition]
|
|
failregex = ^.*Failed authentication attempt for .* from <HOST>.*$
|
|
^.*invalid credentials from <HOST>.*$
|
|
ignoreregex =
|
|
permissions: '0644'
|
|
|
|
# Fail2ban jail for Forgejo HTTP
|
|
- path: /etc/fail2ban/jail.d/forgejo-http.local
|
|
content: |
|
|
[forgejo-http]
|
|
enabled = true
|
|
port = 80,443
|
|
filter = forgejo
|
|
logpath = /var/lib/docker/containers/*forgejo*/*-json.log
|
|
backend = auto
|
|
bantime = 1h
|
|
findtime = 10m
|
|
maxretry = 5
|
|
permissions: '0644'
|
|
|
|
# Docker daemon configuration (hardened)
|
|
- path: /etc/docker/daemon.json
|
|
content: |
|
|
{
|
|
"log-driver": "json-file",
|
|
"log-opts": {
|
|
"max-size": "10m",
|
|
"max-file": "3"
|
|
},
|
|
"storage-driver": "overlay2",
|
|
"live-restore": true,
|
|
"no-new-privileges": true
|
|
}
|
|
permissions: '0644'
|
|
|
|
# UFW configuration script
|
|
- path: /usr/local/bin/configure-firewall.sh
|
|
content: |
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
# Reset UFW
|
|
ufw --force reset
|
|
|
|
# Default policies
|
|
ufw default deny incoming
|
|
ufw default deny outgoing
|
|
|
|
# Inbound: HTTP/HTTPS for Forgejo web
|
|
ufw allow in 80/tcp comment 'HTTP'
|
|
ufw allow in 443/tcp comment 'HTTPS'
|
|
|
|
# Inbound: Git SSH (public)
|
|
ufw allow in ${git_ssh_port}/tcp comment 'Git SSH'
|
|
|
|
# Inbound: VPC traffic (includes system SSH on non-standard port)
|
|
ufw allow in from ${vpc_ip_range} comment 'VPC internal'
|
|
|
|
# Outbound: Only necessary traffic (matching DO firewall)
|
|
ufw allow out 53/tcp comment 'DNS'
|
|
ufw allow out 53/udp comment 'DNS'
|
|
ufw allow out 80/tcp comment 'HTTP'
|
|
ufw allow out 443/tcp comment 'HTTPS'
|
|
ufw allow out 123/udp comment 'NTP'
|
|
ufw allow out to ${vpc_ip_range} comment 'VPC'
|
|
|
|
# Enable logging for security audit
|
|
ufw logging high
|
|
|
|
# Enable UFW
|
|
ufw --force enable
|
|
|
|
echo "Firewall configured successfully"
|
|
permissions: '0755'
|
|
|
|
# Swap configuration script (needed for 1GB RAM)
|
|
- path: /usr/local/bin/configure-swap.sh
|
|
content: |
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
SWAP_FILE="/swapfile"
|
|
SWAP_SIZE="2G"
|
|
|
|
if [ ! -f "$SWAP_FILE" ]; then
|
|
echo "Creating $SWAP_SIZE swap file..."
|
|
fallocate -l $SWAP_SIZE $SWAP_FILE
|
|
chmod 600 $SWAP_FILE
|
|
mkswap $SWAP_FILE
|
|
swapon $SWAP_FILE
|
|
|
|
# Add to fstab
|
|
echo "$SWAP_FILE none swap sw 0 0" >> /etc/fstab
|
|
|
|
# Adjust swappiness for better performance
|
|
echo "vm.swappiness=10" >> /etc/sysctl.conf
|
|
sysctl vm.swappiness=10
|
|
|
|
echo "Swap configured successfully"
|
|
else
|
|
echo "Swap file already exists"
|
|
fi
|
|
permissions: '0755'
|
|
|
|
# Unattended upgrades configuration
|
|
- path: /etc/apt/apt.conf.d/50unattended-upgrades
|
|
content: |
|
|
Unattended-Upgrade::Allowed-Origins {
|
|
"$${distro_id}:$${distro_codename}";
|
|
"$${distro_id}:$${distro_codename}-security";
|
|
"$${distro_id}:$${distro_codename}-updates";
|
|
};
|
|
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
|
Unattended-Upgrade::MinimalSteps "true";
|
|
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
|
Unattended-Upgrade::Automatic-Reboot "false";
|
|
permissions: '0644'
|
|
|
|
# Auto-upgrades configuration
|
|
- path: /etc/apt/apt.conf.d/20auto-upgrades
|
|
content: |
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
APT::Periodic::AutocleanInterval "7";
|
|
permissions: '0644'
|
|
|
|
# ==========================================================================
|
|
# FORGEJO DOCKER COMPOSE STACK
|
|
# ==========================================================================
|
|
|
|
# Docker Compose file
|
|
- path: /opt/forgejo/docker-compose.yml
|
|
content: |
|
|
services:
|
|
# PostgreSQL Database
|
|
forgejo-db:
|
|
image: postgres:16-alpine
|
|
container_name: forgejo-db
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: $${POSTGRES_USER:-forgejo}
|
|
POSTGRES_PASSWORD: $${POSTGRES_PASSWORD}
|
|
POSTGRES_DB: $${POSTGRES_DB:-forgejo}
|
|
volumes:
|
|
- /var/lib/forgejo/forgejo-db:/var/lib/postgresql/data
|
|
networks:
|
|
- forgejo-internal
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-forgejo} -d $${POSTGRES_DB:-forgejo}"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256M
|
|
reservations:
|
|
memory: 128M
|
|
|
|
# Forgejo Git Server
|
|
forgejo:
|
|
image: codeberg.org/forgejo/forgejo:11.0.8-rootless
|
|
container_name: forgejo
|
|
restart: unless-stopped
|
|
depends_on:
|
|
forgejo-db:
|
|
condition: service_healthy
|
|
environment:
|
|
FORGEJO__database__DB_TYPE: postgres
|
|
FORGEJO__database__HOST: forgejo-db:5432
|
|
FORGEJO__database__NAME: $${POSTGRES_DB:-forgejo}
|
|
FORGEJO__database__USER: $${POSTGRES_USER:-forgejo}
|
|
FORGEJO__database__PASSWD: $${POSTGRES_PASSWORD}
|
|
FORGEJO__server__DOMAIN: $${FORGEJO_DOMAIN}
|
|
FORGEJO__server__ROOT_URL: https://$${FORGEJO_DOMAIN}/
|
|
FORGEJO__server__SSH_DOMAIN: $${FORGEJO_DOMAIN}
|
|
FORGEJO__server__SSH_PORT: ${git_ssh_port}
|
|
FORGEJO__server__SSH_LISTEN_PORT: ${git_ssh_port}
|
|
FORGEJO__server__START_SSH_SERVER: "true"
|
|
FORGEJO__server__HTTP_PORT: 3000
|
|
FORGEJO__server__LFS_START_SERVER: "true"
|
|
FORGEJO__security__INSTALL_LOCK: "true"
|
|
FORGEJO__security__SECRET_KEY: $${FORGEJO_SECRET_KEY}
|
|
FORGEJO__security__INTERNAL_TOKEN: $${FORGEJO_INTERNAL_TOKEN}
|
|
FORGEJO__security__PASSWORD_COMPLEXITY: "lower,upper,digit"
|
|
FORGEJO__security__MIN_PASSWORD_LENGTH: "12"
|
|
FORGEJO__oauth2__JWT_SECRET: $${FORGEJO_JWT_SECRET}
|
|
FORGEJO__service__DISABLE_REGISTRATION: "true"
|
|
FORGEJO__service__REQUIRE_SIGNIN_VIEW: "false"
|
|
FORGEJO__service__ENABLE_NOTIFY_MAIL: "false"
|
|
FORGEJO__actions__ENABLED: "true"
|
|
FORGEJO__actions__DEFAULT_ACTIONS_URL: "https://code.forgejo.org"
|
|
FORGEJO__repository__DEFAULT_BRANCH: "main"
|
|
FORGEJO__repository__ENABLE_PUSH_CREATE_USER: "true"
|
|
FORGEJO__repository__ENABLE_PUSH_CREATE_ORG: "true"
|
|
FORGEJO__lfs__PATH: /data/lfs
|
|
FORGEJO__webhook__ALLOWED_HOST_LIST: "private"
|
|
FORGEJO__webhook__SKIP_TLS_VERIFY: "false"
|
|
FORGEJO__log__MODE: "console"
|
|
FORGEJO__log__LEVEL: "Info"
|
|
volumes:
|
|
- /var/lib/forgejo/forgejo-data:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
networks:
|
|
- forgejo-internal
|
|
- forgejo-public
|
|
ports:
|
|
- "${git_ssh_port}:${git_ssh_port}"
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 60s
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 384M
|
|
reservations:
|
|
memory: 192M
|
|
|
|
# Caddy Reverse Proxy with auto-SSL and rate limiting
|
|
caddy:
|
|
build:
|
|
context: ./caddy
|
|
dockerfile: Dockerfile
|
|
container_name: forgejo-caddy
|
|
restart: unless-stopped
|
|
depends_on:
|
|
forgejo:
|
|
condition: service_healthy
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- /opt/forgejo/Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- caddy_data:/data
|
|
- caddy_config:/config
|
|
networks:
|
|
- forgejo-public
|
|
environment:
|
|
FORGEJO_DOMAIN: $${FORGEJO_DOMAIN}
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 64M
|
|
reservations:
|
|
memory: 32M
|
|
|
|
# Forgejo Actions Runner (CI/CD)
|
|
forgejo-runner:
|
|
image: code.forgejo.org/forgejo/runner:6.3.1
|
|
container_name: forgejo-runner
|
|
restart: unless-stopped
|
|
depends_on:
|
|
forgejo:
|
|
condition: service_healthy
|
|
docker-dind:
|
|
condition: service_started
|
|
environment:
|
|
DOCKER_HOST: tcp://docker-dind:2376
|
|
DOCKER_TLS_VERIFY: "1"
|
|
DOCKER_CERT_PATH: /certs/client
|
|
volumes:
|
|
- /var/lib/forgejo/runner-data:/data
|
|
- dind-certs-client:/certs/client:ro
|
|
networks:
|
|
- forgejo-internal
|
|
- dind-network
|
|
command: >
|
|
sh -c '
|
|
if [ ! -f /data/.runner ]; then
|
|
echo "Runner not registered. Run: docker compose exec forgejo-runner forgejo-runner register"
|
|
sleep infinity
|
|
fi
|
|
forgejo-runner daemon --config /data/config.yaml
|
|
'
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 256M
|
|
reservations:
|
|
memory: 128M
|
|
|
|
# Docker-in-Docker for Runner (builds images in CI/CD)
|
|
docker-dind:
|
|
image: docker:27-dind
|
|
container_name: forgejo-dind
|
|
restart: unless-stopped
|
|
privileged: true
|
|
environment:
|
|
DOCKER_TLS_CERTDIR: /certs
|
|
DOCKER_TLS_SAN: DNS:docker-dind,DNS:docker,DNS:localhost
|
|
volumes:
|
|
- dind-certs-ca:/certs/ca
|
|
- dind-certs-client:/certs/client
|
|
- dind-storage:/var/lib/docker
|
|
networks:
|
|
- dind-network
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 3G
|
|
reservations:
|
|
memory: 1G
|
|
|
|
networks:
|
|
forgejo-internal:
|
|
driver: bridge
|
|
internal: true
|
|
forgejo-public:
|
|
driver: bridge
|
|
dind-network:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
caddy_data:
|
|
caddy_config:
|
|
dind-certs-ca:
|
|
dind-certs-client:
|
|
dind-storage:
|
|
permissions: '0644'
|
|
|
|
# Caddy Dockerfile with rate-limit plugin
|
|
- path: /opt/forgejo/caddy/Dockerfile
|
|
content: |
|
|
FROM caddy:2-builder AS builder
|
|
RUN xcaddy build --with github.com/mholt/caddy-ratelimit
|
|
|
|
FROM caddy:2-alpine
|
|
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
|
permissions: '0644'
|
|
|
|
# Caddyfile for reverse proxy with rate limiting
|
|
- path: /opt/forgejo/Caddyfile
|
|
content: |
|
|
{
|
|
order rate_limit before basicauth
|
|
}
|
|
|
|
${domain} {
|
|
# Rate limiting - 500 requests per minute per IP
|
|
rate_limit {
|
|
zone forgejo_zone {
|
|
key {remote_host}
|
|
events 500
|
|
window 1m
|
|
}
|
|
}
|
|
|
|
reverse_proxy forgejo:3000
|
|
|
|
encode gzip zstd
|
|
|
|
header {
|
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
|
X-Frame-Options "SAMEORIGIN"
|
|
X-Content-Type-Options "nosniff"
|
|
X-XSS-Protection "1; mode=block"
|
|
Referrer-Policy "strict-origin-when-cross-origin"
|
|
-Server
|
|
}
|
|
|
|
log {
|
|
output file /data/access.log {
|
|
roll_size 10mb
|
|
roll_keep 5
|
|
}
|
|
}
|
|
}
|
|
permissions: '0644'
|
|
|
|
# Script to create .env with Terraform-generated secrets
|
|
- path: /usr/local/bin/setup-forgejo.sh
|
|
content: |
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
ENV_FILE="/opt/forgejo/.env"
|
|
|
|
# Only create if .env doesn't exist (preserves on re-run)
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo "Writing Forgejo configuration..."
|
|
|
|
{
|
|
echo "# Forgejo configuration (secrets from Terraform)"
|
|
echo "FORGEJO_DOMAIN=${domain}"
|
|
echo ""
|
|
echo "# PostgreSQL"
|
|
echo "POSTGRES_USER=forgejo"
|
|
echo "POSTGRES_PASSWORD=${postgres_password}"
|
|
echo "POSTGRES_DB=forgejo"
|
|
echo ""
|
|
echo "# Forgejo Security Keys"
|
|
echo "FORGEJO_SECRET_KEY=${forgejo_secret_key}"
|
|
echo "FORGEJO_INTERNAL_TOKEN=${forgejo_internal_token}"
|
|
echo "FORGEJO_JWT_SECRET=${forgejo_jwt_secret}"
|
|
} > "$ENV_FILE"
|
|
|
|
chmod 600 "$ENV_FILE"
|
|
echo "Configuration saved to $ENV_FILE"
|
|
else
|
|
echo ".env already exists, skipping"
|
|
fi
|
|
permissions: '0755'
|
|
|
|
# DNS wait script - waits for DNS to propagate before starting Caddy
|
|
- path: /usr/local/bin/wait-for-dns.sh
|
|
content: |
|
|
#!/bin/bash
|
|
DOMAIN="${domain}"
|
|
MAX_ATTEMPTS=60
|
|
ATTEMPT=0
|
|
|
|
echo "Waiting for DNS to propagate for $DOMAIN..."
|
|
|
|
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
|
|
if host "$DOMAIN" > /dev/null 2>&1; then
|
|
echo "DNS resolved for $DOMAIN"
|
|
exit 0
|
|
fi
|
|
ATTEMPT=$((ATTEMPT + 1))
|
|
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS - waiting 10s..."
|
|
sleep 10
|
|
done
|
|
|
|
echo "WARNING: DNS did not resolve after $MAX_ATTEMPTS attempts"
|
|
echo "Let's Encrypt certificate may fail - run 'docker compose restart caddy' after DNS propagates"
|
|
exit 0
|
|
permissions: '0755'
|
|
|
|
# Message of the day
|
|
- path: /etc/motd
|
|
content: |
|
|
|
|
+---------------------------------------------------------------+
|
|
| FORGEJO GIT SERVER |
|
|
| |
|
|
| Domain: ${domain}
|
|
| Git SSH Port: ${git_ssh_port}
|
|
| |
|
|
| Data location: /var/lib/forgejo |
|
|
| Docker compose: /opt/forgejo |
|
|
| |
|
|
| Commands: |
|
|
| cd /opt/forgejo && docker compose logs -f |
|
|
| cd /opt/forgejo && docker compose restart |
|
|
| |
|
|
| Runner Registration: |
|
|
| 1. Get token from Forgejo: Site Admin > Actions > Runners |
|
|
| 2. Register runner: |
|
|
| cd /opt/forgejo && docker compose exec forgejo-runner \ |
|
|
| forgejo-runner register --instance https://${domain} |
|
|
| --token YOUR_TOKEN --name realms-runner |
|
|
| --labels ubuntu-latest,docker |
|
|
| 3. Restart: docker compose restart forgejo-runner |
|
|
| |
|
|
+---------------------------------------------------------------+
|
|
|
|
permissions: '0644'
|
|
|
|
runcmd:
|
|
# Ensure .ssh directory exists with correct permissions
|
|
# (authorized_keys is managed by DigitalOcean's native SSH key provisioning)
|
|
- mkdir -p /root/.ssh && chmod 700 /root/.ssh
|
|
|
|
# Ensure sshd_config includes the .d directory (Debian 12 fix)
|
|
- grep -q 'Include /etc/ssh/sshd_config.d' /etc/ssh/sshd_config || sed -i '1i Include /etc/ssh/sshd_config.d/*.conf' /etc/ssh/sshd_config
|
|
|
|
# Comment out Port in main sshd_config so sshd_config.d takes precedence (Debian 12 fix)
|
|
- sed -i 's/^Port /#Port /' /etc/ssh/sshd_config
|
|
|
|
# Restart SSH with new configuration
|
|
- systemctl restart sshd
|
|
|
|
# Wait for any background apt processes to finish (DO images run apt on boot)
|
|
- while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do echo "Waiting for dpkg lock..."; sleep 5; done
|
|
|
|
# Install prerequisites
|
|
- apt-get update
|
|
- DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=60 install -y ca-certificates curl gnupg fail2ban ufw unattended-upgrades apt-listchanges vim git
|
|
|
|
# Add Docker's official GPG key and repository
|
|
- install -m 0755 -d /etc/apt/keyrings
|
|
- curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
|
- chmod a+r /etc/apt/keyrings/docker.gpg
|
|
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list
|
|
|
|
# Install Docker from official repository
|
|
- apt-get update
|
|
- DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=60 install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
|
|
|
# Enable and start Docker
|
|
- systemctl enable docker
|
|
- systemctl start docker
|
|
|
|
# Enable and start fail2ban
|
|
- systemctl enable fail2ban
|
|
- systemctl restart fail2ban
|
|
|
|
# Configure swap (important for 1GB RAM)
|
|
- /usr/local/bin/configure-swap.sh
|
|
|
|
# Create data directories on local disk
|
|
- mkdir -p /var/lib/forgejo/forgejo-data /var/lib/forgejo/forgejo-db /var/lib/forgejo/runner-data
|
|
- chown -R 1000:1000 /var/lib/forgejo/forgejo-data /var/lib/forgejo/runner-data
|
|
- chown -R 999:999 /var/lib/forgejo/forgejo-db
|
|
|
|
# Configure firewall
|
|
- /usr/local/bin/configure-firewall.sh
|
|
|
|
# Generate secrets and create .env
|
|
- /usr/local/bin/setup-forgejo.sh
|
|
|
|
# Wait for DNS to propagate before starting Caddy (for Let's Encrypt)
|
|
- /usr/local/bin/wait-for-dns.sh
|
|
|
|
# Start Forgejo stack (build Caddy with rate-limit plugin, pull others)
|
|
- cd /opt/forgejo && docker compose build caddy
|
|
- cd /opt/forgejo && docker compose pull forgejo forgejo-db forgejo-runner docker-dind
|
|
- cd /opt/forgejo && docker compose up -d
|
|
|
|
# Enable unattended upgrades
|
|
- systemctl enable unattended-upgrades
|
|
- systemctl start unattended-upgrades
|
|
|
|
final_message: "Forgejo server fully deployed after $UPTIME seconds. Visit https://${domain}"
|