Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
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
mainor to any upstream branch.mainmirrors 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
-lofyerbranch. 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
-lofyerbranch 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-githubandscripts/rebrand(the tooling itself).- Rebrand user-facing
GPUStack->ZStack AIOS(runscripts/rebrand). - Footer/topbar trim, navigation moved to top header.
Backend (gpustack-lofyer)
- API key / token prefix:
API_KEY_PREFIXingpustack/security.pyis set tozstack(upstreamgpustack). 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 oldgpustack_keys (fresh customer deployments only). Keep the assertions intests/utils/test_api_keys.pyin 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-lofyerbranch.
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 constantsX-*HTTP headers, cookies (gpustack_session,gpustack_oidc_*)- JS/Python identifiers (e.g.
getGPUStackPlugin,gpustack_workerproctitles) - backend-contract strings (see
SKIP_LINE_PATTERNSin frontendscripts/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:
gpustack/ui/replaced with our rebranded frontenddist/- 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, oldgpustack_keys rejected, masked value format, UI assets present, UI carries the ZStack AIOS brand, no leftoverGPUStackstring inindex.html - runtime: boots with
--disable-worker, confirms/serves<title>ZStack AIOS</title>, logs in viaPOST /auth/login(bootstrap password at/var/lib/gpustack/initial_admin_password) and creates a key viaPOST /v2/api-keysto prove thezstack_prefix is live end to end
The test container is always removed, including on failure.