#!/usr/bin/env bash # Build helm's browser artifacts and put them in the assets bucket. # # AWS_PROFILE=lance-blue scripts/deploy.sh 0.51.0 # scripts/deploy.sh 0.51.0 --dry-run # # One command: fetch the MegaMek release if it is not already unpacked, run the # bridge dump if there is not already one for it, build the binary and the # wasm, write the index and the catalogue, upload, and record the release id. # Both the install and the dump are cached under ~/.cache/helm, so the second # run of the day skips straight to the build. # # --megamek and --bridge-dir override either half when you have one already. # # Three files, under one prefix named after this commit: # # /helm_wasm.wasm the rules, compiled for a browser # //units.json which designs there are, and where the rest is # //.json one group of columns each, named by units.json # //equipment.json every name MegaMek answers to, for search # //catalogue.jsonl the equipment the rules read # # https://lance.blue/assets/helm///units.json # # Two levels because the artifacts are functions of two things. The wasm is # this repository's code alone; the index and the catalogue are this code over # a particular MegaMek. Keyed by one of them only, a rebuild silently replaces # a file somebody is already fetching - which is what an immutable cache # header promises does not happen. # # Nothing is ever overwritten, so nothing is ever invalidated: a new build is a # new prefix. What decides which prefix the site fetches is releases.helm in # infra, which this writes and an apply serves - the same shape headquarters # and arena deploy in, and this script does not apply anything. # # INFRA_DIR overrides where the infra checkout is; the default is the sibling # directory. set -euo pipefail cd "$(dirname "$0")/.." version="" megamek="" bridge="" dry_run="" while [ $# -gt 0 ]; do case "$1" in --megamek) megamek="${2:?--megamek needs a path}" shift 2 ;; --bridge-dir) bridge="${2:?--bridge-dir needs a path}" shift 2 ;; -n | --dry-run) dry_run=1 shift ;; -h | --help) echo "usage: scripts/deploy.sh [--dry-run]" echo " [--megamek ] [--bridge-dir ]" exit 0 ;; -*) echo "deploy: unknown argument $1" >&2 exit 2 ;; *) version="$1" shift ;; esac done # Everything below fails before anything is fetched or built. if [ -z "$version" ] && { [ -z "$megamek" ] || [ -z "$bridge" ]; }; then echo "deploy: which MegaMek? scripts/deploy.sh 0.51.0" >&2 exit 2 fi # Before anything is fetched, dumped or built. A cold run is a 600MB download, # two minutes of JVM and two cargo builds, and finding out after all of it that # the login expired is the whole reason this is up here. A dry run uploads # nothing, so it needs none of it. if [ -z "$dry_run" ]; then : "${AWS_PROFILE:?set AWS_PROFILE; default credentials are almost never the right account}" # The variable being set says nothing about the session behind it. if ! aws sts get-caller-identity --query Account --output text >/dev/null; then echo "deploy: no working AWS session for profile $AWS_PROFILE; log in again" >&2 exit 1 fi fi command -v jq >/dev/null || { echo "deploy: jq is not installed; it writes releases.auto.tfvars.json" >&2 exit 1 } command -v aws >/dev/null || { echo "deploy: the aws cli is not installed" >&2 exit 1 } if ! infra="$(cd "${INFRA_DIR:-../infra}" 2>/dev/null && pwd)"; then echo "deploy: no infra checkout at ${INFRA_DIR:-../infra}; set INFRA_DIR" >&2 exit 1 fi tfvars="$infra/envs/lance.blue/releases.auto.tfvars.json" [ -f "$tfvars" ] || { echo "deploy: $infra has no envs/lance.blue/releases.auto.tfvars.json" >&2 exit 1 } # A dirty tree gets a -dirty ref, which names a build nobody else can rebuild. if [ -n "$(git status --porcelain)" ]; then echo "deploy: helm has uncommitted changes; commit or stash them" >&2 exit 1 fi # Deploying off a branch is fine - the branch is part of the ref, so what is # serving is legible from the id alone. main is the one branch with a rule, # and the remote is asked rather than whatever the last fetch left behind. branch=$(git rev-parse --abbrev-ref HEAD) if [ "$branch" = main ]; then git fetch --quiet origin main if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]; then echo "deploy: on main, but HEAD is not origin/main; pull or push first" >&2 exit 1 fi fi ref="${BUILD_REF:-$(scripts/build-ref.sh)}" echo "deploy: $ref" # The install. fetch-megamek.sh caches the tarball and skips an unpack it has # already done, so this is a no-op after the first run. if [ -z "$megamek" ]; then megamek="$(scripts/fetch-megamek.sh "$version")" fi # The dump. Cached per MegaMek version rather than per run: it is two minutes # of JVM and its inputs are a release that never changes and bridge/, which # changes rarely. Delete the directory to force a fresh one. if [ -z "$bridge" ]; then bridge="${XDG_CACHE_HOME:-$HOME/.cache}/helm/bridge/${version:-unknown}" if [ -f "$bridge/equipment.jsonl" ]; then echo "using the dump in $bridge" else mkdir -p "$bridge" ./bridge/dump.sh "$megamek" "$bridge" fi fi [ -f "$bridge/equipment.jsonl" ] || { echo "deploy: no equipment.jsonl in $bridge; the dump did not finish" >&2 exit 1 } cargo build --release -p helm-cli -j 2 cargo build --release -p helm-wasm --target wasm32-unknown-unknown -j 2 out="$(mktemp -d)" trap 'rm -rf "$out" "$tfvars.new"' EXIT # The MegaMek version is read from the install rather than passed, for the same # reason `helm build` reads it: a flag that can be typed wrong is a provenance # hole, and this one decides which prefix the artifacts land under. ./target/release/helm index \ --megamek "$megamek" \ --bridge-dir "$bridge" \ --helm-version "$ref" \ --out "$out/units.json" # From the build block, which is also what says this index and the loadout # beside it are about the same designs. mm="$(grep -o '"megamek":"[^"]*"' "$out/units.json" | head -1 | cut -d'"' -f4)" [ -n "$mm" ] || { echo "deploy: the index names no MegaMek version" >&2 exit 1 } # The catalogue the rules read in a browser, slimmed to the fields they use. ./target/release/helm catalogue --bridge-dir "$bridge" --out "$out/catalogue.jsonl" cp target/wasm32-unknown-unknown/release/helm_wasm.wasm "$out/helm_wasm.wasm" echo # Every chunk the index names, so a chunk added in helm is published without # this script learning its name. chunks="$(jq -r '.chunks[]' "$out/units.json") equipment.json" for file in units.json $chunks catalogue.jsonl helm_wasm.wasm; do [ -f "$out/$file" ] || { echo "deploy: units.json names $file and helm did not write it" >&2 exit 1 } printf ' %-16s %6s KB\n' "$file" "$(( $(wc -c <"$out/$file") / 1024 ))" done echo if [ -n "$dry_run" ]; then echo "dry run: would publish under $ref/$mm; $tfvars not touched" exit 0 fi bucket="$(tofu -chdir="$infra/envs/lance.blue" output -raw assets_helm_bucket)" # Immutable because a prefix is written once: the ref names this commit and # the MegaMek version names that release, so the same path can only ever hold # the same bytes. cache="public, max-age=31536000, immutable" aws s3 cp "$out/helm_wasm.wasm" "s3://$bucket/$ref/helm_wasm.wasm" \ --content-type application/wasm --cache-control "$cache" --no-progress aws s3 cp "$out/units.json" "s3://$bucket/$ref/$mm/units.json" \ --content-type application/json --cache-control "$cache" --no-progress for file in $chunks; do aws s3 cp "$out/$file" "s3://$bucket/$ref/$mm/$file" \ --content-type application/json --cache-control "$cache" --no-progress done aws s3 cp "$out/catalogue.jsonl" "s3://$bucket/$ref/$mm/catalogue.jsonl" \ --content-type application/x-ndjson --cache-control "$cache" --no-progress # Written beside the file it replaces so the move is atomic; a jq that fails # halfway leaves the current release id in place. jq --arg ref "$ref" '.releases.helm = $ref' "$tfvars" >"$tfvars.new" mv "$tfvars.new" "$tfvars" echo echo "Published under $ref/$mm:" echo " https://lance.blue/assets/helm/$ref/helm_wasm.wasm" echo " https://lance.blue/assets/helm/$ref/$mm/units.json" for file in $chunks; do echo " https://lance.blue/assets/helm/$ref/$mm/$file" done echo " https://lance.blue/assets/helm/$ref/$mm/catalogue.jsonl" echo echo "Wrote releases.helm = $ref" echo "To serve it:" echo " tofu -chdir=$infra/envs/lance.blue plan -out=lance.blue.tfplan" echo " tofu -chdir=$infra/envs/lance.blue apply lance.blue.tfplan"