anvilsign in

collin/anvil

1#!/usr/bin/env bash
2# Run anvil locally in Docker, reachable at https://anvil.localhost.
3#
4# The same image shape as production (deploy/build.sh + run.sh), but built and
5# run on this machine: a container publishing 3000 to a fixed host port, with
6# portless reverse-proxying a stable `.localhost` name onto it. Running in
7# Docker rather than `cargo run` is what makes CI testable — the runner drives
8# the host's Docker socket, which is mounted in.
9#
10# Usage:
11# ./deploy/dev.sh build + (re)start, then print the URL
12# ./deploy/dev.sh --release optimized binary (slower build, faster server)
13# ./deploy/dev.sh --stop stop and remove the container
14# ./deploy/dev.sh --logs follow the container log
15#
16# State lives in the `anvil-dev-data` volume and survives restarts;
17# `docker volume rm anvil-dev-data` starts over.
18set -euo pipefail
19
20cd "$(dirname "$0")/.."
21
22NAME="${ANVIL_DEV_NAME:-anvil}"
23IMAGE="anvil-dev:latest"
24TARGET="x86_64-unknown-linux-musl"
25VOLUME="anvil-dev-data"
26# A stable, collision-resistant port for this project (see `devport -h`), so the
27# published port does not wander between runs. portless maps a name onto it.
28PORT="${ANVIL_DEV_PORT:-$(command -v devport >/dev/null && devport || echo 20640)}"
29SSH_PORT="${ANVIL_DEV_SSH_PORT:-$((PORT + 1))}"
30PROFILE=debug
31CARGO_FLAGS=()
32
33for arg in "$@"; do
34 case "$arg" in
35 --release)
36 PROFILE=release
37 CARGO_FLAGS+=(--release)
38 ;;
39 --stop)
40 docker rm -f "$NAME" >/dev/null 2>&1 || true
41 portless alias --remove "$NAME" >/dev/null 2>&1 || true
42 echo "stopped $NAME"
43 exit 0
44 ;;
45 --logs)
46 exec docker logs -f "$NAME"
47 ;;
48 *)
49 echo "unknown flag: $arg" >&2
50 exit 2
51 ;;
52 esac
53done
54
55echo "==> building anvild ($PROFILE, static musl)"
56# Static musl, exactly as in production: the runtime image is debian-slim and a
57# binary linked against Fedora's glibc would not run there.
58cargo zigbuild --target "$TARGET" --bin anvild "${CARGO_FLAGS[@]}"
59cp "target/$TARGET/$PROFILE/anvild" deploy/anvild
60trap 'rm -f deploy/anvild' EXIT
61
62echo "==> building $IMAGE"
63docker build --quiet --platform linux/amd64 \
64 --build-arg CONFIG=deploy/anvil.dev.toml -t "$IMAGE" . >/dev/null
65
66echo "==> (re)starting container $NAME"
67docker rm -f "$NAME" >/dev/null 2>&1 || true
68# The CI runner is a Docker client, so it needs the socket and the group that
69# owns it. `label=disable` rather than a `:z` relabel: :z would rewrite the
70# label on the *host's* socket, which every other container also uses.
71docker run -d --name "$NAME" --restart unless-stopped \
72 -p "127.0.0.1:$PORT:3000" \
73 -p "127.0.0.1:$SSH_PORT:2222" \
74 -v "$VOLUME:/data" \
75 -v /var/run/docker.sock:/var/run/docker.sock \
76 --security-opt label=disable \
77 --group-add "$(stat -c '%g' /var/run/docker.sock)" \
78 -e "ANVIL_BASE_URL=https://$NAME.localhost" \
79 "$IMAGE" >/dev/null
80
81# Wait for the server to answer before handing over a URL that would 502.
82for _ in $(seq 1 50); do
83 if curl -fsS -o /dev/null "http://127.0.0.1:$PORT/-/healthz" 2>/dev/null; then
84 break
85 fi
86 sleep 0.2
87done
88
89if command -v portless >/dev/null; then
90 echo "==> routing https://$NAME.localhost -> 127.0.0.1:$PORT"
91 portless alias "$NAME" "$PORT" >/dev/null
92 URL="https://$NAME.localhost"
93else
94 echo "==> portless not installed; skipping the .localhost route"
95 URL="http://127.0.0.1:$PORT"
96fi
97
98cat <<EOF
99
100anvil is up.
101
102 web $URL
103 ssh ssh://git@localhost:$SSH_PORT/<owner>/<repo>.git
104 direct http://127.0.0.1:$PORT
105
106First run? Create an account and a repo:
107
108 docker exec $NAME anvild -c /etc/anvil/anvil.toml \\
109 user create <you> --password '<password>' --admin
110 docker exec -i $NAME anvild -c /etc/anvil/anvil.toml \\
111 user add-key <you> --title laptop --key "\$(cat ~/.ssh/id_ed25519.pub)"
112
113Logs: ./deploy/dev.sh --logs Stop: ./deploy/dev.sh --stop
114EOF