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
65 changes: 59 additions & 6 deletions docs/guides/candidate-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ latest-version lookup or automatic updater.
By default installation creates a private, current-user-owned mode-`0700`
`~/.hack-next` directory. Each immutable version directory contains a complete
bundle, its own `native-home`, and its own `cli-home`. An installation receipt binds
the bundle hashes and both homes' filesystem identities. Stable Homebrew `hack`,
its links, and `~/.hack` are untouched. No shell profile or PATH is edited.
the bundle hashes and both homes' filesystem identities. Installation leaves stable
Homebrew `hack`, its links, and `~/.hack` untouched; only an explicit
[Compose test](#test-both-backends) uses your own Hack home. No shell profile or
PATH is edited.

Run the candidate explicitly:

Expand All @@ -41,9 +43,13 @@ Run the candidate explicitly:
python3 "$HOME/.hack-next/manager.py" --root "$HOME/.hack-next" status
```

`hack-next` selects the adjacent verified native executor and the selected version's
private native and CLI homes. These settings override inherited installation
selectors, including `HACK_GLOBAL_CONFIG_PATH`. Explicit native project adaptation,
By default `hack-next` uses the native backend. It selects the adjacent verified
native executor and the selected version's private native and CLI homes. These
settings override inherited installation selectors, including `HACK_NATIVE_BINARY`,
`HACK_NATIVE_HOME`, `HACK_HOME` and `HACK_GLOBAL_CONFIG_PATH`. To run the same
verified candidate on the Compose backend, set `HACK_RUNTIME_BACKEND=compose`; see
[Test both backends](#test-both-backends). Any other non-empty value refuses before
the candidate starts. Explicit native project adaptation,
dependency, AWS profile, routing, and source-sharing settings still reach the CLI.
Arguments, standard input and output, exit codes, and signals reach the selected
candidate. Concurrent observation and stop commands can accompany a foreground
Expand All @@ -63,7 +69,54 @@ Provider setup and actual application acceptance remain separate steps. Installa
does not install the provider, boot a VM, enroll a project, move volumes, or activate
system DNS or trust. Follow [native candidate preparation](native-candidate.md)
using the selected bundle and its `native-home`. Candidate `hack-next update`
retains the existing refusal; use the channel manager's explicit upgrade command.
retains the existing refusal on both backends; use the channel manager's explicit
upgrade command.

## Test both backends

The same selected candidate can run on either backend. You do not need to call the
bundled `hack-cli` directly or create a private `HACK_HOME` to test Compose.

| Backend | Command | Hack home | Routing, DNS and TLS |
| --- | --- | --- | --- |
| Native (default) | `"$HOME/.hack-next/bin/hack-next" up --path /absolute/project` | The version's private `cli-home` and `native-home` | The candidate's native routing, set up through [native candidate preparation](native-candidate.md) |
| Compose | `HACK_RUNTIME_BACKEND=compose "$HOME/.hack-next/bin/hack-next" up --path /absolute/project` | Your own `HACK_HOME` (default `~/.hack`) and `HACK_GLOBAL_CONFIG_PATH` | Your existing global Caddy, CoreDNS and local CA |

With `HACK_RUNTIME_BACKEND=compose`, `hack-next` verifies the selected bundle as
usual and then runs its `hack-cli` on the Compose backend. It keeps your Hack home,
so `https://<project>.hack` routes, container DNS and CA trust use the global
services that `hack global install` already set up. It removes `HACK_NATIVE_BINARY`
and `HACK_NATIVE_HOME` so native selections cannot reach Compose children. The
candidate's private homes are not used. Compose `up` still uses the configurable
Compose startup budget; a cold image build can need a larger
`HACK_COMPOSE_STARTUP_TIMEOUT_MS`.

Compose testing shares state with stable Hack:

- The candidate reads and writes the same project registry, global configuration
and project `.hack/` files as stable `hack`. Stable v4 uses an older registry
lock protocol, so do not run stable `hack` and the candidate against the same
home at the same time. Stop one before you start the other.
- `hack-next` does not set up the shared global services for you. Install or
change them with stable `hack global` commands.
- To test Compose in isolation instead, set `HACK_HOME` to a separate private
directory. That home has no global services, so routing, container DNS and CA
trust are not tested.

Leave `HACK_RUNTIME_BACKEND` unset, or set it to `native`, to return to the native
default. A shell that exported `HACK_RUNTIME_BACKEND=compose` for other work also
selects Compose for `hack-next`; unset it before native testing.

Backend selection needs the current channel manager. The manager released with
`5.0.0-next.3` through `5.0.0-next.5` always selects native, and ignores
`HACK_RUNTIME_BACKEND=compose`. Run the reviewed `upgrade-manager` operation below
once to receive backend selection. This works for every retained bundle, including
`5.0.0-next.5`. Candidate CLIs from `5.0.0-next.5` and earlier refuse `update` only
on the native backend; do not run `hack-next update` on Compose with those bundles.

The bundled `hack-v5` entrypoint follows the same rule for manual bundles:
unset selects native and requires `HACK_NATIVE_HOME`; `compose` needs no native
home and uses your own Hack home.

## Upgrade and return to a retained candidate

Expand Down
10 changes: 8 additions & 2 deletions docs/guides/native-candidate.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,14 @@ export HACK_NATIVE_HOME=/absolute/private/candidate-home
```

Keep the bundle together and invoke this entrypoint by its full path (do not copy
or symlink only the script). It selects the adjacent executor even if inherited
backend variables select another binary. It preserves arguments, exit codes and
or symlink only the script). With `HACK_RUNTIME_BACKEND` unset (or `native`) it
selects the native backend and the adjacent executor, even if an inherited
`HACK_NATIVE_BINARY` selects another binary. An explicit
`HACK_RUNTIME_BACKEND=compose` runs the same bundled CLI on the Compose backend
with your own Hack home and existing global routing services. It needs no
`HACK_NATIVE_HOME` and removes the native selectors. Other values refuse with exit
code 64. The [candidate installer guide](https://github.com/hack-dance/hack/blob/next/docs/guides/candidate-install.md#test-both-backends)
describes Compose testing and its shared-state limits. It preserves arguments, exit codes and
signals through `exec`, and never replaces the installed `hack`. Project-specific
native adaptation, dependency and routing selections remain explicit; this entrypoint
does not prepare a provider or migrate existing projects. An unversioned local
Expand Down
10 changes: 8 additions & 2 deletions docs/guides/v5-dogfooding.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ compatibility, lower resource use or whole-product readiness.

The [version-pinned candidate installer](candidate-install.md) provides `hack-next`
alongside stable `hack`. It does not replace Homebrew Hack, edit PATH, convert
projects, move volumes, enable DNS or trust, or start a VM. Candidate homes and
stable state are separate. Do not alias `hack` to `hack-next` as a rollout step.
projects, move volumes, enable DNS or trust, or start a VM. On the default native
backend, candidate homes and stable state are separate; an explicit Compose test
uses your own Hack home. Do not alias `hack` to `hack-next` as a rollout step.

Each candidate upgrade starts fresh native and CLI homes. Retained bundles keep
their original homes; selecting another version does not migrate application data.
Expand All @@ -35,6 +36,11 @@ These commands do not establish that an arbitrary v4 project can run natively.
the [candidate guide](native-candidate.md), with admitted storage and an explicitly
approved source share and network policy. A refused guard is a result to inspect,
not permission to change host security, erase evidence or raise resource limits.
Test the backend the project will use. `hack-next` runs native by default;
`HACK_RUNTIME_BACKEND=compose hack-next` runs the same candidate on Compose with
your own Hack home, so global routing, DNS and CA trust are checked too. See
[Test both backends](candidate-install.md#test-both-backends), including why
stable `hack` must not run against the same home at the same time.
3. Exercise the project's supported command form: foreground start, readiness,
status, finite logs, noninteractive exec, retaining stop and restart. Exercise
one-off run only where admitted. For an authored project, check generated public
Expand Down
33 changes: 27 additions & 6 deletions scripts/hack-v5.sh
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
#!/bin/sh
# Relocatable frontend for an explicitly selected native candidate home.
# Relocatable frontend for an explicitly selected candidate bundle.
#
# Backend selection: an unset or empty HACK_RUNTIME_BACKEND selects the native
# runtime with this bundle's adjacent executor and the explicit private
# HACK_NATIVE_HOME. An explicit HACK_RUNTIME_BACKEND=compose runs this bundle's
# CLI on the Compose backend with the caller's own Hack home (HACK_HOME, or
# ~/.hack when unset); native selectors are removed so they cannot leak into
# Compose children. Any other value refuses rather than guessing.
set -eu
bundle=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
case "${HACK_NATIVE_HOME:-}" in
/*) ;;
*) echo "Set HACK_NATIVE_HOME to an existing private candidate home (absolute path)." >&2; exit 64 ;;
backend=${HACK_RUNTIME_BACKEND:-native}
case "$backend" in
native|compose) ;;
*) echo "HACK_RUNTIME_BACKEND must be native or compose (unset selects native)." >&2; exit 64 ;;
esac
if [ "$backend" = native ]; then
case "${HACK_NATIVE_HOME:-}" in
/*) ;;
*) echo "Set HACK_NATIVE_HOME to an existing private candidate home (absolute path)." >&2; exit 64 ;;
esac
fi
if [ ! -x "$bundle/hack-cli" ] || [ ! -x "$bundle/hack-native" ]; then
echo "Incomplete candidate bundle: hack-cli and hack-native are required." >&2
exit 69
fi
export HACK_RUNTIME_BACKEND=native
export HACK_NATIVE_BINARY="$bundle/hack-native"
# Candidate CLIs refuse self-update on either backend.
export HACK_CANDIDATE_ENTRYPOINT=hack-v5
if [ "$backend" = native ]; then
export HACK_RUNTIME_BACKEND=native
export HACK_NATIVE_BINARY="$bundle/hack-native"
else
unset HACK_NATIVE_BINARY HACK_NATIVE_HOME
export HACK_RUNTIME_BACKEND=compose
fi
exec "$bundle/hack-cli" "$@"
29 changes: 26 additions & 3 deletions scripts/install-prerelease.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
"d39e77623be1876567e9db66c468e8da96067aeac0d88089df28efc11f850ef8",
"e2e2bcbacf58b21d1086047c94727e5e814776384439700adcf5aabe5c49c2fa",
})
# Launch backends. Unset or empty selects native; anything else refuses.
BACKENDS = frozenset(("native", "compose"))
MANAGER_UPGRADE = ".manager-upgrade.json"


Expand Down Expand Up @@ -782,9 +784,24 @@ def verify_version(self, version, checksum):
"Candidate home identity changed.")
return directory

def environment(self, version):
def environment(self, version, backend="native"):
"""Child environment for one launch.

Native pins this version's executor and both private homes, overriding
inherited installation selectors. Compose is an explicit caller choice:
it keeps the caller's HACK_HOME and HACK_GLOBAL_CONFIG_PATH so the
existing global Caddy/CoreDNS and local CA apply, and removes native
selectors. Either way the CLI knows it is a candidate and refuses
self-update.
"""
directory = self.root / "versions" / version
environment = dict(os.environ)
environment["HACK_CANDIDATE_ENTRYPOINT"] = "hack-next"
if backend == "compose":
environment.pop("HACK_NATIVE_BINARY", None)
environment.pop("HACK_NATIVE_HOME", None)
environment["HACK_RUNTIME_BACKEND"] = "compose"
return environment
environment.pop("HACK_GLOBAL_CONFIG_PATH", None)
environment.update({"HACK_RUNTIME_BACKEND": "native",
"HACK_NATIVE_BINARY": str(directory / "bundle/hack-native"),
Expand Down Expand Up @@ -901,15 +918,21 @@ def install(self, version, archive=None, checksum=None, upgrade=False):
remove_created_stage(stage, created)

def run(self, arguments):
backend = os.environ.get("HACK_RUNTIME_BACKEND") or "native"
require(backend in BACKENDS,
"HACK_RUNTIME_BACKEND must be native or compose (unset selects native).")
version = self.state["selected"]
require(version is not None, "No candidate selected. Use your ordinary stable hack command.")
directory = self.verify_version(version, self.state["installed"][version])
verify_signatures(directory / "bundle")
# Shared launcher locks permit ps/down alongside foreground up, while an
# exclusive selection switch refuses any in-flight launcher. Background
# runtime state is gated by the owning executor on later switches.
child = subprocess.Popen([str(directory / "bundle/hack-v5"), *arguments],
env=self.environment(version))
# Compose runs the verified CLI directly: retained bundles' hack-v5
# entrypoints always select native and would ignore the choice.
entrypoint = "bundle/hack-cli" if backend == "compose" else "bundle/hack-v5"
child = subprocess.Popen([str(directory / entrypoint), *arguments],
env=self.environment(version, backend))
handlers = {}
def forward(signum, _frame):
child.send_signal(signum)
Expand Down
9 changes: 7 additions & 2 deletions src/commands/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,13 @@ const handleUpdate: CommandHandlerFor<Spec> = async ({
ctx,
args,
}): Promise<number> => {
// The native launcher runs hack-cli; PATH fallback can select the stable install.
if (process.env.HACK_RUNTIME_BACKEND === "native") {
// Candidate launchers run hack-cli; PATH fallback can select the stable install.
// `hack-next` and `hack-v5` mark their children on both backends, because an
// explicit Compose selection no longer carries the native backend marker.
if (
process.env.HACK_RUNTIME_BACKEND === "native" ||
(process.env.HACK_CANDIDATE_ENTRYPOINT ?? "") !== ""
) {
return writeResult({
json: args.options.json === true,
result: {
Expand Down
Loading
Loading