#!/usr/bin/env bash # Hand a Railway environment over to an exe.dev VM. # From https://exe.dev/deploy/migrate-railway # # handover my-app.exe.xyz copy config, variables, and data (Railway keeps running) # handover my-app.exe.xyz --move stop the app on Railway, copy the data again, and start it on the VM # # Run it on your computer, in a directory linked with `railway link` to the # project and environment to move. It reads from Railway with your Railway # CLI login; nothing here writes to Railway, except --move, which removes # the running deployment of each app service (railway down) after you say yes. # Everything goes straight to the VM over SSH. set -euo pipefail vm=${1:?usage: handover my-app.exe.xyz [--move]} mode=${2:-} kit=https://exe.dev/deploy/migrate-railway command -v railway >/dev/null || { echo "handover: install the Railway CLI first: https://docs.railway.com/cli" >&2; exit 1; } railway status --json >/dev/null || { echo "handover: run 'railway link' here first, and pick the environment to move" >&2; exit 1; } ssh -n "$vm" true || { echo "handover: can't reach $vm over SSH" >&2; exit 1; } # Railway's description of the environment, and the kit's importer on the VM. tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT railway status --json > "$tmp/status.json" railway environment config --json > "$tmp/environment.json" tar -C "$tmp" -cf - status.json environment.json | ssh "$vm" "umask 077 && mkdir -p railway/variables railway/data && tar -x -C railway && { test -x railway/import || curl -fsSL $kit/import -o railway/import && chmod +x railway/import; }" plan=$(ssh -n "$vm" railway/import plan) col() { cut -f"$1" <<<"$line"; } each() { # KIND: the plan's lines of that kind, one at a time, in $line grep "^$1"$'\t' <<<"$plan" || true } save() { # FILE: stdin to ~/railway/data/FILE on the VM, complete or not at all ssh "$vm" "umask 077 && cat > 'railway/data/$1.part' && mv 'railway/data/$1.part' 'railway/data/$1'" } stopped= stop() { # SERVICE: remove its running deployment on Railway (--move only) [ "$mode" = --move ] || return 0 grep -qxF "$1" <<<"$stopped" && return 0 railway down --service "$1" --yes &2; exit 1 fi while IFS= read -r line <&3; do echo "variables: $(col 2)" railway variable list --service "$(col 2)" --json 'railway/variables/$(col 2).json'" done 3< <(each vars) # A volume can only be read while its service runs: copy it, then stop the service. while IFS= read -r line <&3; do echo "volume: $(col 2) ($(col 3))" railway ssh --service "$(col 2)" /dev/null 2>&1 && cat /tmp/handover.rdb && rm -f /tmp/handover.rdb' | save "$(col 2).rdb" done 3< <(each redis) while IFS= read -r line <&3; do echo "not copied: $(col 2) ($(col 3))"; done 3< <(each skip) if [ "$mode" = --move ]; then ssh -t "$vm" railway/move exit fi ssh -n "$vm" 'date -u +%FT%TZ > railway/handed-over' echo echo "Handed over. Now push the code Railway runs, from each repository's checkout:" each push | awk -F'\t' '!seen[$3]++' | while IFS= read -r line; do echo echo " # $(col 4 | sed 's/^$/the code you deploy with railway up/') (for $(grep "^push"$'\t'".*"$'\t'"$(col 3)"$'\t' <<<"$plan" | cut -f2 | paste -sd, -))" echo " ssh $vm git init -q --bare git/$(col 3).git" echo " git remote add exe $vm:git/$(col 3).git" echo " git push exe $(col 5)" done