Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/check-versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 6 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` 检查所有写了版本号的地方是否一致。

## 发版

Expand Down
31 changes: 17 additions & 14 deletions docs/decisions/024-one-command-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
71 changes: 71 additions & 0 deletions docs/decisions/027-installer-downloads-only-from-github.md
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 20 additions & 7 deletions docs/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 .
Expand Down Expand Up @@ -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
<https://clawhub.ai/futrixdev/skills/envrelay/security-audit>.

```bash
openclaw skills verify @futrixdev/envrelay
```
Expand Down
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
5 changes: 1 addition & 4 deletions skills/envrelay/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down
37 changes: 13 additions & 24 deletions skills/envrelay/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -54,9 +55,8 @@ Options:
-h, --help show this help

Run from inside an installed skill (sh <skill dir>/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
}

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading