From 785569abdd0042479272924ea2eb10ebceadfacb Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Tue, 13 Oct 2026 13:35:30 -0400 Subject: [PATCH] Updates to hello world. --- .gitignore | 1 + Makefile | 5 ++- README.md | 77 +++++++++++++++++++++++++++++++++---------- catalog.toml | 11 +++++++ hello-world/README.md | 10 +++++- tools/check.py | 22 ++++++++++++- tools/package.py | 71 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 tools/package.py diff --git a/.gitignore b/.gitignore index 349a867..6ee7bce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /hello-world/bin/ __pycache__/ *.pyc +/dist/ diff --git a/Makefile b/Makefile index a0f09d4..2fc9bf0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ GOCACHE ?= /tmp/cthulhu-go-cache export GOCACHE -.PHONY: build check +.PHONY: build check package build: go build -buildvcs=false -mod=readonly -o hello-world/bin/hello-world ./hello-world/src @@ -10,3 +10,6 @@ check: build go vet ./... python3 -B tools/check.py +package: + python3 -B tools/package.py + diff --git a/README.md b/README.md index 055684d..966652d 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ Repository material and Hello World are licensed under GPL-3.0-or-later; see `repository.toml` identifies this source. `catalog.toml` explicitly lists its Tentacles, each in a direct top-level folder with its own `tentacle.toml`. -The catalog currently contains discovery metadata, with no release artifacts. -The proposed source URL is -`https://git.stormux.org/storm/cthulhu-tentacles`; it becomes usable after the -maintainer creates and publishes the Git repository. +The catalog lists verified native packages for Linux amd64 and arm64. Prepared +release artifacts live in `dist/`; they become downloadable only after publication. +The discovery source URL is +`https://git.stormux.org/storm/cthulhu-tentacles`. ## Build and check @@ -29,20 +29,63 @@ Checks validate the TOML metadata, registration and suggested bindings, and invoke Hello World through the actual Cthulhu-Go host. They produce no speech or Braille and leave the running reader alone. -## Discovery and the upcoming installation workflow +## Repository installation -After publishing this repository, open global Cthulhu Preferences → Tentacles -→ Add repository and enter its HTTPS Git URL. Its tab should list Hello World, -version 0.1.0, with the description from its manifest. +With the updated reader, open global Cthulhu Preferences → Tentacles → Add +repository and enter the HTTPS Git URL above. Refresh an already added source. +Select Hello World and press Space to review its source/version and requested +speech/Braille access, then **Install and enable**. **Install** instead downloads +and saves the package disabled. The reader verifies the catalog's SHA-256 digest, +package identity and native architecture before saving a launch entry. Catalogs +without a published compatible release show a clear message and remain unchecked. +No source builds or install scripts run during this workflow. Resource limits +do not make untrusted same-user programs safe. -The current reader validates and displays repository catalogs. It does not -yet install or build a Tentacle from a catalog. Manifest launch/runtime/API -fields document this package for the next installation slice; the current -discovery parser does not interpret them. There is no invented download URL, -release digest, or automatic installation step here. +After installation, refresh status until Active and use Cthulhu+Alt+Shift+H. +Disable retains the package; re-enabling and reader startup use installed files +without network access. Removing the source retains installed Tentacles. Removing +a managed Tentacle stops it and deletes its package files after shutdown. +Package updates are not implemented in this slice. See the +[Hello World live checks](hello-world/README.md#live-verification). -For local execution before installation is implemented, see the explicit -[Hello World launch procedure](hello-world/README.md). Future publishing should -add immutable artifacts and verified digests only once the reader has a defined -installation contract. +## Prepare and publish packages +```sh +make check +make package +``` + +`tools/package.py` cross-builds statically linked native binaries for Linux amd64 +and arm64 from the matching adjacent SDK. It creates deterministic archives +containing `tentacle.toml`, `bin/hello-world`, `README.md` and `LICENSE`, plus +`dist/SHA256SUMS` and a generated `dist/catalog.toml`. Go builds use readonly +module metadata, trim paths and omit VCS stamping. To build one architecture: + +```sh +python3 -B tools/package.py --architecture amd64 +``` + +Validate the actual host-architecture package through Cthulhu-Go's installer +and command host, without changing the running reader: + +```sh +cd ../cthulhu-go +CTHULHU_TENTACLE_RELEASE_ROOT=../cthulhu-tentacles GOCACHE=/tmp/cthulhu-go-cache go test ./internal/tentacle -run '^TestBuiltHelloWorldPackageThroughHost$' -count=1 -v +``` + +Before publication, review both repositories' changes and contributing guides. +Commit the reviewed source with the matching SDK available; select that source +revision for the `hello-world-v0.1.0` release. In the Git hosting release page, +upload `dist/hello-world-0.1.0-linux-amd64.tar.gz`, +`dist/hello-world-0.1.0-linux-arm64.tar.gz` and `dist/SHA256SUMS`. Publish and verify +the download URLs and digests **before** publishing the matching catalog change. +Copy the reviewed `dist/catalog.toml` to `catalog.toml`, review the diff, and +submit/push it explicitly. A release filename/version must never be reused for +changed content; bump the version before preparing a later release. + +The package command creates local artifacts only. It does not create commits, +releases, tags, uploads or pushes. Prepared catalog changes are not evidence that +the corresponding release is already available. Live shortcut, speech, Braille, +lock/handoff and interruption checks remain necessary after publication and an +explicit reader restart. For source-only local testing, the development launch +procedure remains in [Hello World](hello-world/README.md#local-launch). diff --git a/catalog.toml b/catalog.toml index 16c84ce..a2ed72d 100644 --- a/catalog.toml +++ b/catalog.toml @@ -4,3 +4,14 @@ format-version = 1 id = "hello-world" manifest = "hello-world/tentacle.toml" +[[tentacles.releases]] +platform = "linux" +architecture = "amd64" +url = "https://git.stormux.org/storm/cthulhu-tentacles/releases/download/hello-world-v0.1.0/hello-world-0.1.0-linux-amd64.tar.gz" +sha256 = "2bad533bc217a5b1bd374115f6151da2a1f6e4ccdbdc93528a5a783f0518f8cd" + +[[tentacles.releases]] +platform = "linux" +architecture = "arm64" +url = "https://git.stormux.org/storm/cthulhu-tentacles/releases/download/hello-world-v0.1.0/hello-world-0.1.0-linux-arm64.tar.gz" +sha256 = "9ef956a04fb1fa98ac01b04fadc122f92b77d708440e0438a7451da62078a9d4" diff --git a/hello-world/README.md b/hello-world/README.md index 356f8e3..39d845f 100644 --- a/hello-world/README.md +++ b/hello-world/README.md @@ -20,6 +20,14 @@ Hello World requests presentation access only. It receives no focused text, uses no network services, and stores no settings. Source and distribution are licensed under GPL-3.0-or-later; see [LICENSE](LICENSE). +## Repository installation + +Refresh the source in global Preferences → Tentacles. Space on Hello World +opens first-use review and installs/enables the package after acceptance. +**Install** saves it disabled instead. The release must be published at the +catalog URL; building locally alone does not make it downloadable. Refresh +status until Active before trying the shortcut. + ## Local launch Run `make build` from the repository root. Rebuild Cthulhu-Go with the matching @@ -72,7 +80,7 @@ Resource limits do not make untrusted same-user programs safe. 5. Check speech off/beeps and disabled Braille settings. Hello World must respect them. Remap or occupy the suggested binding and check core/user precedence. -Audible speech, physical shortcuts, actual display output and the eventual +Audible speech, physical shortcuts, actual display output and the repository-installation workflow require these live checks; protocol tests do not establish them. diff --git a/tools/check.py b/tools/check.py index 1e8ec3f..a7709db 100644 --- a/tools/check.py +++ b/tools/check.py @@ -3,11 +3,13 @@ """Check the repository and the built Tentacle without running a reader.""" import json +import hashlib import os from pathlib import Path import select import subprocess import tomllib +from urllib.parse import urlparse def require(condition, message): @@ -27,7 +29,8 @@ def check_metadata(repoRoot): "Unsupported metadata format") require(repository["id"] == "storm.cthulhu-tentacles" and repository["name"], "Repository identity is missing") - require(catalog["tentacles"] == [{"id": "hello-world", "manifest": "hello-world/tentacle.toml"}], + require(len(catalog["tentacles"]) == 1 and catalog["tentacles"][0]["id"] == "hello-world" + and catalog["tentacles"][0]["manifest"] == "hello-world/tentacle.toml", "Catalog must list exactly Hello World") manifest = read_toml(repoRoot / catalog["tentacles"][0]["manifest"]) require(manifest["format-version"] == 1 and manifest["id"] == "hello-world" @@ -38,6 +41,23 @@ def check_metadata(repoRoot): require((repoRoot / "LICENSE").is_file() and (repoRoot / "hello-world/LICENSE").is_file(), "Repository and Tentacle licenses are required") + seen = set() + for release in catalog["tentacles"][0].get("releases", []): + key = (release["platform"], release["architecture"]) + require(key not in seen and key[0] == "linux" and key[1] in ("amd64", "arm64"), + "Invalid or duplicate release target") + seen.add(key) + parsed = urlparse(release["url"]) + require(parsed.scheme == "https" and parsed.hostname and not parsed.username + and not parsed.fragment, "Release URL must be HTTPS without credentials") + digest = release["sha256"] + require(len(digest) == 64 and all(character in "0123456789abcdef" for character in digest), + "Invalid SHA-256 digest") + artifactPath = repoRoot / "dist" / parsed.path.rsplit("/", 1)[-1] + if artifactPath.exists(): + require(hashlib.sha256(artifactPath.read_bytes()).hexdigest() == digest, + "Prepared package does not match its catalog digest") + def check_registration(programPath): with subprocess.Popen([str(programPath)], stdin=subprocess.PIPE, diff --git a/tools/package.py b/tools/package.py new file mode 100644 index 0000000..081ea8a --- /dev/null +++ b/tools/package.py @@ -0,0 +1,71 @@ +# Copyright (c) 2026 Storm Dragon +# SPDX-License-Identifier: GPL-3.0-or-later +"""Build reproducible Hello World release artifacts and a catalog for review.""" + +import argparse +import gzip +import hashlib +import io +import json +import os +from pathlib import Path +import subprocess +import tarfile +import tomllib + + +def build_package(repoRoot, architecture, version): + distRoot = repoRoot / "dist" + distRoot.mkdir(exist_ok=True) + programPath = distRoot / f"hello-world-linux-{architecture}" + environment = dict(os.environ, CGO_ENABLED="0", GOOS="linux", GOARCH=architecture) + environment.setdefault("GOCACHE", "/tmp/cthulhu-go-cache") + subprocess.run(["go", "build", "-buildvcs=false", "-mod=readonly", "-trimpath", + "-ldflags=-s -w", "-o", str(programPath), "./hello-world/src"], + cwd=repoRoot, env=environment, check=True) + artifactPath = distRoot / f"hello-world-{version}-linux-{architecture}.tar.gz" + with artifactPath.open("wb") as output: + with gzip.GzipFile(filename="", mode="wb", fileobj=output, mtime=0) as compressed: + with tarfile.open(fileobj=compressed, mode="w", format=tarfile.USTAR_FORMAT) as archive: + for name, data, mode in [ + ("tentacle.toml", (repoRoot / "hello-world/tentacle.toml").read_bytes(), 0o644), + ("README.md", (repoRoot / "hello-world/README.md").read_bytes(), 0o644), + ("LICENSE", (repoRoot / "hello-world/LICENSE").read_bytes(), 0o644), + ("bin/hello-world", programPath.read_bytes(), 0o755), + ]: + member = tarfile.TarInfo(name) + member.size, member.mode, member.mtime = len(data), mode, 0 + archive.addfile(member, io.BytesIO(data)) + digest = hashlib.sha256(artifactPath.read_bytes()).hexdigest() + return artifactPath, digest + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--architecture", nargs="+", choices=["amd64", "arm64"], default=["amd64", "arm64"]) + args = parser.parse_args() + if len(set(args.architecture)) != len(args.architecture): + parser.error("Architectures must not be repeated") + repoRoot = Path(__file__).resolve().parents[1] + manifest = tomllib.loads((repoRoot / "hello-world/tentacle.toml").read_text()) + version = manifest["version"] + if not version or any(character not in "0123456789abcdefghijklmnopqrstuvwxyz.-" for character in version): + raise ValueError("Version must be safe for release filenames") + lines = ["format-version = 1", "", "[[tentacles]]", 'id = "hello-world"', + 'manifest = "hello-world/tentacle.toml"'] + checksums = [] + for architecture in args.architecture: + artifactPath, digest = build_package(repoRoot, architecture, version) + releaseURL = f"https://git.stormux.org/storm/cthulhu-tentacles/releases/download/hello-world-v{version}/{artifactPath.name}" + lines += ["", "[[tentacles.releases]]", 'platform = "linux"', + f"architecture = {json.dumps(architecture)}", f"url = {json.dumps(releaseURL)}", + f"sha256 = {json.dumps(digest)}"] + checksums.append(f"{digest} {artifactPath.name}") + print(f"Prepared {artifactPath.name}: {digest}") + (repoRoot / "dist/catalog.toml").write_text("\n".join(lines) + "\n") + (repoRoot / "dist/SHA256SUMS").write_text("\n".join(checksums) + "\n") + print("Prepared dist/catalog.toml and dist/SHA256SUMS for review. Nothing was published.") + + +if __name__ == "__main__": + main()