# zuka builds and deploys itself.
#
# This runs on the build instance — a second zuka on the same host, separate from the
# control plane. That separation is not tidiness: the last step restarts the control
# plane, and a build running inside the thing it restarts would kill itself half way
# through its own deploy.

[run]
# A cold Rust build plus the live suite on two cores. The default 600s is not close
# to enough, and a timeout that trips on a clean build teaches everyone to ignore red.
timeout_secs = 2400

steps = [
  # Cheapest first: formatting and lints fail in seconds, so a badly formatted commit
  # does not occupy the host for twenty minutes before saying so.
  "cargo fmt --check",

  # CARGO_TARGET_DIR on every cargo step, because each run gets a fresh workspace and
  # a target directory inside it would be thrown away — making every build a cold
  # twenty-five minute one. Cargo locks this directory, so concurrent runs queue
  # rather than corrupt each other.
  "CARGO_TARGET_DIR=/var/lib/zuka-build/target cargo clippy --all-targets -- -D warnings",
  "CARGO_TARGET_DIR=/var/lib/zuka-build/target cargo test",

  # musl, statically linked. The host runs glibc 2.39 and the tenant containers 2.36,
  # so a glibc build made here will not start in a container. One static binary runs
  # in both places, which is what lets the control plane and its tenants be the same
  # build.
  "CARGO_TARGET_DIR=/var/lib/zuka-build/target cargo build --release --target x86_64-unknown-linux-musl",

  # The live suite is where the wire protocol and the REST contract are actually
  # proven. It self-skips without ZUKA_LIVE_TEST, which the task sets, and a proof
  # that never runs is not one.
  #
  # Pointed at the musl binary built above, because that is the exact file the deploy
  # step ships. Exercising a separate glibc build and then shipping this one would
  # prove nothing about what actually runs.
  "ZUKA_TEST_BINARY=/var/lib/zuka-build/target/x86_64-unknown-linux-musl/release/zuka deno task test:live",

  # Hand the binary to root and ask for a deploy, but only from main.
  #
  # The guard and the deploy are one step on purpose: `exit 0` only ends its own
  # step, so a guard in a step of its own would skip nothing and every branch would
  # deploy.
  #
  # CI holds no privilege and cannot deploy by itself. It writes the binary and a
  # request file; a root systemd.path unit notices the request and runs the deploy,
  # which restarts the service, checks that the new build is the one answering, and
  # puts the old binary back if it is not. The result comes back through a file so a
  # failed deploy still turns this run red — a build that goes green while the deploy
  # quietly failed is worse than not deploying at all.
  '''
  set -eu
  BIN=/var/lib/zuka-build/target/x86_64-unknown-linux-musl/release/zuka
  BUILD=$("$BIN" version | awk '{print $NF}')

  if [ "$ZUKA_REF" != refs/heads/main ]; then
    echo "$ZUKA_REF is not main - built and tested $BUILD, not deploying"
    exit 0
  fi

  S=/var/lib/zuka-build/staging
  mkdir -p "$S"
  # Clear the previous run's answer first, or a stale OK would be read as this
  # deploy succeeding before root has even started.
  rm -f "$S/deploy.result"
  cp "$BIN" "$S/zuka"
  echo "$BUILD" > "$S/deploy.request"
  echo "requested deploy of $BUILD"

  n=0
  while [ "$n" -lt 180 ]; do
    if [ -f "$S/deploy.result" ]; then
      R=$(cat "$S/deploy.result")
      echo "$R"
      case "$R" in
        "OK $BUILD"*) exit 0 ;;
        OK*)          echo "a different build was deployed; expected $BUILD"; exit 1 ;;
        *)            exit 1 ;;
      esac
    fi
    sleep 2
    n=$((n + 2))
  done
  echo "no deploy result after 180s"
  exit 1
  ''',
]
