diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 35d14a1..f86c174 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "envrelay", - "version": "1.0.1", + "version": "1.0.2", "description": "Move a development environment to a new machine: back up dotfiles, credentials, git repositories, AI coding agent state and installed software into one passphrase-encrypted file, then restore it step by step.", "author": { "name": "FutrixDev", diff --git a/.github/scripts/check-versions.sh b/.github/scripts/check-versions.sh index 462be76..36d29c2 100755 --- a/.github/scripts/check-versions.sh +++ b/.github/scripts/check-versions.sh @@ -33,7 +33,7 @@ version=$(sed -n 's/^version = "\([^"]*\)"$/\1/p' Cargo.toml | head -n 1) # second way: this is the path a user's agent takes. home=$(mktemp -d) trap 'rm -rf "$home"' EXIT -plan=$(HOME="$home" ENVRELAY_DOWNLOAD_URL='' ENVRELAY_BIN_DIR='' \ +plan=$(HOME="$home" ENVRELAY_BIN_DIR='' \ sh skills/envrelay/install.sh --dry-run --bin-only --no-modify-path 2>&1) || { printf '%s\n' "$plan" >&2 exit 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f9fe59..301c323 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,6 +70,6 @@ jobs: - name: shellcheck run: | shellcheck --version - shellcheck skills/envrelay/install.sh .github/scripts/*.sh tests/installer.sh + shellcheck skills/envrelay/install.sh .github/scripts/*.sh tests/installer.sh tests/stubs/curl - name: The versions agree run: sh .github/scripts/check-versions.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19a56de..7105e97 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,7 +119,9 @@ jobs: if-no-files-found: error # Install from the packaged release, as a user would, before anything is - # published. + # published. install.sh downloads only from the release on GitHub, which + # does not exist yet: tests/stubs/curl, first on PATH, serves dist in its + # place. verify: name: verify ${{ matrix.os }} needs: package @@ -130,6 +132,7 @@ jobs: matrix: os: [macos-latest, ubuntu-latest, ubuntu-24.04-arm] steps: + - uses: actions/checkout@v7 - uses: actions/download-artifact@v8 with: name: dist @@ -138,7 +141,8 @@ jobs: run: | home="$RUNNER_TEMP/home" mkdir "$home" - HOME="$home" SHELL=/bin/bash ENVRELAY_DOWNLOAD_URL="$PWD/dist" sh dist/install.sh + HOME="$home" SHELL=/bin/bash PATH="$PWD/tests/stubs:$PATH" RELEASE_DIR="$PWD/dist" \ + sh dist/install.sh "$home/.local/bin/envrelay" --version test -f "$home/.agents/skills/envrelay/SKILL.md" test -L "$home/.claude/skills/envrelay" diff --git a/Cargo.lock b/Cargo.lock index 7d52169..07f967a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -424,7 +424,7 @@ dependencies = [ [[package]] name = "envrelay" -version = "1.0.1" +version = "1.0.2" dependencies = [ "age", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 42a1223..399d353 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ [package] name = "envrelay" -version = "1.0.1" +version = "1.0.2" edition = "2024" rust-version = "1.97.0" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 9c9dc19..7f9c17c 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,7 @@ sh tests/installer.sh sh .github/scripts/check-versions.sh ``` -`tests/installer.sh` runs the installer against a release packaged from this checkout, with a throwaway `HOME` per scenario, so it needs the release build first. `SH=dash sh tests/installer.sh` runs the installer under another shell; CI runs it under sh, dash, bash and zsh. `check-versions.sh` checks that the version is the same everywhere it is written. +`tests/installer.sh` runs the installer against a release packaged from this checkout, with a throwaway `HOME` per scenario, so it needs the release build first. The installer downloads only from GitHub, so a stand-in for curl, [`tests/stubs/curl`](tests/stubs/curl), serves that release in GitHub's place. `SH=dash sh tests/installer.sh` runs the installer under another shell; CI runs it under sh, dash, bash and zsh. `check-versions.sh` checks that the version is the same everywhere it is written. ## Releasing diff --git a/README.zh-CN.md b/README.zh-CN.md index 19d6e72..98180b0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -225,7 +225,7 @@ sh tests/installer.sh sh .github/scripts/check-versions.sh ``` -`tests/installer.sh` 用当前 checkout 打出来的 release 跑安装器,每个场景一个一次性的 `HOME`,所以要先 build release。`SH=dash sh tests/installer.sh` 换一个 shell 跑安装器;CI 在 sh、dash、bash、zsh 下各跑一遍。`check-versions.sh` 检查所有写了版本号的地方是否一致。 +`tests/installer.sh` 用当前 checkout 打出来的 release 跑安装器,每个场景一个一次性的 `HOME`,所以要先 build release。安装器只从 GitHub 下载,所以由一个替身 curl([`tests/stubs/curl`](tests/stubs/curl))代替 GitHub 提供这个 release。`SH=dash sh tests/installer.sh` 换一个 shell 跑安装器;CI 在 sh、dash、bash、zsh 下各跑一遍。`check-versions.sh` 检查所有写了版本号的地方是否一致。 ## 发版 diff --git a/docs/decisions/024-one-command-install.md b/docs/decisions/024-one-command-install.md index 9035b1d..f41709d 100644 --- a/docs/decisions/024-one-command-install.md +++ b/docs/decisions/024-one-command-install.md @@ -4,7 +4,8 @@ Date: 2026-09-24 Status: accepted Extends: ADR-022 (deterministic mechanics scripts) Amended by: ADR-025 (the site moves to a repository of its own), ADR-026 -(frontmatter and manifests that awesome-copilot accepts) +(frontmatter and manifests that awesome-copilot accepts), ADR-027 (the +installer downloads only from EnvRelay's release on GitHub) ## Context @@ -67,9 +68,11 @@ and the arm64 Linux runners and the attestations need a public repository too. - **No sudo, ever.** It refuses to run under sudo and installs into the home directory: the binary into `~/.local/bin` (or `--bin-dir`, `ENVRELAY_BIN_DIR`), the skill into `~/.agents/skills/envrelay`. -- **Everything is downloaded and checked before anything changes.** Each file - must match `SHA256SUMS`; curl is held to HTTPS and TLS 1.2. The new binary is - run once from where it will live, since `/tmp` is noexec on some systems. +- **Everything is downloaded and checked before anything changes**, and only + from this repository's release on GitHub, which nothing can point elsewhere + (ADR-027). Each file must match `SHA256SUMS`; curl is held to HTTPS and TLS + 1.2. The new binary is run once from where it will live, since `/tmp` is + noexec on some systems. - **One real copy of the skill, links for the rest.** `~/.agents/skills` is read by Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, OpenClaw and most other agents. Claude Code gets a symlink in @@ -91,9 +94,9 @@ and the arm64 Linux runners and the attestations need a public repository too. user clicks through; on Linux it prints the distribution's install command for the user to run. - `--dry-run` downloads and changes nothing, and says what would happen. - `ENVRELAY_DOWNLOAD_URL` swaps the GitHub release for a mirror or a local - directory, which is how CI and `tests/installer.sh` install a release that - has not been published. +- Before a release is published, CI and `tests/installer.sh` install it + through `tests/stubs/curl`, a stand-in for curl that serves it in GitHub's + place (ADR-027). **The installer is the one exception to ADR-022's "a script never installs".** It lives at the skill's root, not in `scripts/`, and SKILL.md says it is not @@ -128,13 +131,13 @@ the latest release. scripts and nothing else. The installer and `envrelay` stay behind a prompt. - `compatibility` names what the skill needs to run. - A top-level `clawdis` block declares the required binaries (python3, git), - the operating systems (darwin, linux), the two optional environment - variables the installer reads, and the homepage, and ClawHub's review - compares them with what the code does. v1.0.0 had them in - `metadata.openclaw`, where OpenClaw itself also read them to decide whether - the skill can load. They moved because awesome-copilot's lint refuses a - `metadata` value that is not a string. OpenClaw does not read the new block - (ADR-026). + the operating systems (darwin, linux), the one optional environment variable + the installer reads (`ENVRELAY_BIN_DIR`; two before ADR-027), and the + homepage, and ClawHub's review compares them with what the code does. v1.0.0 + had them in `metadata.openclaw`, where OpenClaw itself also read them to + decide whether the skill can load. They moved because awesome-copilot's lint + refuses a `metadata` value that is not a string. OpenClaw does not read the + new block (ADR-026). - There is no `license` field. ClawHub releases every skill it publishes under MIT-0 and asks for no conflicting license terms in SKILL.md, so the field would be wrong there. The repository's MIT OR Apache-2.0 covers the source, diff --git a/docs/decisions/026-frontmatter-and-manifests-for-awesome-copilot.md b/docs/decisions/026-frontmatter-and-manifests-for-awesome-copilot.md index fb616a8..5517269 100644 --- a/docs/decisions/026-frontmatter-and-manifests-for-awesome-copilot.md +++ b/docs/decisions/026-frontmatter-and-manifests-for-awesome-copilot.md @@ -4,6 +4,8 @@ Date: 2026-09-25 Status: accepted Amends: ADR-024 (one command installs the binary and the skill), ADR-025 (the site moves to a repository of its own) +Amended by: ADR-027 (the installer downloads only from EnvRelay's release on +GitHub) ## Context @@ -49,8 +51,8 @@ No one frontmatter satisfies all of these. ## Decision - **The declarations move to a top-level `clawdis` block**, unchanged: the - required binaries, the operating systems, the two optional environment - variables and the homepage. `metadata` keeps only `version`. + required binaries, the operating systems, the optional environment variables + (one since ADR-027) and the homepage. `metadata` keeps only `version`. - **`plugin.json` at the root is the plugin's Agent Plugins manifest**: the Claude Code one plus the `$schema` that opts into Agent Plugins 1.0. Copilot CLI and VS Code read it with that format's semantics, and it is where diff --git a/docs/decisions/027-installer-downloads-only-from-github.md b/docs/decisions/027-installer-downloads-only-from-github.md new file mode 100644 index 0000000..d1a9ae5 --- /dev/null +++ b/docs/decisions/027-installer-downloads-only-from-github.md @@ -0,0 +1,71 @@ +# ADR-027: The installer downloads only from EnvRelay's release on GitHub + +Date: 2026-09-25 +Status: accepted +Amends: ADR-024 (one command installs the binary and the skill), ADR-026 +(frontmatter and manifests that awesome-copilot accepts) + +## Context + +ADR-024 gave `install.sh` an environment variable that changed where it +downloads from: `ENVRELAY_DOWNLOAD_URL` replaced the GitHub release with a +mirror, or with a local directory holding the release's files. Only the +installer's `--help` and the skill's frontmatter mentioned it, and only two +things ever set it: the release workflow's *verify* jobs, which install a +release before it is published, and `tests/installer.sh`. No mirror exists. + +ClawHub's security audit rated v1.0.0 and v1.0.1 "Review" because of it. Its +one finding (T03, high) is that the variable accepted plain http, while +`SHA256SUMS` came from the same place as the files it checks. Whoever could set +the variable, or answer that http request, chose the binary, and the installer +runs a new binary once (`--version`) before it puts it in place. + +The owner's rule: a variable that only tests use does not belong in what is +released. + +## Decision + +- **`install.sh` downloads only from + `https://github.com/FutrixDev/envrelay-skill/releases`**: the release that + matches the skill beside it, the one `--version` names, or the latest. + Nothing changes that: no variable or option, no http, no `file://` and no + local path. curl stays held to HTTPS and TLS 1.2. +- **The tests replace curl instead.** `tests/stubs/curl`, first on `PATH`, + answers the installer's downloads from a release packaged on the spot. It + refuses a call that is not held to HTTPS and TLS 1.2, or that asks for + anything but this repository's release. `tests/installer.sh` and the + *verify* jobs both use it, so they run the installer users get, calling curl + the way it does for them. +- **The frontmatter's `clawdis` block declares one optional environment + variable**, `ENVRELAY_BIN_DIR`. +- This ships as v1.0.2. + +## Consequences + +- **A mirror or a local copy of a release no longer works.** Anyone who set + `ENVRELAY_DOWNLOAD_URL` now downloads from GitHub. It shipped only in v1.0.0 + and v1.0.1, both released on 2026-09-25. A machine that cannot reach GitHub + cannot use the installer. +- **No test takes a path that users do not.** Every install the tests make + asks for the same URLs a user's does. +- **The stub has to follow the installer.** A change to how `install.sh` calls + curl fails the tests until the stub accepts it, which is the point: the + flags that hold curl to HTTPS are checked on every run. +- **wget stays untested**, as before: the stub stands in for curl, and every + runner has curl. +- **The checksums still come from the release they check.** They catch a + corrupted or mixed-up download, not a forged release. What the installer + trusts is HTTPS to GitHub; build provenance (`gh attestation verify`) is how + anyone checks that a file came from this repository's release workflow. + +## Alternatives rejected + +- **Keep the variable, https only.** The finding would shrink, but a variable + that only tests use would still ship. +- **Sign `SHA256SUMS` and pin the key in `install.sh`.** It would make a mirror + safe, but no one needs a mirror, and a signing key is one more secret to + keep. +- **Take `--passphrase-file` out of the binary too**, the other thing only + tests use. The owner declined: the skill is the core of the product, and it + is the agent's own judgment, guided by SKILL.md's first rule, that keeps the + passphrase out of its hands. diff --git a/docs/publishing.md b/docs/publishing.md index 83b172e..d03655d 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -141,8 +141,8 @@ works as soon as the repository is public, and the README lists it. `chat.plugins.marketplaces` setting. Factory Droid falls back to `.claude-plugin/marketplace.json` as well (its commands were not checked). -Validate before every release; it passes with no errors or warnings as of -v1.0.1: +Validate before every release, and release only when it reports no errors or +warnings: ```bash claude plugin validate --strict . @@ -281,17 +281,30 @@ To publish: clawhub skill publish ./skills/envrelay --owner futrixdev --name EnvRelay --version 1.0.0 --changelog "First release." --categories operations,development --topics backup,restore,migration,dotfiles,developer-environment --source-repo FutrixDev/envrelay-skill --source-commit "$(git rev-parse HEAD)" --source-ref v1.0.0 --source-path skills/envrelay ``` - For a later release, change the tag, `--version` and `--changelog`. The - upload stays hidden while ClawHub reviews it (`clawhub inspect` shows - `pending.publication`). v1.0.0's scan came back clean within a minute; how - long publication takes after that was not verified. + For a later release, change the tag (in `git checkout` and `--source-ref`), + `--version` and `--changelog`. The upload stays hidden while ClawHub + reviews it (`clawhub inspect` shows `pending.publication`). v1.0.0's scan + came back clean within a minute; how long publication takes after that was + not verified. -4. Check the listing: +4. Check the listing, then the version's security audit. They are separate + verdicts: moderation decides whether the listing is public, and it can be + public (`clean`) while the audit on its page says Review, which asks users + to read the findings before they install. ```bash clawhub inspect @futrixdev/envrelay ``` + ```bash + clawhub inspect @futrixdev/envrelay --version 1.0.0 --json + ``` + + The audit is `version.security`. v1.0.0 and v1.0.1 read `suspicious`, + shown as Review, for the download override that ADR-027 removed. The + findings are on + . + ```bash openclaw skills verify @futrixdev/envrelay ``` diff --git a/plugin.json b/plugin.json index 75a1b8d..74dda70 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "envrelay", - "version": "1.0.1", + "version": "1.0.2", "description": "Move a development environment to a new machine: back up dotfiles, credentials, git repositories, AI coding agent state and installed software into one passphrase-encrypted file, then restore it step by step.", "author": { "name": "FutrixDev", diff --git a/skills/envrelay/SKILL.md b/skills/envrelay/SKILL.md index 618edba..7388184 100644 --- a/skills/envrelay/SKILL.md +++ b/skills/envrelay/SKILL.md @@ -4,7 +4,7 @@ description: Use when backing up, restoring, or migrating a development environm compatibility: Needs macOS or Linux with a terminal the user can type into, python3 3.9 or newer, git, and the envrelay binary, which the skill's own installer adds once the user agrees. allowed-tools: Bash(python3 ${CLAUDE_SKILL_DIR}/scripts/*) metadata: - version: "1.0.1" + version: "1.0.2" clawdis: requires: bins: @@ -14,9 +14,6 @@ clawdis: - name: ENVRELAY_BIN_DIR required: false description: Where install.sh puts the envrelay binary instead of ~/.local/bin. - - name: ENVRELAY_DOWNLOAD_URL - required: false - description: A mirror, or a local directory of release assets, that install.sh downloads from instead of GitHub releases. os: - darwin - linux diff --git a/skills/envrelay/install.sh b/skills/envrelay/install.sh index 67180f6..c7d2416 100755 --- a/skills/envrelay/install.sh +++ b/skills/envrelay/install.sh @@ -9,7 +9,8 @@ # In order, it: # # 1. downloads SHA256SUMS, the envrelay binary for this machine and the skill -# from one release, and refuses any file whose checksum does not match; +# from one release on GitHub, over HTTPS, and refuses any file whose +# checksum does not match; # 2. puts envrelay in ~/.local/bin (or --bin-dir), replacing it atomically; # 3. puts the skill in ~/.agents/skills/envrelay, which Codex, Cursor, Gemini # CLI, OpenCode, Copilot and most other agents read, and symlinks it into @@ -54,9 +55,8 @@ Options: -h, --help show this help Run from inside an installed skill (sh /install.sh), it installs the -release that matches that skill's version rather than the latest one. -ENVRELAY_DOWNLOAD_URL replaces the GitHub release URL with a mirror, or with a -local directory holding the release assets. +release that matches that skill's version rather than the latest one. Every +download comes from https://github.com/FutrixDev/envrelay-skill/releases. EOF } @@ -220,22 +220,13 @@ unlink_one() { } fetch() { - case $1 in - https://* | http://*) - if have curl; then - case $1 in - https://*) curl --proto '=https' --tlsv1.2 -fsSL --retry 3 -o "$2" "$1" ;; - *) curl -fsSL --retry 3 -o "$2" "$1" ;; - esac - elif have wget; then - wget -q -O "$2" "$1" - else - die "downloading EnvRelay needs curl or wget" - fi - ;; - file://*) cp "${1#file://}" "$2" ;; - *) cp "$1" "$2" ;; - esac || die "could not download $1 (is there a published release? --version picks one)" + if have curl; then + curl --proto '=https' --tlsv1.2 -fsSL --retry 3 -o "$2" "$1" + elif have wget; then + wget -q -O "$2" "$1" + else + die "downloading EnvRelay needs curl or wget" + fi || die "could not download $1 (is there a published release? --version picks one)" } verify() { @@ -526,14 +517,12 @@ main() { fi detect_platform - [ -n "$version" ] || [ -n "${ENVRELAY_DOWNLOAD_URL:-}" ] || version=$(skill_version) + [ -n "$version" ] || version=$(skill_version) version=${version#v} case $version in *[!0-9A-Za-z.+-]*) die "not a version: $version" ;; esac - if [ -n "${ENVRELAY_DOWNLOAD_URL:-}" ]; then - base=${ENVRELAY_DOWNLOAD_URL%/} - elif [ -n "$version" ]; then + if [ -n "$version" ]; then base=https://github.com/$REPO/releases/download/v$version else base=https://github.com/$REPO/releases/latest/download diff --git a/tests/installer.sh b/tests/installer.sh index 4791bf1..482fafc 100755 --- a/tests/installer.sh +++ b/tests/installer.sh @@ -1,7 +1,9 @@ #!/bin/sh # End-to-end tests for skills/envrelay/install.sh. The release it installs is # packaged on the spot by .github/scripts/package-release.sh, the same script -# the release workflow runs, and every scenario gets a throwaway HOME. +# the release workflow runs, and every scenario gets a throwaway HOME. The +# installer downloads only from EnvRelay's release on GitHub: tests/stubs/curl, +# first on its PATH, serves the packaged release in GitHub's place. # # tests/installer.sh [BINARY] BINARY: target/release/envrelay # SH=dash tests/installer.sh run the installer under another shell @@ -51,7 +53,7 @@ scenario() { link=$h/.claude/skills/envrelay out=$work/$1.out : >"$out" - url=$work/release + release=$work/release test_path=/usr/bin:/bin test_shell=/bin/zsh } @@ -60,8 +62,8 @@ scenario() { # lacks ~/.local/bin. Sets $status; the output is in $out. run_installer() { set +e - env HOME="$h" SHELL="$test_shell" PATH="$test_path" ZDOTDIR= XDG_CONFIG_HOME= \ - CLAUDE_CONFIG_DIR= ENVRELAY_BIN_DIR= SUDO_USER= ENVRELAY_DOWNLOAD_URL="$url" \ + env HOME="$h" SHELL="$test_shell" PATH="$root/tests/stubs:$test_path" ZDOTDIR= XDG_CONFIG_HOME= \ + CLAUDE_CONFIG_DIR= ENVRELAY_BIN_DIR= SUDO_USER= RELEASE_DIR="$release" \ "$SH" "${installer:-$work/release/install.sh}" "$@" >"$out" 2>&1 status=$? set -e @@ -72,8 +74,8 @@ run_piped() { script=$1 shift set +e - env HOME="$h" SHELL="$test_shell" PATH="$test_path" ZDOTDIR= XDG_CONFIG_HOME= \ - CLAUDE_CONFIG_DIR= ENVRELAY_BIN_DIR= SUDO_USER= ENVRELAY_DOWNLOAD_URL="$url" \ + env HOME="$h" SHELL="$test_shell" PATH="$root/tests/stubs:$test_path" ZDOTDIR= XDG_CONFIG_HOME= \ + CLAUDE_CONFIG_DIR= ENVRELAY_BIN_DIR= SUDO_USER= RELEASE_DIR="$release" \ "$SH" -s -- "$@" <"$script" >"$out" 2>&1 status=$? set -e @@ -116,7 +118,6 @@ check "replaces the whole skill" absent "$skill/scripts/gone_in_this_release.py" check "leaves no staging behind after an upgrade" no_leftovers # Inside an installed skill it asks for the release that matches the skill. -url= installer=$skill/install.sh run_installer --dry-run --bin-only check "asks for the skill's own release" said "/releases/download/v${version#envrelay }" @@ -192,7 +193,6 @@ metadata: name: envrelay --- EOF -url= installer=$link/install.sh run_installer --dry-run --bin-only check "exits 0" status_is 0 @@ -249,6 +249,7 @@ run_installer --dry-run check "exits 0" status_is 0 check "changes nothing" home_empty check "says it is a dry run" said "Dry run" +check "downloads from the latest release on GitHub" said "from https://github.com/FutrixDev/envrelay-skill/releases/latest/download" check "shows where the skill would go" said "would install the skill in $skill" check "shows the link it would make" said "would link $link" check "shows the PATH change" said "would add $h/.local/bin to PATH in $h/.zshrc" @@ -280,7 +281,7 @@ check "keeps a skill another tool installed" [ "$(cat "$skill/SKILL.md")" = thei scenario tampered-binary cp -R "$work/release" "$work/tampered-binary.release" for f in "$work/tampered-binary.release"/envrelay-*-*.tar.gz; do printf x >>"$f"; done -url=$work/tampered-binary.release +release=$work/tampered-binary.release run_installer check "fails" failed_run check "names the mismatch" said "does not match SHA256SUMS" @@ -289,11 +290,20 @@ check "installs nothing" home_empty scenario tampered-skill cp -R "$work/release" "$work/tampered-skill.release" printf x >>"$work/tampered-skill.release/envrelay-skill.tar.gz" -url=$work/tampered-skill.release +release=$work/tampered-skill.release run_installer check "fails" failed_run check "installs nothing, not even the binary" home_empty +scenario missing-asset +cp -R "$work/release" "$work/missing-asset.release" +rm "$work/missing-asset.release/envrelay-skill.tar.gz" +release=$work/missing-asset.release +run_installer +check "fails" failed_run +check "names the download" said "could not download https://github.com/FutrixDev/envrelay-skill/releases/latest/download/envrelay-skill.tar.gz" +check "installs nothing, not even the binary" home_empty + scenario truncated size=$(wc -c <"$work/release/install.sh") for pct in 10 25 50 75 90 99; do diff --git a/tests/stubs/curl b/tests/stubs/curl new file mode 100755 index 0000000..612e340 --- /dev/null +++ b/tests/stubs/curl @@ -0,0 +1,53 @@ +#!/bin/sh +# A stand-in for curl, put first on PATH by tests/installer.sh and by the +# release workflow's verify job. install.sh downloads only from EnvRelay's +# release on GitHub, which does not exist yet when those run; this serves +# RELEASE_DIR, a release packaged by .github/scripts/package-release.sh, in its +# place. It takes only the one way install.sh calls curl, so every install in +# the tests also checks that each download is held to HTTPS and TLS 1.2 and +# asks for EnvRelay's release. A file the release lacks gets curl's answer to a +# 404. +set -eu + +refuse() { + printf 'curl (test stand-in): %s\n' "$*" >&2 + exit 2 +} + +[ -d "${RELEASE_DIR:-}" ] || refuse "RELEASE_DIR is not a directory: ${RELEASE_DIR:-unset}" +proto='' tls=0 out='' url='' +while [ $# -gt 0 ]; do + case $1 in + --proto | --retry | -o) + [ $# -ge 2 ] || refuse "$1 needs a value" + case $1 in + --proto) proto=$2 ;; + -o) out=$2 ;; + esac + shift + ;; + --tlsv1.2) tls=1 ;; + -fsSL) ;; + -*) refuse "unexpected option: $1" ;; + *) + [ -z "$url" ] || refuse "more than one URL: $url $1" + url=$1 + ;; + esac + shift +done +[ "$proto" = =https ] || refuse "not held to HTTPS: $url" +[ "$tls" = 1 ] || refuse "not held to TLS 1.2: $url" +[ -n "$out" ] || refuse "no -o FILE: $url" +releases=https://github.com/FutrixDev/envrelay-skill/releases +case $url in +"$releases"/latest/download/* | "$releases"/download/v*/*) ;; +*) refuse "not a download from EnvRelay's release on GitHub: $url" ;; +esac + +file=$RELEASE_DIR/${url##*/} +if [ ! -f "$file" ]; then + echo 'curl: (22) The requested URL returned error: 404' >&2 + exit 22 +fi +cp "$file" "$out"