feat: add Phase 1 — Gitea on Unraid
- phase1_gitea_unraid.sh: 9-step deploy (dirs, docker-compose, app.ini, container start, wait, admin user, API token, save to .env, create org). Every step has idempotency check — running twice changes nothing. - phase1_post_check.sh: 5 independent verification checks - phase1_teardown.sh: stop container + optionally remove data, with prompts Also adds inline comments to lib/common.sh and preflight.sh explaining WHY decisions were made (SSH flags, API tmpfile pattern, port checks, etc.) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
171
phase1_gitea_unraid.sh
Executable file
171
phase1_gitea_unraid.sh
Executable file
@@ -0,0 +1,171 @@
|
||||
#!/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
|
||||
|
||||
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"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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'"
|
||||
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}"
|
||||
render_template "${SCRIPT_DIR}/templates/docker-compose-gitea.yml.tpl" "$TMPFILE"
|
||||
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
|
||||
render_template "${SCRIPT_DIR}/templates/app.ini.tpl" "$TMPFILE"
|
||||
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
|
||||
# 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"
|
||||
Reference in New Issue
Block a user