#!/usr/bin/env bash set -euo pipefail # ============================================================================= # phase1_gitea_unraid.sh — Deploy Gitea on Unraid # Produces: Running Gitea instance, admin user, API token in .env, org created # ============================================================================= SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" source "${SCRIPT_DIR}/lib/common.sh" load_env require_vars UNRAID_IP UNRAID_SSH_USER UNRAID_SSH_PORT \ UNRAID_GITEA_PORT UNRAID_GITEA_SSH_PORT UNRAID_GITEA_DATA_PATH \ GITEA_ADMIN_USER GITEA_ADMIN_PASSWORD GITEA_ADMIN_EMAIL \ GITEA_ORG_NAME GITEA_INSTANCE_NAME \ GITEA_DB_TYPE GITEA_VERSION \ GITEA_INTERNAL_URL GITEA_DOMAIN if [[ "${GITEA_DB_TYPE}" != "sqlite3" ]]; then require_vars GITEA_DB_HOST GITEA_DB_PORT GITEA_DB_NAME GITEA_DB_USER GITEA_DB_PASSWD fi phase_header 1 "Gitea on Unraid" # Alias for shorter references in template rendering. # Templates use $DATA_PATH as a generic variable name so the same template # works for both Unraid (Phase 1) and Fedora (Phase 2). DATA_PATH="$UNRAID_GITEA_DATA_PATH" # --------------------------------------------------------------------------- # DB-specific variables — set based on GITEA_DB_TYPE for template rendering. # For sqlite3, the DB service blocks are stripped entirely. # For external DBs, these vars populate the docker-compose DB service. # --------------------------------------------------------------------------- _set_db_vars() { case "${GITEA_DB_TYPE}" in postgres) DB_DOCKER_IMAGE="postgres:16-alpine" DB_ENV_VARS=" - POSTGRES_USER=${GITEA_DB_USER} - POSTGRES_PASSWORD=${GITEA_DB_PASSWD} - POSTGRES_DB=${GITEA_DB_NAME}" DB_HEALTHCHECK='["CMD-SHELL", "pg_isready -U '"${GITEA_DB_USER}"'"]' DB_DATA_DIR="postgresql/data" ;; mysql) DB_DOCKER_IMAGE="mysql:8.0" DB_ENV_VARS=" - MYSQL_ROOT_PASSWORD=${GITEA_DB_PASSWD} - MYSQL_DATABASE=${GITEA_DB_NAME} - MYSQL_USER=${GITEA_DB_USER} - MYSQL_PASSWORD=${GITEA_DB_PASSWD}" DB_HEALTHCHECK='["CMD", "mysqladmin", "ping", "-h", "localhost"]' DB_DATA_DIR="mysql" ;; mssql) DB_DOCKER_IMAGE="mcr.microsoft.com/mssql/server:2022-latest" DB_ENV_VARS=" - ACCEPT_EULA=Y - SA_PASSWORD=${GITEA_DB_PASSWD}" DB_HEALTHCHECK='["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '"'"'${GITEA_DB_PASSWD}'"'"' -Q \"SELECT 1\" -C -N"]' DB_DATA_DIR="mssql/data" ;; esac export DB_DOCKER_IMAGE DB_ENV_VARS DB_HEALTHCHECK DB_DATA_DIR } # Strip conditional blocks from a rendered file. # Usage: _strip_block _strip_block() { local file="$1" start="$2" end="$3" sed -i.bak "/${start}/,/${end}/d" "$file" rm -f "${file}.bak" } # --------------------------------------------------------------------------- # Step 1: Create data directories # --------------------------------------------------------------------------- log_step 1 "Creating data directories on Unraid..." if ssh_exec UNRAID "test -d '${DATA_PATH}/data'"; then log_info "Data directory already exists — skipping" else ssh_exec UNRAID "mkdir -p '${DATA_PATH}/data' '${DATA_PATH}/config'" # Create DB data directory for external databases if [[ "${GITEA_DB_TYPE}" != "sqlite3" ]]; then ssh_exec UNRAID "mkdir -p '${DATA_PATH}/db'" fi log_success "Data directories created" fi # --------------------------------------------------------------------------- # Step 2: Render + SCP docker-compose file # --------------------------------------------------------------------------- log_step 2 "Deploying docker-compose.yml..." if ssh_exec UNRAID "test -f '${DATA_PATH}/docker-compose.yml'"; then log_info "docker-compose.yml already exists — skipping" else TMPFILE=$(mktemp) # Set variables for template export DATA_PATH GITEA_PORT="${UNRAID_GITEA_PORT}" GITEA_SSH_PORT="${UNRAID_GITEA_SSH_PORT}" if [[ "$GITEA_DB_TYPE" == "sqlite3" ]]; then # No DB service needed — render template then strip DB blocks render_template "${SCRIPT_DIR}/templates/docker-compose-gitea.yml.tpl" "$TMPFILE" \ "\${GITEA_VERSION} \${DATA_PATH} \${GITEA_PORT} \${GITEA_SSH_PORT}" _strip_block "$TMPFILE" "DB_SERVICE_START" "DB_SERVICE_END" _strip_block "$TMPFILE" "DB_DEPENDS_START" "DB_DEPENDS_END" else # External DB — set DB-specific vars then render, strip sqlite markers _set_db_vars render_template "${SCRIPT_DIR}/templates/docker-compose-gitea.yml.tpl" "$TMPFILE" \ "\${GITEA_VERSION} \${DATA_PATH} \${GITEA_PORT} \${GITEA_SSH_PORT} \${DB_DOCKER_IMAGE} \${DB_ENV_VARS} \${DB_DATA_DIR} \${DB_HEALTHCHECK}" fi scp_to UNRAID "$TMPFILE" "${DATA_PATH}/docker-compose.yml" rm -f "$TMPFILE" log_success "docker-compose.yml deployed" fi # --------------------------------------------------------------------------- # Step 3: Render + SCP app.ini # --------------------------------------------------------------------------- log_step 3 "Deploying app.ini..." if ssh_exec UNRAID "test -f '${DATA_PATH}/config/app.ini'"; then log_info "app.ini already exists — skipping" else TMPFILE=$(mktemp) # Generate a random secret key for this instance GITEA_SECRET_KEY=$(openssl rand -hex 32) export GITEA_SECRET_KEY if [[ "$GITEA_DB_TYPE" == "sqlite3" ]]; then render_template "${SCRIPT_DIR}/templates/app.ini.tpl" "$TMPFILE" \ "\${GITEA_DOMAIN} \${GITEA_DB_TYPE} \${GITEA_SECRET_KEY}" _strip_block "$TMPFILE" "EXTDB_BLOCK_START" "EXTDB_BLOCK_END" else render_template "${SCRIPT_DIR}/templates/app.ini.tpl" "$TMPFILE" \ "\${GITEA_DOMAIN} \${GITEA_DB_TYPE} \${GITEA_SECRET_KEY} \${GITEA_DB_HOST} \${GITEA_DB_PORT} \${GITEA_DB_NAME} \${GITEA_DB_USER} \${GITEA_DB_PASSWD}" _strip_block "$TMPFILE" "SQLITE_BLOCK_START" "SQLITE_BLOCK_END" fi scp_to UNRAID "$TMPFILE" "${DATA_PATH}/config/app.ini" rm -f "$TMPFILE" log_success "app.ini deployed" fi # --------------------------------------------------------------------------- # Step 4: Start Gitea container # --------------------------------------------------------------------------- log_step 4 "Starting Gitea container..." CONTAINER_STATUS=$(ssh_exec UNRAID "docker ps --filter name=gitea --format '{{.Status}}'" 2>/dev/null || true) if [[ "$CONTAINER_STATUS" == *"Up"* ]]; then log_info "Gitea container already running — skipping" else # Try modern "docker compose" first (plugin), fall back to standalone "docker-compose" ssh_exec UNRAID "cd '${DATA_PATH}' && docker compose up -d 2>/dev/null || docker-compose up -d" log_success "Gitea container started" fi # --------------------------------------------------------------------------- # Step 5: Wait for Gitea to be ready # --------------------------------------------------------------------------- log_step 5 "Waiting for Gitea to be ready..." wait_for_http "${GITEA_INTERNAL_URL}/api/v1/version" 120 # --------------------------------------------------------------------------- # Step 6: Create admin user # --------------------------------------------------------------------------- log_step 6 "Creating admin user..." if curl -sf -u "${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASSWORD}" "${GITEA_INTERNAL_URL}/api/v1/user" -o /dev/null 2>/dev/null; then log_info "Admin user already exists — skipping" else # Create admin via CLI inside the container (not the API) because no API # token exists yet at this point. || true prevents set -e from exiting if # the user was partially created in a previous interrupted run. ssh_exec UNRAID "docker exec gitea gitea admin user create \ --username '${GITEA_ADMIN_USER}' \ --password '${GITEA_ADMIN_PASSWORD}' \ --email '${GITEA_ADMIN_EMAIL}' \ --admin \ --must-change-password=false" || true # Verify with API call — this is the real success check if curl -sf -u "${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASSWORD}" "${GITEA_INTERNAL_URL}/api/v1/user" -o /dev/null 2>/dev/null; then log_success "Admin user created" else log_error "Failed to create admin user" exit 1 fi fi # --------------------------------------------------------------------------- # Step 7+8: Generate API token and save to .env # --------------------------------------------------------------------------- log_step 7 "Generating API token..." if [[ -n "${GITEA_ADMIN_TOKEN:-}" ]]; then # Verify existing token works if curl -sf -H "Authorization: token ${GITEA_ADMIN_TOKEN}" "${GITEA_INTERNAL_URL}/api/v1/user" -o /dev/null 2>/dev/null; then log_info "API token already exists and is valid — skipping" else log_warn "Existing token is invalid — generating new one" GITEA_ADMIN_TOKEN="" fi fi if [[ -z "${GITEA_ADMIN_TOKEN:-}" ]]; then # Delete any stale token with the same name (idempotent re-run safety). # Gitea returns 409 Conflict if a token with this name already exists. curl -sf -u "${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASSWORD}" \ -X DELETE \ "${GITEA_INTERNAL_URL}/api/v1/users/${GITEA_ADMIN_USER}/tokens/migration-token" \ -o /dev/null 2>/dev/null || true # Generate token using basic auth (username:password). # The .sha1 field in the response contains the full token — this is the # ONLY time it's returned, so we must save it immediately. TOKEN_RESPONSE=$(curl -sf -u "${GITEA_ADMIN_USER}:${GITEA_ADMIN_PASSWORD}" \ -X POST \ -H "Content-Type: application/json" \ -d '{"name":"migration-token","scopes":["all"]}' \ "${GITEA_INTERNAL_URL}/api/v1/users/${GITEA_ADMIN_USER}/tokens") GITEA_ADMIN_TOKEN=$(printf '%s' "$TOKEN_RESPONSE" | jq -r '.sha1') if [[ -z "$GITEA_ADMIN_TOKEN" ]] || [[ "$GITEA_ADMIN_TOKEN" == "null" ]]; then log_error "Failed to generate API token" exit 1 fi save_env_var "GITEA_ADMIN_TOKEN" "$GITEA_ADMIN_TOKEN" log_success "API token generated and saved to .env" fi # --------------------------------------------------------------------------- # Step 9: Create organization # --------------------------------------------------------------------------- log_step 9 "Creating organization '${GITEA_ORG_NAME}'..." if curl -sf -H "Authorization: token ${GITEA_ADMIN_TOKEN}" "${GITEA_INTERNAL_URL}/api/v1/orgs/${GITEA_ORG_NAME}" -o /dev/null 2>/dev/null; then log_info "Organization already exists — skipping" else curl -sf -X POST \ -H "Authorization: token ${GITEA_ADMIN_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"username\":\"${GITEA_ORG_NAME}\",\"full_name\":\"${GITEA_INSTANCE_NAME}\",\"visibility\":\"public\"}" \ "${GITEA_INTERNAL_URL}/api/v1/orgs" -o /dev/null if curl -sf -H "Authorization: token ${GITEA_ADMIN_TOKEN}" "${GITEA_INTERNAL_URL}/api/v1/orgs/${GITEA_ORG_NAME}" -o /dev/null 2>/dev/null; then log_success "Organization created" else log_error "Failed to create organization" exit 1 fi fi log_success "Phase 1 complete — Gitea is running on Unraid"