refactor: two-phase setup — setup.sh (user/dirs/uv) + bootstrap.sh (recover/deploy)
Matches the beanflows pattern. No GitLab dependency — repo reaches the
server via rsync, Gitea becomes the remote once it's running.
setup.sh — pipeable phase 1: infra_service user, /opt/server-infra,
/data/server-infra, uv installation
bootstrap.sh — pipeable phase 2: validates prereqs, recovers umami +
reverse-proxy compose files, creates data dirs, sets ownership
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
132
bootstrap.sh
Normal file
132
bootstrap.sh
Normal file
@@ -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@<server-ip> '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@<server-ip>:${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 ""
|
||||
170
setup.sh
170
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@<server-ip> '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 <outfile> <container_name> [<container_name> ...]
|
||||
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://<server-ip>: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 <server-ip>:3000"
|
||||
echo ""
|
||||
echo " 4. To restart any service if containers go down:"
|
||||
echo " sudo -u ${SERVICE_USER} docker compose -f ${DEPLOY_DIR}/<service>/docker-compose.yml up -d"
|
||||
echo " 2. Then run bootstrap:"
|
||||
echo " ssh root@${SERVER_IP} 'bash -s' < bootstrap.sh"
|
||||
echo ""
|
||||
echo "=================================================================="
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user