macOS or Linux:
curl -fsSL https://raw.githubusercontent.com/Behnam-RK/dezhban/main/scripts/install.sh -o ~/dezhban-install.sh && sudo bash ~/dezhban-install.shWindows (elevated PowerShell):
irm https://raw.githubusercontent.com/Behnam-RK/dezhban/main/scripts/install.ps1 | iexLeft to their defaults, all of these install the CLI, the menubar app on macOS,
and register the background service — without starting it. Run
install.sh from a file at a terminal on a machine that does not yet have
dezhban and you can decline the app or the service, or cancel outright; the
section below says what it asks. The Windows script never prompts.
Because run from a file at a terminal, the installer asks. Piped, it is defined not to.
That distinction is a policy, not a technical limit. Every prompt in
scripts/install.sh reads /dev/tty directly, and /dev/tty is there either
way — so a piped run could stop and ask. It does not, because the script gates
the terminal test on stdin — [ -t 0 ], alongside a readable /dev/tty and
DEZHBAN_ASSUME_YES not being set to 1 (below). Stdin is your terminal when you run
the file and a pipe when you pipe it, and it is the only stream that tells the
two apart. Testing stdout would not work: curl | sudo bash still has your terminal
on stdout, so the pipe would go interactive — the one thing every comment and
doc here promises it does not. A piped install therefore behaves identically on
your laptop and in CI, which is the point.
Run from a file at a real terminal, here is what it asks. On a fresh
machine, a menu: install with today's defaults, choose components, or cancel.
Choosing components asks whether to install the menubar app (macOS only) and
whether to register the service — the command-line tool is always installed, so
it is not one of the questions. Then, on fresh installs only, it offers to run
dezhban setup there and then, so you finish with a configured guard rather
than a set of instructions.
On a machine that already has dezhban, a different menu: upgrade (or
reinstall, whichever the version comparison calls for), uninstall, or cancel.
Uninstall asks "keep your config?" and then makes you type uninstall before
anything is removed. The setup wizard is deliberately not offered on this path —
you configured this machine once already.
Downloading first also lets you read the script before running it as root,
which is the right habit for anything that installs a kill switch. The command
above chains both steps, so to read it first, run only the curl half, check
it exited 0, read the file, and then run the sudo bash half. Your reading is
what replaces the && there — it is the same check, done by eye.
Run each of those as a separate command rather than pasting them together. A
prompt reads from your terminal, so anything pasted after a line that prompts —
sudo asking for your password, the installer's own menu, a pager — is eaten as
the answer to that prompt rather than run as a command.
Delete ~/dezhban-install.sh when you are done, whichever way you installed.
Re-running the installer is how you
upgrade, and a sudo bash ~/dezhban-install.sh typed from memory would run the
copy you downloaded months ago.
Download into your home directory, and chain the two commands with &&. A
failed download can leave a stale or half-written file behind — curl -o
truncates the target and writes what it received, and a truncated script still
runs as far as it parses — so a bare second command would run that. And /tmp
is world-writable under a predictable name, so what is there might be a file
another account put there. It would run as root.
Unattended is still one line, and still supported:
curl -fsSL https://raw.githubusercontent.com/Behnam-RK/dezhban/main/scripts/install.sh | sudo bashDEZHBAN_ASSUME_YES=1 forces that same no-prompt behaviour even at a real
terminal, for a script that wants the defaults deliberately. Set it inside
the sudo command — sudo's default env_reset drops variables set in front of
it, so the obvious spelling is silently inert:
sudo DEZHBAN_ASSUME_YES=1 bash ~/dezhban-install.sh # works
DEZHBAN_ASSUME_YES=1 sudo bash ~/dezhban-install.sh # ignoredVERSION=X.Y.Z pins an exact release and needs the same placement:
sudo VERSION=0.13.0 bash ~/dezhban-install.sh
curl -fsSL https://raw.githubusercontent.com/Behnam-RK/dezhban/main/scripts/install.sh | sudo VERSION=0.13.0 bashIf you skipped the wizard (or used the unattended form):
sudo dezhban setup # choose your settings
sudo dezhban start # arm itIf you also declined the service at the component prompt, register it first — this is what the installer itself prints in that case:
sudo dezhban install # register the service (skipped above)
sudo dezhban start # then arm the kill switchEverything below is why this is the recommended path, what else exists, and how to verify what you downloaded.
macOS's Gatekeeper blocks a double-clicked, unsigned .pkg or .app because
the file carries a com.apple.quarantine extended attribute — set by
whatever downloaded it (a browser, AirDrop, Mail). curl does not set
that attribute. This is documented, intentional Apple behavior, not a
loophole: Apple's own guidance describes Unix networking tools like curl
and scp as exempt, because Gatekeeper's quarantine model is about
"downloaded from the internet by an end-user-facing app", not about content
provenance in general.
So scripts/install.sh genuinely installs with zero Gatekeeper friction —
not because it evades a check, but because the check was never designed to
fire on this path in the first place. This is the same mechanism rustup,
Homebrew's own installer, and most modern CLI tools rely on.
The script does not merely rely on that, though. ditto -xk faithfully
restores whatever extended attributes an archive carries, so an asset that
ever arrived by some other route — a proxy that rewrites downloads, a mirror,
a user who fetched the zip in a browser and re-ran the script against it —
would drag a quarantine flag in with it. The install steps therefore strip
com.apple.quarantine from both /usr/local/bin/dezhban and
/Applications/Dezhban.app explicitly, and verify the app's ad-hoc signature
survived the round-trip. It is a no-op on the normal path; it turns the
guarantee from lucky into enforced on every other one.
This matters more than cosmetics for the CLI specifically: a quarantined bare
executable is refused on exec too, not just an app bundle on double-click.
A flagged /usr/local/bin/dezhban would fail when launchd tried to start
it — the kill switch would simply never come up.
There is no Apple Developer certificate ($99/yr; a hobby project with no
revenue), and the install.sh path already solves the friction that signing would
for free. What dezhban does instead: checksums, always, plus an
ed25519 signature over every release. Full story, why the install
scripts check the checksum but not the signature, and how to add real Apple
signing later: releasing.md § Unsigned artifacts, signed
checksums.
- Detects OS/arch, resolves the requested version (
VERSION=X.Y.Zenv var pins one; otherwise the latest release — which is never a-rcbuild). - Downloads the platform binary (+ the app bundle on macOS, unless you opted
out at the component prompt) and
SHA256SUMS. - Verifies the checksum. A mismatch aborts the install outright — this is not a warning, it's a hard stop. This is what actually protects you on this path: HTTPS gets the bytes to you unmodified in transit, and the checksum proves those are the bytes CI actually built.
- Installs the binary (and app, on macOS) and registers the service —
never starting it. A kill switch that arms itself during install, before
you've configured anything, is how you lock yourself out of your own
network. See
packaging/macos/scripts/postinstallandpackaging/linux/postinstall.shfor the same rule enforced by the.pkgand.deb/.rpmpaths too. - Fetches the matching uninstaller (
packaging/macos/uninstall.shorpackaging/linux/uninstall.sh, from the same tag just installed) to/usr/local/share/dezhban/uninstall.sh, and theLICENSEfrom that same tag to/usr/local/share/dezhban/LICENSE— the license requires that anyone who receives the software receives it too. Both are downloaded to a temporary file and moved into place only once complete, so a fetch that fails or times out is a warning rather than a failed install and never replaces a good copy from an earlier install with a truncated one — or with nothing.install.ps1fetches no uninstaller (Windows has none) but does fetch theLICENSE, to%ProgramFiles%\dezhban\LICENSE, besidedezhban.exe.
Re-running either script upgrades or reinstalls: it replaces the binary, and
if a service was already running, stops it first and restarts it after — but
only when a restart is actually safe right now (dezhban upgrade can-activate, the same rule dezhban upgrade apply honors — never through
FULL BLOCK or an open switch window, see upgrade.md). Refused:
the new binary is installed and the old daemon keeps enforcing on it until you
run sudo dezhban restart yourself, once the posture clears. Either way,
/etc/dezhban/ (your config) and /var/db/dezhban/ (learned endpoints,
state) are never touched.
Uninstalling. Run from a file at a real terminal, an existing install
offers "Uninstall" in its menu — shows exactly what will be removed, asks whether to keep your
config (default: yes), and requires typing uninstall to confirm. Or run it
directly any time: sudo sh /usr/local/share/dezhban/uninstall.sh (add
KEEP_CONFIG=1 to keep /etc/dezhban). It always runs panic first — removes
the firewall rules even with no daemon running — before touching anything
else, so you're never left locked out mid-removal.
The script alone is not a complete removal: it cannot reach your login keychain, so Dezhban's Touch ID key survives it (it prints the command that clears that). On macOS, Settings → Remove Dezhban… in the app does both halves — see Removing dezhban completely.
Download dezhban-<version>.pkg from the
Releases page and:
sudo installer -pkg dezhban-<version>.pkg -target /Unsigned, so a double-click hits Gatekeeper — see
releasing.md
for the terminal-only workaround, or use install.sh instead, which doesn't
have this problem at all.
Also on the Releases page:
sudo dpkg -i dezhban_<version>_<arch>.deb # Debian/Ubuntu
sudo rpm -i dezhban-<version>-1.<arch>.rpm # Fedora/RHELDeclares an nftables dependency (the Linux backend shells out to nft).
Registers the service on install (postinstall.sh, same never-auto-start
rule) and tears down rules + unregisters on removal (preremove.sh — it has
to run before the package manager deletes the binary, or there's nothing
left to call panic/stop/uninstall on). The LICENSE lands at
/usr/share/doc/dezhban/LICENSE — the per-package documentation directory both
dpkg and rpm own, so the package manager removes it again on uninstall. The
.pkg and install.sh put it in /usr/local/share/dezhban/ instead, and the
menubar app carries its own copy at
Dezhban.app/Contents/Resources/LICENSE.
dezhban-<os>-<arch> (or .exe on Windows) on the Releases page, for
scripting your own install, or dezhban-<version>-<os>-<arch>.tar.gz if you
want an archive with the binary, LICENSE, and README.md together. These
are what package managers consume — see the Homebrew note below.
A formula template exists (packaging/homebrew/dezhban.rb.tmpl +
scripts/gen-homebrew-formula.sh), but it isn't published: that requires a
separate behnam-rk/homebrew-tap repository that doesn't exist yet. When it
does, it will be a CLI-only formula, not a cask — Homebrew is removing
unsigned casks from the official tap by September 2026, but that policy
targets signed/notarized .app bundles, not plain binary formulae, so this
stays viable without an Apple Developer ID. Homebrew can't register a
privileged system service either way, so even once published, brew install
would still need sudo dezhban install && sudo dezhban setup to finish.
git clone https://github.com/Behnam-RK/dezhban && cd dezhban
task build
sudo bash scripts/install-local.shDifferent from everything above: this compiles locally rather than downloading a release. See development.md.
Every release publishes SHA256SUMS and SHA256SUMS.sig:
curl -fsSLO https://github.com/Behnam-RK/dezhban/releases/download/vX.Y.Z/SHA256SUMS
curl -fsSLO https://github.com/Behnam-RK/dezhban/releases/download/vX.Y.Z/dezhban-darwin-arm64
shasum -a 256 -c SHA256SUMS --ignore-missingSHA256SUMS.sig is the ed25519 signature dezhban upgrade verifies
internally; there's no convenient CLI verifier for it outside the Go binary
itself today. Why: releasing.md § Unsigned artifacts, signed
checksums.
Once installed, upgrade.md covers dezhban upgrade — the
in-place update path (macOS: fully self-serve; Linux/Windows: checks and
tells you, but leaves the actual update to your package manager).