AkurAI Build
Menu

Public repository / main

AkurAI-EC2 / tests/test_manifest_helper.sh

Latest change a2153fae6258fc7ad3dee8b9e7316314f1b8abda - install: default to standalone copy — the AkurAI Build worker sandbox cannot traverse symlinks out of ~/.local/bin, so the symlink default broke every deploy job shelling out to akurai-ec2 (second occurrence; regression test added); --symlink stays as explicit dev opt-in by Ólafur Búi Ólafsson

#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
CLI="$ROOT/bin/akurai-ec2"
HELPER="$ROOT/libexec/akurai-ec2-manifest.py"
FIXTURES="$ROOT/tests/fixtures"
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT

check_helper() {
  local name="$1"
  local out="$TMPDIR/$name.tsv"
  python3 "$HELPER" "$FIXTURES/$name.toml" > "$out"
  diff -u "$FIXTURES/$name.expected.tsv" "$out"
}

make_app_fixture() {
  local name="$1"
  local version="$2"
  local dest="$TMPDIR/$name-app"
  mkdir -p "$dest"
  cp "$FIXTURES/$name.toml" "$dest/akurai-deploy.toml"
  printf '%s\n' "$version" > "$dest/VERSION"
  printf '%s\n' "$dest"
}

run_release_dry_run() {
  local name="$1"
  local version="$2"
  local out="$3"
  local appdir
  shift 3

  appdir="$(make_app_fixture "$name" "$version")"
  "$CLI" release "$@" --dry-run -C "$appdir" > "$out" 2>&1
}

check_release_dry_run() {
  local name="$1"
  local version="$2"
  local expect_release="$3"
  local expect_action="$4"
  local out="$TMPDIR/$name.release.out"
  local args=()
  shift 4

  while [ $# -gt 0 ] && [ "$1" != "--" ]; do
    args+=("$1")
    shift
  done
  [ $# -eq 0 ] || shift

  run_release_dry_run "$name" "$version" "$out" "${args[@]}"
  grep -F "$expect_release" "$out" >/dev/null
  grep -F "$expect_action" "$out" >/dev/null
  for expected in "$@"; do
    grep -F "$expected" "$out" >/dev/null
  done
}

check_output_lacks() {
  local out="$1"
  local unexpected="$2"
  if grep -F "$unexpected" "$out" >/dev/null; then
    printf 'unexpected output in %s: %s\n' "$out" "$unexpected" >&2
    return 1
  fi
}

check_release_failure() {
  local name="$1"
  local version="$2"
  local out="$3"
  local status
  shift 3

  set +e
  run_release_dry_run "$name" "$version" "$out" "$@"
  status=$?
  set -e
  [ "$status" -ne 0 ]
  if grep -F "[dry-run] would: deploy-binary" "$out" >/dev/null; then
    printf 'unexpected deploy preview for failed manifest: %s\n' "$name" >&2
    return 1
  fi
}

check_copy_install() {
  local prefix="$TMPDIR/copy-prefix"
  local target="$prefix/bin/akurai-ec2"
  local helper_dir="$prefix/libexec"
  local helper="$helper_dir/akurai-ec2-manifest.py"
  local appdir helper_inode out

  AKURAI_EC2_INSTALL_TARGET="$target" "$ROOT/install.sh" --copy > "$TMPDIR/install-copy.out" 2>&1
  [ -x "$target" ]
  [ -x "$helper" ]
  helper_inode="$(stat -c %i -- "$helper")"
  (
    trap 'chmod 755 "$helper_dir"' EXIT
    chmod 555 "$helper_dir"
    AKURAI_EC2_INSTALL_TARGET="$target" "$ROOT/install.sh" --copy >"$TMPDIR/install-copy-again.out" 2>&1
  )
  [ "$helper_inode" = "$(stat -c %i -- "$helper")" ]

  appdir="$(make_app_fixture "v2-framework" "2.3.4")"
  out="$TMPDIR/install-copy.release.out"
  "$target" release --mode release --bump patch --dry-run -C "$appdir" > "$out" 2>&1
  grep -F "release: framework-site" "$out" >/dev/null
  grep -F "[dry-run] would: deploy-binary framework-site target/x86_64-unknown-linux-musl/release/framework-site 8112 ." "$out" >/dev/null
}

check_symlink_install() {
  local home="$TMPDIR/symlink-home"
  local target="$home/.local/bin/akurai-ec2"
  local appdir out

  HOME="$home" "$ROOT/install.sh" --symlink > "$TMPDIR/install-symlink.out" 2>&1
  [ -L "$target" ]
  [ "$(readlink -f -- "$target")" = "$(readlink -f -- "$CLI")" ]

  appdir="$(make_app_fixture "v2-framework" "2.3.4")"
  out="$TMPDIR/install-symlink.release.out"
  "$target" release --mode release --bump patch --dry-run -C "$appdir" > "$out" 2>&1
  grep -F "release: framework-site" "$out" >/dev/null
  grep -F "[dry-run] would: deploy-binary framework-site target/x86_64-unknown-linux-musl/release/framework-site 8112 ." "$out" >/dev/null
}

# The default install MUST be a standalone copy: the AkurAI Build worker
# sandbox cannot traverse symlinks out of ~/.local/bin, so a symlink default
# breaks every deploy job that shells out to akurai-ec2 (regression 2026-07-19).
check_default_install_is_copy() {
  local home="$TMPDIR/default-home"
  local target="$home/.local/bin/akurai-ec2"

  HOME="$home" "$ROOT/install.sh" > "$TMPDIR/install-default.out" 2>&1
  [ -f "$target" ] && [ ! -L "$target" ] && [ -x "$target" ]
  cmp -s -- "$CLI" "$target"
}

check_immutable_cli_backup() {
  local home="$TMPDIR/backup-home"
  local target="$home/.local/bin/akurai-ec2"
  local old="$TMPDIR/old-akurai-ec2"
  local backup

  mkdir -p "$(dirname "$target")"
  printf 'old cli snapshot\n' >"$old"
  chmod 755 "$old"
  ln -s "$old" "$target"
  AKURAI_EC2_INSTALL_TARGET="$target" "$ROOT/install.sh" --copy >"$TMPDIR/backup-install.out" 2>&1

  backup="$(printf '%s\n' "$target".bak.*)"
  [ -f "$backup" ] && [ ! -L "$backup" ]
  cmp -s "$old" "$backup"
  [ -f "$target" ] && [ ! -L "$target" ]
}

check_broken_symlink_copy_install() {
  local prefix="$TMPDIR/broken-prefix"
  local target="$prefix/bin/akurai-ec2"
  local helper_dir="$prefix/libexec"
  local helper="$helper_dir/akurai-ec2-manifest.py"
  local helper_src helper_inode

  mkdir -p "$(dirname "$target")" "$helper_dir"
  for helper_src in "$ROOT"/libexec/akurai-ec2-*.py; do
    install -m 755 -- "$helper_src" "$helper_dir/$(basename -- "$helper_src")"
  done
  helper_inode="$(stat -c %i -- "$helper")"
  ln -s "$prefix/missing/akurai-ec2" "$target"
  (
    trap 'chmod 755 "$helper_dir"' EXIT
    chmod 555 "$helper_dir"
    AKURAI_EC2_INSTALL_TARGET="$target" "$ROOT/install.sh" --copy >"$TMPDIR/broken-install.out" 2>&1
  )

  [ -f "$target" ] && [ ! -L "$target" ] && [ -x "$target" ]
  cmp -s "$CLI" "$target"
  [ "$helper_inode" = "$(stat -c %i -- "$helper")" ]
}

check_space_array_publish() {
  local fakebin="$TMPDIR/fakebin"
  local log="$TMPDIR/fake-remote.log"
  local appdir out

  mkdir -p "$fakebin"
  ln -s "$CLI" "$fakebin/akurai-ec2"
  cat > "$fakebin/ssh" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
log="${AKURAI_FAKE_REMOTE_LOG:?}"
cmd="${*: -1}"
printf 'ssh\t%s\n' "$cmd" >> "$log"
cat >/dev/null || true
if [[ "$cmd" == *"curl -s -o /dev/null -w '%{http_code}'"* ]]; then
  printf '200\n'
fi
EOF
  cat > "$fakebin/scp" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
log="${AKURAI_FAKE_REMOTE_LOG:?}"
printf 'scp\t%s\n' "${*: -1}" >> "$log"
EOF
  chmod 755 "$fakebin/ssh" "$fakebin/scp"

  appdir="$(make_app_fixture "v2-space-array" "0.0.1")"
  out="$TMPDIR/v2-space-array.publish.out"
  AKURAI_FAKE_REMOTE_LOG="$log" PATH="$fakebin:$PATH" AKURAI_EC2_SSH="fake-remote" \
    "$CLI" release publish -C "$appdir" > "$out" 2>&1

  grep -F "publish: space-array" "$out" >/dev/null
  grep -F "sudo mkdir -p '/srv/space dir' && sudo chown spacey:spacey '/srv/space dir'" "$log" >/dev/null
  grep -F "sudo mkdir -p '/srv/plain' && sudo chown spacey:spacey '/srv/plain'" "$log" >/dev/null
  [ "$(grep -c "^ssh	sudo mkdir -p '/srv/" "$log")" -eq 2 ]
}

bash -n "$CLI"

check_helper "v1-flat"
check_helper "v2-rust"
check_helper "v2-framework"
check_helper "v2-nested-default-action"
check_helper "v2-space-array"
check_immutable_cli_backup
check_broken_symlink_copy_install

check_release_dry_run \
  "v1-flat" \
  "0.1.2" \
  "release: akurai-vpn" \
  "[dry-run] would: deploy-binary akurai-vpn target/x86_64-unknown-linux-musl/release/akurai-vpn 8098 ." \
  -- \
  "release (dry-run): akurai-vpn → v0.1.3"

check_release_dry_run \
  "v2-rust" \
  "1.4.9" \
  "release: akurai-rust" \
  "[dry-run] would: deploy-binary akurai-rust target/x86_64-unknown-linux-musl/release/akurai-rust 8104 ." \
  --mode release --bump patch -- \
  "[dry-run] would: nginx-proxy rust.akurai.test 8104; tls rust.akurai.test"

check_release_dry_run \
  "v2-framework" \
  "2.3.4" \
  "release: framework-site" \
  "[dry-run] would: deploy-binary framework-site target/x86_64-unknown-linux-musl/release/framework-site 8112 ." \
  --mode release --bump patch -- \
  "data='[\"/opt/framework-site/cache\",\"/opt/framework-site/uploads\"]'" \
  "[dry-run] nginx: leaving existing site.akurai.test vhost untouched (manage_nginx=false)"

helper_err_out="$TMPDIR/v2-helper-error.release.err"
check_release_failure "v2-helper-error" "0.0.1" "$helper_err_out"
grep -F "error: nested arrays or inline tables are not supported" "$helper_err_out" >/dev/null
grep -F "manifest helper failed: akurai-deploy.toml" "$helper_err_out" >/dev/null

artifact_app="$(make_app_fixture v2-framework 2.3.4)"
artifact_path="$artifact_app/prebuilt"
printf 'immutable artifact\n' >"$artifact_path"
artifact_error="$TMPDIR/artifact-digest.err"
set +e
AKURAI_RELEASE_ARTIFACT="$artifact_path" \
AKURAI_RELEASE_SHA256="0000000000000000000000000000000000000000000000000000000000000000" \
  "$CLI" release --mode publish --yes -C "$artifact_app" >"$artifact_error" 2>&1
artifact_status=$?
set -e
[ "$artifact_status" -ne 0 ]
grep -F "build artifact digest mismatch" "$artifact_error" >/dev/null

check_release_dry_run \
  "v2-framework" \
  "2.3.4" \
  "publish: framework-site" \
  "[dry-run] would: deploy-binary framework-site target/x86_64-unknown-linux-musl/release/framework-site 8112 ." \
  -- \
  "[dry-run] nginx: leaving existing site.akurai.test vhost untouched (manage_nginx=false)" \
  "publish (dry-run): framework-site - nothing written, synchronized, or deployed"
check_output_lacks "$TMPDIR/v2-framework.release.out" "publish: framework-site ✓"

check_release_dry_run \
  "v2-framework" \
  "2.3.4" \
  "publish: framework-site" \
  "[dry-run] would: deploy-binary framework-site target/x86_64-unknown-linux-musl/release/framework-site 8112 ." \
  --mode publish -- \
  "[dry-run] nginx: leaving existing site.akurai.test vhost untouched (manage_nginx=false)" \
  "publish (dry-run): framework-site - nothing written, synchronized, or deployed"
check_output_lacks "$TMPDIR/v2-framework.release.out" "publish: framework-site ✓"

check_release_dry_run \
  "v2-framework" \
  "2.3.4" \
  "update: framework-site" \
  "[dry-run] would: deploy-binary framework-site target/x86_64-unknown-linux-musl/release/framework-site 8112 ." \
  --mode update -- \
  "[dry-run] nginx: leaving existing site.akurai.test vhost untouched (manage_nginx=false)" \
  "update (dry-run): framework-site - nothing written, synchronized, or deployed"
check_output_lacks "$TMPDIR/v2-framework.release.out" "update: framework-site ✓"

check_release_dry_run \
  "v2-nested-default-action" \
  "0.4.0" \
  "publish: nested-default-app" \
  "[dry-run] would: deploy-binary nested-default-app target/x86_64-unknown-linux-musl/release/nested-default-app 8130 ." \
  -- \
  "[dry-run] nginx: leaving existing nested.akurai.test vhost untouched (manage_nginx=false)" \
  "publish (dry-run): nested-default-app - nothing written, synchronized, or deployed"
check_output_lacks "$TMPDIR/v2-nested-default-action.release.out" "publish: nested-default-app ✓"

check_release_dry_run \
  "v1-flat" \
  "0.1.2" \
  "release: akurai-vpn" \
  "[dry-run] would: deploy-binary akurai-vpn target/x86_64-unknown-linux-musl/release/akurai-vpn 8098 ." \
  patch -- \
  "release (dry-run): akurai-vpn → v0.1.3"

check_release_dry_run \
  "v1-flat" \
  "0.1.2" \
  "release: akurai-vpn" \
  "[dry-run] would: deploy-binary akurai-vpn target/x86_64-unknown-linux-musl/release/akurai-vpn 8098 ." \
  minor -- \
  "release (dry-run): akurai-vpn → v0.2.0"

check_release_dry_run \
  "v1-flat" \
  "0.1.2" \
  "release: akurai-vpn" \
  "[dry-run] would: deploy-binary akurai-vpn target/x86_64-unknown-linux-musl/release/akurai-vpn 8098 ." \
  major -- \
  "release (dry-run): akurai-vpn → v1.0.0"

check_release_dry_run \
  "v2-space-array" \
  "0.0.1" \
  "publish: space-array" \
  "[dry-run] would: deploy-binary space-array /bin/true 8126" \
  publish --

check_release_dry_run \
  "v2-space-array" \
  "0.0.1" \
  "publish: space-array" \
  "[dry-run] would: deploy-binary space-array /bin/true 8126" \
  ec2 --

conflict_out="$TMPDIR/v2-framework.conflict.err"
check_release_failure "v2-framework" "2.3.4" "$conflict_out" publish --mode release
grep -F "release: conflicting mode values (legacy 'publish'=publish, --mode=release)" "$conflict_out" >/dev/null

conflict_bump_out="$TMPDIR/v1-flat.bump-conflict.err"
check_release_failure "v1-flat" "0.1.2" "$conflict_bump_out" minor --bump patch
grep -F "release: conflicting bump values (legacy 'minor'=minor, --bump=patch)" "$conflict_bump_out" >/dev/null

check_copy_install
check_symlink_install
check_default_install_is_copy
check_space_array_publish