Files
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> f1973ddb36 chore: update current ZStack target to v2.2.3
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-04 15:11:09 +08:00

11 KiB

Agent Instructions - ZStack AIOS (GPUStack downstream fork)

This is the combined workspace for our ZStack AIOS product, a downstream fork of GPUStack. It bundles both the frontend and backend repos so agents and contributors work from a single root going forward.

Layout

gpustack-all-lofyer/          <- this repo (tooling only)
├── AGENTS.md                 # workspace-level conventions
├── scripts/
│   ├── bootstrap             # clone the two forks into place
│   ├── build-image           # frontend build + image build + verify
│   ├── verify-image          # smoke test a built image
│   ├── sync-upstream         # bump both forks to a new upstream release
│   └── lib/common.sh         # shared helpers
├── gpustack-ui-lofyer/       # frontend fork  (upstream: gpustack/gpustack-ui)
└── gpustack-lofyer/          # backend  fork  (upstream: gpustack/gpustack)

This repo tracks only the workspace tooling. The two forks live in their own repositories and are not submodules, so each keeps a clean upstream history that can be rebased onto new releases. After cloning this repo, run scripts/bootstrap to lay the forks out at the paths above.

Each sub-repo is an independent git repository with its own history, remotes, and AGENTS.md / CLAUDE.md. Read the sub-repo's own instructions before working in it.

Remotes (origin = private registry, upstream = GitHub):

Repo origin
gpustack-all-lofyer ssh://git@git.digiman.live:11022/root/gpustack-all-lofyer.git
gpustack-ui-lofyer ssh://git@git.digiman.live:11022/root/gpustack-ui.git
gpustack-lofyer ssh://git@git.digiman.live:11022/root/gpustack.git

Workspace scripts

Run these from the workspace root. Every script refuses to proceed unless both forks sit on the same v<version>-lofyer branch, so a build can never mix a frontend and backend from different upstream releases.

scripts/bootstrap [v<version>]   # clone both forks, checkout v<version>-lofyer
scripts/build-image              # npm build + image build + full verification
scripts/build-image --skip-ui-build   # reuse an existing frontend dist/
scripts/build-image --no-verify       # build only
scripts/build-image --push            # push the image after a green build
scripts/build-image --dry-run         # show the plan
scripts/verify-image             # smoke test the current version's image
scripts/verify-image --static-only    # skip booting a container
scripts/sync-upstream --list     # upstream releases available in BOTH repos
scripts/sync-upstream v2.2.3     # bump both forks to v2.2.3
scripts/sync-upstream v2.2.3 --dry-run
scripts/sync-upstream --continue # resume after resolving a cherry-pick conflict

Versioning

Both repos track the same upstream release and keep customizations on a v<upstream-version>-lofyer branch. Frontend and backend are always bumped together to the same upstream tag. Current target: v2.2.3.

Repo Branch Upstream base Upstream repo
gpustack-ui-lofyer v2.2.3-lofyer tag v2.2.3 gpustack/gpustack-ui
gpustack-lofyer v2.2.3-lofyer tag v2.2.3 gpustack/gpustack

Branch rules

  • Never commit customizations to main or to any upstream branch. main mirrors upstream and must stay clean so it can be fast-forwarded/mirrored.
  • All customization work lives on v<upstream-version>-lofyer (e.g. v2.2.1-lofyer), one such branch per upstream release we follow.
  • One upstream version = one -lofyer branch. Do not reuse an old branch for a new upstream version; cut a fresh one from the new tag and re-apply commits.
  • Keep the frontend and backend -lofyer branch names in lockstep (same version suffix) so a customer build always pairs matching branches.
  • Only fast-forward-safe, curated customization commits belong on -lofyer. Prefer small, cherry-pickable commits so the next version bump replays cleanly.

Updating to a new upstream version

Use scripts/sync-upstream, which does the whole bump for both repos:

scripts/sync-upstream --list            # releases available in BOTH upstreams
scripts/sync-upstream v2.2.3 --dry-run  # review the replay plan first
scripts/sync-upstream v2.2.3            # cut branches + replay fork commits
scripts/build-image                     # verify the result end to end
git -C gpustack-ui-lofyer push -u origin v2.2.3-lofyer
git -C gpustack-lofyer   push -u origin v2.2.3-lofyer
# then update the version table + "Current target" above

What it does per repo: fetch upstream, require a clean worktree and that the tag exists in both upstreams, cut v<version>-lofyer from the tag, then cherry-pick our fork-only commits oldest-first. On conflict it stops and tells you exactly what to resolve; afterwards run scripts/sync-upstream --continue, then re-run with the target version to replay the remainder. Nothing is pushed automatically.

Identifying fork-only commits is the subtle part, and the script handles it: git log --cherry-pick --right-only alone is not sufficient, because upstream commits that were rebased or squashed into the new release get a different patch-id and would be replayed a second time. The script therefore also drops any commit that an upstream/* branch or any tag contains. If you ever do this by hand, verify with git branch -r --contains <sha> | grep upstream.

Untracked files (e.g. work in progress under src/pages/) are treated as yours and left untouched; only tracked modifications block the bump.

Customizations to re-apply each version

Frontend (gpustack-ui-lofyer)

Fork-only commits carried on top of upstream. Details in gpustack-ui-lofyer/AGENTS.md + CLAUDE.md. Notably:

  • scripts/sync-github and scripts/rebrand (the tooling itself).
  • Rebrand user-facing GPUStack -> ZStack AIOS (run scripts/rebrand).
  • Footer/topbar trim, navigation moved to top header.

Backend (gpustack-lofyer)

  • API key / token prefix: API_KEY_PREFIX in gpustack/security.py is set to zstack (upstream gpustack). This drives API keys, worker registration tokens, cluster registration tokens (zstack_{access}_{secret}), and the masked display value. We intentionally do not stay backward compatible with old gpustack_ keys (fresh customer deployments only). Keep the assertions in tests/utils/test_api_keys.py in sync with the prefix.
  • Image build tooling: pack/Dockerfile.lofyer + hack/package-lofyer (thin overlay build, see "Shipping a customer build" below). These are fork-only files, so cherry-pick them onto each new -lofyer branch.

Note the backend origin points at the private registry and upstream at GitHub, mirroring the frontend convention:

upstream  https://github.com/gpustack/gpustack
origin    ssh://git@192.168.0.23:11022/root/gpustack.git

Brand rules

User-facing brand is ZStack AIOS (upstream GPUStack). Functional identifiers are not rebranded, to preserve compatibility with upstream contracts across the frontend/backend boundary:

  • lowercase gpustack (npm/pip package, URLs, paths, k8s namespace)
  • GPUSTACK_* env constants
  • X-* HTTP headers, cookies (gpustack_session, gpustack_oidc_*)
  • JS/Python identifiers (e.g. getGPUStackPlugin, gpustack_worker proctitles)
  • backend-contract strings (see SKIP_LINE_PATTERNS in frontend scripts/rebrand)

The API key prefix (zstack) is a deliberate exception: it is user-facing and, by our choice, not upstream-compatible.

Shipping a customer build

A release requires both the rebranded frontend and the backend on matching v<version>-lofyer branches. The deliverable is a single control-plane container image built by gpustack-lofyer/hack/package-lofyer.

Building the image

One command from the workspace root:

scripts/build-image        # frontend build -> image -> full verification

It resolves the version from the (matching) v<version>-lofyer branches, builds the rebranded frontend, delegates to gpustack-lofyer/hack/package-lofyer for the image, then runs scripts/verify-image. Flags: --skip-ui-build, --no-verify, --push, --dry-run. Env: NAMESPACE, REPOSITORY, TAG, IMAGE, BASE_IMAGE, SMOKE_PORT.

The backend script can also be used directly if you only want the image step:

cd gpustack-lofyer && ./hack/package-lofyer   # -> mesastack/gpustack:v<version>

It derives the upstream tag from the current branch name, so it always overlays a matching official base image. Knobs: UI_BUILD=1, DRY_RUN=1, PUSH=1, TAG, IMAGE, BASE_IMAGE, NAMESPACE, REPOSITORY, UI_REPO.

Why a thin overlay, not make package

Upstream pack/Dockerfile rebuilds the entire control plane (PostgreSQL 17, Higress gateway stack, Prometheus/Grafana, Skopeo compiled from source, ROCm amd_smi, vLLM extras). Those components are hardcoded and cannot be skipped, so a full make package costs 30-60+ minutes and tens of GB. Our fork only diverges in two places, so pack/Dockerfile.lofyer overlays them onto the official release image (gpustack/gpustack:v<version>) in seconds:

  1. gpustack/ui/ replaced with our rebranded frontend dist/
  2. fork-patched Python sources (currently just gpustack/security.py)

The script stages only files that actually differ from the upstream tag (git diff <tag>..HEAD -- 'gpustack/**/*.py'), keeping the layer small and auditable. The Dockerfile self-checks the result at build time (asserts API_KEY_PREFIX == zstack, and that the UI carries the ZStack AIOS brand).

Note this image is the control plane (server + worker). Inference runners use separate gpustack-runner images pulled at runtime and are unaffected.

If you ever need a fully self-built image with no upstream base (clean supply chain), use PACKAGE_UI_DOWNLOAD=false make package after staging our dist/ into gpustack/ui/, and expect the long build.

Verifying a build

scripts/build-image runs this automatically; use it standalone to re-check an existing image:

scripts/verify-image                 # static + runtime checks
scripts/verify-image --static-only   # skip booting a container
IMAGE=mesastack/gpustack:v2.2.3 scripts/verify-image

Checks performed:

  • static: gpustack version, API_KEY_PREFIX == zstack, old gpustack_ keys rejected, masked value format, UI assets present, UI carries the ZStack AIOS brand, no leftover GPUStack string in index.html
  • runtime: boots with --disable-worker, confirms / serves <title>ZStack AIOS</title>, logs in via POST /auth/login (bootstrap password at /var/lib/gpustack/initial_admin_password) and creates a key via POST /v2/api-keys to prove the zstack_ prefix is live end to end

The test container is always removed, including on failure.