Repository navigation
Expand file tree
/
Copy pathvps.sh
More file actions
executable file
·129 lines (114 loc) · 6.05 KB
/
Copy pathvps.sh
File metadata and controls
executable file
·129 lines (114 loc) · 6.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/usr/bin/env bash
# Reach the production VPS and its database over SSH. Nothing secret is ever stored on this machine: credentials
# are read from deploy/.env.prod on the VPS for each command and shown masked unless a connection needs them.
#
# Configuration (environment or .env in the repository root):
# VPS_SSH user@host or an alias from ~/.ssh/config (required)
# VPS_APP_DIR repository checkout on the VPS (default: ~/<repo name>)
# VPS_DB_LOCAL_PORT local end of the database tunnel (default: 15432)
#
# Commands:
# ssh shell in the app directory
# env deploy/.env.prod with secret values masked
# ps | logs [service] production compose status / logs
# deploy-status image tag per replica, what /version answers, and whether backups are fresh
# backup-status age of the last successful off-host backup; non-zero when stale or missing
# tunnel forward localhost:$VPS_DB_LOCAL_PORT → Postgres on the VPS until Ctrl-C
# psql [sql] interactive psql through the tunnel, or run one statement and exit
# datagrip open the tunnel in the background and print a ready-to-paste JDBC URL
# (DataGrip: New → Data Source from URL). --no-tunnel / --no-clipboard for scripting.
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
env_file="deploy/.env.prod"
# .env fills in whatever the environment did not provide; never overrides it.
if [[ -f "$root/.env" ]]; then
while IFS='=' read -r key value; do
[[ "$key" =~ ^[A-Z_][A-Z0-9_]*$ ]] || continue
[[ -z "${!key:-}" ]] && export "$key=$value"
done < <(grep -E '^[A-Z_]+=' "$root/.env" || true)
fi
usage() { sed -n '2,21p' "$0" >&2; exit 2; }
cmd="${1:-}"; shift || true
[[ -n "$cmd" ]] || usage
if [[ -z "${VPS_SSH:-}" ]]; then
echo "VPS_SSH is not set. Put VPS_SSH=user@host (or an ~/.ssh/config alias) in .env or the environment." >&2
exit 2
fi
app_dir="${VPS_APP_DIR:-~/$(basename "$root")}"
local_port="${VPS_DB_LOCAL_PORT:-15432}"
remote() { ssh "$VPS_SSH" "cd $app_dir && $*"; }
remote_env() { ssh "$VPS_SSH" "cat $app_dir/$env_file"; }
# Runs the compose command the Makefile builds (core file plus every enabled brick's override.prod.yml), on the host.
prod() { remote "eval \"\$(make -s --no-print-directory print-prod) $*\""; }
env_value() { # KEY DEFAULT
local v; v="$(printf '%s\n' "$REMOTE_ENV" | grep -E "^$1=" | head -n1 | cut -d= -f2- || true)"
# One pair of surrounding quotes is compose syntax (KEY="a,b"), not part of the value.
if [[ "$v" =~ ^\"(.*)\"$ || "$v" =~ ^\'(.*)\'$ ]]; then v="${BASH_REMATCH[1]}"; fi
printf '%s' "${v:-$2}"
}
load_remote_env() { REMOTE_ENV="$(remote_env)"; }
tunnel_running() { pgrep -f "ssh .*-L ${local_port}:127.0.0.1:" >/dev/null; }
open_tunnel() { # background, once
if tunnel_running; then echo "tunnel already open on localhost:${local_port}" >&2; return; fi
ssh -f -N -o ExitOnForwardFailure=yes -L "${local_port}:127.0.0.1:$1" "$VPS_SSH"
echo "tunnel: localhost:${local_port} → ${VPS_SSH}:127.0.0.1:$1 (pid $(pgrep -f "ssh .*-L ${local_port}:127.0.0.1:" | head -n1)); close with: pkill -f 'ssh .*-L ${local_port}:'" >&2
}
case "$cmd" in
ssh)
exec ssh -t "$VPS_SSH" "cd $app_dir && exec \$SHELL -l" ;;
env)
remote_env | sed -E 's/^([A-Z0-9_]*(PASSWORD|TOKEN|SECRET|KEY)[A-Z0-9_]*=).+$/\1****/' ;;
ps)
prod ps ;;
logs)
prod logs -f --tail=200 ${1:+"$1"} ;;
deploy-status)
prod "ps --format '{{.Service}} {{.Image}} {{.Status}}'" | grep -E '^app ' || true
load_remote_env
domain="$(env_value DOMAIN "")"
[[ -n "$domain" ]] && { echo "https://${domain}/version →"; curl -fsS --max-time 10 "https://${domain}/version" || true; echo; }
if ! remote "cat .bricks 2>/dev/null" | sed -e 's/#.*//' -e 's/[[:space:]]*$//' | grep -qx backup; then
echo "backups: brick off (make add BRICK=backup; bricks/backup/README.md)"
elif status_out="$(prod "run --rm -T backup status" 2>/dev/null)"; then
echo "backups: ok — ${status_out//$'\n'/; }"
else
status_out="${status_out//$'\n'/; }"
echo "backups: STALE — ${status_out:-no answer; run make vps-backup-status}"
fi ;;
backup-status)
prod "run --rm -T backup status" ;;
tunnel)
load_remote_env
port="$(env_value POSTGRES_HOST_PORT 5432)"
echo "localhost:${local_port} → ${VPS_SSH}:127.0.0.1:${port} (Ctrl-C to close)"
exec ssh -N -o ExitOnForwardFailure=yes -L "${local_port}:127.0.0.1:${port}" "$VPS_SSH" ;;
psql)
load_remote_env
port="$(env_value POSTGRES_HOST_PORT 5432)"
db="$(env_value POSTGRES_DB app)"; user="$(env_value POSTGRES_USER app)"; pass="$(env_value POSTGRES_PASSWORD "")"
open_tunnel "$port"
export PGPASSWORD="$pass"
if command -v psql >/dev/null; then
psql_cmd=(psql -h localhost -p "$local_port" -U "$user" -d "$db")
else
psql_cmd=(docker run --rm -it --network host -e PGPASSWORD postgres:17-alpine psql -h localhost -p "$local_port" -U "$user" -d "$db")
fi
if [[ $# -gt 0 ]]; then "${psql_cmd[@]}" -c "$*"; else "${psql_cmd[@]}"; fi ;;
datagrip)
want_tunnel=true; want_clip=true
for a in "$@"; do case "$a" in --no-tunnel) want_tunnel=false ;; --no-clipboard) want_clip=false ;; esac; done
load_remote_env
port="$(env_value POSTGRES_HOST_PORT 5432)"
db="$(env_value POSTGRES_DB app)"; user="$(env_value POSTGRES_USER app)"; pass="$(env_value POSTGRES_PASSWORD "")"
$want_tunnel && open_tunnel "$port"
url="jdbc:postgresql://localhost:${local_port}/${db}?user=${user}&password=${pass}"
if $want_clip; then
if command -v wl-copy >/dev/null; then printf '%s' "$url" | wl-copy && echo "(copied to clipboard)" >&2
elif command -v xclip >/dev/null; then printf '%s' "$url" | xclip -selection clipboard && echo "(copied to clipboard)" >&2
elif command -v pbcopy >/dev/null; then printf '%s' "$url" | pbcopy && echo "(copied to clipboard)" >&2
fi
fi
echo "DataGrip → New → Data Source from URL → paste:"
echo "$url" ;;
*) usage ;;
esac