locorum

command module
v0.0.0-...-c2515dd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 10, 2026 License: MIT Imports: 32 Imported by: 0

README

Locorum

Early development (0.x) — expect rough edges and occasional breaking changes between releases. Don't point it at production data.

About

Locorum is a local development environment for WordPress. It uses Docker to spin up isolated WordPress sites, each with its own nginx, PHP, MySQL, and Redis containers, routed through a shared Traefik reverse proxy that terminates TLS using per-site certificates issued by mkcert.

The desktop UI is built with Gio, a pure-Go immediate-mode GUI framework.


Install

Pre-built downloads for every release are on the GitHub Releases page. All platforms require Docker (Docker Desktop on macOS / Windows; Docker Engine or rootless Docker on Linux) to be installed and running.

For trusted HTTPS in your browser (no certificate warnings), install mkcert and run mkcert -install once before launching Locorum. Without mkcert sites still work, but browsers will show an "untrusted certificate" warning that you have to click through.

macOS
  1. Download Locorum-<version>-macos-universal.dmg (one universal binary works on both Intel and Apple Silicon).
  2. Open the DMG and drag Locorum to your Applications folder.
  3. See First-Run Notes → macOS before launching for the first time.
Windows
  1. Download the MSI for your CPU:
    • Locorum-<version>-windows-amd64.msi — Intel / AMD 64-bit (most PCs)
    • Locorum-<version>-windows-arm64.msi — Snapdragon / Surface Pro X
  2. Double-click to install (admin prompt expected).
  3. See First-Run Notes → Windows before launching.
Linux
  1. Download the tarball for your CPU:
    • locorum-<version>-linux-amd64.tar.gz — x86_64
    • locorum-<version>-linux-arm64.tar.gz — aarch64
  2. Extract and install:
    tar -xzf locorum-<version>-linux-amd64.tar.gz
    cd locorum-<version>-linux-amd64
    sudo install -m755 locorum /usr/local/bin/locorum
    
  3. (Optional) Add a menu entry:
    mkdir -p ~/.local/share/applications ~/.local/share/icons/hicolor/256x256/apps
    cp locorum.desktop ~/.local/share/applications/
    cp locorum.png ~/.local/share/icons/hicolor/256x256/apps/locorum.png
    
  4. The binaries are built against glibc 2.35 — that covers Ubuntu 22.04+, Debian 12+, and current Arch / CachyOS / Fedora. Most distros also have the Gio runtime libs (libwayland, libx11, libxkbcommon, libegl1, libvulkan, libxcursor) installed by default; if Locorum complains about a missing library, install the matching -dev-less package via your distro's package manager.

First-Run Notes

macOS Gatekeeper

Locorum isn't notarized by Apple yet, so Gatekeeper blocks unsigned apps on the first launch. One-time workaround:

  1. Open Finder → Applications.
  2. Right-click (or Control-click) Locorum.appOpen.
  3. In the dialog, click Open again.

After that, double-click works normally. macOS remembers the override per app.

Windows SmartScreen

The MSI isn't code-signed yet, so Microsoft Defender SmartScreen warns on install:

Microsoft Defender SmartScreen prevented an unrecognized app from starting.

Click More infoRun anyway. The warning won't reappear once the app is installed.


Building from Source

Prerequisites
  • Go 1.25+
  • Docker (running and accessible)
  • mkcert (optional but recommended) — trusted local HTTPS. After installing, run mkcert -install once.
Linux / WSL2

Gio's Linux backend uses CGO. The native file-picker dialog (sqweek/dialog) needs GTK3 too. Install the system libraries:

sudo apt install gcc pkg-config libwayland-dev libx11-dev libx11-xcb-dev \
    libxkbcommon-x11-dev libgles2-mesa-dev libegl1-mesa-dev libffi-dev \
    libxcursor-dev libvulkan-dev libgtk-3-dev

Equivalent on Arch/CachyOS:

sudo pacman -S --needed pkgconf wayland libx11 libxcb libxkbcommon-x11 \
    mesa libxcursor vulkan-headers gtk3
macOS

Gio's macOS backend uses CGO. Install Xcode command-line tools:

xcode-select --install
Windows

No extra toolchain required — Gio v0.9 builds Windows targets in pure Go (no CGO, no MinGW).

Build and run
make build       # → build/bin/locorum (with version ldflags)
go run .         # dev iteration
./build/bin/locorum

On first launch the app:

  1. Sets up ~/.locorum/ (config, SQLite database, per-site web-server configs, Traefik state, mkcert certificates).
  2. Wipes any leftover Locorum-owned Docker resources (matched by the io.locorum.platform label), then creates the global network, mail and DB-admin containers, and the Traefik router.
  3. Opens the desktop window. If mkcert isn't installed (or mkcert -install hasn't been run), a banner explains the next step — sites still work, just with an untrusted-cert warning.
Release builds

Release artifacts (with embedded icon, manifest, version metadata, and installer wrapping) are built via make:

Target Output Tools needed
make tarball-linux-amd64 build/dist/locorum-<version>-linux-amd64.tar.gz rsvg-convert
make tarball-linux-arm64 build/dist/locorum-<version>-linux-arm64.tar.gz aarch64 GCC + arm64 Gio headers (use the CI runner)
make dist-windows Locorum-<version>-windows-{amd64,arm64}.msi gogio, wix (.NET)
make dist-macos Locorum-<version>-macos-universal.dmg macOS host, gogio, create-dmg

A real release happens automatically when you push a bare-semver tag:

git tag 0.1.0
git push origin 0.1.0

The Release workflow builds every artifact on its native runner, then GoReleaser publishes a draft GitHub Release with all five binaries + SHA256SUMS + auto-generated notes. Review the draft and click Publish.

Cross-compilation quick-build

For a quick "does it compile" check across platforms (no icon, manifest, or version metadata — bare binary only):

GOOS=linux   GOARCH=arm64 go build -o build/bin/locorum-linux-arm64 .
GOOS=windows GOARCH=amd64 go build -o build/bin/locorum.exe .
GOOS=darwin  GOARCH=arm64 go build -o build/bin/locorum-macos .

Cross-builds that need CGO (Linux/macOS) require the matching cross toolchain on the host.


Developing with Gio

Gio is immediate-mode: the entire UI is redrawn every frame by calling layout functions. There is no virtual DOM or persistent widget tree.

Key concepts:

  • app.Window — creates the OS window and delivers events.
  • app.FrameEvent — triggers a redraw; you respond by laying out the entire UI.
  • layout.Context (gtx) — carries constraints, the operations list, and dimensions.
  • op.Ops — accumulates drawing operations for the frame.
  • widget.* — types that hold persistent state (click state, editor text, scroll position).
Project structure
main.go                    Entry point: window creation, event loop, startup/shutdown
internal/
  app/                     Filesystem setup, global Docker infra
  docker/                  Thin wrapper over the Docker SDK
  storage/                 SQLite + embedded migrations
  sites/                   SiteManager — core business logic
  types/                   Shared data model
  utils/                   Filesystem / WSL / platform helpers
  version/                 Build-time identity (Version, Commit, Date)
  ui/
    ui.go                  Root UI struct, top-level Layout, error banner
    state.go               Mutex-protected shared state
    theme.go               Color palette, spacing, typography
    sidebar.go             Left panel (logo, search, site list)
    sitedetail.go          Right panel
    newsite.go             New-site modal
    widgets.go             Reusable primitives
    ...                    See CLAUDE.md for the full file map
Event loop
for {
    switch e := w.Event().(type) {
    case app.DestroyEvent:
        return e.Err
    case app.FrameEvent:
        gtx := app.NewContext(&ops, e)
        ui.Layout(gtx)       // Lay out the entire UI
        e.Frame(gtx.Ops)     // Submit the frame
    }
}
Background operations

Long-running operations (Docker container management, file dialogs, link checks) must run in goroutines so they don't block the UI. After completion, update UIState via its locking helpers — they call state.Invalidate() internally to wake the event loop:

state.SetSiteToggling(siteID, true)
go func() {
    err := sm.StartSite(siteID)
    state.SetSiteToggling(siteID, false)
    if err != nil {
        state.ShowError("Failed to start site: " + err.Error())
    }
}()
Backend ↔ UI

SiteManager talks to the UI through callbacks — sm.OnSitesUpdated and sm.OnSiteUpdated, both wired by ui.New(). The backend never imports internal/ui.

Adding a UI component
  1. New file in internal/ui/.
  2. Struct holds persistent widget state (widget.Clickable, widget.Editor, widget.List).
  3. Implement Layout(gtx layout.Context, th *material.Theme) layout.Dimensions.
  4. Wire it into ui.go.

See the add-ui-component skill in .claude/skills/ for a fuller scaffold, and CLAUDE.md for the full architecture / invariants.

Useful Gio resources

Hooks

Hooks let you attach commands to the lifecycle events of a site (start, stop, delete, clone, version-change, multisite, export). Add them from the Hooks tab in the site detail panel.

Three task types:

Type Where it runs Use it for
exec Inside one of the site's containers (default: php; pick web, database, or redis from the dropdown) wp …, composer …, mysql …, anything that needs the container environment
exec-host On your machine's shell (bash -c … on Linux/macOS/WSL, cmd /C … on native Windows) rsync, git push, calls to your host's CLI tools
wp-cli Inside the php container, prefixed with wp One-liner WordPress CLI commands

Every task receives a LOCORUM_* environment-variable bundle: site id, slug, name, primary URL, file paths, DB credentials, OS, etc. Variables expand at shell evaluation time, e.g. wp option update siteurl ${LOCORUM_PRIMARY_URL}.

By default a failing hook warns (logs the error and continues). Toggle "Fail the lifecycle method when a hook errors" at the bottom of the Hooks tab to switch the site to strict mode — the lifecycle method aborts on the first failure.

Per-run logs are written to ~/.locorum/hooks/runs/<site-slug>/<event>-<timestamp>.log. Locorum keeps 30 days or 50 runs per site (whichever is fewer) and prunes older logs at startup.

To skip every hook (useful when debugging Locorum itself), set LOCORUM_SKIP_HOOKS=1 before launching.

pre-start and other "containers down" events can only run exec-host hooks — exec and wp-cli are rejected at save time because the containers don't exist yet.


Migrations

Install the migrate CLI once:

go install -tags 'sqlite' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

Create a new migration:

migrate create -ext sql -dir internal/storage/migrations create_<table_name>_table

See the add-migration skill in .claude/skills/ and internal/storage/migrations/ for examples. Migrations are embedded into the binary at build time and applied automatically on startup.


License

MIT © 2026 Peter Booker.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
app
applog
Package applog wires the process-wide slog handler that fan-outs to stderr and a rolling text log under ~/.locorum/logs/locorum.log.
Package applog wires the process-wide slog handler that fan-outs to stderr and a rolling text log under ~/.locorum/logs/locorum.log.
assets
Package assets reconciles bundled (embedded) configuration files against the user's on-disk copy at ~/.locorum/config/.
Package assets reconciles bundled (embedded) configuration files against the user's on-disk copy at ~/.locorum/config/.
cli
Package cli implements Locorum's command-line surface.
Package cli implements Locorum's command-line surface.
config
Package config is a typed facade over Locorum's flat key/value `settings` table.
Package config is a typed facade over Locorum's flat key/value `settings` table.
daemon
Package daemon owns the process-coordination machinery for Locorum: the owner.lock that arbitrates which process owns Docker lifecycle, the local IPC transport (Unix domain socket on POSIX, named pipe on Windows), the JSON-RPC server bound to a SiteManager, and the matching client that CLI / MCP processes shell over.
Package daemon owns the process-coordination machinery for Locorum: the owner.lock that arbitrates which process owns Docker lifecycle, the local IPC transport (Unix domain socket on POSIX, named pipe on Windows), the JSON-RPC server bound to a SiteManager, and the matching client that CLI / MCP processes shell over.
dbengine
Package dbengine is the per-engine plug-in surface for Locorum's database story.
Package dbengine is the per-engine plug-in surface for Locorum's database story.
dbengine/fake
Package fake provides an in-memory dbengine.Execer for tests so they can exercise Snapshot / Restore / marker-write code paths without a running Docker daemon.
Package fake provides an in-memory dbengine.Execer for tests so they can exercise Snapshot / Restore / marker-write code paths without a running Docker daemon.
docker/fake
Package fake provides an in-memory docker.Engine for unit tests.
Package fake provides an in-memory docker.Engine for unit tests.
genmark
Package genmark encodes the convention that Locorum-managed files carry a signature so the user can opt out of further regeneration by removing the line.
Package genmark encodes the convention that Locorum-managed files carry a signature so the user can opt out of further regeneration by removing the line.
git
Package git wraps the user's installed git CLI for the small set of operations Locorum needs: cloning a remote, fetching, listing branches, creating and removing worktrees, and detecting dirty state.
Package git wraps the user's installed git CLI for the small set of operations Locorum needs: cloning a remote, fetching, listing branches, creating and removing worktrees, and detecting dirty state.
health
Package health is Locorum's system-health surface.
Package health is Locorum's system-health surface.
hooks
Package hooks lets users attach commands to the lifecycle events of a Locorum site (start, stop, delete, clone, version-change, multisite, export).
Package hooks lets users attach commands to the lifecycle events of a Locorum site (start, stop, delete, clone, version-change, multisite, export).
hooks/fake
Package fake provides in-memory hooks.Runner / ContainerExecer / HostExecer implementations for tests in other packages.
Package fake provides in-memory hooks.Runner / ContainerExecer / HostExecer implementations for tests in other packages.
integration
Package integration holds real-Docker integration tests, all build- tagged `integration`.
Package integration holds real-Docker integration tests, all build- tagged `integration`.
mcp
Package mcp implements a Model Context Protocol server that exposes Locorum's daemon as a curated set of tools to local AI agents.
Package mcp implements a Model Context Protocol server that exposes Locorum's daemon as a curated set of tools to local AI agents.
orch
Package orch is the small step-orchestrator at the heart of Locorum's site-lifecycle implementation.
Package orch is the small step-orchestrator at the heart of Locorum's site-lifecycle implementation.
platform
Package platform identifies the host Locorum is running on.
Package platform identifies the host Locorum is running on.
router
Package router defines the contract for the global routing layer that terminates TLS, performs hostname-based routing, and forwards traffic to per-site web containers and global services (mail, adminer).
Package router defines the contract for the global routing layer that terminates TLS, performs hostname-based routing, and forwards traffic to per-site web containers and global services (mail, adminer).
router/fake
Package fake is an in-memory Router used by tests that want to verify SiteManager wiring without spinning up Docker or Traefik.
Package fake is an in-memory Router used by tests that want to verify SiteManager wiring without spinning up Docker or Traefik.
router/traefik
Package traefik implements router.Router using a single Traefik v3 container backed by Traefik's file provider.
Package traefik implements router.Router using a single Traefik v3 container backed by Traefik's file provider.
secrets
Package secrets provides a process-wide secret registry used to redact known-sensitive substrings (DB passwords, auth tokens, salts) from any string that is about to be persisted, logged, or returned to a user- facing surface.
Package secrets provides a process-wide secret registry used to redact known-sensitive substrings (DB passwords, auth tokens, salts) from any string that is about to be persisted, logged, or returned to a user- facing surface.
sites/configyaml
Package configyaml projects a site's row + hooks onto a portable YAML file at <slug>/.locorum/config.yaml.
Package configyaml projects a site's row + hooks onto a portable YAML file at <slug>/.locorum/config.yaml.
sites/sitesteps
Package sitesteps assembles the per-site lifecycle Plan from individual orch.Step implementations.
Package sitesteps assembles the per-site lifecycle Plan from individual orch.Step implementations.
testutil
Package testutil holds shared test fixtures and helpers.
Package testutil holds shared test fixtures and helpers.
tls
Package tls handles certificate lifecycle for the global routing layer.
Package tls handles certificate lifecycle for the global routing layer.
tls/fake
Package fake is an in-memory tls.Provider for unit tests.
Package fake is an in-memory tls.Provider for unit tests.
ui
updatecheck
Package updatecheck queries GitHub Releases for newer versions of Locorum and persists the answer between launches.
Package updatecheck queries GitHub Releases for newer versions of Locorum and persists the answer between launches.
version
Package version exposes build-time identity for the Locorum binary.
Package version exposes build-time identity for the Locorum binary.
wpcli
Package wpcli ensures the pinned wp-cli phar (see internal/version for the version + SHA-512 pin) is present at ~/.locorum/bin/wp so every PHP container can bind-mount it as /usr/local/bin/wp.
Package wpcli ensures the pinned wp-cli phar (see internal/version for the version + SHA-512 pin) is present at ~/.locorum/bin/wp so every PHP container can bind-mount it as /usr/local/bin/wp.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL