# hub-agent

On-site controller agent. Runs on any computer in the venue and bridges local hardware — receipt printers, cash drawers, barcode scanners — to the Appmint cloud over WebSocket. Bring your own box; the agent doesn't care what kind it is.

## Architecture

```
Tablet UI ──HTTPS──► Appengine ──WebSocket──► hub-agent (your box) ──USB──► Hardware
                       cloud                  this app                  printer / scanner / drawer
```

- The agent maintains a long-lived WS to appengine.
- Server forwards device commands; agent executes them locally.
- Scanner reads + drawer-opened events flow up the WS to the cloud, which fans them out to subscribed UIs.

## What it runs on

Anything from the last decade with USB and a network port:

| Class                  | Examples                                                              | Notes                                                                                          |
|------------------------|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
| **Mini PC** *(recommended for production)* | Beelink S12 (N95/N100), Intel NUC, generic N100 sticks ($150-280)     | NVMe / eMMC instead of SD card, real PSU, fanned, dual-NIC, runs forever                       |
| **Laptop / desktop you already own**       | Any Win10+ / macOS 12+ / Ubuntu 20+ box                               | Cheapest path — if it boots and has USB, you're done                                           |
| **Single-board computer**                  | Raspberry Pi 4/5, Orange Pi 5, Rock Pi, Banana Pi                     | Cheap, but SD cards die from constant writes — use industrial cards or boot off USB SSD        |
| **Whatever**                               | Old back-office tower, repurposed thin client, NAS with Docker        | Linux is Linux. If `node --version` works, the agent works.                                    |

Sizing: idle Node process, ~80MB RAM, no disk hot path. **2GB RAM and 8GB free disk is plenty.** Don't overthink it.

What we don't do: sell you a box. Customer picks what fits their venue, we run on it.

## Install

One command, either platform. It works out the host, downloads the release,
verifies its checksum, bootstraps Node, drops the agent under a standard install
dir, registers a service that auto-starts on boot, runs the setup wizard, and
starts the service.

```bash
curl -sSL https://web.appmint.space/hub-agent/install | sudo bash
```

Windows cannot pipe to bash — in **Terminal (Administrator)**:

```powershell
irm https://web.appmint.space/hub-agent/install.ps1 | iex
```

Send clients to <https://web.appmint.space/hub-agent/>, which carries the same
commands plus the current version and checksum.

Re-running any of them upgrades in place and keeps the existing config.

### Installing a specific version

The published installers are pinned: the one at `/hub-agent/install` installs
the release it shipped with, so the same command cannot quietly produce two
different agents. To install an older build, use its versioned URL:

```bash
curl -sSL https://web.appmint.space/hub-agent/0.1.1/install | sudo bash
```

### Per-platform detail

These are what the one-liner above dispatches to. Run them directly only when
working from a clone.

#### Linux (any Debian/Ubuntu/Armbian box, including Pi / Orange Pi)

```bash
sudo ./scripts/install.sh
```

Installs to `/opt/appmint-hub`, config at `/etc/appmint-hub/config.json`, systemd unit `appmint-hub.service`. Logs: `journalctl -u appmint-hub -f`.

#### macOS

```bash
sudo ./scripts/install-mac.sh
```

Installs to `/opt/appmint-hub`, config at `/etc/appmint-hub/config.json`, LaunchDaemon `io.appmint.hub`. Logs: `tail -f /var/log/appmint-hub.out.log`. Stop: `sudo launchctl bootout system /Library/LaunchDaemons/io.appmint.hub.plist`.

Requires Homebrew (for Node install). If you already have Node 20+, no Homebrew needed.

#### Windows

Right-click `scripts/install.bat` → **Run as administrator**.

Or in an elevated PowerShell:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\install-windows.ps1
```

Installs to `%ProgramFiles%\AppmintHub`, config at `%ProgramData%\AppmintHub\config.json`, Windows service `AppmintHub`. Logs: Event Viewer → Applications and Services → AppmintHub. Status: `Get-Service AppmintHub`. Stop: `Stop-Service AppmintHub`.

Requires winget (Windows 11+) or Chocolatey for Node install. If Node 20+ is already installed, neither is needed.

## Config

`/etc/appmint-hub/config.json` (Linux/macOS) or `%ProgramData%\AppmintHub\config.json` (Windows):

```jsonc
{
  "hubName": "bar-back-office",
  "apiKey": "...",
  "serverUrl": "wss://appengine.appmint.io",
  "endpoints": [
    {
      "name": "kitchen-printer",
      "kind": "printer",
      "capabilities": ["print", "drawer"],
      "config": {
        "interface": "printer:/dev/usb/lp0",
        "driver": "epson",
        "width": 48
      }
    },
    {
      "name": "bar-scanner",
      "kind": "scanner",
      "capabilities": [],
      "config": { "path": "/dev/hidraw0" }
    }
  ]
}
```

The wizard asks for hub name + API key + org id on first run (server URL defaults to `wss://appengine.appmint.io` — override with `APPMINT_HUB_SERVER` env var if pointing at a non-prod server). To get a key:

1. Open the **Business Made** app → **Operations → Devices** (sidebar) → switch to the **Hubs** tab.
2. Click **Add hub**, give it a `name` (URL-safe id, e.g. `bar-1`) + display name, save.
3. The new hub appears in the grid. Click **Edit** on it; an **API key** block now shows.
4. Click **Generate** — the server mints a key and returns the cleartext **once**. **Copy it now**; the server only stores the hash and can't show it again. Click **Regenerate** later if you ever lose it (the previous key is invalidated).
5. Paste that key into the setup wizard prompt.

Override individual values via env vars (handy for systemd `Environment=`):

- `APPMINT_HUB_CONFIG` — path to config.json
- `APPMINT_HUB_NAME`, `APPMINT_HUB_API_KEY`, `APPMINT_HUB_SERVER`
- `APPMINT_HUB_LOG_LEVEL` — `debug | info | warn | error`

## Run

```bash
# As a service (production)
sudo systemctl enable --now appmint-hub          # Linux
sudo launchctl kickstart -k system/io.appmint.hub  # macOS
Start-Service AppmintHub                         # Windows

# Logs
journalctl -u appmint-hub -f                     # Linux
tail -f /var/log/appmint-hub.out.log             # macOS
# Event Viewer → Applications and Services → AppmintHub  (Windows)

# Foreground (dev)
npx ts-node src/index.ts
```

## Hardware notes

### Receipt printer + cash drawer

The drawer plugs into the printer's **DK / cash-drawer port** with an RJ11/RJ12 cable (same physical 6P jack — RJ12 just has all 6 pins wired). The printer plugs into your box over USB. The agent kicks the drawer by sending an ESC/POS command to the printer; the printer pulses 12-24V down the drawer line.

```
your box ──USB──► printer ──RJ11/12──► drawer
```

Don't try to wire a drawer directly to USB or GPIO — the solenoid needs 12-24V at ~1A, which neither will provide. Either go through a printer (the normal path) or use a powered USB cash-drawer trigger box if you don't have a printer.

If the drawer doesn't fire on the first kick:
- Check the cable is at least 6P4C (4-conductor). True 2-conductor RJ11 phone cable won't carry the drawer signal.
- Try drawer 2 instead of drawer 1 in the driver — Epson uses pins 4-5 for drawer 1 and 2-5 for drawer 2; cables can be wired either way.

### Permissions (Linux)

The agent reads `/dev/hidraw*` (scanners) and writes `/dev/usb/lp*` (printers). The systemd unit runs as `root` for simplicity. If you want to drop privileges:

```bash
sudo usermod -aG input,lp appmint
# Edit /etc/systemd/system/appmint-hub.service:
# User=appmint
# Group=appmint
```

## Capabilities

| Capability | Status   | Notes                                                  |
|------------|----------|--------------------------------------------------------|
| `print`    | ready    | ESC/POS via node-thermal-printer (USB or network)      |
| `drawer`   | ready    | Kicked through the printer's RJ12 port                 |
| `scanner`  | ready    | HID keyboard mode; reads `/dev/hidraw*`                |
| `weigh`    | TODO     | USB scale support not yet wired                        |
| `display`  | TODO     | Customer-facing pole displays                          |
| `kds`      | TODO     | Kitchen display push                                   |
| `charge`   | n/a here | Stripe Terminal integrates direct from server          |
| `page`     | n/a here | SMS-as-pager runs server-side via Twilio               |

## Update

```bash
# Linux
cd /opt/appmint-hub && sudo git pull && sudo npm ci && sudo npm run build && sudo npm prune --omit=dev
sudo systemctl restart appmint-hub

# macOS
cd /opt/appmint-hub && sudo git pull && sudo npm ci && sudo npm run build && sudo npm prune --omit=dev
sudo launchctl kickstart -k system/io.appmint.hub

# Windows (elevated PowerShell)
cd "$env:ProgramFiles\AppmintHub"; git pull; npm ci; npm run build; npm prune --omit=dev
Restart-Service AppmintHub
```

Or just re-run the installer for your platform — it preserves config and updates everything else.

## Uninstall

```bash
# Linux
sudo systemctl disable --now appmint-hub
sudo rm /etc/systemd/system/appmint-hub.service
sudo rm -rf /opt/appmint-hub /etc/appmint-hub
sudo systemctl daemon-reload

# macOS
sudo launchctl bootout system /Library/LaunchDaemons/io.appmint.hub.plist
sudo rm /Library/LaunchDaemons/io.appmint.hub.plist
sudo rm -rf /opt/appmint-hub /etc/appmint-hub

# Windows (elevated PowerShell)
Stop-Service AppmintHub
node "$env:ProgramFiles\AppmintHub\scripts\win-service.js" uninstall
Remove-Item -Recurse -Force "$env:ProgramFiles\AppmintHub", "$env:ProgramData\AppmintHub"
```
