summaryrefslogtreecommitdiffstats
path: root/scripts/dev-web.sh
blob: 1faca8a155b58c1d076ee80a5e2b3ee890c47015 (plain) (blame)
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env bash
# Run the whole browser stack on this machine, without nginx.
#
#   ./scripts/dev-web.sh
#   ./scripts/dev-web.sh --reset     # wipe the local dev database first
#
# It builds the server, the gateway and the browser app, creates a local TLS
# identity under target/dev-web/, and starts:
#
#   game server   127.0.0.1:11525   (target/dev-web/server.db)
#   webd          127.0.0.1:8890
#   static app    127.0.0.1:8899   (web/play/?ws=ws://127.0.0.1:8890/ws)
#
# The page and the WebSocket are on different loopback ports. The app only
# honours ?ws= on localhost, so this cannot be used to redirect a real player's
# password. In production nginx keeps both on one origin.
#
# Environment overrides:
#   ZAGAN_DEV_GAME_PORT=11525
#   ZAGAN_DEV_GATEWAY_PORT=8890
#   ZAGAN_DEV_WEB_PORT=8899
#   ZAGAN_DEV_DIR=target/dev-web
#   ZAGAN_DEV_OPEN=1          open the URL in the desktop browser
set -euo pipefail

ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cd "$ROOT"

DEV_DIR=${ZAGAN_DEV_DIR:-"$ROOT/target/dev-web"}
mkdir -p "$DEV_DIR"
DEV_DIR=$(cd "$DEV_DIR" && pwd)
GAME_PORT=${ZAGAN_DEV_GAME_PORT:-11525}
GATEWAY_PORT=${ZAGAN_DEV_GATEWAY_PORT:-8890}
WEB_PORT=${ZAGAN_DEV_WEB_PORT:-8899}
ORIGIN="http://127.0.0.1:$WEB_PORT"
URL="$ORIGIN/play/?ws=ws%3A%2F%2F127.0.0.1%3A$GATEWAY_PORT%2Fws"

die() { echo "dev-web: $*" >&2; exit 1; }

command -v cargo >/dev/null || die "cargo is required"
command -v openssl >/dev/null || die "openssl is required"
command -v python3 >/dev/null || die "python3 is required for the static server"
command -v ss >/dev/null || die "ss is required to wait for the dev ports"

if [ "${1:-}" = "--reset" ]; then
  rm -f "$DEV_DIR/server.db"
  echo "==> removed $DEV_DIR/server.db"
fi

echo "==> building server, gateway and browser app"
cargo build --quiet --bin server
cargo build --quiet -p zagan-bet-webd --bin zagan-bet-webd
if command -v npm >/dev/null 2>&1; then
  if [ ! -d web/play/node_modules ]; then
    (cd web/play && npm ci --silent)
  fi
  (cd web/play && npm run build --silent >/dev/null)
fi

if [ ! -f "$DEV_DIR/identity.p12" ] || [ ! -f "$DEV_DIR/cert.pem" ]; then
  echo "==> creating a local TLS identity"
  (cd "$DEV_DIR" && "$ROOT/target/debug/server" --init >/dev/null)
fi

fingerprint=$(openssl x509 -in "$DEV_DIR/cert.pem" -pubkey -noout \
  | openssl pkey -pubin -outform DER \
  | sha256sum | awk '{print $1}')
[ -n "$fingerprint" ] || die "could not read the local certificate fingerprint"

for port in "$GAME_PORT" "$GATEWAY_PORT" "$WEB_PORT"; do
  if (exec 3<>"/dev/tcp/127.0.0.1/$port") 2>/dev/null; then
    exec 3>&-
    die "port $port is already in use; stop the other process or set the ZAGAN_DEV_*_PORT override"
  fi
done

pids=()
cleanup() {
  trap - EXIT INT TERM
  for pid in "${pids[@]}"; do
    kill "$pid" 2>/dev/null || true
  done
  wait 2>/dev/null || true
}
trap cleanup EXIT INT TERM

echo "==> starting game server on 127.0.0.1:$GAME_PORT"
# The server loads identity.p12 from its working directory. Run it inside the
# dev directory so it never touches the repository's own identity files.
(cd "$DEV_DIR" && exec env SERVER_DB="$DEV_DIR/server.db" \
  "$ROOT/target/debug/server" -i 127.0.0.1 -p "$GAME_PORT") \
  >"$DEV_DIR/server.log" 2>&1 &
pids+=($!)

echo "==> starting gateway on 127.0.0.1:$GATEWAY_PORT"
ZAGAN_WEB_BIND="127.0.0.1:$GATEWAY_PORT" \
ZAGAN_WEB_ORIGIN="$ORIGIN" \
ZAGAN_UPSTREAM="127.0.0.1:$GAME_PORT" \
ZAGAN_UPSTREAM_FINGERPRINT="$fingerprint" \
ZAGAN_WEB_COOKIE_SECURE=0 \
"$ROOT/target/debug/zagan-bet-webd" >"$DEV_DIR/webd.log" 2>&1 &
pids+=($!)

echo "==> starting static app on 127.0.0.1:$WEB_PORT"
python3 -m http.server "$WEB_PORT" --bind 127.0.0.1 --directory "$ROOT/web" \
  >"$DEV_DIR/static.log" 2>&1 &
pids+=($!)

wait_port() {
  local port=$1 name=$2
  for _ in $(seq 1 100); do
    if ss -ltn 2>/dev/null \
      | awk -v p=":$port" '$4 ~ p { found = 1 } END { exit found ? 0 : 1 }'; then
      return 0
    fi
    sleep 0.1
  done
  echo "dev-web: $name did not listen on $port; see $DEV_DIR" >&2
  return 1
}

wait_port "$GAME_PORT" "the game server" || exit 1
wait_port "$GATEWAY_PORT" "the gateway" || exit 1
wait_port "$WEB_PORT" "the static server" || exit 1

cat <<EOF

  Open: $URL

  Game server: 127.0.0.1:$GAME_PORT   ($DEV_DIR/server.db)
  Gateway:     127.0.0.1:$GATEWAY_PORT
  Static app:  127.0.0.1:$WEB_PORT
  Logs:        $DEV_DIR/{server,webd,static}.log
  Fingerprint: $fingerprint

  Register a new account on first use. Ctrl+C stops all three processes.
EOF

if [ "${ZAGAN_DEV_OPEN:-0}" = "1" ]; then
  command -v xdg-open >/dev/null 2>&1 && xdg-open "$URL" >/dev/null 2>&1 || true
fi

# Stop the script when any child dies unexpectedly as well.
wait -n || true