Install
Get running in a few steps
Four pieces on four machines. The Gateway is a Linux server (the broker); the OSH Client, CA Signer & iPhone Approver are cross-platform — see the apps block below. Copy-paste per platform.
gateway — linux server
# ═══ Option A · ONE COMMAND (recommended) — installs, writes a SQLite config, auto-generates a CA token, starts ═══
# YOUR_SERVER = the address clients reach this box at — its LAN or public IP, or a domain. NOT localhost / 127.0.0.1.
# e.g. http://192.168.1.50:8443 (same LAN) · http://203.0.113.10:8443 (public IP) · https://gw.example.com (domain)
curl -fsSL https://download-osh.moninotes.com/install.sh | sudo sh -s -- --public-url http://YOUR_SERVER:8443
# SQLite single-node · no PostgreSQL. Public HTTPS domain? add: --autocert gw.example.com --public-url https://gw.example.com
# (bare IP = http only — Let's Encrypt needs a domain.) Then watch for the root-enroll QR:
sudo journalctl -u pam-zta-gateway -f -o cat # -o cat prints the FULL QR (strips the log prefix that otherwise truncates it) · widen the window if it still wraps
# ═══ Option B · MANUAL (advanced) — if you downloaded the .deb / .rpm above ═══
# 1) Install — --no-install-recommends keeps PostgreSQL OUT (SQLite needs no DB server)
sudo apt install --no-install-recommends ./pam-zta-gateway_0.2.0_amd64.deb # Ubuntu / Debian
sudo dnf install --setopt=install_weak_deps=False ./pam-zta-gateway-0.2.0-1.x86_64.rpm # RHEL / Rocky / Fedora
# 2) Point it at SQLite + set a CA token (writes the whole config)
sudo sh -c 'cat > /etc/pam-zta/gateway.toml' <<EOF
[server]
listen_addr = ":8443"
public_url = "http://YOUR_SERVER:8443" # IP or domain clients reach (NOT localhost) — goes into the enroll QR
[ca]
token = "$(openssl rand -hex 32)"
[database]
driver = "sqlite"
path = "/var/lib/pam-zta/gateway.db"
[cert]
ttl = 300
[gateway_key]
key_path = "/var/lib/pam-zta/gateway_key.pem"
EOF
sudo chown root:pam-zta /etc/pam-zta/gateway.toml && sudo chmod 640 /etc/pam-zta/gateway.toml
# 3) Start + watch for the root-enroll QR
sudo systemctl enable --now pam-zta-gateway
sudo journalctl -u pam-zta-gateway -f -o cat # -o cat prints the FULL QR (strips the log prefix that otherwise truncates it) · widen the window if it still wraps
grep token /etc/pam-zta/gateway.toml # hand this CA token to the CA operator
# ─── Connect to this gateway ───
# OSH client → http://SERVER:8443 (osh --gateway http://SERVER:8443)
# CA Signer → ws://SERVER:8443/v1/ca/ws + the [ca].token above
# HA / multi-node? Use PostgreSQL instead of SQLite ([database] host/user/... ).
desktop & ios — apps
# macOS — CA Signer (.dmg)
Open the .dmg ▸ drag "PAM-ZTA CA" to Applications ▸ open ▸ plug YubiKey ▸ enter PIN (PIV 9c) ▸ "Save PIN to Keychain".
# macOS — OSH Client (.pkg · all-in-one: GUI app + `osh` command)
Double-click OSH-1.2.0-arm64.pkg ▸ Install. # notarized · installs the OSH app + the `osh` CLI into PATH
osh connect dev@your-server # or open the OSH app and click Connect · (new terminal: run `rehash`)
# Windows — CA Signer / OSH Client (.exe · not yet code-signed)
Run the .exe ▸ on SmartScreen "Windows protected your PC" ▸ More info ▸ Run anyway.
# Ubuntu/Debian — CA Signer / OSH Client (.deb · amd64)
sudo apt install ./CA-0.1.0-amd64.deb # CA Signer (Operator Console)
sudo apt install ./OSH-0.1.0-amd64.deb # OSH Client
# iOS (OSH Approver)
1. Install TestFlight from the App Store.
2. Open the TestFlight link above ▸ Install OSH.
3. Open OSH ▸ scan the enrollment QR from the Gateway ▸ approve with Face ID.
using osh — enroll once, then connect
# 1) First time on a gateway: point osh at it, then enroll this device (an admin approves it once).
export OSH_GATEWAY=https://your-gateway:8443 # add to ~/.bashrc → skip --gateway every time
osh enroll --force # registers this device · an admin approves it in the App Signer
osh status # optional: expect status:ok, ca_connected:true
# 2) Log in — ONE command. The login user must MATCH an access rule (e.g. root — not just any name):
osh root@10.0.0.5 # short for: osh connect root@10.0.0.5
# • osh asks the Gateway for a certificate for root@10.0.0.5
# • your iPhone buzzes → review who / where / what → Approve with Face ID
# • the CA (YubiKey) signs a short-lived cert → you land in the SSH session ✅
# (session recorded to ~/.osh/sessions/ · cert expires in minutes · nothing static left behind)
# Rejected? 401 = device not enrolled on THIS gateway → re-run osh enroll --force
# 403 = device not approved yet, or the login user doesn't match a rule
🔐CA Signer requires a YubiKey (hardware-only, fail-closed). Pull the YubiKey and signing stops — exactly by zero-trust design.