Automate Laravel Forge from
swamp. This model wraps the Forge v2
org-scoped JSON:API to list servers, audit whether expected sites exist, and
idempotently provision sites for a git monorepo — create the site with the
repository attached in one call (composer: false by default; a monorepo has no
root composer.json, so the real build belongs in the deploy script), and set
the deploy script. It can
also push a site's .env from a vault reference, persisting only key names and a
byte count — never the secret values, install a LetsEncrypt SSL certificate
for a site's bare (non-www) domain, and manage deployments — set a site's
deploy script, trigger a deploy (polling to completion), and read the deploy log.
The Forge API token is supplied through globalArguments.apiToken, wired to a
vault expression at model-creation time — never a literal token.
swamp extension pull @goodcraft/forge# Create the model with the token wired to a vault (never inline).
# `organization` is your Forge org slug — v2 routes are org-scoped.
swamp model create @goodcraft/forge forge \
--global-arg apiToken='${{ vault.get(op-secrets, "Laravel Forge/FORGE_API_TOKEN") }}' \
--global-arg organization='your-org-slug'
# Read-only: list servers (proves the token + snapshots state)
swamp model method run forge sync
# Audit whether expected sites already exist (read-only)
swamp model method run forge auditSite \
--input sites='[{"server":123,"domain":"app.example.com","directory":"/app","branch":"main"}]'
# Provision sites — dryRun defaults true; plan first, then re-run with dryRun=false.
# `repository` is required (the git repo to connect, e.g. "your-org/your-monorepo").
swamp model method run forge provisionSite \
--input repository='your-org/your-monorepo' \
--input sites='[{"server":123,"domain":"app.example.com","directory":"/app","branch":"main","deployScript":"cd $FORGE_RELEASE_DIRECTORY/app && composer install --no-dev"}]'
# Install a LetsEncrypt SSL cert for the bare (non-www) domain — no www. variant,
# no redirect. dryRun defaults true; re-run with dryRun=false to request the cert.
swamp model method run forge installSsl \
--input server=123 --input domain='app.example.com' --input dryRun=false
# Set a site's deploy script (pass multi-line script via --input-file YAML)
swamp model method run forge setDeployScript --input-file deploy.yaml
# Trigger a deploy and poll until it finishes (dryRun defaults true)
swamp model method run forge deploy \
--input server=123 --input domain='app.example.com' --input dryRun=false
# Read the latest deploy log (read-only) — handy for diagnosing a failed deploy
swamp model method run forge deployLog --input server=123 --input domain='app.example.com'sync— list the organization's servers (read-only).auditSite— diff a set of expected sites against live state;ready=truemeans every expected site is present.provisionSite— create site + connect git (composer: false) + set the deploy script.dryRun: true(default) plans without writing anything.repositoryis required.setEnv— write a site's.envfromenv(pass a vault expression); persists only key names + a byte count, never the values.installSsl— obtain + install a LetsEncrypt certificate for the site's bare domain. Non-www by design (nowww.entry, no redirect) and idempotent (skips when an active cert already covers the domain).dryRun: true(default) plans without requesting a cert.setDeployScript— set a site's deployment script (dryRun: truedefault).deploy— trigger a deployment (runs the deploy script) and, withwait: true(default), poll the deployment history until it finishes or fails.dryRun: true(default) plans without deploying.deployLog— fetch the latest deployment log for a site (read-only).
Each method calls the Forge REST API directly with Deno fetch, which passes
Forge's Cloudflare protection natively (Python urllib/requests get a 1010
block). Site creation and git installs are asynchronous — the model polls until
the site reaches installed and verifies the repository sticks (a failed clone
silently reverts it to null). Pure audit/diff logic lives in _lib/ with
colocated unit tests, so the API methods stay thin.
MIT — see LICENSE.txt.