flowchart TD
subgraph DIST["GoClaw Distribution"]
BIN["<b>Single Binary</b><br/>cgo: SQLite + tree-sitter"]
DOC["<b>Docker</b><br/>Multi-stage Alpine"]
SVC["<b>OS Service</b><br/>systemd + launchd"]
BUILD["<b>CGO_ENABLED=1 go build -o goclaw ./cmd/gateway</b><br/>C compiler per target ยท Linux builds: glibc 2.34+"]
BIN --> BUILD
DOC --> BUILD
SVC --> BUILD
end
GoClaw needs cgo: mattn/go-sqlite3 (the database) and smacker/go-tree-sitter (the code
analysis tools) are C code compiled into the binary, so a build with CGO_ENABLED=0 does not
compile. The Linux release binaries link glibc dynamically and need glibc 2.34 or newer
(Ubuntu 22.04, Debian 12, RHEL 9 or later); they do not run on musl (Alpine). On an older or
musl system, build from source there or use the Docker image, which builds its own binary.
| # | Method | OpenClaw | GoClaw | Status |
|---|---|---|---|---|
| 17.1 | Single binary | โ | One binary per platform with SQLite and tree-sitter compiled in (cgo); Linux builds need glibc 2.34+ | Done |
| 17.2 | From source | pnpm install && pnpm build |
go build -o goclaw ./cmd/gateway with a C compiler; add CGO_CFLAGS="-DSQLITE_ENABLE_FTS5" as the release does, or the knowledge graph falls back from full-text search to LIKE |
Done |
| 17.3 | Docker | Dockerfile + docker-compose | โ Multi-stage Alpine build (cgo on musl, non-root user) + docker-compose.yaml | Done |
| 17.4 | OS service install | launchd/systemd/schtasks | goclaw service install for systemd (Linux) and launchd (macOS); --system [--run-as <user>] for a system service run by an ordinary user |
Done |
| 17.5 | OS service uninstall | Platform-specific | goclaw service uninstall (stop + disable + remove) |
Done |
| 17.6 | Cross-compilation | โ | Release matrix: darwin arm64/amd64 built on macOS; linux amd64 (gcc), arm64 (aarch64-linux-gnu-gcc), armv7 (arm-linux-gnueabihf-gcc) and windows amd64 (x86_64-w64-mingw32-gcc) cross-compiled on Ubuntu 24.04 |
Done |
| 17.7 | npm install | npm i -g openclaw (primary) |
โ | TODO |
| 17.8 | Shell script (macOS/Linux) | curl openclaw.ai/install.sh | bash |
curl -fsSL https://goclaw.ulab.mn/install.sh | sh |
Done |
| 17.9 | PowerShell (Windows) | irm openclaw.ai/install.ps1 | iex |
โ (the Windows binary is a release asset) | TODO |
| 17.10 | macOS .app / .dmg | Signed app bundle with Sparkle | โ | TODO |
| 17.11 | Homebrew | โ | โ | TODO |
| 17.12 | Go install | โ | โ The module is ulab.mn/agents/goclaw and its main package ./cmd/gateway, so go install would name the binary gateway; build from source instead |
TODO |
| 17.13 | GitHub Releases | Release assets (binaries) | Every tag publishes its assets to a GitHub Release, to https://goclaw.ulab.mn/releases/<version>/ and to the release bucket (https://storage.googleapis.com/goclaw-releases/releases/<version>/) |
Done |
curl -fsSL https://goclaw.ulab.mn/install.sh | sh installs the latest release on macOS
(arm64, amd64) and Linux (amd64, arm64, armv7):
https://goclaw.ulab.mn/releases/latest.json, falling back to
https://goclaw-hosting.web.app and then to the release bucket
(https://storage.googleapis.com/goclaw-releases) when the one before does not answer,
and stops when none does. It downloads from the mirror that answered.goclaw-<os>-<arch> and that release's checksums.json, and stops unless the
binary's SHA-256 matches (it also stops when no SHA-256 tool is found)./usr/local/bin/goclaw through a staged file and a rename, so it replaces a
running binary; it asks for sudo when the directory is not writable.goclaw setup, goclaw, goclaw service install, goclaw doctor.A tag vX.Y.Z runs .github/workflows/release.yml, which publishes to
https://goclaw.ulab.mn/releases/<version>/ (Firebase Hosting), to a GitHub Release of the
same name and, from that Release, to the release bucket gs://goclaw-releases, public at
https://storage.googleapis.com/goclaw-releases/releases/<version>/:
| Asset | What it is |
|---|---|
goclaw-darwin-arm64, goclaw-darwin-amd64 |
macOS binaries, codesigned when the signing certificate is configured |
goclaw-linux-amd64, goclaw-linux-arm64, goclaw-linux-armv7 |
Linux binaries (glibc 2.34+) |
goclaw-windows-amd64.exe |
Windows binary |
goclaw-fleet-<version>.tar.gz |
The fleet toolkit (ansible/) |
goclaw-<version>-linux-amd64-sbom.cdx.json, goclaw-<version>-linux-amd64-sbom.spdx.json |
CycloneDX 1.5 and SPDX 2.3 SBOMs read from the linux-amd64 binary (SBOM) |
goclaw-<version>-vex.json |
OpenVEX statements from security/vex.json, stamped with the release |
goclaw-<version>-govulncheck.json, goclaw-<version>-govulncheck.txt |
govulncheck report |
checksums.json |
SHA-256 of every asset above |
releases/latest.json ({"version": โฆ, "published_at": โฆ}) and
releases/latest/checksums.json name the newest release; install.sh and goclaw update
read them. Firebase Hosting replaces the whole site on every deploy, so a docs-only deploy
(.github/workflows/deploy-site.yml) first rebuilds dist/releases/ with
scripts/restore_releases.sh, from the GitHub Release (else the bucket, else the live site),
checking every asset against checksums.json.
The site keeps only the newest release; the bucket keeps every version from v0.2.2 on, under
the same paths (releases/<version>/โฆ, releases/latest.json, releases/latest/checksums.json).
A versioned file is never replaced once uploaded and is served with
Cache-Control: public, max-age=31536000, immutable; the two latest files are no-cache
and move only forward, so republishing an older version leaves them on the newer one.
The Dockerfile, without its comments:
# Stage 1: Build
FROM golang:1.27-alpine AS builder
WORKDIR /app
RUN apk add --no-cache git build-base sqlite-dev
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux \
CGO_CFLAGS="-DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_JSON1" \
go build -tags="sqlite_fts5 sqlite_json" -o goclaw ./cmd/gateway
# Stage 2: Runtime
FROM alpine:3.21
RUN apk add --no-cache ca-certificates tzdata bash sqlite-libs
RUN adduser -D -u 10001 goclaw
WORKDIR /app
COPY --from=builder /app/goclaw .
COPY --from=builder /app/web ./web
RUN touch config.yaml && chown -R goclaw:goclaw /app \
&& mkdir -p /home/goclaw/.goclaw && chown goclaw:goclaw /home/goclaw/.goclaw
USER goclaw
EXPOSE 8080
CMD ["./goclaw"]
The gateway runs as the unprivileged user goclaw (uid 10001); its state directory is
/home/goclaw/.goclaw.
Docker Compose: docker-compose.yaml builds the image and runs it as goclaw-gateway on
port 8080 with restart: unless-stopped. It mounts:
./config.yaml at /app/config.yaml. It must set gateway.bind: lan, or the gateway
listens on the container's loopback and the port mapping reaches nothing. The gateway reads
it when the state directory has no config.yaml of its own.goclaw-state at /home/goclaw/.goclaw, the state directory: the database
(gc_state.sqlite), the secrets vault, the workspace, memory and logs.Set GOCLAW_SECRET_KEY under environment (the file has the line commented out). Without
it the vault key is derived from the user name and home directory, which are the same in every
container built from this image, so anyone with the image can derive it.
| Platform | Service | Features |
|---|---|---|
| Linux | systemd (goclaw.service) |
Restart=always, RestartSec=5, journal logging; WantedBy=default.target for a per-user unit (~/.config/systemd/user/, with lingering so it starts at boot), multi-user.target for --system (/etc/systemd/system/) |
| macOS | launchd (goclaw.plist in ~/Library/LaunchAgents/, or /Library/LaunchDaemons/ for --system) |
RunAtLoad=true, KeepAlive=true, stdout/stderr to ~/Library/Logs/goclaw.stdout.log and goclaw.stderr.log |
Service install auto-detects binary path via os.Executable() fallback to $PATH lookup.
The systemd unit comes from goclaw's own template (systemdUnitTemplate in
internal/cli/service.go): kardianos/service's writes RestartSec=120 and
WantedBy=multi-user.target for every unit, and a per-user unit enabled for that target never
starts at boot. goclaw doctor and the gateway's start-up log report an installed unit that
will not start at boot, with the command that fixes it.
| # | Feature | OpenClaw | GoClaw | Status |
|---|---|---|---|---|
| 21.1 | CLI update command | openclaw update |
goclaw update [--version vX.Y.Z] [--force] [--backup] |
Done |
| 21.2 | macOS Sparkle | appcast.xml, signed updates |
โ | TODO |
| 21.3 | Auto-update config | update.auto.* (stableDelayHours, jitter) |
โ | TODO |
| 21.4 | Control UI update | "Update & Restart" button | โ | TODO |
| 21.5 | Version check | npm registry / GitHub | โ (goclaw update reads releases/latest.json when it is run; nothing checks on its own) |
TODO |
How goclaw update works (internal/cli/cmd_update.go, internal/cli/update_backup.go):
--version, or from https://goclaw.ulab.mn/releases/latest.json
(the release bucket's when the site does not answer), and stops when that version is
already running, unless --force.releases/<version>/checksums.json and the asset for this OS and architecture to
a temporary file, and stops unless its SHA-256 matches. Both come from the site first and,
when it does not have that version (it keeps only the newest) or its copy does not
match, from the release bucket; so --version can go back to any release from v0.2.2 on.codesign --verify --strict) when the binary is
signed; an unsigned binary is installed with a warning.--backup is passed, snapshots gc_state.sqlite and kg.sqlite (using VACUUM INTO)
together with config.yaml, credentials/, workspace/, agents/,
memory/, skills/, extensions/ and plugins/ into
<state dir>/backups/goclaw-backup_<version>_<new-version>_<timestamp>.tar.gz, keeping the 3
most recent update archives.<binary>.prev (one step of rollback) and the new one into
its place, asking for sudo when the directory (such as /usr/local/bin) is not writable./etc/systemd/system/goclaw.service or /Library/LaunchDaemons/goclaw.plist exists, else
the per-user one (running as $SUDO_USER when goclaw update was invoked through sudo).
When that fails it says to run goclaw service restart (or sudo goclaw service restart
for a system service).The fleet toolkit's make upgrade runs it on each host
(Upgrade and restart).
| Aspect | OpenClaw | GoClaw |
|---|---|---|
| Primary install | npm (requires Node.js) | Single binary (install.sh or a release asset) |
| Runtime deps | Node.js 22+ | glibc 2.34+ on Linux; nothing else (SQLite and tree-sitter compiled in with cgo) |
| Docker | Dockerfile | โ Multi-stage Alpine (non-root; state in a named volume) |
| OS service | launchd/systemd/schtasks | systemd + launchd (auto-detect binary) |
| Cross-compile | Platform packages | darwin arm64/amd64, linux amd64/arm64/armv7, windows amd64, each with its C compiler |
| Service features | โ | auto-restart, journal/file logging, KeepAlive |
| macOS app | Signed .app with Sparkle | โ (TODO) |
| Auto-update | Sparkle + npm | goclaw update (run by hand or by the fleet toolkit) |
| Install methods | 6+ | install.sh, release binaries, Docker, source |
Not implemented: npm, a PowerShell installer, a macOS app, Homebrew, go install, and
automatic update checks.