- Refactor railway-deploy.sh: add --sync-env, --dry-run, --detach flags and service selector - Add railway-sync-env.sh for pre-deploy environment variable synchronization - Add RAILWAY_DEPLOYMENT_RUNBOOK.md with step-by-step deployment guide Co-Authored-By: Oz <oz-agent@warp.dev>
317 lines
6.6 KiB
Bash
Executable File
317 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT"
|
|
|
|
PROJECT_ID="${RAILWAY_PROJECT_ID:-a6bc4ea7-e89c-42da-819a-8879fb022a0d}"
|
|
ENVIRONMENT="${RAILWAY_ENVIRONMENT:-production}"
|
|
ENV_FILE="${RAILWAY_ENV_FILE:-$ROOT/.env}"
|
|
|
|
SERVICE_SELECTOR="all"
|
|
SKIP_DEPLOYS=true
|
|
DRY_RUN=false
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Sync selected environment variables from a local .env file to Railway services.
|
|
|
|
Usage:
|
|
scripts/railway-sync-env.sh [all|platform|extraction] [options]
|
|
|
|
Options:
|
|
--project <id> Railway project ID (default: $RAILWAY_PROJECT_ID or script default)
|
|
--env <name> Railway environment name (default: $RAILWAY_ENVIRONMENT or production)
|
|
--env-file <path> Local env file to read (default: ./.env)
|
|
--trigger-deploys Trigger deploys while setting vars (default is --skip-deploys)
|
|
--dry-run Print variables that would be synced (no changes)
|
|
-h, --help Show help
|
|
|
|
Notes:
|
|
- Missing optional vars are skipped.
|
|
- Required vars (COSMOS_ENDPOINT, COSMOS_KEY, JWT_SECRET) must exist unless AZURE_KEYVAULT_URL is set.
|
|
USAGE
|
|
}
|
|
|
|
if [[ $# -gt 0 && "${1#-}" == "$1" ]]; then
|
|
SERVICE_SELECTOR="$1"
|
|
shift
|
|
fi
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--project)
|
|
PROJECT_ID="${2:-}"
|
|
shift 2
|
|
;;
|
|
--env)
|
|
ENVIRONMENT="${2:-}"
|
|
shift 2
|
|
;;
|
|
--env-file)
|
|
ENV_FILE="${2:-}"
|
|
shift 2
|
|
;;
|
|
--trigger-deploys)
|
|
SKIP_DEPLOYS=false
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "Unknown arg: $1" >&2
|
|
usage
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
SERVICES=()
|
|
case "$SERVICE_SELECTOR" in
|
|
all)
|
|
SERVICES=("platform-service" "extraction-service")
|
|
;;
|
|
platform|platform-service)
|
|
SERVICES=("platform-service")
|
|
;;
|
|
extraction|extraction-service)
|
|
SERVICES=("extraction-service")
|
|
;;
|
|
*)
|
|
echo "Unknown service selector: $SERVICE_SELECTOR" >&2
|
|
usage
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
if [[ -z "$PROJECT_ID" ]]; then
|
|
echo "Missing Railway project ID. Use --project or set RAILWAY_PROJECT_ID." >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ -z "$ENVIRONMENT" ]]; then
|
|
echo "Missing Railway environment. Use --env or set RAILWAY_ENVIRONMENT." >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
echo "Env file not found: $ENV_FILE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if ! command -v railway >/dev/null 2>&1; then
|
|
echo "railway CLI not found. Install with: npm i -g @railway/cli" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
if ! railway whoami >/dev/null 2>&1; then
|
|
echo "Railway CLI is not authenticated. Run: railway login" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Load .env values into current shell for lookup.
|
|
# shellcheck disable=SC1090
|
|
set -a
|
|
source "$ENV_FILE"
|
|
set +a
|
|
|
|
get_value() {
|
|
local key="$1"
|
|
printf '%s' "${!key-}"
|
|
}
|
|
|
|
has_value() {
|
|
local key="$1"
|
|
[[ -n "${!key-}" ]]
|
|
}
|
|
|
|
COMMON_REQUIRED=(
|
|
COSMOS_ENDPOINT
|
|
COSMOS_KEY
|
|
JWT_SECRET
|
|
)
|
|
|
|
COMMON_OPTIONAL=(
|
|
AZURE_KEYVAULT_URL
|
|
COSMOS_DATABASE
|
|
DEFAULT_PRODUCT_ID
|
|
CORS_ORIGIN
|
|
PRODUCT_ID
|
|
NODE_ENV
|
|
HOST
|
|
)
|
|
|
|
PLATFORM_OPTIONAL=(
|
|
AZURE_BLOB_CONNECTION_STRING
|
|
AZURE_BLOB_ACCOUNT_NAME
|
|
AZURE_BLOB_ACCOUNT_KEY
|
|
STRIPE_SECRET_KEY
|
|
STRIPE_WEBHOOK_SECRET
|
|
STRIPE_PRICE_PRO
|
|
STRIPE_PRICE_ENTERPRISE
|
|
BILLING_INTERNAL_KEY
|
|
WEBHOOK_INVITATION_REDEEMED_URL
|
|
WEBHOOK_REFERRAL_STATUS_URL
|
|
BACKEND_URL
|
|
PLAN_LIMITS_JSON
|
|
USAGE_WARN_THRESHOLD
|
|
RATE_LIMIT_CONFIG_JSON
|
|
LICENSE_ACTIVATE_LOCKOUT_WINDOW_MS
|
|
LICENSE_ACTIVATE_MAX_FAILED_ATTEMPTS
|
|
COSMOS_AUTO_INIT
|
|
)
|
|
|
|
EXTRACTION_OPTIONAL=(
|
|
GEMINI_API_KEY
|
|
DEFAULT_MODEL_ID
|
|
PYTHON_SIDECAR_URL
|
|
EXTRACTION_CACHE_TTL_MS
|
|
EXTRACTION_CACHE_MAX
|
|
EXTRACTION_CACHE_TTL
|
|
EXTRACTION_CACHE_MAX_SIZE
|
|
USE_MOCK_EXTRACTOR
|
|
SIDECAR_PORT
|
|
SIDECAR_HOST
|
|
AZURE_OPENAI_KEY
|
|
AZURE_OPENAI_ENDPOINT
|
|
)
|
|
|
|
missing_required=()
|
|
for key in "${COMMON_REQUIRED[@]}"; do
|
|
if ! has_value "$key"; then
|
|
missing_required+=("$key")
|
|
fi
|
|
done
|
|
|
|
if [[ ${#missing_required[@]} -gt 0 && -z "$(get_value AZURE_KEYVAULT_URL)" ]]; then
|
|
echo "Missing required variables in $ENV_FILE: ${missing_required[*]}" >&2
|
|
echo "Either set them in the env file, or set AZURE_KEYVAULT_URL and ensure Railway can access Key Vault." >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ ${#missing_required[@]} -gt 0 ]]; then
|
|
echo "Warning: missing ${missing_required[*]} in $ENV_FILE; relying on Key Vault resolution at runtime."
|
|
fi
|
|
|
|
LINK_DIR=""
|
|
cleanup() {
|
|
if [[ -n "$LINK_DIR" && -d "$LINK_DIR" ]]; then
|
|
rm -rf "$LINK_DIR"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
run_railway() {
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
return 0
|
|
fi
|
|
(
|
|
cd "$LINK_DIR"
|
|
railway "$@"
|
|
)
|
|
}
|
|
|
|
if [[ "$DRY_RUN" == false ]]; then
|
|
LINK_DIR="$(mktemp -d)"
|
|
(
|
|
cd "$LINK_DIR"
|
|
railway link --project "$PROJECT_ID" --environment "$ENVIRONMENT" >/dev/null
|
|
)
|
|
fi
|
|
|
|
ensure_service_exists() {
|
|
local svc="$1"
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
return 0
|
|
fi
|
|
|
|
if ! run_railway variable list --service "$svc" --environment "$ENVIRONMENT" --json >/dev/null 2>&1; then
|
|
echo "Unable to access Railway service '$svc' in env '$ENVIRONMENT' (project '$PROJECT_ID')." >&2
|
|
echo "Check project/environment IDs and service names." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
set_variable() {
|
|
local svc="$1"
|
|
local key="$2"
|
|
local value="$3"
|
|
local -a cmd
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo " - would set $key"
|
|
return 0
|
|
fi
|
|
|
|
cmd=(variable set --service "$svc" --environment "$ENVIRONMENT")
|
|
if [[ "$SKIP_DEPLOYS" == true ]]; then
|
|
cmd+=(--skip-deploys)
|
|
fi
|
|
cmd+=("${key}=${value}")
|
|
|
|
run_railway "${cmd[@]}" >/dev/null
|
|
echo " - set $key"
|
|
}
|
|
|
|
sync_service() {
|
|
local svc="$1"
|
|
shift
|
|
local keys=("$@")
|
|
local value
|
|
|
|
local seen="|"
|
|
local synced=0
|
|
local skipped=0
|
|
|
|
echo "Syncing variables for $svc..."
|
|
|
|
for key in "${keys[@]}"; do
|
|
if [[ "$seen" == *"|$key|"* ]]; then
|
|
continue
|
|
fi
|
|
seen+="$key|"
|
|
|
|
value="$(get_value "$key")"
|
|
if [[ -z "$value" ]]; then
|
|
skipped=$((skipped + 1))
|
|
continue
|
|
fi
|
|
|
|
set_variable "$svc" "$key" "$value"
|
|
synced=$((synced + 1))
|
|
done
|
|
|
|
echo "Synced $synced variables to $svc (skipped $skipped unset keys)."
|
|
}
|
|
|
|
for svc in "${SERVICES[@]}"; do
|
|
ensure_service_exists "$svc"
|
|
|
|
if [[ "$svc" == "platform-service" ]]; then
|
|
sync_service "$svc" \
|
|
"${COMMON_REQUIRED[@]}" \
|
|
"${COMMON_OPTIONAL[@]}" \
|
|
"${PLATFORM_OPTIONAL[@]}"
|
|
else
|
|
sync_service "$svc" \
|
|
"${COMMON_REQUIRED[@]}" \
|
|
"${COMMON_OPTIONAL[@]}" \
|
|
"${EXTRACTION_OPTIONAL[@]}"
|
|
fi
|
|
done
|
|
|
|
if [[ "$SKIP_DEPLOYS" == true ]]; then
|
|
echo "Variable sync complete (deploys were skipped)."
|
|
echo "Run scripts/railway-deploy.sh to publish new code/config."
|
|
else
|
|
echo "Variable sync complete (deploys were triggered while setting vars)."
|
|
fi
|