#!/bin/bash # ============================================================================= # FORGEJO RUNNER - FULL SYSTEM DEPLOYMENT WITH SYSTEMD # Supports both DIRECT and DOCKER modes - NO EXTERNAL DEPENDENCIES # ============================================================================= # --- CONFIGURATION VARIABLES --- FORGEJO_URL="http://192.168.2.76:3006" # CHANGE THIS REGISTRATION_TOKEN="your-registration-token" # CHANGE THIS RUNNER_NAME="selfhosted-runner-01" RUNNER_VERSION="latest" # Can be "latest" or specific like "v12.13.2" RUNNER_MODE="direct" # "direct" or "docker" # Direct mode variables - using current user 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 | sed 's/x86_64/amd64/;s/aarch64/arm64/;s/armv7l/armv7/') echo "Detected architecture: $ARCH" # --- STEP 2: Get actual version if "latest" (NO DEPENDENCIES) --- if [ "$RUNNER_VERSION" = "latest" ]; then echo "Fetching latest Forgejo Runner version..." # Try API with grep (no jq needed) RUNNER_VERSION=$(curl -s https://code.forgejo.org/api/v1/repos/forgejo/runner/releases/latest | \ grep -o '"tag_name":"v[0-9]\+\.[0-9]\+\.[0-9]\+"' | \ head -1 | sed 's/"tag_name":"//' | sed 's/"//') # If API fails, try HTML parsing if [ -z "$RUNNER_VERSION" ]; then echo "API failed, trying HTML parsing..." RUNNER_VERSION=$(curl -s https://code.forgejo.org/forgejo/runner/releases | \ grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+' | \ head -1) fi # If still empty, use fallback if [ -z "$RUNNER_VERSION" ]; then echo "Failed to fetch latest version, using fallback v12.13.2" RUNNER_VERSION="v12.13.2" fi echo "Latest version: $RUNNER_VERSION" fi # Remove 'v' prefix for version number VERSION_NUM=${RUNNER_VERSION#v} echo "Using version: $RUNNER_VERSION (number: $VERSION_NUM)" echo "" # ============================================================================= # DIRECT MODE INSTALLATION # ============================================================================= if [ "$RUNNER_MODE" = "direct" ]; then echo "=== DIRECT MODE INSTALLATION ===" echo "Installing runner directly on the system (no Docker required)" echo "Using current user: $USER" echo "" # --- Create directory --- echo "Creating runner directory..." sudo mkdir -p "$RUNNER_HOME/bin" sudo chown -R "$USER":"$USER" "$RUNNER_HOME" # --- Download runner binary --- echo "Downloading Forgejo runner binary..." DOWNLOAD_URL="https://code.forgejo.org/forgejo/runner/releases/download/$RUNNER_VERSION/forgejo-runner-${VERSION_NUM}-linux-$ARCH" echo "Downloading from: $DOWNLOAD_URL" # Download with timeout and retry if ! curl -L -m 60 --retry 3 "$DOWNLOAD_URL" -o "$RUNNER_HOME/bin/forgejo-runner"; then echo "ERROR: Failed to download runner binary" echo "Try manual download:" echo " wget $DOWNLOAD_URL" exit 1 fi chmod +x "$RUNNER_HOME/bin/forgejo-runner" chown -R "$USER":"$USER" "$RUNNER_HOME" # Verify binary echo "Verifying binary..." "$RUNNER_HOME/bin/forgejo-runner" --version 2>/dev/null || echo "Warning: Version check failed" # --- Register runner --- echo "Registering runner with Forgejo..." "$RUNNER_HOME/bin/forgejo-runner" register \ --token "$REGISTRATION_TOKEN" \ --name "$RUNNER_NAME" \ --instance "$FORGEJO_URL" \ --labels "$RUNNER_NAME:host" \ --no-interactive 2>/dev/null || { echo "WARNING: Registration failed, but continuing..." echo "You may need to register manually:" echo " $RUNNER_HOME/bin/forgejo-runner register --token $REGISTRATION_TOKEN --name $RUNNER_NAME --instance $FORGEJO_URL --labels $RUNNER_NAME:host --no-interactive" } # --- Create config.yaml --- echo "Creating configuration..." cat > "$RUNNER_HOME/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: "" host: workdir_parent: "" 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=network.target [Service] Type=simple User=$USER Group=$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 2>/dev/null || echo "Warning: Could not enable service" sudo systemctl start forgejo-runner.service 2>/dev/null || echo "Warning: Could not start service" echo "" echo "=== DIRECT MODE DEPLOYMENT COMPLETE ===" echo "" echo "Service status:" sudo systemctl status forgejo-runner.service --no-pager 2>/dev/null || echo "Service status unavailable" 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 "Installing runner with Docker Compose" echo "" # --- Check and install Docker if needed --- echo "Checking Docker..." if ! command -v docker &> /dev/null; then echo "Docker not found. Installing Docker..." curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER sudo systemctl enable docker --now rm get-docker.sh echo "Docker installed successfully" else echo "Docker is already installed" fi if ! systemctl is-active --quiet docker 2>/dev/null; then echo "Starting Docker service..." sudo systemctl start docker 2>/dev/null || echo "Warning: Could not start Docker" fi echo "Docker is running" # --- Check and install Docker Compose if needed --- echo "Checking Docker Compose..." if ! command -v docker compose &> /dev/null; then echo "Docker Compose not found. Installing..." # Try to install docker-compose-plugin if command -v apt-get &> /dev/null; then apt-get update && apt-get install -y docker-compose-plugin 2>/dev/null || echo "Warning: Could not install docker-compose-plugin" elif command -v yum &> /dev/null; then yum install -y docker-compose-plugin 2>/dev/null || echo "Warning: Could not install docker-compose-plugin" else echo "WARNING: Could not install docker-compose-plugin automatically" echo "Please install it manually" fi else echo "Docker Compose is already installed" fi 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" 2>/dev/null || { echo "WARNING: Registration failed" echo "You may need to register manually:" echo " 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..." 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 2>/dev/null || echo "Warning: Could not enable service" sudo systemctl start forgejo-runner-docker.service 2>/dev/null || echo "Warning: Could not start service" echo "" echo "=== DOCKER MODE DEPLOYMENT COMPLETE ===" echo "" echo "Service status:" sudo systemctl status forgejo-runner-docker.service --no-pager 2>/dev/null || echo "Service status unavailable" 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 "" if [ "$RUNNER_MODE" = "direct" ]; then echo "To check runner logs: sudo journalctl -u forgejo-runner -f" else echo "To check runner logs: docker compose -f $COMPOSE_DIR/docker-compose.yml logs -f runner" fi echo "============================================================"