138 lines
4.7 KiB
Bash
Executable file
138 lines
4.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# deployment-node.sh - To be run on the execution node (SOLARIA, PIHA, VPS)
|
|
# This script pulls the latest changes and ensures services are running.
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
REPO_PATH="${HOME}/homelab-codex-ws"
|
|
RUNTIME_PATH="/opt/homelab"
|
|
HOSTNAME=$(hostname | tr '[:lower:]' '[:upper:]')
|
|
CURRENT_OS_HOST=$(hostname)
|
|
|
|
echo "--- Starting Deployment on ${HOSTNAME} ---"
|
|
|
|
# 1. Update Repository
|
|
if [ ! -d "$REPO_PATH" ]; then
|
|
echo "Error: Repository not found at $REPO_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$REPO_PATH"
|
|
echo "Pulling latest changes..."
|
|
git pull
|
|
|
|
# Resolve host directory: search hosts/*/host.yaml for os_hostname == $(hostname)
|
|
HOST_LOGICAL=$(python3 -c "
|
|
import yaml, os, sys
|
|
current = '${CURRENT_OS_HOST}'
|
|
hosts_dir = os.path.join('${REPO_PATH}', 'hosts')
|
|
for entry in sorted(os.listdir(hosts_dir)):
|
|
host_yaml = os.path.join(hosts_dir, entry, 'host.yaml')
|
|
if not os.path.isfile(host_yaml):
|
|
continue
|
|
try:
|
|
with open(host_yaml) as f:
|
|
data = yaml.safe_load(f)
|
|
if data and data.get('os_hostname') == current:
|
|
print(entry)
|
|
sys.exit(0)
|
|
except Exception:
|
|
pass
|
|
")
|
|
|
|
if [ -n "$HOST_LOGICAL" ]; then
|
|
HOST_DIR="${REPO_PATH}/hosts/${HOST_LOGICAL}"
|
|
else
|
|
HOST_LOGICAL_FB="$(echo "$CURRENT_OS_HOST" | tr '[:upper:]' '[:lower:]')"
|
|
echo "WARN: no os_hostname match for ${CURRENT_OS_HOST}, falling back to lowercase hostname dir" >&2
|
|
HOST_DIR="${REPO_PATH}/hosts/${HOST_LOGICAL_FB}"
|
|
fi
|
|
|
|
if [ ! -d "$HOST_DIR" ]; then
|
|
echo "Error: No host directory found for ${CURRENT_OS_HOST} (tried ${HOST_DIR})" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 2. Identify Services
|
|
SERVICES=()
|
|
if [ -f "${HOST_DIR}/services.txt" ]; then
|
|
mapfile -t SERVICES < <(grep -v '^\s*#' "${HOST_DIR}/services.txt" | grep -v '^\s*$')
|
|
elif [ -f "${HOST_DIR}/services.yaml" ]; then
|
|
SERVICES=($(python3 -c "
|
|
import yaml, sys
|
|
try:
|
|
with open('${HOST_DIR}/services.yaml', 'r') as f:
|
|
data = yaml.safe_load(f)
|
|
if data and 'services' in data:
|
|
if isinstance(data['services'], dict):
|
|
print(' '.join(data['services'].keys()))
|
|
elif isinstance(data['services'], list):
|
|
print(' '.join(data['services']))
|
|
except Exception as e:
|
|
print(f'Error parsing YAML: {e}', file=sys.stderr)
|
|
sys.exit(1)
|
|
"))
|
|
fi
|
|
|
|
if [ ${#SERVICES[@]} -eq 0 ]; then
|
|
echo "No services found for ${HOSTNAME}. Skipping service deployment."
|
|
exit 0
|
|
fi
|
|
|
|
# 3. Deploy Services
|
|
for service in "${SERVICES[@]}"; do
|
|
# Services that ship their own deploy-local.sh have a dedicated, orchestrated
|
|
# deploy path (e.g. control-plane via deploy-control-plane.sh → deploy-local.sh).
|
|
# Deploying them in this loop too would invoke `docker compose up` with a
|
|
# different Compose project name than their dedicated path uses (this loop runs
|
|
# from ${REPO_PATH}; deploy-local.sh runs from services/<svc>/), so `up
|
|
# -d --remove-orphans` would Recreate and tear down the already-running
|
|
# containers — exactly what wiped the control-plane on VPS on 2026-06-25.
|
|
# Their own deploy path owns them; skip here.
|
|
if [ -f "${REPO_PATH}/services/${service}/deploy-local.sh" ]; then
|
|
echo "Skipping ${service}: ma własną ścieżkę deployu (services/${service}/deploy-local.sh), pomijam"
|
|
continue
|
|
fi
|
|
|
|
echo "Deploying service: ${service}..."
|
|
|
|
COMPOSE_FILE="${REPO_PATH}/services/${service}/docker-compose.yml"
|
|
|
|
if [ ! -f "$COMPOSE_FILE" ]; then
|
|
echo "Warning: Compose file not found for ${service} at ${COMPOSE_FILE}"
|
|
continue
|
|
fi
|
|
|
|
TARGET_DIR="${RUNTIME_PATH}/services/${service}"
|
|
mkdir -p "$TARGET_DIR"
|
|
|
|
OVERRIDE_FILE="${HOST_DIR}/runtime/${service}/docker-compose.override.yml"
|
|
|
|
COMPOSE_CMD="docker compose -f ${COMPOSE_FILE}"
|
|
if [ -f "$OVERRIDE_FILE" ]; then
|
|
echo "Using override file for ${service}"
|
|
COMPOSE_CMD="${COMPOSE_CMD} -f ${OVERRIDE_FILE}"
|
|
fi
|
|
|
|
ENV_FILE="${REPO_PATH}/services/${service}/.env"
|
|
if [ -f "$ENV_FILE" ]; then
|
|
COMPOSE_CMD="${COMPOSE_CMD} --env-file ${ENV_FILE}"
|
|
fi
|
|
|
|
# Services with their own Dockerfile need a rebuild on every deploy —
|
|
# otherwise `up -d` sees "container running, image tag unchanged" and skips
|
|
# rebuilding even when src/ changed, silently leaving the old code running
|
|
# (backlog c858dbc). Services with a prebuilt image (no Dockerfile) don't
|
|
# need --build.
|
|
if [ -f "${REPO_PATH}/services/${service}/Dockerfile" ]; then
|
|
echo "Building ${service} (has Dockerfile)"
|
|
$COMPOSE_CMD up -d --build --remove-orphans
|
|
else
|
|
echo "Using prebuilt image for ${service}"
|
|
$COMPOSE_CMD up -d --remove-orphans
|
|
fi
|
|
done
|
|
|
|
echo "--- Deployment Complete ---"
|