AI-Development-Server/scripts/development/refresh-plotdirector.sh

170 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
SOURCE_REPO="/srv/repos/PlotDirector"
PUBLISH_DIR="/srv/apps/plotdirector-dev/current"
SERVICE_NAME="plotdirector-dev"
SMOKE_URL="http://127.0.0.1/Account/Login"
SMOKE_HOST="dev.plotdirector.com"
CONFIGURATION="Release"
SKIP_BUILD=0
DRY_RUN=0
usage() {
cat <<'USAGE'
Usage: scripts/development/refresh-plotdirector.sh [options]
Quickly refresh the PlotDirector development instance from the current working
tree. This script does not pull, switch branches, commit, push, reset, clean,
change database schema, run SQL scripts, or modify runtime secrets.
Options:
--help Show this help text.
--dry-run Show intended actions without changing the running application.
--skip-build Skip dotnet build and publish the current working tree directly.
Defaults:
Source repository: /srv/repos/PlotDirector
Publish path: /srv/apps/plotdirector-dev/current
Service: plotdirector-dev
Smoke route: http://127.0.0.1/Account/Login with Host dev.plotdirector.com
USAGE
}
log() {
printf '%s\n' "$*"
}
fail() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
show_recent_logs() {
log "Latest ${SERVICE_NAME} logs:"
sudo -n journalctl -u "$SERVICE_NAME" --no-pager -n 100 | sed -E \
-e 's/(Password=)[^;[:space:]]+/\1<redacted>/g' \
-e 's/(SecretKey=)[^;[:space:]]+/\1<redacted>/g' \
-e 's/(ApiKey=)[^;[:space:]]+/\1<redacted>/g' || true
}
cleanup() {
if [[ -n "${PUBLISH_TMP:-}" && -d "${PUBLISH_TMP:-}" ]]; then
rm -rf "$PUBLISH_TMP"
fi
}
trap cleanup EXIT
while [[ "$#" -gt 0 ]]; do
case "$1" in
--help)
usage
exit 0
;;
--dry-run)
DRY_RUN=1
;;
--skip-build)
SKIP_BUILD=1
;;
*)
fail "Unknown option: $1"
;;
esac
shift
done
[[ -d "$SOURCE_REPO/.git" ]] || fail "PlotDirector repository not found at ${SOURCE_REPO}"
[[ -f "${SOURCE_REPO}/PlotLine/PlotLine.csproj" ]] || fail "PlotLine project not found"
command -v dotnet >/dev/null 2>&1 || fail "dotnet command not found"
command -v rsync >/dev/null 2>&1 || fail "rsync command not found"
command -v curl >/dev/null 2>&1 || fail "curl command not found"
sudo -n true >/dev/null 2>&1 || fail "passwordless non-interactive sudo is required"
case "$PUBLISH_DIR" in
""|"/"|"/srv"|"/srv/"|"/srv/apps"|"/srv/apps/")
fail "Unsafe publish path: ${PUBLISH_DIR}"
;;
esac
if [[ "$PUBLISH_DIR" != "/srv/apps/plotdirector-dev/current" ]]; then
fail "Publish path must be /srv/apps/plotdirector-dev/current; got ${PUBLISH_DIR}"
fi
branch="$(git -C "$SOURCE_REPO" branch --show-current)"
dirty="no"
if [[ -n "$(git -C "$SOURCE_REPO" status --porcelain)" ]]; then
dirty="yes"
fi
log "PlotDirector source: ${SOURCE_REPO}"
log "Current branch: ${branch:-detached}"
log "Working tree dirty: ${dirty}"
log "Publish target: ${PUBLISH_DIR}"
if [[ "$SKIP_BUILD" != "1" ]]; then
log "Building PlotDirector (${CONFIGURATION})..."
dotnet build "${SOURCE_REPO}/PlotLine/PlotLine.csproj" --configuration "$CONFIGURATION"
else
log "Skipping build by request."
fi
PUBLISH_TMP="$(mktemp -d /tmp/plotdirector-refresh.XXXXXX)"
log "Publishing to temporary directory..."
dotnet publish "${SOURCE_REPO}/PlotLine/PlotLine.csproj" --configuration "$CONFIGURATION" --no-build --output "$PUBLISH_TMP"
[[ -f "${PUBLISH_TMP}/PlotLine.dll" ]] || fail "Publish output did not contain PlotLine.dll"
if [[ "$DRY_RUN" == "1" ]]; then
log "Dry-run mode: publish succeeded; running application was not changed."
log "Would stop ${SERVICE_NAME}, replace ${PUBLISH_DIR}, start ${SERVICE_NAME}, and smoke test ${SMOKE_URL}."
exit 0
fi
log "Stopping ${SERVICE_NAME}..."
sudo -n systemctl stop "$SERVICE_NAME"
log "Replacing published application files..."
sudo -n install -d -m 0750 -o plotdirector -g plotdirector "$PUBLISH_DIR"
sudo -n rsync -a --delete "${PUBLISH_TMP}/" "${PUBLISH_DIR}/"
sudo -n chown -R plotdirector:plotdirector "$PUBLISH_DIR"
sudo -n find "$PUBLISH_DIR" -type d -exec chmod 0750 {} +
sudo -n find "$PUBLISH_DIR" -type f -exec chmod 0640 {} +
log "Starting ${SERVICE_NAME}..."
sudo -n systemctl start "$SERVICE_NAME"
for _ in {1..20}; do
if systemctl is-active --quiet "$SERVICE_NAME"; then
break
fi
sleep 1
done
if ! systemctl is-active --quiet "$SERVICE_NAME"; then
log "${SERVICE_NAME} did not become active."
show_recent_logs
exit 1
fi
log "Smoke testing ${SMOKE_URL}..."
smoke_ok=0
for _ in {1..30}; do
if curl --fail --silent --show-error --noproxy '*' -H "Host: ${SMOKE_HOST}" "$SMOKE_URL" >/dev/null 2>&1; then
smoke_ok=1
break
fi
sleep 1
done
if [[ "$smoke_ok" != "1" ]]; then
log "Smoke test failed for ${SMOKE_URL}."
curl --fail --silent --show-error --noproxy '*' -H "Host: ${SMOKE_HOST}" "$SMOKE_URL" >/dev/null || true
show_recent_logs
exit 1
fi
log "Refresh complete. ${SERVICE_NAME} is active and ${SMOKE_URL} returned HTTP 200."