#!/usr/bin/env bash # One-time bootstrap for the AndyTranscribe staging instance on z00. # Creates ~/andyTranscibe-stage, a unique .env, the stage Caddy Docker network, # and the Gitea STAGE_DEPLOY_PATHS secret. set -euo pipefail GITEA_DIR="${GITEA_DIR:-${HOME}/gitea}" REPO_OWNER="${REPO_OWNER:-ben}" REPO_NAME="${REPO_NAME:-AndyTranscribe}" REGISTRY_HOST="${REGISTRY_HOST:-gitea.z00.nu}" STAGE_PATH="${STAGE_PATH:-${HOME}/andyTranscibe-stage}" GITEA_REPO_URL="https://${REGISTRY_HOST}/${REPO_OWNER}/${REPO_NAME}.git" STAGE_URL="${STAGE_URL:-https://stage.transcribe.z00.nu}" REVERB_PUBLIC_HOST="${REVERB_PUBLIC_HOST:-reverb.stage.transcribe.z00.nu}" CONTAINER_PREFIX="${CONTAINER_PREFIX:-andytranscribe-stage}" CADDY_NETWORK="${CADDY_NETWORK:-andytranscribe-stage-caddy}" PROD_CADDY_NETWORK="${PROD_CADDY_NETWORK:-caddy-proxy-manager-test_caddy-test-network}" CADDY_CONTAINER="${CADDY_CONTAINER:-}" CREDENTIALS_FILE="${GITEA_DIR}/.credentials" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" upsert_env() { local file="$1" local key="$2" local value="$3" python3 - "$file" "$key" "$value" <<'PY' import re import sys path, key, value = sys.argv[1], sys.argv[2], sys.argv[3] text = open(path).read() pattern = re.compile(r"^" + re.escape(key) + r"=.*$", re.M) replacement = f"{key}={value}" if pattern.search(text): text = pattern.sub(replacement, text, count=1) else: text = text.rstrip("\n") + "\n" + replacement + "\n" open(path, "w").write(text) PY } if [[ ! -f "${CREDENTIALS_FILE}" ]]; then echo "Missing ${CREDENTIALS_FILE}" >&2 exit 1 fi # shellcheck disable=SC1090 source "${CREDENTIALS_FILE}" API="https://${REGISTRY_HOST}/api/v1" AUTH=(-u "${ADMIN_USERNAME}:${ADMIN_PASSWORD}") if ! curl -fsS "${AUTH[@]}" "${API}/repos/${REPO_OWNER}/${REPO_NAME}" >/dev/null 2>&1; then echo "Repository ${REPO_OWNER}/${REPO_NAME} not found on ${REGISTRY_HOST}" >&2 exit 1 fi if [[ ! -d "${STAGE_PATH}/.git" ]]; then mkdir -p "$(dirname "${STAGE_PATH}")" git clone "${GITEA_REPO_URL}" "${STAGE_PATH}" fi ( cd "${STAGE_PATH}" # Never fetch from the prod checkout; CI deploys by SHA against this origin. git remote set-url origin "${GITEA_REPO_URL}" git fetch origin || true if git rev-parse --verify origin/stage >/dev/null 2>&1; then git checkout stage git pull --ff-only origin stage || true else echo "Remote branch origin/stage not found yet; leaving $(git branch --show-current)." fi ) mkdir -p \ "${STAGE_PATH}/database" \ "${STAGE_PATH}/storage/app/private/recordings" \ "${STAGE_PATH}/storage/app/public" \ "${STAGE_PATH}/storage/framework/cache" \ "${STAGE_PATH}/storage/framework/sessions" \ "${STAGE_PATH}/storage/framework/views" \ "${STAGE_PATH}/storage/logs" \ "${STAGE_PATH}/bootstrap/cache" if [[ ! -f "${STAGE_PATH}/database/database.sqlite" ]]; then touch "${STAGE_PATH}/database/database.sqlite" fi ENV_SRC="${STAGE_PATH}/.env.example" if [[ ! -f "${ENV_SRC}" ]]; then ENV_SRC="${REPO_ROOT}/.env.example" fi if [[ ! -f "${STAGE_PATH}/.env" ]]; then cp "${ENV_SRC}" "${STAGE_PATH}/.env" upsert_env "${STAGE_PATH}/.env" "APP_ENV" "production" upsert_env "${STAGE_PATH}/.env" "APP_DEBUG" "false" upsert_env "${STAGE_PATH}/.env" "APP_KEY" "base64:$(openssl rand -base64 32)" upsert_env "${STAGE_PATH}/.env" "APP_URL" "${STAGE_URL}" upsert_env "${STAGE_PATH}/.env" "PUBLIC_APP_URL" "${STAGE_URL}" # Prod already binds 18080 (app), 18180 (reverb), and 18090 (whisper). upsert_env "${STAGE_PATH}/.env" "APP_HOST_PORT" "18280" upsert_env "${STAGE_PATH}/.env" "REVERB_HOST_PORT" "18281" upsert_env "${STAGE_PATH}/.env" "WHISPER_HOST_PORT" "18290" upsert_env "${STAGE_PATH}/.env" "REVERB_APP_ID" "$(openssl rand -hex 8)" upsert_env "${STAGE_PATH}/.env" "REVERB_APP_KEY" "$(openssl rand -hex 16)" upsert_env "${STAGE_PATH}/.env" "REVERB_APP_SECRET" "$(openssl rand -hex 20)" upsert_env "${STAGE_PATH}/.env" "REVERB_PUBLIC_HOST" "${REVERB_PUBLIC_HOST}" upsert_env "${STAGE_PATH}/.env" "REVERB_PUBLIC_PORT" "443" upsert_env "${STAGE_PATH}/.env" "REVERB_PUBLIC_SCHEME" "https" upsert_env "${STAGE_PATH}/.env" "CONTAINER_PREFIX" "${CONTAINER_PREFIX}" upsert_env "${STAGE_PATH}/.env" "CADDY_NETWORK" "${CADDY_NETWORK}" echo "Wrote ${STAGE_PATH}/.env" else echo "Keeping existing ${STAGE_PATH}/.env" fi if ! docker network inspect "${CADDY_NETWORK}" >/dev/null 2>&1; then docker network create "${CADDY_NETWORK}" echo "Created Docker network ${CADDY_NETWORK}" fi if [[ -z "${CADDY_CONTAINER}" ]]; then CADDY_CONTAINER="$( docker network inspect "${PROD_CADDY_NETWORK}" \ --format '{{range .Containers}}{{.Name}}{{"\n"}}{{end}}' \ | grep -i caddy \ | head -1 || true )" fi if [[ -z "${CADDY_CONTAINER}" ]]; then echo "Could not find a Caddy container on ${PROD_CADDY_NETWORK}." >&2 echo "Set CADDY_CONTAINER and re-run: docker network connect ${CADDY_NETWORK} " >&2 exit 1 fi if docker network inspect "${CADDY_NETWORK}" --format '{{range .Containers}}{{.Name}}{{"\n"}}{{end}}' | grep -qx "${CADDY_CONTAINER}"; then echo "Caddy container ${CADDY_CONTAINER} already on ${CADDY_NETWORK}" else docker network connect "${CADDY_NETWORK}" "${CADDY_CONTAINER}" echo "Attached ${CADDY_CONTAINER} to ${CADDY_NETWORK}" fi set_secret() { local name="$1" local value="$2" local tmp tmp="$(mktemp)" python3 -c 'import json,sys; json.dump({"data": sys.argv[1]}, open(sys.argv[2], "w"))' "${value}" "${tmp}" curl -fsS "${AUTH[@]}" -X PUT \ "${API}/repos/${REPO_OWNER}/${REPO_NAME}/actions/secrets/${name}" \ -H "Content-Type: application/json" \ --data-binary @"${tmp}" >/dev/null rm -f "${tmp}" } set_secret "STAGE_DEPLOY_PATHS" "${STAGE_PATH}" echo "Stage instance ready at ${STAGE_PATH}" echo "Public URL: ${STAGE_URL}" echo "Reverb host: ${REVERB_PUBLIC_HOST}" echo "Caddy network: ${CADDY_NETWORK} (container ${CADDY_CONTAINER})" echo "Gitea secret STAGE_DEPLOY_PATHS=${STAGE_PATH}" echo echo "Still required: DNS for stage.transcribe.z00.nu and ${REVERB_PUBLIC_HOST}," echo "plus Caddy Proxy Manager hosts pointing at ${CONTAINER_PREFIX}-app:80" echo "and ${CONTAINER_PREFIX}-reverb:8080." echo "Then: git checkout -b stage && git push -u origin stage"