Omahub
← All plugins
I

Deploys

by Ian Swope

Apps you deploy over ssh, in the Omarchy bar: whether each one answers, which revision is live, and how far that is behind what you pushed.

Security review

No obvious issues detected

Deterministic scan — not a security guarantee

None
Risk level
None
Analyzed commit
584b4c2
Scanned
1 month ago

No potentially dangerous behavior detected in the analyzed commit.

Automated analysis only — not a security guarantee.

AI advisory review

No obvious issues detected

Language-model assessment · ~deepseek/deepseek-v4-flash-latest — advisory only

Low
AI risk level
Low
Recommendation
install
Model
~deepseek/deepseek-v4-flash-latest
Analyzed commit
584b4c2
Reviewed
1 month ago

The plugin is a well-engineered monitoring widget that performs read-only health checks and git queries over SSH, with careful input validation and sanitization. The only mutating action (deploy) is user-initiated and runs in a visible terminal. No hidden persistence, obfuscation, or destructive behavior was found.

  • The plugin executes a bash script that performs network operations (curl, ssh) on a timer, but all inputs are validated and the script is read-only except for user-triggered deploys.
  • The deploy command is user-defined and runs in a terminal, which is expected and clearly documented.
  • The plugin relies on external tools (jq, git, ssh, curl) that must be present, but this is a normal dependency.
How this check works

This review combines the deterministic scan (the rule-based results above) with an independent look at the plugin's code by a language model. The model reads a trimmed sample of the repository's files, the manifest, and the README, then gives a plain-language risk level and a recommendation: install (no notable danger), review (look closer first), or avoid (clearly dangerous).

It runs on the same analyzed commit as the deterministic scan and is strictly advisory — it is not a security guarantee and never blocks a plugin by itself. A human moderator still reviews plugins before they are listed.

AI advisory only — automated analysis, not a security guarantee.

Install
$ omarchy plugin add https://github.com/ianswope/omarchy-deploys --enable
Developer Tools #bar #quickshell #system

Deploys

Apps you deploy over ssh, in the Omarchy bar: whether each one answers, which revision is live, and how far that is behind what you pushed.

The Deploys panel

What it shows

One row per app:

  • Health: status code and latency from the app's health URL.
  • Live revision: the commit checked out on the server, read with git rev-parse.
  • Drift: commits on origin/<branch> that are not deployed yet, and local commits that are not pushed yet.
  • Age of the deployed commit.

The bar turns urgent only when an app fails its health check. Undeployed commits get the count badge instead, because that is the normal state between a push and a deploy.

The panel reports how old its idea of "pushed" is (origin as of 2h ago). It does not fetch on every poll. f fetches and refreshes.

Assumption

The revision on the server is read from the git checkout at the configured path. That is the deployed revision when the deploy script resets that checkout to the branch and builds from it, which is how bin/deploy-style Docker Compose deploys work. If your build comes from somewhere other than the checkout, the number is the checkout's, not the running image's.

Configuration

The bar's settings schema has no array type, so apps are listed in ~/.config/omarchy/deploys.json:

{
  "apps": [
    {
      "name": "PromoEQP",
      "url": "https://promoeqp.com",
      "health": "/up",
      "ssh": "root@promoeqp.com",
      "path": "/opt/promoeqp-rails",
      "repo": "~/Projects/promoeqp-rails",
      "branch": "main",
      "deploy": "/opt/promoeqp-rails/bin/deploy"
    }
  ]
}
Field Meaning
name Label in the panel
url Base URL, and it must begin with http:// or https://. Omit to skip the health check
health Path appended to url. Defaults to /up. Empty string checks url itself
ssh ssh target for the read-only revision check, matching [A-Za-z0-9._@%+:-]. Omit to skip it
path Git checkout on the server. May not contain shell metacharacters
repo Local clone, for the drift comparison. ~ is expanded
branch Branch to compare against. Defaults to main
deploy Command run on the server by d. Omit to disable deploying

Every field is optional. An app with only name and url is a plain health check.

Press e to open the file. Point configPath in the widget's settings somewhere else if you prefer.

Keys

Key Action
j / k or arrows move the cursor
enter open the app in the browser
d deploy the selected app (asks first)
y copy the selected app's deploy command
f fetch from origin, then refresh
r refresh without fetching
e open the config file
esc close

In the confirmation dialog, left/right/tab pick a button and enter activates it. It opens with Cancel selected.

On the bar icon: left click opens the panel, right click refreshes. In the panel, right click on a row deploys it, middle click copies its deploy command.

ssh

Revision checks run ssh -o BatchMode=yes -o ConnectTimeout=5, so they need key-based auth that works without a prompt, and the host key already in known_hosts. Unknown hosts are reported, not accepted automatically.

The only remote command is:

git -C <path> rev-parse HEAD; git -C <path> log -1 --format=%cI

Every app is probed in parallel, so refresh time does not scale with the number of servers.

What it does not do

  • Nothing remote is written or restarted except by d, which runs your own deploy command in a visible terminal so the output and any failure are on screen.
  • Deploying asks first, and the dialog opens with Cancel selected.
  • No polling of origin. A stale comparison is labelled stale rather than presented as current.
  • Values from the config are checked before use rather than trusted. ssh joins its command arguments and hands them to the remote login shell, so the remote path is quoted for that shell and refused outright if it carries shell metacharacters; an ssh target may not begin with a dash and the option list is terminated before it, because ssh -F<file> would otherwise load a config file of someone else’s choosing, and a deploy command is quoted for the remote shell rather than restricted, since being shell syntax is its purpose; a url must name an http scheme before it reaches either curl or the browser, because curl reads a leading dash as options and omarchy-launch-browser passes its argument straight to the browser binary, where --gpu-launcher=<command> runs that command; and a revision must be 40 hex characters, so nothing else is displayed as one. The poll is unattended, so none of these can wait for the deploy confirmation.
  • Nothing from git, curl or ssh is rendered as markup. All external text is stripped of angle brackets, cleared of control characters and length-clamped as it enters the model, and every Text item is pinned to Text.PlainText. QML's default AutoText would treat a commit subject or error containing <img src="http://host/x"> as rich text and make the shell fetch it.

Settings

Setting Default Meaning
refreshIntervalSec 300 how often to re-probe
showCount true show the count of apps needing attention beside the bar icon
configPath empty app list location, when not ~/.config/omarchy/deploys.json

Requirements

Dependency Needed for
Omarchy Quattro (omarchy-shell) the plugin host
jq the status helper
bash, coreutils date, awk, sed
git local drift comparison
openssh reading the deployed revision
curl health checks
wl-clipboard y

Install

omarchy plugin add https://github.com/ianswope/omarchy-deploys.git --enable

Remove

omarchy plugin remove ianswope.deploys

That disables the widget, drops it from the bar layout and deletes ~/.config/omarchy/plugins/ianswope.deploys. The app list at ~/.config/omarchy/deploys.json is left in place; delete it separately if you want it gone.

Checking what the panel sees

~/.config/omarchy/plugins/ianswope.deploys/bin/omarchy-deploys-status | jq

Takes a config path as the first argument and 1 as the second to fetch first. It creates and restarts nothing.

License

MIT. See LICENSE.