# Local server management with `appctl.sh`

`appctl.sh` is a one-stop control script for running the OpenConnector Node backend on a
local machine or Linux server. It wraps the common lifecycle commands — start, stop, restart,
status, logs, backups, runtime-data operations — with sensible defaults that read the same
`.env` file the Node server uses.

The script lives at the repository root. Make sure it is executable (`chmod +x appctl.sh`)
once after cloning.

## Quick start

```bash
./appctl.sh start       # start API server in the background
./appctl.sh status      # PID, memory, health, driver, disk
./appctl.sh logs 0      # tail logs in real time (Ctrl-C to exit)
./appctl.sh stop        # graceful shutdown
```

`start` waits up to 30 seconds for `/health` to return 200 before reporting success. If the
health probe fails it prints the last 20 log lines and exits non-zero.

## Commands

| Command                  | Description                                                                  |
| ------------------------ | ---------------------------------------------------------------------------- |
| `start`                  | Start the API server in the background (`nohup`, PID written to `.pid`).     |
| `stop`                   | Graceful stop via SIGTERM (server closes its DB pool first); force-kill after 10 s. |
| `restart`                | `stop` + `start`.                                                            |
| `status`                 | Running state, PID, port, uptime, RSS, live `/health` probe, driver, DB, disk usage. |
| `logs [N]`               | Show last `N` lines of `open-connector.log` (default 100). `0` follows the log. |
| `health`                 | One-shot probe of `http://localhost:${PORT}/health`.                         |
| `dev`                    | Foreground dev mode — API + web console together (same as `npm run dev`). Refuses to start if a background instance is running. |
| `build`                 | Full build: `npm install` (if missing) + provider registry + web console (`dist/web/`) + typecheck. |
| `build:web`             | Build only the web console into `dist/web/` (`npm run build --workspace web`). |
| `fix-check`              | Run `npm run fix-check` (oxlint + oxfmt + typecheck).                        |
| `data <subcmd> [args]`   | Delegate to `scripts/runtime-data.ts`. See below.                            |
| `env`                    | Print resolved environment with secrets masked.                              |
| `backup`                 | Driver-aware database backup. See below.                                     |
| `help`                   | Show the full usage banner.                                                  |

### `data` — runtime data management

Wraps `scripts/runtime-data.ts`. Same commands work for SQLite, PostgreSQL, and MySQL once
`OOMOL_CONNECT_DB_DRIVER` and `OOMOL_CONNECT_DATABASE_URL` are set.

```bash
./appctl.sh data reset --yes                              # wipe all runtime rows (irreversible)
./appctl.sh data rotate-key --plain                       # re-encrypt secrets as plaintext
OOMOL_CONNECT_NEW_ENCRYPTION_KEY=new-secret \
  ./appctl.sh data rotate-key                             # rotate to a new AES-GCM key
```

### `backup` — driver-aware backup

Backups land in `./backups/` and the last 7 are retained.

| Driver     | Mechanism                                                                                          |
| ---------- | ------------------------------------------------------------------------------------------------- |
| `sqlite`   | Pauses the server, copies `connect.sqlite` plus any `-wal` / `-shm` sidecar, then resumes.         |
| `postgres` | `pg_dump --no-owner --clean --if-exists`. Requires `pg_dump` on `PATH` (`brew install libpq`).    |
| `mysql`    | `mysqldump --single-transaction --no-tablespaces`. Requires `mysqldump` (`brew install mysql-client`). |

```bash
./appctl.sh backup
# → backups/connect_20260719_213000.sql
```

## Build pipeline

OpenConnector is a **full-stack workspace**, not a pure frontend or pure backend project.
Three separate build steps exist with different purposes:

| Step                          | What it produces                                  | When it runs                                    |
| ----------------------------- | ------------------------------------------------- | ----------------------------------------------- |
| `npm install` + `postinstall` | `node_modules/` and a fresh `src/providers/registry.generated.ts` | Once after clone, and whenever dependencies change. |
| Web console build             | `dist/web/` (compiled React served by Node)       | Required for the bundled web UI in production.  |
| Backend TypeScript            | (nothing — Node runs `.ts` directly via native type-stripping) | Never; `src/` is the runtime source.            |

> The backend never needs a JS build. Node 22+ strips TypeScript types at load time, so
> `node src/server/index.ts` runs the source directly. `npm run typecheck` only validates
> types — it does not emit JavaScript.

### `build` — one command for everything

```bash
./appctl.sh build     # = npm install (if missing) + ensure-generated + vite build + typecheck
```

Use this before the first production start, after pulling code that changed providers, or
whenever the web console source changes. The server reads `dist/web/` at boot via
`registerStaticRoutes`; without it the console is unavailable (the API itself still works).

### `start` runs prerequisites on demand

`./appctl.sh start` runs `scripts/ensure-generated.ts` automatically (mirroring `npm start`).
If `node_modules/` is missing it runs `npm install` first. You do not need to remember these
manually — `start` is safe to invoke on a fresh clone.

### `dev` mode

`./appctl.sh dev` runs the Vite dev server (HMR) **alongside** the API, so changing
`web/src/*` hot-reloads without a rebuild. Use this for iterative frontend work; use
`build` + `start` for production-parity verification.

## Environment resolution

`appctl.sh` mirrors the precedence used by `src/server/load-env.ts`:

1. Shell environment (always wins).
2. `${INSTALL_DIR}/.env`
3. `${INSTALL_DIR}/.env.local`
4. `/etc/default/open-connector` (system-level, optional)

So `PORT=8080 ./appctl.sh start` overrides `.env` for a single run, while editing `.env`
changes every subsequent start.

## Common variables

| Variable                       | Default            | Purpose                                                  |
| ------------------------------ | ------------------ | -------------------------------------------------------- |
| `PORT`                         | `3000`             | HTTP port.                                               |
| `HOST`                         | `127.0.0.1`        | Bind address.                                            |
| `OOMOL_CONNECT_ORIGIN`         | `http://localhost:${PORT}` | Public origin used in OAuth redirects.          |
| `OOMOL_CONNECT_DATA_DIR`       | `./data`           | Transit files + SQLite path.                             |
| `OOMOL_CONNECT_DB_DRIVER`      | `sqlite`           | `sqlite` \| `postgres` \| `mysql`.                       |
| `OOMOL_CONNECT_DATABASE_URL`   | unset              | Required for `postgres` and `mysql`.                     |
| `OOMOL_CONNECT_DB_POOL_MAX`    | `10`               | Pool size for `postgres` / `mysql`.                      |
| `OOMOL_CONNECT_ENCRYPTION_KEY` | unset              | AES-GCM passphrase for credential encryption.           |
| `OOMOL_CONNECT_ADMIN_TOKEN`    | unset              | Admin UI / `/api/*` bearer token.                        |
| `OOMOL_CONNECT_RUNTIME_TOKEN`  | unset              | `/v1/*` bootstrap token.                                 |
| `NODE_BIN`                     | auto-detected      | Override the Node.js binary path. Requires Node 22+.     |

Secrets are masked in `./appctl.sh env` and `./appctl.sh status` output; database URLs are
printed with the password stripped.

## Background vs foreground

- **Background mode** (`./appctl.sh start`) writes `open-connector.pid` + `open-connector.log`
  at the repo root. Use `status` / `logs` / `stop` to manage it.
- **Foreground mode** (`./appctl.sh dev`) replaces the shell with the dev process — useful for
  iterative development with the web console. It refuses to start if a background instance is
  already running on the same port.

## Files written by the script

```
./open-connector.pid      # background PID (start / restart)
./open-connector.log      # stdout + stderr (background mode)
./backups/                # driver-aware backup output (retention: last 7)
```

## Linux server setup

`appctl.sh` is self-contained — there is no `init` subcommand to install a systemd unit. For a
production deployment, wrap it with your process supervisor of choice:

```ini
# /etc/systemd/system/open-connector.service
[Service]
WorkingDirectory=/opt/open-connector
ExecStart=/opt/open-connector/appctl.sh start
ExecStop=/opt/open-connector/appctl.sh stop
Restart=on-failure
# Pull env from /etc/default/open-connector (auto-loaded by appctl.sh)
EnvironmentFile=/etc/default/open-connector

[Install]
WantedBy=multi-user.target
```

For quick local-Linux usage without systemd, just call `./appctl.sh start` directly.

## See also

- [configuration.md](./configuration.md) — full environment reference, including how schema
  migrations run on startup for SQLite / PostgreSQL / MySQL.
- [credentials.md](./credentials.md) — how credentials are encrypted at rest.
- [quickstart.md](./quickstart.md) — first-time setup walkthrough.
