Forge
Laravel Forge servers and deployments in the Omarchy bar. The icon tells you whether anything is broken; the panel tells you what, and lets you fix it without opening a browser.
Uses the Forge v2 API. One icon covers every organization you have a token for.
<img src="screenshots/panel.png" alt="The panel, with two organizations and a server unfolded into its sites" width="420">Getting started
1. Install the plugin.
omarchy plugin add https://github.com/acobrerosf/omarchy-forge.git --enable
2. Create a Forge API token at https://forge.laravel.com/profile/api.
Tick these scopes:
| Scope | What it buys you |
|---|---|
user:view |
required — confirms the token works |
organization:view |
required — finds the organizations to watch |
server:view |
required — servers, sites, deployment status |
site:manage-deploys |
deploy from the bar, and read deployment logs |
server:manage-logs |
read a site's application and nginx logs |
server:manage-services |
restart nginx, PHP-FPM, supervisor, redis or the database; reboot a server |
site:manage-commands |
run a command on a site, toggle maintenance mode |
recipe:view |
list the organization's recipes and follow a run |
recipe:manage |
run a recipe on a server |
The first three are the minimum. Leave the rest off and the widget is read-only — everything that
would change something says which scope it wanted instead of failing quietly. The rest are named
for what they let you change, with two exceptions in each direction: deployment logs and site logs
are reads that Forge files under a write scope anyway, so a strictly read-only token is refused
those two and told which scope it wanted; and recipe:view is a genuine read scope, wanted for
the recipe list and for following a run that recipe:manage started.
3. Add the token. Click Set up Forge in the panel, or run:
~/.config/omarchy/plugins/acobrerosf.forge/omarchy-forge setup
It asks for the token, then lets you pick which of that token's organizations to watch.
4. That's it. The Forge mark appears in the bar. Click it, or press its key, and the panel opens
with your organizations. Move with j/k, press enter to unfold, press d on a site and then Y
to deploy it.
Tokens are stored in your login keyring, never in a config file, and the widget never handles them
— every request is made by the bundled omarchy-forge helper.
Reading the bar
<img src="screenshots/bar.png" alt="The Forge mark in the Omarchy bar" width="224">The mark carries one badge for everything being watched, showing the worst thing it can see:
| Badge | Meaning |
|---|---|
| none | everything is up and no deployment is failing |
| hollow | a site is in maintenance mode |
| pulsing | a deployment is running |
| warning | no token, a missing token, or the last request errored |
| solid | a server is unreachable, or a deployment failed |
Middle-click the icon to refresh.
Using the panel
Organizations unfold into servers, servers into sites with their latest deployment, branch and commit. A server whose site starts failing unfolds itself once.
| Key | Action |
|---|---|
j k or ↑ ↓ |
move the cursor |
| enter / space | unfold an organization or server, or open a site |
l or → |
go deeper — on an unfolded server, opens its actions |
h or ← |
back, or fold up the current row |
d |
arm a deploy of the site under the cursor — Y sends it |
e |
the server's event feed — what Forge has done to it, newest first |
o |
open the site's URL, or a server's page in Forge |
f |
open the row in the Forge dashboard |
s |
copy an ssh forge@… command for the server |
r |
refresh now |
a |
add an organization |
Y |
send whatever enter, a click or d has armed |
| esc | back one level, or close |
Anything that changes something takes two keys: enter (or d, or a click) arms the row and
says so, and a capital Y sends it. Any other key cancels — enter again, a lower-case y, j,
escape — and does nothing else; an arm left alone lapses after eight seconds. Y rather than a
second enter, so a doubled keypress or a mistyped j or k can never be the last key before
something changes on a server.
The line at the bottom of the panel names what the row under the cursor can do.
Mouse: left click a row to unfold it or open a site; click the ⚙ on a server row for its actions;
right click to open it in Forge. Inside a view, the trail at the top left is the way back. A click
arms an action but never sends one — that is always Y — and a click while something is armed
cancels it.
A site
Opening a site gives you its actions and its details, all from data the refresh already fetched.
| Actions | deploy, toggle maintenance mode, reload its PHP-FPM, run a command, read the deployment log, read the three site logs, list its heartbeats, open the site, open it in Forge, copy its ssh command |
| Details | PHP version, app type, status, maintenance mode, isolation, zero-downtime, releases kept, aliases, healthcheck |
An action that can't run says why — a site with no repository, or one that has never deployed.
Maintenance mode takes the site offline behind a 503, and brings it back the same way. Forge
does the work out on the server, so the row says enabling… until it has landed.
Reload PHP-FPM gracefully reloads the pool for the site's own PHP version. The server view's PHP rows act on the server's default version, which is the wrong pool for an isolated site running another one.
Run a command opens a prompt: type a command, enter to arm, Y to run. It runs as forge in
the site's directory. Nothing is remembered between opens — no history, no repeat key, on purpose.
The output arrives when the run finishes.
Deployment log opens the latest deploy's output, ANSI stripped, scrolled to the bottom.
Application log, Nginx error log and Nginx access log open what the site itself is
writing, in the same pane. Forge hands back the tail of each rather than the whole file, as of the
moment you asked — r asks again. The nginx error log is the one to open when a site is up and
answering 500. An empty log says so rather than showing Forge's placeholder line.
Site logs need the server:manage-logs scope. That is Forge's name for it and it reads like a
write scope, because the same scope clears them; a token without it is told so on the pane.
Heartbeats
A heartbeat is a dead-man switch: something out there — a cron job, a queue worker — is supposed to
ping Forge on a schedule, and Forge raises the alarm when it stops. Heartbeats lists the ones
this site has, worst first, with what each is called, whether it is beating, missing or still
pending, how often it is expected and how much grace it gets.
Read when you ask and never polled, so r looks again. Forge records no timestamps on a heartbeat,
so a row cannot say when one was last seen — its schedule is what makes a missing legible. The
ping URL is deliberately not shown: it is the credential that marks the site alive, and it belongs
in the dashboard rather than in a bar widget. Creating and editing heartbeats is the dashboard's
too; this reads them.
| Key | In a log, or a command's, a recipe's or an event's output |
|---|---|
j k or ↑ ↓ |
scroll |
g / G |
top / bottom |
c |
copy the whole thing |
w |
save it to ~/Downloads/ — the panel says where it landed |
r |
look at a site log, a command or recipe run, or an event's output again |
h or esc |
back |
A server
Press l on an unfolded server, or click the ⚙ on any server row.
| Restart nginx | the web server in front of every site on it |
| Reload PHP-FPM | a graceful reload of the server's PHP version |
| Restart PHP-FPM | the harder version |
| Restart supervisor | every queue worker and daemon on the server |
| Restart redis | the server's redis, and whatever its sites keep in it |
| Restart MySQL | or MariaDB, or Postgres — named for what the server runs |
| Reboot server | every site on it goes down with it; still offered when the server is unreachable |
| Server events | what Forge has done to the server, newest first — also e from any row |
| Run a recipe | the organization's saved scripts; enter arms one, Y runs it here |
| Monitors | Forge's own alerts for this server — CPU, disk, memory — and which are firing |
A server lists only the services its type runs, since Forge reports no service status to go on: an app server has all of them, a web server no redis or database, a worker PHP and supervisor, a load balancer nginx alone, a database or cache server its one service. A server with no database has no database row, and a type the widget doesn't know lists everything and lets Forge answer.
Forge does all of this asynchronously, so the answer is "requested" — what changed shows up in the next refresh.
Events
Every deploy, command run, key install and environment change Forge performs on a server is an event, and the feed lists them thirty at a time with the site each was about and when. Enter on one opens what it printed, in the same pane as a deployment log and with the same keys; enter on the last row, Older events…, fetches the next thirty. An unreachable server's feed is where the reason usually is.
Forge records what an event did and what it printed, not whether it succeeded — there is no
status on an event, so the feed has no red rows. Open the output to find out. Reading events needs
only the server:view scope, so unlike deployment logs this works on a read-only token.
Recipes
A recipe is a shell script saved in your Forge organization. Run a recipe lists the ones this
organization has; enter arms one and Y runs it, on the server whose view you opened the list
from — one server at a time, which is the difference between this and the dashboard. The output
arrives when the run finishes, in the same pane as a deployment log and with the same keys.
The row says who the recipe runs as, root or forge, and its first line. It will not arm on a
server that is not ready, and says which instead. Nothing here can edit or create a recipe: this
runs what is already written, and the dashboard is where writing it belongs.
Monitors
Forge can watch a server's CPU load, disk and memory and alert on a threshold. Monitors lists
what this server is watching — CPU load ≥ 90%, Disk ≥ 80% — with how often Forge checks it, who
gets the email, and whether it is firing. A firing monitor is red and says how long it has been
that way; one Forge is still installing, or failed to install, says that instead.
Read when you ask and never polled, so r looks again. Reading them needs only server:view, the
scope the refresh already uses. Creating and deleting monitors stays in the dashboard, where the
threshold can be thought about.
Forge answers the run with no id to follow, so the widget recognises the run afterwards in the recipe's own list of runs — by the server, and by being newer than the last one it followed. That list has no sort and pages 30 at a time, so a recipe with a long history is searched three pages deep before a look gives up and tries again.
Notifications
Deployments that finish or fail between refreshes raise a desktop notification, naming the organization when more than one is watched. Clicking it opens the site.
More than one organization
One token belongs to an account; an account can see several organizations. A token that already sees three organizations only needs adding once. An organization on somebody else's Forge account needs a token of its own.
Press a in the panel, or:
omarchy-forge add # store a token, pick its organizations
omarchy-forge accounts # who is configured, and what each one watches
omarchy-forge login --account clientco # replace a token after rotating it
omarchy-forge remove acme # stop watching one organization
omarchy-forge remove --account clientco # drop an account, its token, and its orgs
All of them show in one list, grouped per organization. Nothing needs restarting — a new organization is picked up within a refresh.
Settings
Editable in Setup → Plugins, or in ~/.config/omarchy/shell.json:
| Key | Default | What |
|---|---|---|
refreshIntervalSec |
60 |
seconds between refreshes (15–3600) |
watchDeployments |
true |
also fetch sites and deployment status |
notifyDeployments |
true |
notify when a deployment finishes or fails |
organization |
"" |
which organizations this copy shows — empty for all |
dashboardUrlTemplate |
https://forge.laravel.com/{org}/{server}/{site} |
where f and right-click point — {org}/{server} are slugs, {site} an id, {serverId} also available |
organization is a filter, not a second place to configure one. A slug narrows the widget to that
organization; a comma-separated list narrows it to those. You can put a second copy in the bar
pinned to one organization if you would rather have separate icons.
Rate limits
Forge allows 60 requests a minute per Forge account, shared with anything else on it — including its own dashboard in a browser tab.
A refresh costs two requests per organization, whatever your server count, and that figure doesn't change with the number of monitors. Opening a deployment log is one more, and so is a site log, and so is a server's event feed — one per page of thirty, and one for each event's output you open, one per page of the recipe list, one per page of a server's monitors and one per page of a site's heartbeats. Running a command costs about a dozen, spread over a hundred seconds, and running a recipe costs the same.
The widget keeps a budget per account and backs off before it runs out, telling you on the row rather than failing silently. If Forge refuses anyway, the panel says "rate limited" and everything on that account waits until the limit resets, then resumes on its own.
Organizations with more than 150 sites are checked in rotation across several refreshes — nothing is dropped, but a status change at the far end can be noticed one rotation late. The panel says when that is happening.
The CLI
omarchy-forge works on its own, and is useful for scripting even if you never open the panel:
omarchy-forge setup # guided setup
omarchy-forge add # store a token and pick organizations to watch
omarchy-forge accounts # accounts, tokens, and what each one watches
omarchy-forge orgs [account] # organizations a token can see
omarchy-forge org [slug] # show or set the default organization
omarchy-forge remove <slug> # stop watching an organization
omarchy-forge rename OLD NEW # give an account a different CLI handle
omarchy-forge logout [account] # remove an account's token
omarchy-forge status [--org SLUG] # server health as a table
omarchy-forge doctor # every account: token, auth, rate limit left
omarchy-forge api --account default GET /orgs/acme/servers # raw request
remove and logout say what they are about to drop and wait for a capital Y, the same as the
panel. --yes answers for them, and a script needs it: with no terminal to ask on they refuse
rather than assume.
To have it on your PATH:
ln -sf ~/.config/omarchy/plugins/acobrerosf.forge/omarchy-forge ~/.local/bin/omarchy-forge
api always prints one JSON envelope, whatever went wrong:
{"ok": true, "status": 200, "rateRemaining": 57, "rateReset": null, "body": { }, "error": null}
A third argument is a JSON request body; pass - to read it from stdin instead. --account names
the credential to use and --org picks it by organization; with neither, the default organization's
account is used. $FORGE_TOKEN (with $FORGE_ACCOUNT) overrides the keyring for one run.
State lives in ~/.local/state/omarchy/forge.json — which accounts exist, which organizations each
reaches, and which is the default. Tokens are never in there.
Requirements
- Omarchy 4.0 or newer
curl,jq,secret-tool— all on a stock Omarchy installwl-copyfor the copy actionsgum, optionally — nicer organization picking; there is a numbered fallback without it
Known limits
- The dashboard URL is a template. The Forge API hands out no web link, so
dashboardUrlTemplateis a setting. - The server view restarts, it never stops. Stopping a service and power-cycling a server are deliberately absent: a service stopped from the bar is one nothing here could start again.
- Events have no status. Forge's event record says what was done and what it printed, not whether it worked, so the feed cannot colour a failed step — open its output to find out.
- The server list stops at 150 rows and says so rather than quietly showing a prefix. Sites are not capped — past 150 they are checked in rotation.
- A command run has no history and no partial output. You see the run you just started, and its output arrives when it finishes. A recipe run is the same, and adds one of its own: it goes to one server at a time, where the dashboard can send a recipe to several at once.
- A recipe run is recognised, not received. Forge's answer to starting one carries no id, so the run is found afterwards in the recipe's run list — three pages of thirty deep. A recipe with more than ninety runs on record can outrun that, and the pane says it is still looking.
- Monitors and heartbeats are read, never watched. Both are on-demand views, so a monitor firing or a heartbeat going missing never changes the bar icon and never raises a notification. Forge offers no organization-wide list of either and no way to attach them to the server or site requests the refresh already makes, so watching them would cost one request per server plus one per site on every tick — a second sweep, against a budget the flat two-per-organization figure above depends on.
- Logs need a scope named for writing. Forge gates deployment output behind
site:manage-deploysand a site's own logs behindserver:manage-logs, the scope that clears them. That is Forge's choice, not this plugin's — a strictly read-only token can watch a deployment fail and not be told why.
License
MIT