#!/usr/bin/env bash set -Eeuo pipefail root=$(cd "$(dirname "$0")/.." && pwd) bash -n "$root/install.sh" "$root/scripts/tallynote-update.sh" grep -Eq '^RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK$' "$root/systemd/tallynote.service" grep -Eq '^RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK$' "$root/systemd/tallynote-update.service" output=$(bash "$root/install.sh" --dry-run --release-base-url https://releases.example.test/releases) grep -q 'dry-run' <<<"$output" grep -q '\[阶段\] 检查运行环境' <<<"$output" grep -q '\[完成\] dry-run 预览完成' <<<"$output" output=$(bash "$root/install.sh" --dry-run --version 1.2.3 --release-base-url https://releases.example.test/releases) grep -q 'release: 1.2.3' <<<"$output" grep -q '\[阶段\] 使用指定版本:1.2.3' <<<"$output" if bash "$root/install.sh" --dry-run --release-base-url http://insecure.example.test/releases >/dev/null 2>&1; then echo 'expected non-HTTPS URL to fail' >&2 exit 1 fi if TALLYNOTE_HOST=0.0.0.0 bash "$root/install.sh" --dry-run --release-base-url https://releases.example.test/releases >/dev/null 2>&1; then echo 'expected non-local listener without public origin to fail' >&2 exit 1 fi output=$(TALLYNOTE_HOST=0.0.0.0 TALLYNOTE_PORT=3000 \ TALLYNOTE_PUBLIC_ORIGIN=http://203.0.113.10:3000 \ TALLYNOTE_ALLOW_INSECURE_HTTP=true \ bash "$root/install.sh" --dry-run --version 1.2.3 --release-base-url https://releases.example.test/releases) grep -q 'release: 1.2.3' <<<"$output" output=$(TALLYNOTE_HOST=::1 TALLYNOTE_PORT=3443 \ bash "$root/install.sh" --dry-run --version 1.2.3 --release-base-url https://releases.example.test/releases) grep -q 'release: 1.2.3' <<<"$output" if TALLYNOTE_HOST=0.0.0.0 TALLYNOTE_PORT=65536 TALLYNOTE_PUBLIC_ORIGIN=http://203.0.113.10:3000 TALLYNOTE_ALLOW_INSECURE_HTTP=true \ bash "$root/install.sh" --dry-run --release-base-url https://releases.example.test/releases >/dev/null 2>&1; then echo 'expected invalid listener port to fail' >&2 exit 1 fi if TALLYNOTE_HOST=0.0.0.0 TALLYNOTE_PUBLIC_ORIGIN=http://203.0.113.10:3000 \ bash "$root/install.sh" --dry-run --release-base-url https://releases.example.test/releases >/dev/null 2>&1; then echo 'expected public HTTP without explicit opt-in to fail' >&2 exit 1 fi tmp=$(mktemp -d) cleanup_tmp() { if [[ -d "$tmp" ]]; then rm -r "$tmp" 2>/dev/null || true fi } trap cleanup_tmp EXIT cat >"$tmp/uname" <<'EOF' #!/usr/bin/env bash printf 'i686\n' EOF chmod +x "$tmp/uname" if TALLYNOTE_UNAME_BIN="$tmp/uname" bash "$root/install.sh" --dry-run >/dev/null 2>&1; then echo 'expected ia32 to fail' >&2 exit 1 fi if [[ "$(uname -s)" != Linux ]]; then if bash "$root/scripts/build-release.sh" 1.0.0 /tmp/tallynote-installer-release-test >/dev/null 2>&1; then echo 'expected non-Linux release build to fail on this host' >&2 exit 1 fi fi # Exercise installer helpers without mutating the host. Removing the final # main invocation lets this subprocess source the exact production code. installer_lib="$tmp/install-lib.sh" sed '$d' "$root/install.sh" > "$installer_lib" bash -c ' script=$1 set -- source "$script" [[ "$APPLY" -eq 1 ]] [[ "$REQUIRE_SIGNATURE" == false ]] ' _ "$installer_lib" bash -c ' script=$1 mode_dir=$2 owner_parent=$3 set -- source "$script" mkdir -p "$mode_dir" chmod 700 "$mode_dir" [[ "$(stat_mode_bits "$mode_dir")" == 448 ]] mkdir -p "$owner_parent" if (assert_path_chain "$owner_parent/child") >/dev/null 2>&1; then echo "expected non-root path parent to fail" >&2 exit 1 fi ' _ "$installer_lib" "$tmp/mode" "$tmp/user-parent" # The port probe must distinguish a listening TCP port from a free one. port_tools="$tmp/port-tools" mkdir -p "$port_tools" printf '%s\n' '#!/usr/bin/env bash' 'printf "%s\\n" "LISTEN 0 128 127.0.0.1:3443 0.0.0.0:*"' > "$port_tools/ss" chmod 755 "$port_tools/ss" bash -c ' script=$1 tools=$2 set -- source "$script" PATH="$tools:$PATH" state=0 port_listener_state 3443 || state=$? [[ "$state" == 1 ]] state=0 port_listener_state 3444 || state=$? [[ "$state" == 0 ]] ' _ "$installer_lib" "$port_tools" # The lsof fallback must treat its normal "no matches" exit status as a free # port, while still reporting a listener when it returns a PID. lsof_tools="$tmp/lsof-tools" mkdir -p "$lsof_tools" printf '%s\n' '#!/usr/bin/env bash' 'exit 127' > "$lsof_tools/ss" printf '%s\n' '#!/usr/bin/env bash' 'case "$*" in *TCP:3443*) printf "%s\\n" 4242; exit 0 ;; *) exit 1 ;; esac' > "$lsof_tools/lsof" chmod 755 "$lsof_tools/ss" "$lsof_tools/lsof" bash -c ' script=$1 tools=$2 set -- source "$script" PATH="$tools:$PATH" state=0 port_listener_state 3443 || state=$? [[ "$state" == 1 ]] state=0 port_listener_state 3444 || state=$? [[ "$state" == 0 ]] ' _ "$installer_lib" "$lsof_tools" # Public-IP discovery accepts a valid IPv4 response and rejects malformed # values without making the test depend on an external service. bash -c ' script=$1 set -- source "$script" PUBLIC_IP_URL=https://ip.example.test curl() { printf "%s\\n" "198.51.100.7"; } [[ "$(detect_public_ipv4)" == "198.51.100.7" ]] curl() { printf "%s\\n" "999.1.1.1"; } if detect_public_ipv4 >/dev/null 2>&1; then echo "expected invalid public IPv4 response to fail" >&2 exit 1 fi ' _ "$installer_lib" # The service health probe maps wildcard listeners to loopback and must return # promptly when the local endpoint is healthy. bash -c ' script=$1 set -- source "$script" curl() { [[ "$*" == *"http://127.0.0.1:3011/health"* ]] || return 1; } wait_for_service_health 0.0.0.0 3011 ' _ "$installer_lib" # A RETURN trap installed by install_release must be cleared while its local # temporary variables still exist; otherwise set -u fails at the end of main. release_fixture="$tmp/release-fixture" mkdir -p "$release_fixture/dist/server/cli" "$release_fixture/dist/web" "$release_fixture/bin" \ "$release_fixture/scripts" "$release_fixture/runtime/bin" "$release_fixture/systemd" printf '%s\n' '{"version":"1.0.0"}' > "$release_fixture/package.json" printf '%s\n' server > "$release_fixture/dist/server/index.js" printf '%s\n' cli > "$release_fixture/dist/server/cli/admin-init.js" printf '%s\n' web > "$release_fixture/dist/web/index.html" printf '%s\n' '#!/bin/sh' > "$release_fixture/bin/tallynote" cp "$root/bin/tallynote-admin-init" "$release_fixture/bin/tallynote-admin-init" printf '%s\n' '#!/bin/sh' > "$release_fixture/scripts/tallynote-update.sh" printf '%s\n' '#!/bin/sh' > "$release_fixture/scripts/tallynote-update-runner.sh" printf '%s\n' '#!/bin/sh' > "$release_fixture/uninstall.sh" printf '%s\n' '[Unit]' > "$release_fixture/systemd/tallynote.service" printf '%s\n' '[Unit]' > "$release_fixture/systemd/tallynote-update.service" printf '%s\n' '[Unit]' > "$release_fixture/systemd/tallynote-update.path" printf '%s\n' 'TALLYNOTE_HOST=127.0.0.1' > "$release_fixture/systemd/tallynote.env.example" chmod 755 "$release_fixture/bin/tallynote" "$release_fixture/bin/tallynote-admin-init" "$release_fixture/scripts"/*.sh "$release_fixture/uninstall.sh" release_archive="$tmp/release-fixture.tar.gz" tar -C "$release_fixture" -czf "$release_archive" . bash -c ' script=$1 archive=$2 destination=$3 set -- source "$script" PREFIX="$destination/prefix" ensure_root_directory() { mkdir -p "$1"; } chown() { :; } mv() { if [[ "${1:-}" == -Tf ]]; then shift; /bin/mv -f "$@"; else /bin/mv "$@"; fi } install_release "$archive" 1.0.0 [[ -x "$PREFIX/releases/1.0.0/bin/tallynote-admin-init" ]] set_env_key() { local key=$1 value=$2 escaped; :; } set_env_key test value ' _ "$installer_lib" "$release_archive" "$tmp/install-release" # The production admin wrapper must load a release-relative runtime, change to # the release root, and forward CLI arguments without requiring pnpm. wrapper_prefix="$tmp/wrapper-prefix" mkdir -p "$wrapper_prefix/releases/1.0.0/runtime/bin" "$wrapper_prefix/releases/1.0.0/dist/server/cli" ln -s "$wrapper_prefix/releases/1.0.0" "$wrapper_prefix/current" printf '%s\n' '#!/usr/bin/env bash' 'pwd -P > "$TALLYNOTE_WRAPPER_LOG"' 'printf "%s\n" "$@" >> "$TALLYNOTE_WRAPPER_LOG"' > "$wrapper_prefix/releases/1.0.0/runtime/bin/node" chmod 755 "$wrapper_prefix/releases/1.0.0/runtime/bin/node" printf '%s\n' cli > "$wrapper_prefix/releases/1.0.0/dist/server/cli/admin-init.js" TALLYNOTE_INSTALL_PREFIX="$wrapper_prefix" TALLYNOTE_CONFIG_DIR="$tmp/no-config" TALLYNOTE_WRAPPER_LOG="$tmp/wrapper.log" \ bash "$root/bin/tallynote-admin-init" --generate wrapper_expected_root=$(cd "$wrapper_prefix/releases/1.0.0" && pwd -P) grep -Fxq "$wrapper_expected_root" "$tmp/wrapper.log" grep -Fxq -- '--generate' "$tmp/wrapper.log" # The first-install prompt is optional and must support an explicit later # initialization path without blocking the rest of the install. admin_wizard_dir="$tmp/admin-wizard" mkdir -p "$admin_wizard_dir" printf '%s\n' yes remaining-input > "$admin_wizard_dir/input" : > "$admin_wizard_dir/output" cat > "$admin_wizard_dir/admin-init" <<'EOF' #!/usr/bin/env bash if [[ "${1:-}" == --check ]]; then printf '%s\n' empty else printf '%s\n' initialized > "$TALLYNOTE_ADMIN_WIZARD_RESULT" fi EOF chmod 755 "$admin_wizard_dir/admin-init" bash -c ' script=$1 dir=$2 set -- source "$script" INSTALL_FIRST_INSTALL=1 NON_INTERACTIVE=0 PROMPT_INPUT="$dir/input" PROMPT_OUTPUT="$dir/output" ADMIN_INIT_PATH="$dir/admin-init" TALLYNOTE_ADMIN_WIZARD_RESULT="$dir/result" export TALLYNOTE_ADMIN_WIZARD_RESULT run_initial_admin_wizard [[ -f "$dir/result" ]] ' _ "$installer_lib" "$admin_wizard_dir" # An upgrade must never reopen the first-admin wizard, even if a damaged or # deliberately empty database would otherwise report an uninitialized state. printf '%s\n' yes > "$admin_wizard_dir/upgrade-input" rm -f "$admin_wizard_dir/upgrade-result" bash -c ' script=$1 dir=$2 set -- source "$script" INSTALL_FIRST_INSTALL=0 NON_INTERACTIVE=0 PROMPT_INPUT="$dir/upgrade-input" PROMPT_OUTPUT="$dir/upgrade-output" ADMIN_INIT_PATH="$dir/admin-init" TALLYNOTE_ADMIN_WIZARD_RESULT="$dir/upgrade-result" export TALLYNOTE_ADMIN_WIZARD_RESULT run_initial_admin_wizard [[ ! -e "$dir/upgrade-result" ]] ' _ "$installer_lib" "$admin_wizard_dir" # Validation or password errors after the base service is committed must leave # the installation usable and point the operator at the standalone command. failed_wizard_dir="$tmp/failed-admin-wizard" mkdir -p "$failed_wizard_dir" printf '%s\n' yes > "$failed_wizard_dir/input" : > "$failed_wizard_dir/output" cat >"$failed_wizard_dir/admin-init" <<'EOF' #!/usr/bin/env bash if [[ "${1:-}" == --check ]]; then printf '%s\n' empty exit 0 fi exit 1 EOF chmod 755 "$failed_wizard_dir/admin-init" bash -c ' script=$1 dir=$2 set -- source "$script" INSTALL_FIRST_INSTALL=1 NON_INTERACTIVE=0 PROMPT_INPUT="$dir/input" PROMPT_OUTPUT="$dir/output" ADMIN_INIT_PATH="$dir/admin-init" run_initial_admin_wizard [[ -x "$dir/admin-init" ]] ' _ "$installer_lib" "$failed_wizard_dir" # Duplicate security-sensitive EnvironmentFile assignments are rejected even # when the first value looks valid (systemd uses the later value). duplicate_env="$tmp/duplicate.env" printf '%s\n' 'TALLYNOTE_UPDATE_REQUIRE_SIGNATURE=true' 'TALLYNOTE_UPDATE_REQUIRE_SIGNATURE=false' > "$duplicate_env" bash -c ' script=$1 env_file=$2 set -- source "$script" stat_uid() { printf "0"; } stat_mode_bits() { printf "384"; } if (validate_existing_env "$env_file") >/dev/null 2>&1; then echo "expected duplicate environment assignment to fail" >&2 exit 1 fi ' _ "$installer_lib" "$duplicate_env" # Existing installations must validate the network settings they preserve on # upgrade, including the direct-IP HTTP combination used by the documented # installer command. network_env="$tmp/network.env" printf '%s\n' \ 'TALLYNOTE_HOST=0.0.0.0' \ 'TALLYNOTE_PORT=3000' \ 'TALLYNOTE_PUBLIC_ORIGIN=http://203.0.113.10:3000' \ 'TALLYNOTE_ALLOW_INSECURE_HTTP=true' > "$network_env" bash -c ' script=$1 env_file=$2 set -- source "$script" PREFIX=/opt/tallynote DATA_DIR=/var/lib/tallynote stat_uid() { printf "0"; } stat_mode_bits() { printf "384"; } validate_existing_env "$env_file" ' _ "$installer_lib" "$network_env" if sed 's/^TALLYNOTE_PORT=.*/TALLYNOTE_PORT=65536/' "$network_env" > "$tmp/invalid-port.env"; then if bash -c ' script=$1 env_file=$2 set -- source "$script" PREFIX=/opt/tallynote DATA_DIR=/var/lib/tallynote stat_uid() { printf "0"; } stat_mode_bits() { printf "384"; } validate_existing_env "$env_file" ' _ "$installer_lib" "$tmp/invalid-port.env" >/dev/null 2>&1; then echo 'expected invalid existing listener port to fail' >&2 exit 1 fi fi printf '%s\n' \ 'TALLYNOTE_HOST=0.0.0.0' \ 'TALLYNOTE_PORT=3000' \ 'TALLYNOTE_PUBLIC_ORIGIN=http://203.0.113.10:3000' \ 'TALLYNOTE_ALLOW_INSECURE_HTTP=false' > "$tmp/public-http-without-opt-in.env" if bash -c ' script=$1 env_file=$2 set -- source "$script" PREFIX=/opt/tallynote DATA_DIR=/var/lib/tallynote stat_uid() { printf "0"; } stat_mode_bits() { printf "384"; } validate_existing_env "$env_file" ' _ "$installer_lib" "$tmp/public-http-without-opt-in.env" >/dev/null 2>&1; then echo 'expected public HTTP without opt-in in existing env to fail' >&2 exit 1 fi bash -c ' script=$1 set -- source "$script" PREFIX=/opt/tallynote DATA_DIR=/var/lib/tallynote stat_uid() { printf "0"; } stat_mode_bits() { printf "384"; } validate_public_origin "http://[2001:db8::10]:3000" ' _ "$installer_lib" printf '%s\n' \ 'TALLYNOTE_HOST=0.0.0.0' \ 'TALLYNOTE_PORT=3000' \ 'TALLYNOTE_PUBLIC_ORIGIN=https://tallynote.example.com' \ 'TALLYNOTE_COOKIE_SECURE=true' > "$tmp/public-https.env" bash -c ' script=$1 env_file=$2 set -- source "$script" PREFIX=/opt/tallynote DATA_DIR=/var/lib/tallynote stat_uid() { printf "0"; } stat_mode_bits() { printf "384"; } validate_existing_env "$env_file" ' _ "$installer_lib" "$tmp/public-https.env" printf '%s\n' \ 'TALLYNOTE_HOST=::1' \ 'TALLYNOTE_PORT=3443' > "$tmp/ipv6-default-origin.env" bash -c ' script=$1 env_file=$2 set -- source "$script" PREFIX=/opt/tallynote DATA_DIR=/var/lib/tallynote stat_uid() { printf "0"; } stat_mode_bits() { printf "384"; } validate_existing_env "$env_file" ' _ "$installer_lib" "$tmp/ipv6-default-origin.env" if bash -c ' script=$1 set -- source "$script" validate_public_origin "http://example.test:65536" ' _ "$installer_lib" >/dev/null 2>&1; then echo 'expected invalid public origin port to fail' >&2 exit 1 fi # A fresh interactive install reads from the controlling terminal even when # the installer script itself is piped from curl. The test substitutes files # for that terminal and verifies both listener choices without touching the # host filesystem. interactive_dir="$tmp/interactive" mkdir -p "$interactive_dir/config" printf '\n\n' > "$interactive_dir/local-input" : > "$interactive_dir/local-output" env -u TALLYNOTE_HOST -u TALLYNOTE_PORT -u TALLYNOTE_PUBLIC_ORIGIN -u TALLYNOTE_ALLOW_INSECURE_HTTP \ bash -c ' script=$1 dir=$2 set -- source "$script" APPLY=1 NON_INTERACTIVE=0 CONFIG_DIR="$dir/config" PROMPT_INPUT="$dir/local-input" PROMPT_OUTPUT="$dir/local-output" configure_network_interactively [[ "$INSTALL_HOST" == 127.0.0.1 ]] [[ "$INSTALL_PORT" == 3000 ]] [[ "$INSTALL_PUBLIC_ORIGIN" == http://127.0.0.1:3000 ]] [[ "$INSTALL_ALLOW_INSECURE_HTTP" == false ]] ' _ "$installer_lib" "$interactive_dir" printf '2\n3443\nhttp://203.0.113.10:3443\nyes\n' > "$interactive_dir/public-input" : > "$interactive_dir/public-output" env -u TALLYNOTE_HOST -u TALLYNOTE_PORT -u TALLYNOTE_PUBLIC_ORIGIN -u TALLYNOTE_ALLOW_INSECURE_HTTP \ bash -c ' script=$1 dir=$2 set -- source "$script" APPLY=1 NON_INTERACTIVE=0 CONFIG_DIR="$dir/config" PROMPT_INPUT="$dir/public-input" PROMPT_OUTPUT="$dir/public-output" configure_network_interactively [[ "$INSTALL_HOST" == 0.0.0.0 ]] [[ "$INSTALL_PORT" == 3443 ]] [[ "$INSTALL_PUBLIC_ORIGIN" == http://203.0.113.10:3443 ]] [[ "$INSTALL_ALLOW_INSECURE_HTTP" == true ]] ' _ "$installer_lib" "$interactive_dir" printf '2\n3000\nhttp://203.0.113.10:3000\nno\n' > "$interactive_dir/refuse-input" : > "$interactive_dir/refuse-output" if env -u TALLYNOTE_HOST -u TALLYNOTE_PORT -u TALLYNOTE_PUBLIC_ORIGIN -u TALLYNOTE_ALLOW_INSECURE_HTTP \ bash -c ' script=$1 dir=$2 set -- source "$script" APPLY=1 NON_INTERACTIVE=0 CONFIG_DIR="$dir/config" PROMPT_INPUT="$dir/refuse-input" PROMPT_OUTPUT="$dir/refuse-output" configure_network_interactively ' _ "$installer_lib" "$interactive_dir" >/dev/null 2>&1; then echo 'expected public HTTP confirmation refusal to stop configuration' >&2 exit 1 fi # Explicit environment variables take precedence over the prompt, including # when a terminal is available. env -u TALLYNOTE_PUBLIC_ORIGIN -u TALLYNOTE_ALLOW_INSECURE_HTTP \ TALLYNOTE_HOST=0.0.0.0 TALLYNOTE_PORT=3000 \ bash -c ' script=$1 dir=$2 set -- source "$script" APPLY=1 NON_INTERACTIVE=0 CONFIG_DIR="$dir/config" PROMPT_INPUT="$dir/local-input" PROMPT_OUTPUT="$dir/local-output" if interactive_network_available; then echo "expected explicit network environment to skip prompt" >&2 exit 1 fi ' _ "$installer_lib" "$interactive_dir" # A release archive is extracted under umask 077, then explicitly normalized # so the tallynote system user can traverse and execute the shipped tree. source_tmp="$tmp/source" mkdir -p "$source_tmp/dist/server" "$source_tmp/bin" "$source_tmp/scripts" "$source_tmp/runtime/bin" printf '%s\n' 'server' > "$source_tmp/dist/server/index.js" printf '%s\n' '#!/bin/sh' > "$source_tmp/uninstall.sh" printf '%s\n' '#!/bin/sh' > "$source_tmp/bin/tallynote" printf '%s\n' '#!/bin/sh' > "$source_tmp/scripts/runner.sh" printf '%s\n' 'node' > "$source_tmp/runtime/bin/node" chmod 755 "$source_tmp/bin/tallynote" "$source_tmp/scripts/runner.sh" "$source_tmp/runtime/bin/node" chmod 755 "$source_tmp/uninstall.sh" archive_tmp="$tmp/release.tar.gz" tar -C "$source_tmp" -czf "$archive_tmp" . bash -c ' script=$1 archive=$2 destination=$3 set -- source "$script" safe_extract "$archive" "$destination" normalize_release_tree "$destination" [[ "$(stat_mode "$destination/dist")" == 755 ]] [[ "$(stat_mode "$destination/dist/server/index.js")" == 644 ]] [[ "$(stat_mode "$destination/bin/tallynote")" == 755 ]] [[ "$(stat_mode "$destination/uninstall.sh")" == 755 ]] ' _ "$installer_lib" "$archive_tmp" "$tmp/unpacked" # A normal public-release install only needs the detached SHA-256 manifest; # absence of a signature and public key must not block archive verification. checksum_tmp="$tmp/SHA256SUMS" (cd "$(dirname -- "$archive_tmp")" && sha256sum "$(basename -- "$archive_tmp")") > "$checksum_tmp" bash -c ' script=$1 archive=$2 checksum=$3 set -- source "$script" REQUIRE_SIGNATURE=false verify_archive "$archive" "$checksum" "" "" ' _ "$installer_lib" "$archive_tmp" "$checksum_tmp" # Newline/control characters in release configuration must never become extra # systemd EnvironmentFile assignments. if TALLYNOTE_RELEASE_API_URL=$'https://git.awaioi.com/api/v1\nEVIL=1' bash "$root/install.sh" --dry-run >/dev/null 2>&1; then echo 'expected control characters in release URL to fail' >&2 exit 1 fi # The publisher is safe to exercise on every host in dry-run mode. When an # OpenSSL build supports Ed25519, also verify the exact detached signature. publisher_tmp=$(mktemp -d) printf 'test-release' > "$publisher_tmp/tallynote-1.0.0-linux-x64-glibc.tar.gz" if "$root/scripts/publish-gitea-release.sh" v1.0.0 "$publisher_tmp" --dry-run >/dev/null 2>&1; then test -s "$publisher_tmp/SHA256SUMS" else echo 'publisher dry-run failed' >&2 exit 1 fi openssl_test_bin=${TALLYNOTE_OPENSSL_BIN:-$(command -v openssl || true)} if [[ -n "$openssl_test_bin" ]] && "$openssl_test_bin" genpkey -algorithm ED25519 -out "$publisher_tmp/key" >/dev/null 2>&1; then TALLYNOTE_RELEASE_SIGNING_KEY_FILE="$publisher_tmp/key" TALLYNOTE_OPENSSL_BIN="$openssl_test_bin" \ "$root/scripts/publish-gitea-release.sh" v1.0.0 "$publisher_tmp" --dry-run >/dev/null 2>&1 "$openssl_test_bin" pkey -in "$publisher_tmp/key" -pubout -out "$publisher_tmp/pub" >/dev/null 2>&1 "$openssl_test_bin" pkeyutl -verify -pubin -inkey "$publisher_tmp/pub" -rawin \ -in "$publisher_tmp/SHA256SUMS" -sigfile "$publisher_tmp/SHA256SUMS.sig" >/dev/null 2>&1 # Exercise the 404 -> create -> assets -> upload flow with a local curl # shim. The shim records argv and verifies the secret only arrives through # the temporary curl config file, never as a process argument. if command -v jq >/dev/null 2>&1; then fake_curl="$publisher_tmp/fake-curl" fake_trace="$publisher_tmp/curl-args" fake_config_seen="$publisher_tmp/curl-config-seen" cat > "$fake_curl" <<'EOF' #!/usr/bin/env bash set -Eeuo pipefail out=''; format=''; method='GET'; url=''; previous=''; config='' for arg in "$@"; do case "$previous" in out) out=$arg; previous=''; continue ;; format) format=$arg; previous=''; continue ;; method) method=$arg; previous=''; continue ;; config) config=$arg; previous=''; continue ;; esac case "$arg" in -o) previous=out ;; -w) previous=format ;; -X) previous=method ;; --config) previous=config ;; -d*|-F*) method=POST ;; http://*|https://*) url=$arg ;; esac done printf '%s\n' "$*" >> "$TALLYNOTE_FAKE_CURL_TRACE" [[ "$*" != *"$TALLYNOTE_FAKE_TOKEN"* ]] || { echo 'token leaked in curl argv' >&2; exit 91; } [[ -n "$config" && -s "$config" ]] || { echo 'curl auth config missing' >&2; exit 92; } grep -q "Authorization: token $TALLYNOTE_FAKE_TOKEN" "$config" printf '%s\n' seen > "$TALLYNOTE_FAKE_CURL_CONFIG_SEEN" code=200; body='{}' if [[ "$url" == */releases/tags/* ]]; then if [[ ! -f "$TALLYNOTE_FAKE_RELEASE_CREATED" ]]; then code=404; body='{}'; else code=200; body='{"id":42}'; fi elif [[ "$url" == */releases && "$method" == POST ]]; then printf '%s' created > "$TALLYNOTE_FAKE_RELEASE_CREATED" code=201; body='{"id":42}' elif [[ "$url" == */assets && "$method" == GET ]]; then code=200; body='[]' elif [[ "$url" == */assets\?name=* ]]; then code=201; body='{"id":1}' elif [[ "$method" == DELETE ]]; then code=204; body='' fi if [[ -n "$out" ]]; then printf '%s' "$body" > "$out" else printf '%s' "$body" fi if [[ "$format" == '%{http_code}' ]]; then printf '%s' "$code" fi EOF chmod 700 "$fake_curl" TALLYNOTE_FAKE_CURL_TRACE="$fake_trace" TALLYNOTE_FAKE_CURL_CONFIG_SEEN="$fake_config_seen" \ TALLYNOTE_FAKE_RELEASE_CREATED="$publisher_tmp/release-created" TALLYNOTE_FAKE_TOKEN='secret-token' \ TALLYNOTE_CURL_BIN="$fake_curl" GITEA_API_URL='https://gitea.example/api/v1' \ GITHUB_REPOSITORY='awaioi/TallyNote' GITEA_TOKEN='secret-token' \ TALLYNOTE_RELEASE_SIGNING_KEY_FILE="$publisher_tmp/key" \ TALLYNOTE_OPENSSL_BIN="$openssl_test_bin" \ "$root/scripts/publish-gitea-release.sh" v1.0.0 "$publisher_tmp" >/dev/null if grep -q 'secret-token' "$fake_trace"; then echo 'token leaked in curl argv' >&2 exit 1 fi test -s "$fake_config_seen" fi fi if [[ -d "$publisher_tmp" ]]; then rm -r "$publisher_tmp" 2>/dev/null || true fi printf '%s\n' 'installer shell tests passed'