|
| 1 | +redact_secrets() { |
| 2 | + local value="$1" |
| 3 | + local var_name="$2" |
| 4 | + |
| 5 | + # Check if variable name suggests it contains a secret (case-insensitive) |
| 6 | + local lower_var_name=$(echo "$var_name" | tr '[:upper:]' '[:lower:]') |
| 7 | + if [[ "$lower_var_name" =~ (password|secret|key|token|auth|private|credential) ]]; then |
| 8 | + echo "[REDACTED]" |
| 9 | + else |
| 10 | + echo "$value" |
| 11 | + fi |
| 12 | +} |
| 13 | + |
| 14 | +cmd_deploy_dry_run() { |
| 15 | + load_config |
| 16 | + |
| 17 | + local SKIP_BUILD=false |
| 18 | + if [ "$1" = "--skip-build" ]; then |
| 19 | + SKIP_BUILD=true |
| 20 | + fi |
| 21 | + |
| 22 | + # Detect package manager |
| 23 | + PKG_MANAGER=$(detect_pkg_manager) |
| 24 | + PKG_INSTALL_CMD=$(get_pkg_install_cmd "$PKG_MANAGER") |
| 25 | + PKG_RUN_CMD=$(get_pkg_run_cmd "$PKG_MANAGER" "build") |
| 26 | + |
| 27 | + # Detect build directory for frontend |
| 28 | + local BUILD_DIR="dist" |
| 29 | + if [ -d "build" ]; then |
| 30 | + BUILD_DIR="build" |
| 31 | + elif [ -d "public" ]; then |
| 32 | + BUILD_DIR="public" |
| 33 | + fi |
| 34 | + |
| 35 | + echo "" |
| 36 | + echo "===========================================" |
| 37 | + echo " DEPLOYMENT DRY RUN MODE" |
| 38 | + echo "===========================================" |
| 39 | + echo "" |
| 40 | + |
| 41 | + # Configuration Section |
| 42 | + echo "Configuration (from $SHIPNODE_CONFIG_FILE):" |
| 43 | + echo " App Type: $APP_TYPE" |
| 44 | + echo " SSH User: $SSH_USER" |
| 45 | + echo " SSH Host: $SSH_HOST" |
| 46 | + echo " SSH Port: $(redact_secrets "$SSH_PORT" "SSH_PORT")" |
| 47 | + echo " Remote Path: $REMOTE_PATH" |
| 48 | + echo " Package Manager: $PKG_MANAGER" |
| 49 | + echo " Zero Downtime: ${ZERO_DOWNTIME:-true}" |
| 50 | + |
| 51 | + if [ "$APP_TYPE" = "backend" ]; then |
| 52 | + echo " PM2 App Name: $PM2_APP_NAME" |
| 53 | + echo " Backend Port: $(redact_secrets "$BACKEND_PORT" "BACKEND_PORT")" |
| 54 | + fi |
| 55 | + |
| 56 | + if [ -n "$DOMAIN" ]; then |
| 57 | + echo " Domain: $DOMAIN" |
| 58 | + fi |
| 59 | + |
| 60 | + # Show redacted secrets |
| 61 | + echo "" |
| 62 | + echo "Security - Secrets Redacted:" |
| 63 | + echo " - Variables containing: PASSWORD, SECRET, KEY, TOKEN, AUTH, PRIVATE, CREDENTIAL" |
| 64 | + echo " - SSH connection details shown, but authentication keys are redacted" |
| 65 | + |
| 66 | + # Local Build Commands |
| 67 | + echo "" |
| 68 | + echo "Local Build Commands:" |
| 69 | + if [ "$SKIP_BUILD" = true ]; then |
| 70 | + echo " [SKIPPED via --skip-build flag]" |
| 71 | + else |
| 72 | + if [ "$APP_TYPE" = "frontend" ]; then |
| 73 | + echo " 1. $PKG_RUN_CMD" |
| 74 | + echo " (builds frontend for production)" |
| 75 | + echo " 2. Detected build output: $BUILD_DIR/" |
| 76 | + else |
| 77 | + echo " [Backend builds happen on remote server]" |
| 78 | + fi |
| 79 | + fi |
| 80 | + |
| 81 | + # Remote Commands Section |
| 82 | + echo "" |
| 83 | + echo "Remote Deployment Commands:" |
| 84 | + |
| 85 | + if [ "$ZERO_DOWNTIME" = "true" ]; then |
| 86 | + local timestamp=$(date +"%Y%m%d%H%M%S") |
| 87 | + local release_path="$REMOTE_PATH/releases/$timestamp" |
| 88 | + |
| 89 | + echo " Mode: Zero-Downtime Deployment" |
| 90 | + echo "" |
| 91 | + echo " Deployment Flow:" |
| 92 | + echo " 1. Acquire deployment lock at $REMOTE_PATH/.shipnode/deploy.lock" |
| 93 | + echo " 2. Create new release directory: $release_path" |
| 94 | + echo " 3. Setup release structure (releases/, shared/, .shipnode/)" |
| 95 | + echo "" |
| 96 | + echo " 4. Rsync local files to release directory:" |
| 97 | + echo " Source: ./" |
| 98 | + echo " Target: $SSH_USER@$SSH_HOST:$release_path/" |
| 99 | + echo " Excludes: node_modules, .env, .git, .gitignore, shipnode.conf, *.log" |
| 100 | + echo "" |
| 101 | + echo " 5. Remote setup commands:" |
| 102 | + echo " - cd $release_path" |
| 103 | + if [ -f "$REMOTE_PATH/shared/.env" ] || [ -f ".env" ]; then |
| 104 | + echo " - ln -sf $REMOTE_PATH/shared/.env .env" |
| 105 | + fi |
| 106 | + |
| 107 | + if [ "$APP_TYPE" = "backend" ]; then |
| 108 | + echo " - $PKG_INSTALL_CMD" |
| 109 | + if [ -f "package.json" ] && [ "$SKIP_BUILD" = false ]; then |
| 110 | + if jq -e '.scripts.build' package.json >/dev/null 2>&1; then |
| 111 | + echo " - $PKG_RUN_CMD (detected build script in package.json)" |
| 112 | + fi |
| 113 | + fi |
| 114 | + echo " - [Link .env into build/ if AdonisJS framework detected]" |
| 115 | + fi |
| 116 | + echo "" |
| 117 | + echo " 6. Run pre-deploy hook (if PRE_DEPLOY_HOOK is configured)" |
| 118 | + echo "" |
| 119 | + echo " 7. Atomic symlink switch:" |
| 120 | + echo " ln -sfn $release_path $REMOTE_PATH/current.tmp" |
| 121 | + echo " mv -Tf $REMOTE_PATH/current.tmp $REMOTE_PATH/current" |
| 122 | + echo "" |
| 123 | + |
| 124 | + if [ "$APP_TYPE" = "backend" ]; then |
| 125 | + echo " 8. Reload application:" |
| 126 | + echo " - Generate PM2 ecosystem config at $REMOTE_PATH/shared/ecosystem.config.cjs" |
| 127 | + echo " - pm2 startOrReload ecosystem.config.cjs --update-env" |
| 128 | + echo " - pm2 save" |
| 129 | + echo "" |
| 130 | + |
| 131 | + if [ "${HEALTH_CHECK_ENABLED:-true}" = "true" ]; then |
| 132 | + echo " 9. Health check (if enabled):" |
| 133 | + echo " - Endpoint: http://localhost:$BACKEND_PORT${HEALTH_CHECK_PATH:-/health}" |
| 134 | + echo " - Retries: ${HEALTH_CHECK_RETRIES:-3}" |
| 135 | + echo " - Timeout: ${HEALTH_CHECK_TIMEOUT:-30}s per attempt" |
| 136 | + echo " - [On failure: automatic rollback to previous release]" |
| 137 | + echo "" |
| 138 | + echo " 10. Record successful release in $REMOTE_PATH/.shipnode/releases.json" |
| 139 | + echo "" |
| 140 | + echo " 11. Cleanup old releases (keep ${KEEP_RELEASES:-5} most recent)" |
| 141 | + echo "" |
| 142 | + echo " 12. Run post-deploy hook (if POST_DEPLOY_HOOK is configured)" |
| 143 | + echo "" |
| 144 | + echo " 13. Release deployment lock" |
| 145 | + else |
| 146 | + echo " 9. Record successful release" |
| 147 | + echo " 10. Cleanup old releases (keep ${KEEP_RELEASES:-5} most recent)" |
| 148 | + echo " 11. Run post-deploy hook (if configured)" |
| 149 | + echo " 12. Release deployment lock" |
| 150 | + fi |
| 151 | + else |
| 152 | + echo " 8. Record successful release" |
| 153 | + echo " 9. Run post-deploy hook (if POST_DEPLOY_HOOK is configured)" |
| 154 | + echo " 10. Cleanup old releases (keep ${KEEP_RELEASES:-5} most recent)" |
| 155 | + echo " 11. Release deployment lock" |
| 156 | + fi |
| 157 | + |
| 158 | + echo "" |
| 159 | + echo " Directory Structure:" |
| 160 | + echo " $REMOTE_PATH/" |
| 161 | + echo " ├── current/ -> symlink to active release" |
| 162 | + echo " ├── releases/" |
| 163 | + echo " │ └── $timestamp/ <- new release" |
| 164 | + echo " ├── shared/" |
| 165 | + echo " │ ├── .env # persistent environment" |
| 166 | + echo " │ └── ecosystem.config.cjs # PM2 config" |
| 167 | + echo " └── .shipnode/" |
| 168 | + echo " ├── deploy.lock # prevents concurrent deploys" |
| 169 | + echo " └── releases.json # release history" |
| 170 | + |
| 171 | + else |
| 172 | + echo " Mode: Legacy Deployment (Non-Zero-Downtime)" |
| 173 | + echo "" |
| 174 | + echo " Deployment Steps:" |
| 175 | + echo " 1. Create remote directory: $REMOTE_PATH" |
| 176 | + echo "" |
| 177 | + echo " 2. Rsync local files:" |
| 178 | + echo " Source: ./" |
| 179 | + echo " Target: $SSH_USER@$SSH_HOST:$REMOTE_PATH/" |
| 180 | + echo " Excludes: node_modules, .env, .git, .gitignore, shipnode.conf, *.log" |
| 181 | + echo "" |
| 182 | + echo " 3. Remote build commands:" |
| 183 | + if [ "$APP_TYPE" = "backend" ]; then |
| 184 | + echo " - cd $REMOTE_PATH" |
| 185 | + echo " - $PKG_INSTALL_CMD" |
| 186 | + if [ -f "package.json" ] && [ "$SKIP_BUILD" = false ]; then |
| 187 | + if jq -e '.scripts.build' package.json >/dev/null 2>&1; then |
| 188 | + echo " - $PKG_RUN_CMD (detected build script)" |
| 189 | + fi |
| 190 | + fi |
| 191 | + echo " - [Link .env into build/ if needed]" |
| 192 | + fi |
| 193 | + echo "" |
| 194 | + echo " 4. Run pre-deploy hook (if PRE_DEPLOY_HOOK is configured)" |
| 195 | + echo "" |
| 196 | + |
| 197 | + if [ "$APP_TYPE" = "backend" ]; then |
| 198 | + echo " 5. Start/Reload with PM2:" |
| 199 | + echo " - Generate ecosystem.config.cjs" |
| 200 | + echo " - pm2 startOrReload ecosystem.config.cjs --update-env" |
| 201 | + echo " - pm2 save" |
| 202 | + fi |
| 203 | + |
| 204 | + echo "" |
| 205 | + echo " 6. Run post-deploy hook (if POST_DEPLOY_HOOK is configured)" |
| 206 | + fi |
| 207 | + |
| 208 | + # Caddy Configuration (if domain is set) |
| 209 | + if [ -n "$DOMAIN" ]; then |
| 210 | + echo "" |
| 211 | + echo "Caddy Configuration:" |
| 212 | + if [ "$APP_TYPE" = "backend" ]; then |
| 213 | + echo " - Configure reverse proxy: $DOMAIN -> localhost:$BACKEND_PORT" |
| 214 | + echo " - Config file: /etc/caddy/conf.d/$PM2_APP_NAME.caddy" |
| 215 | + else |
| 216 | + local SERVE_PATH="$REMOTE_PATH" |
| 217 | + if [ "$ZERO_DOWNTIME" = "true" ]; then |
| 218 | + SERVE_PATH="$REMOTE_PATH/current" |
| 219 | + fi |
| 220 | + local APP_NAME=$(basename "$REMOTE_PATH") |
| 221 | + echo " - Configure static file server: $DOMAIN -> $SERVE_PATH" |
| 222 | + echo " - Config file: /etc/caddy/conf.d/$APP_NAME.caddy" |
| 223 | + echo " - SPA support enabled (try_files {path} /index.html)" |
| 224 | + fi |
| 225 | + echo " - Log file: /var/log/caddy/$(basename "$REMOTE_PATH").log" |
| 226 | + fi |
| 227 | + |
| 228 | + echo "" |
| 229 | + echo "===========================================" |
| 230 | + echo " DRY RUN COMPLETE - No changes made" |
| 231 | + echo "===========================================" |
| 232 | + echo "" |
| 233 | + echo "To execute this deployment, run:" |
| 234 | + echo " shipnode deploy" |
| 235 | + if [ "$SKIP_BUILD" = true ]; then |
| 236 | + echo "" |
| 237 | + echo "Or with --skip-build flag:" |
| 238 | + echo " shipnode deploy --skip-build" |
| 239 | + fi |
| 240 | + echo "" |
| 241 | +} |
| 242 | + |
1 | 243 | cmd_deploy() { |
2 | 244 | load_config |
3 | 245 |
|
|
0 commit comments