A clean OpenConnect wrapper for Fortinet VPN on macOS.
morti handles login, TOTP generation, reconnects, monitoring, and disconnect flow without the usual manual steps.
- Connect to a Fortinet VPN through OpenConnect
- Generate TOTP codes automatically from your secret
- Read passwords and TOTP secrets from Apple Keychain or 1Password
- Retry failed connections and monitor the tunnel in the background
- Show clear status, logs, and notifications
- Keep the workflow down to explicit commands:
connect,disconnect,status
Install:
curl -fsSL https://raw.githubusercontent.com/arastu/morti/main/install.sh | bashThen:
nano ~/.morti.conf
source ~/.zshrc
morti connect| Command | Description |
|---|---|
morti connect |
Connect and start the background monitor |
morti disconnect |
Disconnect and stop the monitor |
morti reconnect |
Disconnect and connect again cleanly |
morti status |
Show VPN and monitor state |
morti debug |
Run OpenConnect in the foreground with verbose output |
morti logs |
Tail ~/.morti.log |
morti setup |
Create a fresh ~/.morti.conf |
morti help |
Show the built-in help card |
curl -fsSL https://raw.githubusercontent.com/arastu/morti/main/install.sh | bashgit clone https://github.com/arastu/morti.git
cd morti
bash install.shbrew install openconnect terminal-notifier oath-toolkit
cp morti.sh ~/.morti.sh
cp morti.conf.example ~/.morti.conf
chmod 600 ~/.morti.conf
echo 'source ~/.morti.sh' >> ~/.zshrc
source ~/.zshrcYour config file lives at ~/.morti.conf.
# Required
MORTI_SERVER="vpn.yourcompany.com"
MORTI_USERNAME="your.username"
MORTI_PASSWORD="your-password"
# MORTI_PASSWORD_CMD="security find-generic-password -w -a your.username -s morti-vpn-password"
# MORTI_PASSWORD_CMD="op item get 'Fortinet VPN' --fields label=password"
# TOTP: choose one
MORTI_TOTP_SECRET="BASE32SECRETHERE"
# MORTI_TOTP_SECRET_CMD="security find-generic-password -w -a your.username -s morti-vpn-totp-secret"
# MORTI_TOTP_SECRET_CMD="op item get 'Fortinet VPN' --fields label=totp_secret"
# MORTI_TOTP_CMD="totp-cli p account"
# MORTI_TOTP_CMD="op item get 'Fortinet VPN' --otp"More options are documented in morti.conf.example, including:
MORTI_SERVERCERTMORTI_REALMMORTI_PING_HOSTMORTI_EXTRA_ARGS
You can keep secrets out of ~/.morti.conf and fetch them at runtime:
security add-generic-password -U -a your.username -s morti-vpn-password -w 'your-password'
security add-generic-password -U -a your.username -s morti-vpn-totp-secret -w 'BASE32SECRETHERE'Then in ~/.morti.conf:
MORTI_PASSWORD_CMD="security find-generic-password -w -a your.username -s morti-vpn-password"
MORTI_TOTP_SECRET_CMD="security find-generic-password -w -a your.username -s morti-vpn-totp-secret"If you use the op CLI:
MORTI_PASSWORD_CMD="op item get 'Fortinet VPN' --fields label=password"
MORTI_TOTP_CMD="op item get 'Fortinet VPN' --otp"OpenConnect needs root privileges to create and manage the tunnel. To avoid repeated password prompts:
sudo visudo -f /etc/sudoers.d/openconnectAdd:
yourusername ALL=(ALL) NOPASSWD: /opt/homebrew/bin/openconnect
yourusername ALL=(ALL) NOPASSWD: /bin/kill
morti connect
-> load ~/.morti.conf
-> generate a fresh OTP
-> clean up any morti-managed session
-> start openconnect
-> verify tunnel health
-> start background monitor
If the tunnel drops, the monitor retries the connection automatically.
Set these before source ~/.morti.sh if you want to override defaults:
| Variable | Default | Purpose |
|---|---|---|
MORTI_CONFIG_FILE |
~/.morti.conf |
Config file path |
MORTI_LOG_FILE |
~/.morti.log |
Log file path |
MORTI_MAX_RETRIES |
5 |
Retry count |
MORTI_RETRY_DELAY |
3 |
Delay between retries |
MORTI_MONITOR_INTERVAL |
30 |
Health check interval |
MORTI_OPENCONNECT_PATH |
/opt/homebrew/bin/openconnect |
OpenConnect binary path |
MORTI_PASSWORD_CMD |
unset | Command used to fetch the VPN password |
MORTI_TOTP_SECRET_CMD |
unset | Command used to fetch the raw TOTP secret |
MORTI_TOTP_CMD |
unset | Command used to fetch the current OTP directly |
Config file not found
Run:
morti setupor:
cp morti.conf.example ~/.morti.confFailed to generate OTP
Make sure oath-toolkit is installed, or verify your MORTI_TOTP_CMD.
Connection keeps failing
Run:
morti debugIf needed, try MORTI_EXTRA_ARGS="--no-dtls" or pin the server certificate with MORTI_SERVERCERT.
Tunnel is up but traffic is broken
Set MORTI_PING_HOST to an internal reachable IP so the monitor can detect a dead tunnel.
morti disconnect does not stop the monitor
Check:
morti status
rm -f ~/.morti.monitor.pidcurl -fsSL https://raw.githubusercontent.com/arastu/morti/main/uninstall.sh | bashOr:
bash uninstall.shMIT
