Installer Release Flow
Canonical install URL
Use one public install URL in docs, demos, and release notes:
curl -fsSL https://twin.igot.ai/installer.sh | bashThe docs build copies the repository root bootstrapper into the Cloudflare Pages static assets:
cd docsnpm run sync-installerThat command copies:
../install.sh -> docs/public/installer.sh../install.sh -> docs/public/install.shKeep /install.sh as a compatibility alias, but write new user-facing examples with /installer.sh.
What gets compiled
The packaged binary entrypoint is:
installer/cmd/ostwin-installer/main.goFrom a local checkout, compile it directly with:
cd installergo build -o /tmp/ostwin-installer ./cmd/ostwin-installerFor release builds, .goreleaser.yml is the source of truth:
builds: - id: ostwin-installer dir: installer main: ./cmd/ostwin-installer binary: ostwin-installer env: - CGO_ENABLED=0 ldflags: - "-s -w" - "-X main.version={{ .Version }}" - "-X main.commit={{ .Commit }}" - "-X main.date={{ .Date }}" - "-X main.sourceRef={{ .Tag }}"GoReleaser builds darwin, linux, and windows archives for amd64 and arm64. The sourceRef linker value is important: tagged releases make the installer download the matching source archive instead of whatever happens to be on main.
GitHub release workflow
The active installer workflow is .github/workflows/installer-release.yml.
Pull requests and main pushes validate the package:
cd installergo mod verifygo test ./...go build -o /tmp/ostwin-installer ./cmd/ostwin-installercd ..bash -n install.shbash -n build.shgoreleaser checkgoreleaser release --snapshot --clean --skip=publishTag pushes publish the release:
git tag vX.Y.Zgit push origin vX.Y.ZThe tag path runs:
goreleaser release --cleanThat publishes:
install.shchecksums.txtostwin-installer_darwin_amd64.tar.gzostwin-installer_darwin_arm64.tar.gzostwin-installer_linux_amd64.tar.gzostwin-installer_linux_arm64.tar.gzostwin-installer_windows_amd64.tar.gzostwin-installer_windows_arm64.tar.gzThe release install.sh is the bootstrap script. It downloads the matching packaged ostwin-installer archive from GitHub Releases, verifies checksums.txt when available, then delegates to the native .agents/install.sh or .agents/install.ps1 inside the source archive.
Cloudflare Pages consistency
Cloudflare Pages serves files from the docs build output. docs/scripts/sync-installer.mjs copies root install.sh into docs/public/installer.sh and docs/public/install.sh before astro build, so both files are emitted into docs/dist/.
Cloudflare Pages also reads docs/public/_headers during the docs build. The installer endpoints set explicit shell-script content type and a short cache:
/installer.sh Content-Type: text/x-shellscript; charset=utf-8 Cache-Control: public, max-age=300
/install.sh Content-Type: text/x-shellscript; charset=utf-8 Cache-Control: public, max-age=300For OSTwin, the consistency rule is:
- The repository root
install.showns the public bootstrapper source. - The docs build copies that file into Cloudflare Pages static assets.
- Cloudflare serves
https://twin.igot.ai/installer.shdirectly. - The installer itself downloads versioned release assets from GitHub, not from Cloudflare.
This keeps Cloudflare deployment independent from installer release publishing. A docs deploy can update the public bootstrapper copy or prose, but it does not need to rebuild installer binaries.
Docs deployment
The active Cloudflare Pages deployment workflow is:
.github/workflows/deploy-docs.ymlIt runs on main pushes that touch install.sh, docs/**, or the deploy workflow itself, and it can also be started with workflow_dispatch.
The workflow requires these repository secrets:
CLOUDFLARE_API_TOKENCLOUDFLARE_ACCOUNT_IDThe deploy command should remain:
cd docsnpm install --package-lock=falsenpm run buildnpx wrangler pages deploy dist --project-name=ostwin-docsThe docs project already has wrangler as a dev dependency, and docs/wrangler.toml sets the Pages output directory to dist. The workflow uses npm install --package-lock=false because this repo does not currently include docs/package-lock.json; switch to npm ci only after adding and maintaining that lockfile.
Release checklist
Before tagging:
cd installergo test ./...go build -o /tmp/ostwin-installer ./cmd/ostwin-installercd ..bash -n install.shbash -n .agents/install.shcd docsnpm install --package-lock=falsenpm run buildAfter tagging:
- Confirm
.github/workflows/installer-release.ymlcompleted. - Confirm the release includes
install.sh,checksums.txt, and all six platform archives. - Confirm the public URL resolves:
curl -I https://twin.igot.ai/installer.shcurl -fsSL https://twin.igot.ai/installer.sh | bash -s -- --dry-run --yesIf the public URL returns 404, deploy the docs site or verify that the Cloudflare Pages custom domain is attached to the project that contains docs/public/installer.sh in its build output.
If the GitHub release URL returns 404, the latest release does not have installer assets. Tag a new release after .github/workflows/installer-release.yml is green. The Cloudflare bootstrapper URL can still work because it serves the root install.sh copy directly.