# Shellular host, operator notes

Optional. Nothing installs this automatically, no dependency references it, and a workstation without it is a normal workstation. It exists so [shellular.dev](https://shellular.dev)'s mobile app can drive this machine: full terminals, a file browser, port listing and proxying, and coding agents over ACP.

The identical command surface exists in `owly-agent`. Both repositories drive **one** daemon, because the vendor allows one host per machine and user.

## Why the unit template lives here and not in `systemd/user/`

`dev-packages/infrastructure-ansible/.../systemd-user-units.yml` finds every `*.service`, `*.service.j2`, `*.timer` and `*.timer.j2` in `dev-packages/root-package-scripts/systemd/user/`, templates it into `~/.config/systemd/user/` and runs `enable --now` on each, for every developer the `development_environment_local` role touches.

A `shellular-host.service.j2` placed there would therefore not be an optional integration. It would be a mandatory 200-300 MB daemon on every workstation in the company, paired to nobody. **The template lives here as `shellular-host.service.tmpl`, where nothing discovers it**, and `install.sh` substitutes its placeholders with `sed` at install time. Do not move it, and do not rename it to `.j2`.

## Commands

| Command                    | What it does                                                                                         |
| -------------------------- | ---------------------------------------------------------------------------------------------------- |
| `dowl shellular:install`   | Install the pinned version, write the unit, start it, print the QR code, seed the account allowlist  |
| `dowl shellular:start`     | Start or restart the host; `--takeover` moves ownership to this repository                           |
| `dowl shellular:stop`      | Stop the host, leaving the unit enabled                                                              |
| `dowl shellular:restart`   | Alias for `start`                                                                                    |
| `dowl shellular:status`    | Ownership, browse root, version against the pin, memory, allowlist, relay                            |
| `dowl shellular:logs`      | Follow the journal                                                                                   |
| `dowl shellular:pair`      | Print the pairing QR code                                                                            |
| `dowl shellular:clients`   | Review, approve and delete paired devices (vendor CLI passthrough)                                   |
| `dowl shellular:users`     | Manage the account allowlist (vendor CLI passthrough)                                                |
| `dowl shellular:update`    | Move the pin to a newer release after a smoke test; `--to <version>` picks one, including a rollback |
| `dowl shellular:uninstall` | Stop, disable and remove the unit and the version trees; `--purge` also removes `~/.shellular`       |

`status` reports pin drift and an empty allowlist as **findings** and exits non-zero.

## Ownership

The owner is the repository recorded in the unit's `WorkingDirectory`, and it decides exactly one thing: whose `shellular/pin.json` the running host obeys. It is not about what the phone can see. The file browser is rooted at `$HOME`, so both repositories are visible without any handover.

`dowl shellular:start --takeover` rewrites `WorkingDirectory` and restarts. A restart drops open phone terminals and ACP agent sessions. In day-to-day use you should never need it.

## What a paired device can actually do

**A paired, approved device has whatever access your Unix user has.** `--dir` confines the file browser and nothing else; terminals are `node-pty` login shells with no chroot, no namespace and no path filter. Verified from the shipped code, not inferred.

Pairing a device is equivalent to handing out a shell on this workstation. The controls that actually bind are the pairing key (32 bytes, exchanged through the QR code, treat the QR as a credential), device approval (`requires-approval` always; `always-allow` is never written by this tooling), and the account allowlist, which `install` seeds and which must stay non-empty.

Note that this repository's MCP daemon on `127.0.0.1:8080` is reachable from the phone like anything else on loopback, because Shellular proxies to local services on request.

## The unit's environment

Exactly six variables: `HOME`, `PATH`, `SHELL`, `TERM`, `LANG` and `CLAUDE_CODE_ENTRYPOINT`. No `EnvironmentFile`, no `.env`, ever, because a phone terminal inherits the daemon's environment.

`CLAUDE_CODE_ENTRYPOINT=shellular` is the one entry that is not plumbing. The Agent SDK, which is how Shellular drives Claude Code over ACP, stamps `sdk-ts` on sessions it starts unless the variable is already set, and both Claude Code resume pickers filter `sdk-ts` out. Without this line, sessions started from the phone never appear on the desktop. The value must not be `cli`: Claude Code keeps any inherited entrypoint except that one, which it rewrites to `sdk-cli` in SDK mode, and `sdk-cli` is filtered exactly like `sdk-ts`.

## Version pinning

`shellular/pin.json` holds an exact release plus the registry's own `dist.integrity`, verified before the downloaded tree is trusted. The vendor ships roughly weekly and the licence already changed once from MIT to AGPL-3.0-only, so a range would be a moving target.

The **in-app update button is inert by design**: the host refuses a client update request when it is not running under pm2, which is one of the reasons we supervise it with systemd instead of the vendor's own `shellular start`. `dowl shellular:update` is the update path, and it smoke-tests the undocumented `__daemon` entrypoint we depend on before repointing anything.

## Licence and vendor trust

AGPL-3.0-only. We run it unmodified and internally, and neither distribute it nor offer it as a network service, so section 13 is not triggered. That changes if anyone vendors, patches or embeds the CLI.

The relay is operated by the vendor and stated to forward ciphertext only. We have not audited that, and the vendor holds neither SOC 2 nor ISO 27001. `--server` accepts any relay and the relay is open source, so self-hosting is the exit. **Revisit that before this is used routinely for customer-facing work from this repository.**
