The connector

View on GitHub

The connector, the ovenlight command, runs on the computer with your apps. It publishes each app as its own node (a device in your tailnet) with HTTPS, and manages guests. Support's setup steps are the short version. This page has the full one, from installing to uninstalling, then Troubleshooting, every command, and for reference, installing by hand and what it installs.

What the computer needs

The computer must stay on and awake while anyone uses your apps. A laptop can keep serving with its lid closed once you change one setting (how). It must be one of these:

  • A Mac with macOS 13 or later, logged in to its desktop and kept awake: in System Settings, under Energy (Battery, Options on a laptop), turn on Prevent automatic sleeping when the display is off. The connector runs only while you're logged in, so a Mac without a display needs automatic login (Users & Groups, Automatically log in as). FileVault turns automatic login off, so after any restart, such as a power cut or a macOS update, the connector waits until someone logs in at the Mac. ovenlight doctor checks that the Mac won't sleep.
  • A Linux computer with systemd, such as Ubuntu. The connector is a systemd user service, and with linger on, it runs from startup with nobody logged in. install.sh turns linger on, or prints the sudo command for it when it can't.
  • A Windows 11 PC that never sleeps: in Settings, under System > Power (Power & battery on a laptop), set sleep to Never, which ovenlight doctor checks. The connector runs as you from startup, with nobody signed in. On a work or school account (Microsoft Entra ID) or a domain account, it runs only while you're signed in.

You also need a Tailscale account with MagicDNS and HTTPS certificates turned on in the admin console, under DNS; ovenlight doctor checks both. The computer doesn't need the Tailscale app. Tailscale's free Personal plan is for non-commercial use, so a business needs one of Tailscale's paid plans.

A laptop with its lid closed

Closing a laptop's lid puts it to sleep and takes your apps offline, until you change one setting. Then it stays fully awake with the lid closed, so keep it plugged in, and never put it in a bag while it's on, where it can overheat.

  • On a Mac, turn off sleep in Terminal:

    sudo pmset -a disablesleep 1
    

    The Mac then never sleeps, lid open or closed, even on battery. The same command with 0 in place of 1 turns sleep back on. ovenlight doctor says so when closing the lid would still put the Mac to sleep. On an Apple Silicon laptop, the Amphetamine app's Power Protect add-on can turn this setting on for a session and off again when it ends.

  • On Windows 11, set what closing the lid does to Do nothing when plugged in. It's in Control Panel, under Power Options > Choose what closing the lid does, and on Windows 11 24H2 or later also in Settings, under System > Power & battery. ovenlight doctor doesn't check this one.

  • On Linux, the commands under A computer you have keep a laptop awake with its lid closed.

Install

On a Mac or Linux, in a terminal:

curl -fsSL https://ovenlight.app/install.sh | sh

On Windows, sign in as the account that will own the apps, which must be an administrator: elevating with another account's password installs the connector for that account. Open PowerShell with Run as administrator and run:

irm https://ovenlight.app/install.ps1 | iex

Never used a terminal? Here's how to open one.

The script checks the download before it installs anything, and stops with nothing installed if a check fails (Read the script first has what it checks and changes). Install by hand covers checking a download yourself, installing without the script and building from source.

Open a terminal

The command goes in a terminal, a window where you type commands:

  • Mac: press Command-Space, type Terminal and press Return.
  • Linux: on Ubuntu, press Ctrl-Alt-T. Elsewhere, open Terminal from your apps. If the command then says curl isn't found, install it with sudo apt install curl (or your system's package manager) and run the command again.
  • Windows: open Start, type PowerShell, choose Run as administrator, and choose Yes when Windows asks.

Copy the command, paste it into the terminal (Command-V on a Mac, Ctrl-Shift-V on Linux, Ctrl-V on Windows) and press Return (Enter on a PC). The script says what it does as it goes. When it's done, open a new terminal window for the next steps it lists.

Read the script first

To read the script before it runs, save a copy, read it, and run that copy. On a Mac or Linux, curl -fsSLO https://ovenlight.app/install.sh saves it and sh install.sh runs it. On Windows, in an administrator PowerShell, irm https://ovenlight.app/install.ps1 -OutFile $HOME\install.ps1 saves it and powershell -ExecutionPolicy Bypass -File $HOME\install.ps1 runs it. Reading it at its address in a browser isn't the same: the site could send your terminal another one.

The script takes the verified steps for you, all but comparing the index's serial with the one your connector holds. Then it unpacks the archive into ~/ovenlight-connector ($HOME\ovenlight-connector on Windows) and runs its install script. On a Mac or Linux, when ~/.local/bin isn't on your PATH, it adds the line that puts it there to your zsh or bash profile and names the file, or prints the line for another shell. Running it again installs the latest release over yours, keeping your apps, node state and update settings.

Checking the signature needs an ssh-keygen that can read the release key: macOS 14 or later, or OpenSSH 8.2 or later on Linux and 8.9 or later on Windows. Without one, the script says so and checks the download against the index alone, trusting HTTPS as installing by hand without the check does. On Windows it then also checks that Snowy Ghost LLC signed ovenlight.exe, install.ps1 and uninstall.ps1. What it trusts has the rest.

Publish an app

Run your app on the computer with the connector. It must serve plain HTTP on 127.0.0.1, and serve static files only from a public folder, never from the project's folder with its data and code. Give it a port nothing else uses (on a Mac, avoid 5000 and 7000, where AirPlay Receiver listens). No app yet? ovenlight new "Family List" --dir ~/src makes a starter app that suits Ovenlight and says how to publish it.

Choose a name you don't mind people seeing: once the app has its HTTPS certificate, public Certificate Transparency logs list its full address, which includes your tailnet's name. Then:

ovenlight publish --port 4317 --name "Recipe Box"

Recipe Box becomes its own node, at https://recipe-box.<your-tailnet>.ts.net/. The hostname comes from the name, and --slug chooses another. For each new app, publish prints a login link: open it and sign in with your Tailscale account. The node then gets its HTTPS certificate, which can take up to a minute the first time (publish waits for it when the node is already signed in), and the connector renews it before it runs out.

An app's sign-in expires on your tailnet's key expiry schedule (180 days by default), and doctor warns 30 days ahead. A stored API credential (Share an app) turns key expiry off, and with an API access token, later apps sign in as you without the browser (the first still needs its login link).

These commands help along the way, and Commands lists every one:

  • ovenlight status: apps, node state, URL, owner, whether each app answers, and any login link.
  • ovenlight doctor: checks, each with its fix. It exits 1 on any failure.
  • ovenlight check recipe-box: how the app will look and work on the phone, with fixes.
  • ovenlight unpublish recipe-box: stops serving it (see Unpublish).

Open it on your phone

On your phone, open Ovenlight, choose Use My Own Computers, tap Connect, and sign in with the same Tailscale account. If you've already joined someone else's app, Use My Own Computers is in Settings, under the More button. If your tailnet has device approval on, approve the phone in the admin console. An app you signed in with a login link waits there for approval too, listed by its slug. Ovenlight then finds your published apps by itself. Add App, under the More button, also takes an app's address, but only one on your own tailnet; apps on other people's computers come by invite.

Keep an app running

An app started in a terminal stops with that terminal, and doesn't come back after a restart. To have the connector run it instead, stop your copy (Ctrl-C in its terminal, never pkill by name) and, in the app's folder, give publish the command that starts it:

ovenlight publish --port 4317 --name "Recipe Box" --run 'npm start'

ovenlight logs recipe-box -f follows its output (-n <lines> shows more of it), and ovenlight restart recipe-box stops the command and starts it again.

The connector runs the command in the app's folder, with PORT set to the app's port and HOST to 127.0.0.1. That folder is the one --dir names. Without --dir, an app given a command when it has none (a new app, or one after --run=) takes the current directory, and keeps it until --dir changes it. Keep projects out of /tmp, which a restart empties, and out of Desktop, Documents, Downloads, iCloud Drive, cloud storage folders and external drives: macOS asks on the Mac's screen before the connector may use those, and the app waits until you click Allow.

A project's own tools aren't on the command's PATH: run them through npx (npx vite) or an npm script, and a Python virtual environment's tools by path (.venv/bin/uvicorn, or .venv\Scripts\uvicorn on Windows) or through uv run.

On macOS and Linux, the command runs with /bin/sh in your login shell's environment, without what only a terminal's shell sets up. So a tool set up only in ~/.zshrc or ~/.bashrc (nvm, pyenv) isn't found, and ovenlight logs <slug> says command not found. Put its directory first on PATH in the command, such as --run 'PATH=/path/to/node/bin:$PATH npm start', with the directory from command -v node. A variable exported only in those files is missing too, and nothing warns about it: keep settings the app needs in a file in its folder, outside the public one. On Windows, the command runs with cmd, without the connector's administrator rights. Keep it running in the building guide has the rest: quoting, virtual environments and Windows.

Whenever the command exits, the connector starts it again, waiting longer each time, up to a minute. Once a run has lasted a minute and served on the app's port, the wait starts over. Output goes to <slug>.log in ~/Library/Logs/ovenlight on macOS, and in the logs folder of the state directory on Linux and Windows. status shows whether the command runs. Publishing the app again keeps the command and folder, and --run= or unpublish stops running it.

Unpublish

unpublish stops serving an app. Its node stays in your tailnet, offline, so publishing it again needs no new login. A shareable app's node is deleted instead (Share an app). On your phone, the app's tile stays until you remove it (touch and hold it, then choose Remove App). Ovenlight doesn't add a removed app back, even when you publish it again: add it by its address with Add App, under the More button.

Connect a coding agent

The connector is also an MCP server, ovenlight mcp, which speaks over stdio. Through it, your coding agent (Claude Code, Codex and others that support local MCP servers) reads the building guide, publishes your app, checks it, restarts it, and reads its logs and the feedback people send. Install the connector first, then connect the agent on the same computer. If you build on your laptop and run your apps on a Linux server of your own, set up the server and give your agent the note in Let your agent send your apps instead.

In Claude Code, install Ovenlight's plugin. It adds the MCP server and a skill that reads the guide whenever you ask to put an app on your phone:

/plugin marketplace add snowy-ghost/ovenlight

then:

/plugin install ovenlight@snowy-ghost

If you install the connector after the plugin, reconnect the ovenlight server in /mcp.

In Codex, on a Mac:

codex mcp add ovenlight -- \
  ~/Library/Application\ Support/ovenlight/bin/ovenlight mcp

On Linux, the path is ~/.local/state/ovenlight/bin/ovenlight (under $XDG_STATE_HOME when that's set). On Windows, in PowerShell:

codex mcp add ovenlight -- "$env:LOCALAPPDATA\ovenlight\bin\ovenlight.exe" mcp

To add only the MCP server to Claude Code, without the plugin's skill, on a Mac (on Linux, with the path above):

claude mcp add --scope user ovenlight -- \
  ~/Library/Application\ Support/ovenlight/bin/ovenlight mcp

On Windows, in PowerShell:

claude mcp add --scope user ovenlight -- "$env:LOCALAPPDATA\ovenlight\bin\ovenlight.exe" mcp

For another agent, add a local MCP server whose command is the connector's binary and whose one argument is mcp. Write the binary's path out in full, with no ~ or %LOCALAPPDATA%, such as /Users/you/Library/Application Support/ovenlight/bin/ovenlight on a Mac (What it installs has the path on each system).

Then ask your agent to put your app on your phone. It publishes and checks the app, and asks you for what only you can do, such as opening the login link and connecting your phone.

The MCP tools

Tool Arguments What it does
guide none The building guide, as ovenlight guide prints it
status none What ovenlight status --json shows: your apps, their addresses, whether each answers, sharing and updates
doctor none The checks ovenlight doctor runs, each problem with its fix
check_app slug The checks ovenlight check runs: how one app will look and work on the phone
publish name, with port for a new app, and an existing app's slug to update it or change its name Publishes an app or updates one, keeping the command the connector runs for it
unpublish slug Stops serving an app that isn't shareable, and stops its command
restart_app slug Restarts an app the connector runs, then lists the checks that fail or warn once it's back
logs slug, and lines (100 unless you say, up to 1000) The last lines of an app's output, for an app the connector runs
feedback_list app (a slug) and limit (20 unless you say, up to 200), both optional The notes and screenshots people sent from Ovenlight, newest first, each screenshot as the path of a PNG on the computer

The MCP server has no tool to share an app or invite anyone, or to set the command that runs an app. An agent with a shell sets an app's command with ovenlight publish --slug <slug> --dir '<folder>' --run '<command>', which you approve. Once the app is shareable, you change its command yourself, in a terminal. The MCP server lists what else takes a terminal, and why.

An agent with a shell can run the commands too. ovenlight doctor --json and ovenlight check <slug> --json print an object with ok, false when a check fails, and a list of checks. Each check has an id, the app it's about when there is one, a status of ok, warn or fail, and a message. Most problems also have a fix and an actor, which says who applies it: agent or person. A fix the person makes on a web page, such as the Tailscale admin console, comes with its url. Both commands exit 1 when a check fails.

Share an app

Your friend needs only Ovenlight. Their phone joins your tailnet as a guest device that can reach only the apps you invited them to, and none of your other devices.

Sharing uses tagged devices, which Tailscale's plans count: each shareable app is one, and so is each guest's phone, however many of your apps it holds. Three shared apps and ten guests use 13, for example. The free Personal plan includes a set number and charges for more (pricing). Removing a guest from all your apps deletes their device, so it stops counting. ovenlight guests says how many tagged devices your tailnet has and how many are Ovenlight's, and when each guest last opened each app.

First create an API access token in the Tailscale admin console, under Settings, Keys, and store it once; auth set asks for it, with input hidden:

ovenlight auth set

Tokens expire after at most 90 days; run auth set again with a new one. An OAuth client works too (auth set --oauth-client-id <id>); security.md lists the scopes it needs.

Then make each app shareable:

ovenlight publish --slug recipe-box --shareable

It asks you to confirm the owner, then shows the exact change it will make to your tailnet policy, and changes nothing until you type yes. Only one connector per tailnet can share apps, so it refuses while a node in the tailnet is tagged for an app and isn't one of this connector's own, and says what to do. security.md explains what it changes and what it refuses to touch.

Then, for each person:

ovenlight share recipe-box --to "Sam"

It prints a link, a QR code and a message to send. The other sharing commands:

  • ovenlight share garden-log --to "Sam" --existing: the same Sam, for another app or another device.
  • ovenlight guests: guests, the day each last opened each app, and open invites.
  • ovenlight revoke Sam: removes Sam from every app; add --app <slug> to remove just one.
  • ovenlight share --cancel <id>: withdraws an invite that hasn't been used, or a review invite with places left (devices that joined with it keep the app).
  • ovenlight share recipe-box --to "App Review" --review: an invite for an app store's review, which lasts 90 days and works on up to 10 devices (development.md).

Apart from a review invite, an invite link works once and expires after 7 days. Your friend's phone joins without waiting for your approval, even if your tailnet has device approval on (security.md says why). You can also share apps and remove guests in Ovenlight on your phone: touch and hold one of your apps, or open People & Sharing under the More button.

Revoking takes effect at once on the connector. As soon as the guest's phone notices it's no longer in your tailnet, it removes the app and its data. If the guest still has another of your apps, the phone stays in your tailnet and removes the revoked one about 3 minutes later.

With Send Feedback, in an app's menu, guests can send you a note and, unless they turn it off, a screenshot of the page. ovenlight feedback lists what arrives, and so does People & Sharing in Ovenlight; security.md has the limits.

Unpublishing a shareable app removes its guests, cancels its open invites, deletes its node from the tailnet (so publishing it again logs in a new node, which serves the app's existing HTTPS certificate) and offers to take its tags and guest rule out of your policy. When any of that fails or has to wait, unpublish says what's left and what to run, and security.md has the details.

Update

The connector updates itself. Releases up to 1.0.9 don't: run the install command once, and from then on it does.

It checks once a day and installs a new release two days after it first sees it, at a quiet moment in the night: once nothing has used your apps or the connector for 10 minutes. Each computer's night starts at its own time between 02:00 and 04:00 and ends at 05:00, local time.

  • A release that fixes a security problem in yours goes in at the first quiet 10 minutes, day or night.
  • On a computer that's never quiet, or asleep every night, updates go in busy or not: a security fix from the start of the next night, and any other update from the start of the first night a day after it was due. Each waits until the connector has been running and awake for 10 minutes.

Installing restarts the connector, so the apps it runs stop and open connections drop for a few seconds (about 10 on Linux, up to a minute on Windows). If the new release doesn't serve your apps again within 10 minutes, the connector goes back to the one before. Unpublishing an app before the new release serves it again also makes the connector go back. On Windows, so does running Stop or Restart from Start and stop in those 10 minutes, or shutting down or signing out of a work, school or domain account. A restart of your computer in those 10 minutes starts the 10 minutes over. A failed update, whether it went back or its download broke off, is tried again at the start of the next night, busy or not.

ovenlight status shows the last check, a release waiting its turn and when it installs, the last update, one that failed, and whether yours has a known security problem. security.md has what the connector checks before it installs anything. Only a release updates itself: from source, pull and run the install script again.

  • ovenlight update off stops installing updates. The connector still checks daily. ovenlight doctor warns while updates are off, says so when no check has worked for seven days, and still fails when your release has a known security problem. Turn updates back on at least once every four weeks, and leave them on until ovenlight status shows no release waiting: a connector that misses a change of release keys can't verify updates after that.
  • ovenlight update on turns them back on. A release already overdue then installs within the hour, busy or not.

When status or doctor says your release needs its install script, run the install command again. Install a release by hand covers checking a release yourself and staying on an older one.

Move to a new computer

The connector keeps everything in two folders: its config (the app list, the commands it runs for apps, and the API credential) and its state (node state, guests and invites). On a Mac they are ~/.config/ovenlight and ~/Library/Application Support/ovenlight; on Linux, ~/.config/ovenlight and ~/.local/state/ovenlight, or under $XDG_CONFIG_HOME and $XDG_STATE_HOME when those are set; on Windows, one folder holds both: %LOCALAPPDATA%\ovenlight. Back them up. To move, stop the old computer's connector if it still runs (the uninstall script stops it and keeps its data), copy the folders to the new computer and install the connector there: every app and guest carries over. An app the connector runs needs its project folder at the same path on the new computer, and the tools its command uses; otherwise publish it again from its new folder with --dir. After restoring an older backup, run ovenlight guests and revoke anyone who shouldn't be there: a guest removed after that backup can regain access, and anyone who joined after it needs a new invite.

Without a backup, first delete the old computer's app nodes in the admin console (Machines). Otherwise the new nodes come up named <slug>-1, and every saved tile and invite points at the old name. Then publish each app again and send new invites; existing guests lose access.

On a new or restored phone, your own apps come back once you tap Connect again. Apps shared with you don't move: ask for a new invite.

Uninstall

Uninstalling changes nothing in your tailnet. So first, with the connector still running, run ovenlight unpublish <slug> for each shareable app, which takes it out of your tailnet. Other app nodes stay in your tailnet, offline, until you remove them in the admin console, and the API credential works until you revoke it there.

~/ovenlight-connector/uninstall.sh

Add --purge to delete the connector's data too.

On Windows, run this in an administrator PowerShell, adding --purge to the second line to delete the data too:

cd $HOME\ovenlight-connector
powershell -ExecutionPolicy Bypass -File .\uninstall.ps1

If you installed from another folder, run the uninstall script beside the install script you ran (connector/uninstall.sh, or connector\uninstall.ps1 on Windows, in a clone).

uninstall.ps1 also takes the bin folder out of your PATH. On Linux, linger stays on, since other services of yours may need it: loginctl disable-linger turns it off.

--purge deletes node state, the config, the stored API credential, guest records, the feedback inbox, the policy backups, the updater's record and settings, and the logs. Without it, all of that stays, so a reinstall needs no new logins and keeps updates off if you turned them off. Neither deletes ~/ovenlight-connector, the unpacked release the uninstall script runs from. Afterwards, rm -r ~/ovenlight-connector deletes it, or on Windows, cd ~ and then Remove-Item -Recurse -Force $HOME\ovenlight-connector. The PATH line the one-line installer added stays in the profile it named, since other tools may use ~/.local/bin too.

Troubleshooting

Start with ovenlight doctor. It checks the computer's side and gives a fix for each problem it finds, such as an app on the wrong address or a node waiting for its login. ovenlight check <slug> looks at one app the way your phone will. When ovenlight isn't found, or neither finds the problem, look here.

  • Your terminal says command not found for ovenlight (on Windows, that it isn't recognized). Open a new terminal window: the install puts it on your PATH only for windows opened after it. If it's still not found on a Mac or Linux, add export PATH="$HOME/.local/bin:$PATH" to your shell's profile and open a new window. The profile is ~/.zshrc for zsh, and for bash, ~/.bashrc on Linux or ~/.bash_profile on a Mac (or the ~/.bash_login or ~/.profile bash reads in its place). Until then, run ~/.local/bin/ovenlight. On Windows, run & "$env:LOCALAPPDATA\ovenlight\bin\ovenlight.exe".
  • The connector isn't running. ovenlight status gives the command that starts it, the same as in Start and stop. If it starts and then stops again, the end of its log says why, or after a crash the stderr log beside it (the user journal on Linux). What it installs says where each is.
  • Your coding agent doesn't have Ovenlight's tools. If your laptop sends apps to a Linux server of your own, that's expected: the agent runs the connector over ssh (Let your agent send your apps), so don't install the connector on the laptop. Otherwise, install it and connect the agent, then start a new session (or in Claude Code, reconnect ovenlight in /mcp). A setting you added by hand must name the binary at its installed path. claude mcp list checks Claude Code's servers, and /mcp in Codex lists its tools.
  • Your app doesn't appear on your phone. The phone must be signed in to the same Tailscale account as the computer. To check, tap the More button in Ovenlight, then Settings, then Advanced, which shows the phone's account and tailnet. If it's another account, choose Sign Out in Settings and connect again with the right one. With device approval on in your tailnet, the phone and any app that signed in with a login link wait for you to approve them (Open it on your phone). An app you removed from the phone stays removed until you add it back (Unpublish).
  • Ovenlight says it can't reach one of your apps. The computer has to be on, awake and online (What the computer needs covers a Mac's login and a laptop's lid). Once it is, ovenlight status says whether the connector is running.
  • The phone shows an error page from the app, such as 400, 403 or Vite's "Blocked request", though it works at 127.0.0.1. Usually the app turns away its tailnet name, which the phone sends as Host with every request and as Origin with a form. Servers that check hosts need to allow .ts.net, as the building guide shows for Vite's dev server and Django. ovenlight check <slug> tests Host but sends no form. An app you already self-host may need more, which check can miss: Home Assistant answers 400 until it trusts the connector as a proxy. Follow the app's recipe in Apps you already run, or Any app for one without a recipe.
  • An app's output in ovenlight logs <slug> says command not found. A project's own tools, such as vite, aren't on the command's PATH. On a Mac or Linux, neither are tools set up only in ~/.zshrc or ~/.bashrc, such as nvm and pyenv. On Windows, a tool installed after you published the app is found once you run ovenlight restart <slug>. Keep an app running has the fixes.
  • A change doesn't show on the phone. Choose Reload in the app's menu on the phone, after ovenlight restart <slug> if the app's command doesn't reload when files change. For a new icon or color, run ovenlight publish --slug <slug> again, then touch and hold the app in Ovenlight and choose Refresh Icon (the building guide has the details).
  • Sharing fails with 401 Unauthorized, or doctor says the API credential doesn't work. Access tokens expire. ovenlight auth status --check tests the one you stored, and ovenlight auth set replaces it (Share an app).
  • A friend can't open an app you shared. See Support.

For apps on your own server, Your own server has more, for ssh and reaching the server.

Commands

ovenlight help lists these. To see a command's flags, add -h after it, as in ovenlight share -h. For auth and update, add it after both words: ovenlight auth set -h. Every command also takes --config <file> and --state <dir>, to use an app list and state directory other than the installed connector's (What it installs).

Apps

  • ovenlight new "<App Name>" [--dir <parent>] [--port <n>]: writes a starter app in a new folder under <parent>, on a free port from 20000 to 29999 unless --port picks one, and prints how to run and publish it.
  • ovenlight publish --port <n> --name "<App>" [--slug <slug>]: publishes an app, or updates the one with that slug (Publish an app).
  • ovenlight publish ... --run '<command>' [--dir <path>]: has the connector run the app and keep it running, and --run= stops that (Keep an app running).
  • ovenlight logs <slug> [-n <lines>] [-f]: the last 100 lines of output of an app the connector runs, or the last -n, fenced in <untrusted-...> tags for coding agents. With -f, it keeps printing new lines, fenced too, until Ctrl-C.
  • ovenlight restart <slug>: stops an app the connector runs and starts it again.
  • ovenlight unpublish <slug>: stops serving an app (Unpublish).
  • ovenlight status [--json]: whether the connector runs, each app's address and whether it answers, any login link, sharing and updates.
  • ovenlight doctor [--json]: checks the whole setup, with a fix for each problem.
  • ovenlight check <slug> [--json]: checks how one app will look and work on the phone, with a fix for each problem.
  • ovenlight guide: prints the building guide for the installed release.
  • ovenlight mcp: the MCP server for coding agents (Connect a coding agent).

Sharing

  • ovenlight auth set [--oauth-client-id <id>]: stores a Tailscale API access token, or an OAuth client's secret, asking for it with input hidden (Share an app).
  • ovenlight auth status [--check]: which credential is stored, and with --check, whether Tailscale's API still accepts it.
  • ovenlight auth remove: deletes the stored credential. Revoke it in the admin console too.
  • ovenlight publish --slug <slug> --shareable [--owner <login>] [--owner-label <name>]: makes an app shareable once you've confirmed the owner and typed yes to the policy change it shows (Share an app).
  • ovenlight share <slug> --to "<Name>": an invite link, a QR code and a message to send. For someone you already share with, add --existing, or give --person <ID> in place of --to. --review makes one for an app store's review.
  • ovenlight share --cancel <id>: withdraws an open invite.
  • ovenlight guests [--all] [--sync] [--json]: your guests, when each last opened each app, and open invites. --all adds removed guests, and --sync checks the tailnet's device list first.
  • ovenlight revoke <name | device ID | invite ID> [--app <slug>]: removes a guest from every app, or from one with --app (If something goes wrong says when to use a device or invite ID). --person <ID>, from ovenlight guests, in place of a name removes every device and open invite of that person.
  • ovenlight feedback [--app <slug>] [-n <count>] [--json]: the last 20 notes and screenshots people sent, or the last -n (0 for all), with what guests wrote and every page address fenced in <untrusted-...> tags for coding agents.
  • ovenlight setup-sharing --owner <login> [--owner-label <name>]: changes the recorded owner and how guests see you. --rollback in their place restores the policy saved before the last change, and --remove <slug> takes an app you no longer share out of the policy (Later changes).

The connector itself

  • ovenlight version: the connector's version.
  • ovenlight update off and ovenlight update on: stop installing updates, and start again (Update).
  • ovenlight update now --index <file>: installs the release a canary index names right away. It's for testing a release (development.md).
  • ovenlight run: runs the connector. Its background service does this, so use the commands in Start and stop instead.

Install by hand

Without the one-line installer, download your computer's archive, unpack it and run its install script.

On a Mac, in Terminal:

mkdir -p ~/ovenlight-connector && cd ~/ovenlight-connector
curl -fsSL https://downloads.ovenlight.app/connector/latest/ovenlight-connector-macos.tar.gz |
  tar xz --strip-components 1
./install.sh

On Linux, use linux-amd64 or linux-arm64 in place of macos. If install.sh prints an export PATH line, add that line to the profile install.sh names and open a new terminal window. On Linux, if it prints a sudo loginctl enable-linger command, run that too, so the connector keeps serving with nobody logged in and after a restart.

On Windows, signed in as the account that will own the apps, open PowerShell with Run as administrator and run:

mkdir $HOME\ovenlight-connector -Force | Out-Null; cd $HOME\ovenlight-connector
curl.exe -fsSL https://downloads.ovenlight.app/connector/latest/ovenlight-connector-windows-amd64.zip -o connector.zip
tar -xf connector.zip --strip-components 1
powershell -ExecutionPolicy Bypass -File .\install.ps1

On an Arm PC, use windows-arm64 in place of windows-amd64. The ovenlight command works in PowerShell windows opened after the install. ovenlight.exe, install.ps1 and uninstall.ps1 are signed by Snowy Ghost LLC. To check one, open its Properties and look under Digital Signatures.

To download in a browser instead, use these links: macOS (its binary is notarized), Linux amd64 and arm64, and Windows x64 and Arm. These links always point to the latest release. Unpack the archive if your browser hasn't, then run ./install.sh in its ovenlight-connector-<version> folder. On Windows, right-click that folder in File Explorer and choose Copy as path. Then, in PowerShell opened with Run as administrator, type cd and a space, paste the path, press Enter, and run powershell -ExecutionPolicy Bypass -File .\install.ps1.

Check a download first

To check a download before installing it, fetch it with the index the connector updates from (latest.json), the index's signature (latest.json.sig) and the release keys from this repository (allowed_signers). Then check that a release key signed the index, and that the index names the archive's SHA-256. On a Mac or Linux:

mkdir -p ~/ovenlight-connector && cd ~/ovenlight-connector
archive=ovenlight-connector-macos.tar.gz
base=https://downloads.ovenlight.app/connector
curl -fsSL --remote-name-all "$base/latest.json" "$base/latest.json.sig" "$base/latest/$archive"
curl -fsSLO https://raw.githubusercontent.com/snowy-ghost/ovenlight/master/docs/allowed_signers
ssh-keygen -Y verify -f allowed_signers -I team@snowyghost.com -n ovenlight-latest \
  -s latest.json.sig < latest.json
grep -F "\"$archive\": \"$(shasum -a 256 "$archive" | cut -d ' ' -f 1)\"" latest.json

On Linux, put linux-amd64 or linux-arm64 in place of macos, and if shasum is missing, sha256sum in place of shasum -a 256. The first check prints Good "ovenlight-latest" signature. On macOS 13, or Linux with OpenSSH before 8.2, it says unknown or unsupported key type instead: check the download on a Mac with macOS 14 or later, or Linux with OpenSSH 8.2 or later. The second check prints the index, which names the release after "latest" and its serial after "serial". If both checks pass, and the serial is no lower than the one ovenlight status shows for the index your connector holds, unpack the archive and run its install script, in the same window:

tar xzf "$archive" --strip-components 1
./install.sh

On Windows, in PowerShell opened with Run as administrator:

mkdir $HOME\ovenlight-connector -Force | Out-Null; cd $HOME\ovenlight-connector
$archive = 'ovenlight-connector-windows-amd64.zip'
$base = 'https://downloads.ovenlight.app/connector'
curl.exe -fsSL --remote-name-all "$base/latest.json" "$base/latest.json.sig" "$base/latest/$archive"
curl.exe -fsSLO https://raw.githubusercontent.com/snowy-ghost/ovenlight/master/docs/allowed_signers
cmd /c 'ssh-keygen -Y verify -f allowed_signers -I team@snowyghost.com -n ovenlight-latest -s latest.json.sig < latest.json'
(Get-Content latest.json -Raw | ConvertFrom-Json).archives.$archive -eq (Get-FileHash $archive).Hash
(Get-Content latest.json -Raw | ConvertFrom-Json).serial

On an Arm PC, use windows-arm64 in place of windows-amd64. The first check needs OpenSSH 8.9 or later (ssh -V shows yours): an older one can't read the release key that signs with Touch ID, and says unknown or unsupported key type. If yours is older, check the download on a Mac with macOS 14 or later, or Linux with OpenSSH 8.2 or later. If the first check prints Good "ovenlight-latest" signature, the second True, and the last the index's serial, no lower than the one ovenlight status shows, unpack the archive and run its install script, in the same window:

tar -xf $archive --strip-components 1
powershell -ExecutionPolicy Bypass -File .\install.ps1

If a check fails, don't install the download. For five minutes after a release comes out, the server may still hand out some of the last release's files, so a check that fails then is worth trying again. A serial that stays lower than the one ovenlight status shows means the server is sending an older index than your connector holds, which the other checks pass too: report it to team@snowyghost.com.

Install a release by hand

A release that changes what the install script sets up can't come as an update: status and doctor say when yours needs its install script. To install it, or the latest release at any time, run the install command again. To also check that the index is no older than the one your connector holds, take the verified steps yourself instead. Either replaces the files in ~/ovenlight-connector, and the install script reinstalls the connector.

Each release from 1.0.1 on also stays in its own folder, such as https://downloads.ovenlight.app/connector/1.0.9/, with its checksums signed by the keys in allowed_signers as it was at that release's tag. The release you updated from is in ovenlight status's Last update line. To install an older release and stay on it, first run ovenlight update off, or the connector may move to the newest again. Check it on a Mac or Linux with its version in place of 1.0.9, and the Linux changes above:

mkdir -p ~/ovenlight-connector && cd ~/ovenlight-connector
release=1.0.9
archive=ovenlight-connector-macos.tar.gz
base="https://downloads.ovenlight.app/connector/$release"
curl -fsSL --remote-name-all "$base/$archive" "$base/SHA256SUMS" "$base/SHA256SUMS.sig"
curl -fsSL -o allowed_signers "https://raw.githubusercontent.com/snowy-ghost/ovenlight/connector-v$release/docs/allowed_signers"
ssh-keygen -Y verify -f allowed_signers -I team@snowyghost.com -n file \
  -s SHA256SUMS.sig < SHA256SUMS
shasum -a 256 -c --ignore-missing SHA256SUMS
tar tzf "$archive" | cut -d / -f 1 | sort -u

On Windows, check it in an administrator PowerShell, with its version in place of 1.0.9 (a release after 1.0.9 needs OpenSSH 8.9 or later, as above):

mkdir $HOME\ovenlight-connector -Force | Out-Null; cd $HOME\ovenlight-connector
$release = '1.0.9'
$archive = 'ovenlight-connector-windows-amd64.zip'
$base = "https://downloads.ovenlight.app/connector/$release"
curl.exe -fsSL --remote-name-all "$base/$archive" "$base/SHA256SUMS" "$base/SHA256SUMS.sig"
curl.exe -fsSL -o allowed_signers "https://raw.githubusercontent.com/snowy-ghost/ovenlight/connector-v$release/docs/allowed_signers"
cmd /c 'ssh-keygen -Y verify -f allowed_signers -I team@snowyghost.com -n file -s SHA256SUMS.sig < SHA256SUMS'
[bool](Select-String -SimpleMatch "$((Get-FileHash $archive).Hash.ToLower())  $archive" SHA256SUMS)
tar -tf $archive | ForEach-Object { ($_ -split '/')[0] } | Sort-Object -Unique

If the first check prints Good "file" signature, the second the archive's name with OK (True on Windows), and the third only the release's own folder, such as ovenlight-connector-1.0.9 (an archive's name carries no version), unpack and install it as the verified steps do, in the same window.

From source

This needs Git and Go 1.21 or later (brew install go, your package manager's golang, or go.dev/dl); the go command fetches the version connector/go.mod asks for when yours is older. On macOS and Linux, clone this repository and run its install script:

git clone https://github.com/snowy-ghost/ovenlight
cd ovenlight
connector/install.sh

If go env GOTOOLCHAIN says local, as on Fedora and Red Hat Enterprise Linux, go won't fetch a newer one: run GOTOOLCHAIN=auto connector/install.sh instead.

On Windows, signed in as the account that will own the apps, install Git and Go with winget install Git.Git and winget install GoLang.Go, open a new PowerShell window so it finds them, and run git clone https://github.com/snowy-ghost/ovenlight $HOME\ovenlight. Then open an administrator PowerShell (right-click it and choose Run as administrator) and run:

cd $HOME\ovenlight
powershell -ExecutionPolicy Bypass -File connector\install.ps1

A binary you build isn't code-signed, so Smart App Control, when it's on, may refuse to run it; install.ps1 says so when the connector doesn't start.

What it installs

Running the install script again installs the connector it came with, even an older one, and restarts it, keeping your apps, node state and update settings. It installs:

macOS Linux Windows
Runs as launchd agent com.snowyghost.ovenlight.connector systemd user service ovenlight scheduled task ovenlight
Binary ~/Library/Application Support/ovenlight/bin/ovenlight ~/.local/state/ovenlight/bin/ovenlight %LOCALAPPDATA%\ovenlight\bin\ovenlight.exe
App list ~/.config/ovenlight/config.json ~/.config/ovenlight/config.json %LOCALAPPDATA%\ovenlight\config.json
State directory ~/Library/Application Support/ovenlight/ ~/.local/state/ovenlight/ %LOCALAPPDATA%\ovenlight\
Log ~/Library/Logs/ovenlight.log ~/.local/state/ovenlight/ovenlight.log %LOCALAPPDATA%\ovenlight\ovenlight.log

On Linux, $XDG_CONFIG_HOME and $XDG_STATE_HOME take the place of ~/.config and ~/.local/state when set. At 20 MB the log moves to ovenlight.log.1 beside it, replacing the one before. What bypasses the log, such as a crash, goes to ovenlight.stderr.log beside the log on macOS and Windows, and to the user journal on Linux (journalctl --user -eu ovenlight). The updater keeps a few files of its own in the state directory and the bin folder (security.md).

On macOS and Linux, install.sh links ovenlight in ~/.local/bin, never replacing a file it didn't make. When ~/.local/bin isn't on your PATH, it prints the export PATH line to add to your shell profile, which the one-line installer adds for you. On Windows, install.ps1 adds the bin folder to your PATH. Either way, ovenlight works in terminal windows opened after that.

Start and stop

ovenlight status says whether the connector runs. On macOS:

  • Start: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.snowyghost.ovenlight.connector.plist || launchctl kickstart gui/$(id -u)/com.snowyghost.ovenlight.connector (when the connector is loaded already, bootstrap says it failed, and kickstart starts it)
  • Stop, until you next log in: launchctl bootout gui/$(id -u)/com.snowyghost.ovenlight.connector
  • Restart: launchctl kickstart -k gui/$(id -u)/com.snowyghost.ovenlight.connector

On Linux:

  • Start: systemctl --user enable --now ovenlight
  • Stop: systemctl --user disable --now ovenlight
  • Restart: systemctl --user restart ovenlight

On Windows, in an administrator PowerShell. Stopping disables the task too, since a task that's only stopped starts again within a minute.

  • Start: Enable-ScheduledTask ovenlight; Start-ScheduledTask ovenlight
  • Stop: Disable-ScheduledTask ovenlight; Stop-ScheduledTask ovenlight
  • Restart: Stop-ScheduledTask ovenlight; Start-ScheduledTask ovenlight