diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..bf33ff9 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# Phase 2: Recover compose files, deploy services, start Gitea. +# Pipeable — run after setup.sh and after rsync'ing the repo. +# +# Usage (from workstation): +# ssh root@ 'bash -s' < bootstrap.sh +# +# Prerequisites: +# - setup.sh already run (infra_service user, dirs, uv) +# - Repo rsync'd to /opt/server-infra/ +# +# What it does: +# 1. Validates prerequisites +# 2. Recovers compose files from running containers (umami, reverse-proxy) +# 3. Creates data directories +# 4. Sets correct ownership +# 5. Prints next steps (start Gitea, add proxy host, push repo to Gitea) + +set -euo pipefail + +SERVICE_USER="infra_service" +REPO_DIR="/opt/server-infra" +DATA_DIR="/data/server-infra" +UV="/home/${SERVICE_USER}/.local/bin/uv" + +[ "$(id -u)" = "0" ] || { echo "ERROR: Run as root"; exit 1; } + +log() { echo "$(date '+%H:%M:%S') ==> $*"; } +warn() { echo "$(date '+%H:%M:%S') ==> WARNING: $*" >&2; } + +# ── Preflight checks ─────────────────────────────────────────────────────────── + +if ! id "${SERVICE_USER}" >/dev/null 2>&1; then + echo "ERROR: ${SERVICE_USER} user not found. Run setup.sh first." + exit 1 +fi + +if [ ! -d "${REPO_DIR}/.git" ]; then + echo "ERROR: Repo not found at ${REPO_DIR}. Rsync the repo first:" + echo " rsync -av --chown=root:root ~/Projects/server-infra/ root@:${REPO_DIR}/" + exit 1 +fi + +command -v docker >/dev/null 2>&1 || { echo "ERROR: docker not found"; exit 1; } + +[ -f "${UV}" ] || { echo "ERROR: uv not found at ${UV}. Run setup.sh first."; exit 1; } + +# ── Recover compose files from running containers ────────────────────────────── + +recover_project() { + local outfile="$1" + shift + local containers=("$@") + + local running=0 + for c in "${containers[@]}"; do + if docker inspect "${c}" >/dev/null 2>&1; then + running=1 + break + fi + done + + if [ "${running}" = "0" ]; then + warn "None of [${containers[*]}] are running — skipping recovery." + return 0 + fi + + log "Recovering: ${containers[*]}" + sudo -u "${SERVICE_USER}" \ + "/home/${SERVICE_USER}/.local/bin/uvx" docker-autocompose "${containers[@]}" > "${outfile}" + log " Saved to ${outfile}" +} + +log "Recovering compose files from running containers..." + +recover_project \ + "${REPO_DIR}/umami/docker-compose.yml" \ + umami-umami-1 umami-db-1 + +recover_project \ + "${REPO_DIR}/reverse-proxy/docker-compose.yml" \ + reverse_proxy-app-1 + +# ── Data directories ─────────────────────────────────────────────────────────── + +log "Creating data directories..." +mkdir -p "${DATA_DIR}/gitea" + +# ── Ownership ────────────────────────────────────────────────────────────────── + +log "Setting ownership..." +chown -R "${SERVICE_USER}:${SERVICE_USER}" "${REPO_DIR}" +chown -R "${SERVICE_USER}:${SERVICE_USER}" "${DATA_DIR}" + +# ── Summary ──────────────────────────────────────────────────────────────────── + +SERVER_IP=$(hostname -I | awk '{print $1}') + +echo "" +echo "==================================================================" +echo "" +echo " Bootstrap complete." +echo "" +echo " Services ready in ${REPO_DIR}/:" +[ -f "${REPO_DIR}/umami/docker-compose.yml" ] && echo " umami (recovered)" +[ -f "${REPO_DIR}/reverse-proxy/docker-compose.yml" ] && echo " reverse-proxy (recovered)" +[ -f "${REPO_DIR}/gitea/docker-compose.yml" ] && echo " gitea (from repo)" +echo "" +echo " Service user: ${SERVICE_USER}" +echo " Data dir: ${DATA_DIR}/" +echo "" +echo "==================================================================" +echo "" +echo " Next steps:" +echo "" +echo " 1. Start Gitea:" +echo " sudo -u ${SERVICE_USER} docker compose -f ${REPO_DIR}/gitea/docker-compose.yml up -d" +echo " # Web installer at http://${SERVER_IP}:3000" +echo " # Set ROOT_URL to your public domain (e.g. https://git.yourdomain.com)" +echo "" +echo " 2. Add proxy host in nginx proxy manager:" +echo " Forward hostname → ${SERVER_IP}:3000" +echo "" +echo " 3. Commit recovered compose files and push to Gitea:" +echo " cd ${REPO_DIR}" +echo " sudo -u ${SERVICE_USER} git add umami/docker-compose.yml reverse-proxy/docker-compose.yml" +echo " sudo -u ${SERVICE_USER} git commit -m 'chore: recover compose files'" +echo " sudo -u ${SERVICE_USER} git remote add origin https://git.yourdomain.com/youruser/server-infra.git" +echo " sudo -u ${SERVICE_USER} git push -u origin master" +echo "" +echo "==================================================================" +echo "" diff --git a/setup.sh b/setup.sh index cf8854b..19631a4 100644 --- a/setup.sh +++ b/setup.sh @@ -1,104 +1,28 @@ #!/bin/bash -# Server shared-infra setup: recover compose files, create service user, deploy. -# Run as root on the server. +# Phase 1: Create infra_service user, directories, and install uv. +# Pipeable — no repo needed on the server. # -# Usage: -# sudo bash setup.sh +# Usage (from workstation): +# ssh root@ 'bash -s' < setup.sh # # What it does: -# 1. Recovers docker-compose.yml files from already-running containers (one-time) -# 2. Creates infra_service system user (docker group, nologin) -# 3. Creates /data/server-infra/{service}/ data directories -# 4. Deploys all compose files from this repo to /opt/server-infra/ (idempotent) +# 1. Creates infra_service system user (nologin, docker group) +# 2. Creates /opt/server-infra/ and /data/server-infra/ +# 3. Installs uv as infra_service # -# Services managed by this repo: -# umami — analytics (recovered from running containers) -# reverse-proxy — nginx proxy manager (recovered from running containers) -# gitea — self-hosted git (new, compose file already in repo) +# After this script: rsync the repo, then run bootstrap.sh. set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SERVICE_USER="infra_service" -DEPLOY_DIR="/opt/server-infra" +REPO_DIR="/opt/server-infra" +DATA_DIR="/data/server-infra" -[ "$(id -u)" = "0" ] || { echo "ERROR: Run as root: sudo bash setup.sh"; exit 1; } +[ "$(id -u)" = "0" ] || { echo "ERROR: Run as root"; exit 1; } log() { echo "$(date '+%H:%M:%S') ==> $*"; } -warn() { echo "$(date '+%H:%M:%S') ==> WARNING: $*" >&2; } -# ── Preflight checks ─────────────────────────────────────────────────────────── - -command -v docker >/dev/null 2>&1 || { echo "ERROR: docker not found"; exit 1; } - -# Find uvx: prefer the installing user's local bin, fall back to PATH -UVX="" -for candidate in /root/.local/bin/uvx /home/Deeman/.local/bin/uvx /usr/local/bin/uvx "$(command -v uvx 2>/dev/null || true)"; do - if [ -n "${candidate}" ] && [ -x "${candidate}" ]; then - UVX="${candidate}" - break - fi -done -[ -n "${UVX}" ] || { echo "ERROR: uvx not found — install uv first: curl -LsSf https://astral.sh/uv/install.sh | sh"; exit 1; } - -log "Using uvx at ${UVX}" - -# ── Helper: recover one compose project ─────────────────────────────────────── -# Usage: recover_project [ ...] -recover_project() { - local outfile="$1" - shift - local containers=("$@") - - # Check at least one container from the list is running - local running=0 - for c in "${containers[@]}"; do - if docker inspect "${c}" >/dev/null 2>&1; then - running=1 - break - fi - done - - if [ "${running}" = "0" ]; then - warn "None of [${containers[*]}] are running — skipping recovery." - return 0 - fi - - log "Recovering compose file for: ${containers[*]}" - "${UVX}" docker-autocompose "${containers[@]}" > "${outfile}" - log "Saved to ${outfile}" -} - -# ── Recover compose files ────────────────────────────────────────────────────── - -log "Recovering compose files from running containers..." -mkdir -p /tmp/server-infra-recovery - -recover_project \ - /tmp/server-infra-recovery/umami.yml \ - umami-umami-1 umami-db-1 - -recover_project \ - /tmp/server-infra-recovery/reverse-proxy.yml \ - reverse_proxy-app-1 - -# ── Copy recovered files into this repo ─────────────────────────────────────── - -log "Copying recovered files into repo (${SCRIPT_DIR})..." - -for pair in \ - "/tmp/server-infra-recovery/umami.yml:${SCRIPT_DIR}/umami/docker-compose.yml" \ - "/tmp/server-infra-recovery/reverse-proxy.yml:${SCRIPT_DIR}/reverse-proxy/docker-compose.yml"; do - src="${pair%%:*}" - dst="${pair##*:}" - if [ -f "${src}" ]; then - mkdir -p "$(dirname "${dst}")" - cp "${src}" "${dst}" - log " ${dst}" - fi -done - -# ── Service account ──────────────────────────────────────────────────────────── +# ── Service user ─────────────────────────────────────────────────────────────── log "Creating service user ${SERVICE_USER}..." if ! id "${SERVICE_USER}" >/dev/null 2>&1; then @@ -109,69 +33,45 @@ else fi usermod -aG docker "${SERVICE_USER}" -# ── Data directories ─────────────────────────────────────────────────────────── +# ── Directories ──────────────────────────────────────────────────────────────── -log "Creating data directories..." -mkdir -p /data/server-infra/gitea -chown -R "${SERVICE_USER}:${SERVICE_USER}" /data/server-infra +log "Creating directories..." +mkdir -p "${REPO_DIR}" "${DATA_DIR}" +chown "${SERVICE_USER}:${SERVICE_USER}" "${REPO_DIR}" +chown -R "${SERVICE_USER}:${SERVICE_USER}" "${DATA_DIR}" -# ── Deploy: copy all compose files from repo to deploy dir (idempotent) ──────── -# -# Recovered files (umami, reverse-proxy) are already in ${SCRIPT_DIR} after -# the recovery section above. Gitea compose is committed directly in the repo. -# This section deploys all of them to DEPLOY_DIR. +# ── uv ──────────────────────────────────────────────────────────────────────── -log "Deploying compose files to ${DEPLOY_DIR}..." - -for service in umami reverse-proxy gitea; do - src="${SCRIPT_DIR}/${service}/docker-compose.yml" - dst="${DEPLOY_DIR}/${service}/docker-compose.yml" - if [ -f "${src}" ]; then - mkdir -p "${DEPLOY_DIR}/${service}" - cp "${src}" "${dst}" - log " ${service} → ${dst}" - else - warn " ${service}/docker-compose.yml not in repo — skipping deploy" - fi -done - -chown -R "${SERVICE_USER}:${SERVICE_USER}" "${DEPLOY_DIR}" +if [ ! -f "/home/${SERVICE_USER}/.local/bin/uv" ]; then + log "Installing uv..." + sudo -u "${SERVICE_USER}" bash -c 'curl -LsSf https://astral.sh/uv/install.sh | sh' + log " uv installed." +else + log " uv already installed — skipping." +fi # ── Summary ──────────────────────────────────────────────────────────────────── +SERVER_IP=$(hostname -I | awk '{print $1}') + echo "" echo "==================================================================" echo "" -echo " Setup complete." -echo "" -echo " Deployed services:" -[ -f "${DEPLOY_DIR}/umami/docker-compose.yml" ] && echo " umami → ${DEPLOY_DIR}/umami/docker-compose.yml" -[ -f "${DEPLOY_DIR}/reverse-proxy/docker-compose.yml" ] && echo " reverse-proxy → ${DEPLOY_DIR}/reverse-proxy/docker-compose.yml" -[ -f "${DEPLOY_DIR}/gitea/docker-compose.yml" ] && echo " gitea → ${DEPLOY_DIR}/gitea/docker-compose.yml" +echo " Phase 1 complete." echo "" echo " Service user: ${SERVICE_USER} (docker group)" -echo " Deploy dir: ${DEPLOY_DIR}/" -echo " Data dir: /data/server-infra/" +echo " Repo dir: ${REPO_DIR}/" +echo " Data dir: ${DATA_DIR}/" echo "" echo "==================================================================" echo "" -echo " Next steps:" +echo " Next step — rsync repo from workstation, then run bootstrap:" echo "" -echo " 1. Commit recovered compose files to git:" -echo " cd ${SCRIPT_DIR}" -echo " git add umami/docker-compose.yml reverse-proxy/docker-compose.yml" -echo " git commit -m 'chore: recover compose files from running containers'" +echo " 1. On your workstation:" +echo " rsync -av --chown=root:root ~/Projects/server-infra/ root@${SERVER_IP}:${REPO_DIR}/" echo "" -echo " 2. Start Gitea:" -echo " sudo -u ${SERVICE_USER} docker compose -f ${DEPLOY_DIR}/gitea/docker-compose.yml up -d" -echo " # Then open http://:3000 to complete the web installer" -echo " # Set ROOT_URL to your public domain (e.g. https://git.yourdomain.com)" -echo "" -echo " 3. Add Gitea proxy host in nginx proxy manager:" -echo " Forward to :3000" -echo "" -echo " 4. To restart any service if containers go down:" -echo " sudo -u ${SERVICE_USER} docker compose -f ${DEPLOY_DIR}//docker-compose.yml up -d" +echo " 2. Then run bootstrap:" +echo " ssh root@${SERVER_IP} 'bash -s' < bootstrap.sh" echo "" echo "==================================================================" echo ""