File indexing completed on 2026-07-21 08:46:28
0001
0002
0003
0004
0005
0006
0007
0008
0009 set -euo pipefail
0010
0011 DEPLOY_ROOT="${SWF_DEPLOY_ROOT:-/opt/swf-monitor}"
0012 CURRENT_DIR="$DEPLOY_ROOT/current"
0013 SHARED_STATIC="$DEPLOY_ROOT/shared/static"
0014 MCP_SERVICE="${SWF_MCP_SERVICE:-swf-monitor-mcp-asgi.service}"
0015
0016 DO_UI=false
0017 DO_MCP=false
0018 DO_STATIC=false
0019 DRY_RUN=false
0020
0021 usage() {
0022 cat <<'EOF'
0023 Usage: deploy-lightweight-ui-mcp.sh [--ui] [--mcp] [--static] [--dry-run]
0024
0025 Fast in-place deploy for swf-monitor UI/template/view changes and MCP tool
0026 changes only. Use the full deploy for migrations, requirements/venv changes,
0027 Apache config, systemd units, ops-agent code, bot code, or release changes.
0028 This follows the normal dev-area sync workflow: current checkout to active
0029 /opt/swf-monitor/current release, without creating a release directory or
0030 moving the current symlink.
0031
0032 Options:
0033 --ui Sync UI/web code and templates; recycle mod_wsgi by touching wsgi.py
0034 --mcp Sync MCP code/helpers; restart swf-monitor-mcp-asgi.service
0035 --static Also collect and publish static assets
0036 --dry-run Show rsync changes and planned process actions without applying
0037 --help Show this help
0038
0039 Examples:
0040 sudo ./deploy-lightweight-ui-mcp.sh --ui
0041 sudo ./deploy-lightweight-ui-mcp.sh --mcp
0042 sudo ./deploy-lightweight-ui-mcp.sh --ui --mcp
0043 sudo ./deploy-lightweight-ui-mcp.sh --ui --static
0044 EOF
0045 }
0046
0047 log() {
0048 echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
0049 }
0050
0051 while [[ $
0052 case "$1" in
0053 --ui)
0054 DO_UI=true
0055 ;;
0056 --mcp)
0057 DO_MCP=true
0058 ;;
0059 --static)
0060 DO_STATIC=true
0061 ;;
0062 --dry-run)
0063 DRY_RUN=true
0064 ;;
0065 --help|-h)
0066 usage
0067 exit 0
0068 ;;
0069 *)
0070 echo "Unknown option: $1" >&2
0071 usage >&2
0072 exit 2
0073 ;;
0074 esac
0075 shift
0076 done
0077
0078 if [[ "$DO_UI" != true && "$DO_MCP" != true ]]; then
0079 echo "ERROR: choose --ui and/or --mcp; --static is an add-on" >&2
0080 usage >&2
0081 exit 2
0082 fi
0083
0084 if [[ "$DRY_RUN" != true && "$EUID" -ne 0 ]]; then
0085 echo "ERROR: run with sudo so the active release and systemd service can be updated" >&2
0086 exit 1
0087 fi
0088
0089 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
0090 REPO_ROOT="$SCRIPT_DIR"
0091 SRC_DIR="$REPO_ROOT/src"
0092 TARGET_SRC="$CURRENT_DIR/src"
0093
0094 if [[ ! -d "$SRC_DIR/monitor_app" || ! -f "$SRC_DIR/manage.py" ]]; then
0095 echo "ERROR: source tree not found at $SRC_DIR" >&2
0096 exit 1
0097 fi
0098
0099 if [[ ! -d "$TARGET_SRC" || ! -f "$TARGET_SRC/swf_monitor_project/wsgi.py" ]]; then
0100 echo "ERROR: active deployment source tree not found at $TARGET_SRC" >&2
0101 exit 1
0102 fi
0103
0104 if [[ -d "$REPO_ROOT/.git" ]]; then
0105 OUT_OF_SCOPE=$(git -C "$REPO_ROOT" status --short --untracked-files=all \
0106 | awk '{print $2}' \
0107 | grep -E '(^requirements|^pyproject\.toml$|^apache-swf-monitor\.conf$|\.service$|^config/apache/|/migrations/|^agents/|/panda/|/testbed_bot/)' \
0108 || true)
0109 if [[ -n "$OUT_OF_SCOPE" ]]; then
0110 echo "ERROR: working tree contains changes outside lightweight UI/MCP scope:" >&2
0111 echo "$OUT_OF_SCOPE" >&2
0112 echo "Use the full deploy for these changes, or commit/stash unrelated work before running this script." >&2
0113 exit 1
0114 fi
0115 fi
0116
0117 RSYNC_ARGS=(-a --delete --exclude '__pycache__/' --exclude '*.pyc')
0118 if [[ "$DRY_RUN" == true ]]; then
0119 RSYNC_ARGS+=(--dry-run --itemize-changes)
0120 fi
0121
0122 MONITOR_EXCLUDES=(
0123 --exclude 'migrations/'
0124 --exclude 'management/'
0125 --exclude 'panda/'
0126 --exclude 'testbed_bot/'
0127 --exclude 'tests/'
0128 )
0129 PCS_EXCLUDES=(
0130 --exclude 'migrations/'
0131 --exclude 'management/'
0132 --exclude 'tests/'
0133 )
0134 AI_EXCLUDES=(
0135 --exclude 'migrations/'
0136 --exclude 'tests/'
0137 )
0138
0139 if [[ "$DO_MCP" != true ]]; then
0140 MONITOR_EXCLUDES+=(--exclude 'mcp/')
0141 fi
0142
0143 if [[ "$DO_UI" != true ]]; then
0144 MONITOR_EXCLUDES+=(--exclude 'templates/')
0145 PCS_EXCLUDES+=(--exclude 'templates/')
0146 AI_EXCLUDES+=(--exclude 'templates/')
0147 fi
0148
0149 if [[ "$DO_STATIC" != true ]]; then
0150 MONITOR_EXCLUDES+=(--exclude 'static/')
0151 PCS_EXCLUDES+=(--exclude 'static/')
0152 fi
0153
0154 log "Lightweight deploy source: $REPO_ROOT"
0155 log "Active target: $CURRENT_DIR"
0156
0157 log "Syncing monitor_app lightweight paths..."
0158 rsync "${RSYNC_ARGS[@]}" "${MONITOR_EXCLUDES[@]}" "$SRC_DIR/monitor_app/" "$TARGET_SRC/monitor_app/"
0159
0160
0161
0162
0163 EPICPROD_ROOT="/data/wenauseic/github/swf-epicprod"
0164
0165
0166 TARGET_PCS=$(cd "$CURRENT_DIR" && "$CURRENT_DIR/.venv/bin/python" -c "import pcs, os; print(os.path.dirname(pcs.__file__))")
0167 case "$TARGET_PCS" in
0168 "$CURRENT_DIR"/.venv/*) ;;
0169 *)
0170 echo "ERROR: deployed pcs resolves outside the deployed venv: $TARGET_PCS" >&2
0171 echo "Run the full deploy to freeze swf-epicprod, then retry." >&2
0172 exit 1
0173 ;;
0174 esac
0175 log "Syncing pcs lightweight paths (swf-epicprod -> deployed venv)..."
0176 rsync "${RSYNC_ARGS[@]}" "${PCS_EXCLUDES[@]}" "$EPICPROD_ROOT/pcs/" "$TARGET_PCS/"
0177
0178 log "Syncing ai lightweight paths..."
0179 rsync "${RSYNC_ARGS[@]}" "${AI_EXCLUDES[@]}" "$SRC_DIR/ai/" "$TARGET_SRC/ai/"
0180
0181 if [[ "$DO_UI" == true ]]; then
0182 log "Syncing project-level templates and URL routing..."
0183 rsync "${RSYNC_ARGS[@]}" "$SRC_DIR/templates/" "$TARGET_SRC/templates/"
0184 rsync "${RSYNC_ARGS[@]}" "$SRC_DIR/swf_monitor_project/urls.py" "$TARGET_SRC/swf_monitor_project/urls.py"
0185 fi
0186
0187 if [[ "$DO_MCP" == true ]]; then
0188 log "Syncing MCP ASGI entrypoint..."
0189 rsync "${RSYNC_ARGS[@]}" "$SRC_DIR/swf_monitor_project/mcp_asgi.py" "$TARGET_SRC/swf_monitor_project/mcp_asgi.py"
0190 fi
0191
0192 if [[ "$DO_STATIC" == true ]]; then
0193 if [[ "$DRY_RUN" == true ]]; then
0194 log "Would sync project-level static sources, collect static assets, and sync them to $SHARED_STATIC"
0195 else
0196
0197
0198 log "Syncing project-level static sources..."
0199 rsync "${RSYNC_ARGS[@]}" "$SRC_DIR/static/" "$TARGET_SRC/static/"
0200 log "Collecting static assets..."
0201 cd "$TARGET_SRC"
0202 "$CURRENT_DIR/.venv/bin/python" manage.py collectstatic --noinput --settings=swf_monitor_project.settings
0203 log "Syncing static assets to shared Apache static directory..."
0204 rsync -a --delete "$TARGET_SRC/staticfiles/" "$SHARED_STATIC/"
0205 fi
0206 fi
0207
0208 if [[ "$DO_UI" == true ]]; then
0209 if [[ "$DRY_RUN" == true ]]; then
0210 log "Would touch $TARGET_SRC/swf_monitor_project/wsgi.py to recycle mod_wsgi"
0211 else
0212 log "Recycling mod_wsgi app by touching wsgi.py..."
0213 touch "$TARGET_SRC/swf_monitor_project/wsgi.py"
0214 fi
0215 fi
0216
0217 if [[ "$DO_MCP" == true ]]; then
0218 if [[ "$DRY_RUN" == true ]]; then
0219 log "Would restart $MCP_SERVICE"
0220 else
0221 log "Restarting MCP ASGI worker ($MCP_SERVICE)..."
0222 systemctl restart "$MCP_SERVICE"
0223 fi
0224 fi
0225
0226 log "Lightweight UI/MCP deploy complete"