#!/bin/bash # ============================================================================= # FORGEJO RUNNER - FULL SYSTEM DEPLOYMENT WITH SYSTEMD # Supports both DIRECT and DOCKER modes # ============================================================================= # --- CONFIGURATION VARIABLES --- FORGEJO_URL="https://your-forgejo-domain.com" # CHANGE THIS REGISTRATION_TOKEN="your-registration-token" # CHANGE THIS RUNNER_NAME="selfhosted-runner-01" RUNNER_VERSION="latest" # Can be "latest" or specific like "v4.0.0" RUNNER_MODE="direct" # "direct" or "docker" # Direct mode variables RUNNER_USER="forgejo-runner" RUNNER_HOME="/opt/forgejo-runner" # Docker mode variables COMPOSE_DIR="$HOME/forgejo-runner" DIND_IMAGE="docker:dind" RUNNER_IMAGE="data.forgejo.org/forgejo/runner" # ============================================================================= set -e echo "============================================================" echo "FORGEJO RUNNER DEPLOYMENT" echo "============================================================" echo "Mode: $RUNNER_MODE" echo "Version: $RUNNER_VERSION" echo "Instance: $FORGEJO_URL" echo "" # --- STEP 1: Detect system architecture --- ARCH=$(uname -m) case $ARCH in x86_64) ARCH="amd64" ;; aarch64|arm64) ARCH="arm64" ;; armv7l|armhf) ARCH="armv7" ;; *) echo "Unsupported architecture: $ARCH" exit 1 ;; esac echo "Detected architecture: $ARCH" # --- STEP 2: Get actual version if "latest" --- if [ "$RUNNER_VERSION" = "latest" ]; then echo "Fetching latest version..." RUNNER_VERSION=$(curl -s https://code.forgejo.org/forgejo/runner/releases/latest | \ grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+' | head -1) if [ -z "$RUNNER_VERSION" ]; then echo "Failed to fetch latest version, using fallback v4.0.0" RUNNER_VERSION="v4.0.0" fi echo "Latest version: $RUNNER_VERSION" fi # --- STEP 3: Check Docker --- echo "Checking Docker..." if ! command -v docker &> /dev/null; then echo "ERROR: Docker is not installed. Please install Docker first." exit 1 fi if ! systemctl is-active --quiet docker; then echo "ERROR: Docker service is not running." echo "Start it with: sudo systemctl start docker" exit 1 fi echo "Docker is running as systemd service" echo "" # ============================================================================= # DIRECT MODE INSTALLATION # ============================================================================= if [ "$RUNNER_MODE" = "direct" ]; then echo "=== DIRECT MODE INSTALLATION ===" echo "" # --- Create runner user --- echo "Creating runner user..." if ! id "$RUNNER_USER" &>/dev/null; then sudo useradd -r -m -d "$RUNNER_HOME" -s /bin/bash "$RUNNER_USER" sudo usermod -aG docker "$RUNNER_USER" else echo "User $RUNNER_USER already exists" sudo usermod -aG docker "$RUNNER_USER" fi # --- Download runner binary --- echo "Downloading Forgejo runner binary..." sudo -u "$RUNNER_USER" mkdir -p "$RUNNER_HOME/bin" VERSION_NUM=${RUNNER_VERSION#v} DOWNLOAD_URL="https://code.forgejo.org/forgejo/runner/releases/download/$RUNNER_VERSION/forgejo-runner-${VERSION_NUM}-linux-$ARCH" echo "Downloading from: $DOWNLOAD_URL" sudo -u "$RUNNER_USER" curl -L "$DOWNLOAD_URL" -o "$RUNNER_HOME/bin/forgejo-runner" sudo chmod +x "$RUNNER_HOME/bin/forgejo-runner" sudo chown -R "$RUNNER_USER":"$RUNNER_USER" "$RUNNER_HOME" # --- Register runner --- echo "Registering runner with Forgejo..." sudo -u "$RUNNER_USER" "$RUNNER_HOME/bin/forgejo-runner" register \ --token "$REGISTRATION_TOKEN" \ --name "$RUNNER_NAME" \ --instance "$FORGEJO_URL" \ --labels docker:docker://node:20-bookworm \ --no-interactive # --- Create config.yaml --- echo "Creating configuration..." sudo -u "$RUNNER_USER" tee "$RUNNER_HOME/config.yaml" > /dev/null << 'EOF' log: level: info runner: file: .runner envs: DOCKER_HOST: unix:///var/run/docker.sock cache: enabled: false dir: /opt/forgejo-runner/cache fetch_interval: 30 fetch_timeout: 30s docker: host: unix:///var/run/docker.sock privileged: false options: >- -v /var/run/docker.sock:/var/run/docker.sock --add-host=host.docker.internal:host-gateway container: network: host options: >- --add-host=host.docker.internal:host-gateway volumes: - /opt/forgejo-runner/cache:/cache - /var/run/docker.sock:/var/run/docker.sock EOF # --- Create systemd service --- echo "Creating systemd service..." sudo tee /etc/systemd/system/forgejo-runner.service > /dev/null << EOF [Unit] Description=Forgejo Actions Runner After=docker.service Requires=docker.service [Service] Type=simple User=$RUNNER_USER Group=$RUNNER_USER WorkingDirectory=$RUNNER_HOME ExecStart=$RUNNER_HOME/bin/forgejo-runner daemon --config $RUNNER_HOME/config.yaml Restart=always RestartSec=10 StartLimitBurst=3 StartLimitInterval=60s LimitNOFILE=65536 LimitNPROC=65536 [Install] WantedBy=multi-user.target EOF # --- Enable and start service --- echo "Starting service..." sudo systemctl daemon-reload sudo systemctl enable forgejo-runner.service sudo systemctl start forgejo-runner.service echo "" echo "=== DIRECT MODE DEPLOYMENT COMPLETE ===" echo "" echo "Service status:" sudo systemctl status forgejo-runner.service --no-pager echo "" echo "To view logs:" echo " sudo journalctl -u forgejo-runner -f" echo "" echo "Runner location: $RUNNER_HOME" # ============================================================================= # DOCKER MODE INSTALLATION # ============================================================================= elif [ "$RUNNER_MODE" = "docker" ]; then echo "=== DOCKER MODE INSTALLATION ===" echo "" # --- Create working directory --- echo "Creating working directory..." mkdir -p "$COMPOSE_DIR"/{data,cache} cd "$COMPOSE_DIR" # --- Register runner using Docker --- echo "Registering runner with Forgejo..." docker run -it --rm \ -v "$(pwd)/data:/data" \ -w /data \ --user root \ "${RUNNER_IMAGE}:${RUNNER_VERSION#v}" \ forgejo-runner register \ --no-interactive \ --token "$REGISTRATION_TOKEN" \ --name "$RUNNER_NAME" \ --instance "$FORGEJO_URL" \ --labels "$RUNNER_NAME:host" # --- Generate config.yaml --- echo "Generating configuration..." docker run --rm \ -v "$(pwd)/data:/data" \ -w /data \ "${RUNNER_IMAGE}:${RUNNER_VERSION#v}" \ forgejo-runner generate-config > data/config.yaml # --- Customize config.yaml --- echo "Customizing configuration..." cat > data/config.yaml << EOF instance: $FORGEJO_URL token: "$REGISTRATION_TOKEN" log: level: info runner: file: .runner capacity: 1 timeout: 30m shutdown_timeout: 30m insecure: true fetch_timeout: 30s fetch_interval: 60s report_interval: 10s labels: - "$RUNNER_NAME:host" cache: enabled: true dir: "" container: privileged: false valid_volumes: - '**' docker_host: "" force_pull: false host: workdir_parent: "" EOF # --- Create docker-compose.yml --- echo "Creating docker-compose.yml..." cat > docker-compose.yml << EOF version: '3.8' services: docker-in-docker: image: $DIND_IMAGE container_name: ${RUNNER_NAME}_dind privileged: true command: ['dockerd', '-H', 'tcp://0.0.0.0:2375', '--tls=false'] restart: unless-stopped volumes: - dind-storage:/var/lib/docker runner: image: ${RUNNER_IMAGE}:${RUNNER_VERSION#v} container_name: ${RUNNER_NAME} depends_on: docker-in-docker: condition: service_started environment: DOCKER_HOST: tcp://docker-in-docker:2375 user: 0:0 volumes: - ./data:/data - ./cache:/cache restart: unless-stopped command: /bin/sh -c " apk update && apk add --no-cache nodejs npm && sleep 5 && forgejo-runner daemon --config /data/config.yaml " volumes: dind-storage: EOF # --- Create systemd service for docker-compose --- echo "Creating systemd service..." sudo tee /etc/systemd/system/forgejo-runner-docker.service > /dev/null << EOF [Unit] Description=Forgejo Runner (Docker Compose) After=docker.service Requires=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=$COMPOSE_DIR ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down ExecReload=/usr/bin/docker compose restart StandardOutput=journal User=$USER Group=$USER [Install] WantedBy=multi-user.target EOF # --- Enable and start service --- echo "Starting service..." sudo systemctl daemon-reload sudo systemctl enable forgejo-runner-docker.service sudo systemctl start forgejo-runner-docker.service echo "" echo "=== DOCKER MODE DEPLOYMENT COMPLETE ===" echo "" echo "Service status:" sudo systemctl status forgejo-runner-docker.service --no-pager echo "" echo "To view logs:" echo " docker compose -f $COMPOSE_DIR/docker-compose.yml logs -f runner" echo "" echo "To manage:" echo " cd $COMPOSE_DIR" echo " docker compose up -d # start" echo " docker compose down # stop" echo " docker compose restart # restart" echo "" echo "Runner location: $COMPOSE_DIR" # ============================================================================= # INVALID MODE # ============================================================================= else echo "ERROR: Invalid RUNNER_MODE. Use 'direct' or 'docker'" exit 1 fi echo "" echo "============================================================" echo "DEPLOYMENT COMPLETE" echo "============================================================" echo "" echo "Runner should now appear online in Forgejo Admin -> Actions -> Runners" echo "URL: $FORGEJO_URL/admin/actions/runners" echo "" echo "If runner shows offline, check:" echo " - Registration token is valid" echo " - Forgejo URL is accessible" echo " - Docker is running: sudo systemctl status docker" echo "============================================================"