> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yespapa.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Hosting

> Run your own YesPaPa remote server with Docker or Supabase

YesPaPa provides a free default remote server, but you can self-host your own for maximum control and security. The remote server is only needed for the Outer Ring (mobile app push notifications and one-tap approvals). The Inner Ring (TOTP gate) works fully offline.

This guide covers two options:

* **Option 1: Docker** — A standalone Node.js server you run yourself. Recommended for self-hosting.
* **Option 2: Supabase** — The hosted cloud option (or self-hosted Supabase). This is the current default.

***

## Option 1: Docker (Recommended for Self-Hosting)

The `server/` directory contains a standalone YesPaPa remote server built with Express, WebSocket, SQLite, and JWT authentication. It implements the full remote server protocol and is the reference self-hosted implementation.

### Prerequisites

* [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/)

### Quick Start

Pull the image from DockerHub and run it:

```bash theme={null}
docker run -d \
  --name yespapa-server \
  -p 8080:8080 \
  -e JWT_SECRET=$(openssl rand -base64 32) \
  -v yespapa-data:/app/data \
  --restart unless-stopped \
  skyaiops/yespapa-server:latest
```

Or use Docker Compose. Create a `docker-compose.yml`:

```yaml theme={null}
services:
  yespapa-server:
    image: skyaiops/yespapa-server:latest
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - JWT_SECRET=change-me-to-a-random-string
      - DATABASE_PATH=/app/data/yespapa.db
      - EXPO_PUSH_ENABLED=false
    volumes:
      - yespapa-data:/app/data
    restart: unless-stopped

volumes:
  yespapa-data:
```

Then start it:

```bash theme={null}
docker compose up -d
```

The server starts on port 8080 by default. Verify it is running:

```bash theme={null}
curl http://localhost:8080/health
# Expected: {"status":"ok","version":"0.1.0"}
```

### Configuration

Environment variables are set in `docker-compose.yml`:

| Variable            | Default                        | Description                                                                                     |
| ------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------- |
| `PORT`              | `8080`                         | Port the server listens on                                                                      |
| `JWT_SECRET`        | `change-me-to-a-random-string` | **Must be changed.** Secret used to sign JWT tokens. Use a long random string (32+ characters). |
| `DATABASE_PATH`     | `/app/data/yespapa.db`         | Path to the SQLite database inside the container                                                |
| `EXPO_PUSH_ENABLED` | `false`                        | Set to `true` to enable push notifications via Expo Push API                                    |

Generate a strong JWT secret:

```bash theme={null}
openssl rand -base64 32
```

Then update `docker-compose.yml`:

```yaml theme={null}
environment:
  - JWT_SECRET=your-generated-secret-here
```

### Data Persistence

All data is stored in a Docker volume named `yespapa-data`, mapped to `/app/data` inside the container. This volume persists across container restarts and rebuilds. The SQLite database (`yespapa.db`) lives in this volume.

To back up your data:

```bash theme={null}
docker compose cp yespapa-server:/app/data/yespapa.db ./yespapa-backup.db
```

### Connecting YesPaPa to Your Docker Server

During `yespapa init`, provide your server URL and select the `selfhosted` server type:

```
Remote server URL: http://your-server-ip:8080
Remote server type: selfhosted
```

If your server is behind a reverse proxy with TLS, use the `https://` URL instead.

If already initialized, you can reinitialize:

```bash theme={null}
yespapa uninstall && yespapa init
```

### API Endpoints

The Docker server exposes these endpoints:

| Endpoint               | Description                                                       |
| ---------------------- | ----------------------------------------------------------------- |
| `GET /health`          | Health check, returns server status and version                   |
| `/api/auth/*`          | Authentication (register, login, token refresh)                   |
| `/api/hosts/*`         | Host registration and management                                  |
| `/api/commands/*`      | Command approval queue (create, list, approve, deny)              |
| `/api/grace-periods/*` | Grace period management                                           |
| `/api/push/*`          | Push notification registration                                    |
| `/ws`                  | WebSocket endpoint for real-time command and grace period updates |

### Push Notifications

Push notifications are disabled by default. To enable them:

1. Set `EXPO_PUSH_ENABLED=true` in `docker-compose.yml`
2. Restart the container: `docker compose restart`

Push notifications use the Expo Push API, so the server needs outbound internet access to reach `https://exp.host/--/api/v2/push/send`.

### Running Without Docker

You can also run the server directly with Node.js (v18+):

```bash theme={null}
cd server
npm install
npm run build
JWT_SECRET=your-secret-here npm start
```

### Updating

To update to a newer version:

```bash theme={null}
docker pull skyaiops/yespapa-server:latest
docker compose up -d
```

Or pin a specific version:

```bash theme={null}
docker pull skyaiops/yespapa-server:0.2.0
# Update the image tag in docker-compose.yml, then:
docker compose up -d
```

### Docker Troubleshooting

#### Container fails to start

* Check logs: `docker compose logs yespapa-server`
* Ensure port 8080 is not in use: `lsof -i :8080`
* Verify the Docker volume exists: `docker volume ls | grep yespapa-data`

#### "Connection refused" from YesPaPa daemon

* Ensure the server is running: `docker compose ps`
* If the daemon is on the same machine, use `http://localhost:8080` or `http://host.docker.internal:8080`
* If on a different machine, ensure the server port is accessible (check firewalls)
* Verify with: `curl http://your-server-ip:8080/health`

#### Database errors or corruption

* Stop the server: `docker compose down`
* Back up the current database from the volume
* Remove the volume to start fresh: `docker volume rm server_yespapa-data`
* Restart: `docker compose up -d`

#### WebSocket connection drops

* If behind a reverse proxy (nginx, Caddy), ensure WebSocket upgrade is configured
* For nginx, add to your location block:
  ```
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  ```

***

## Option 2: Supabase (Hosted / Cloud)

Supabase is the current default remote backend. You can use the hosted Supabase platform (free tier available) or self-host the full Supabase stack.

### What You Need

* A [Supabase](https://supabase.com) project (free tier works)
* The Supabase CLI (`npx supabase`)

### Setup Steps

#### 1. Create a Supabase Project

Go to [supabase.com/dashboard](https://supabase.com/dashboard) and create a new project. Note your:

* **Project URL**: `https://<your-project-ref>.supabase.co`
* **Anon Key**: Found in Settings > API > Project API keys > `anon` `public`
* **Service Role Key**: Found in Settings > API > Project API keys > `service_role` (used by Edge Functions only, never exposed to clients)

#### 2. Apply Database Migrations

From the repo root:

```bash theme={null}
# Link to your Supabase project
npx supabase link --project-ref <your-project-ref>

# Apply migrations
npx supabase db push
```

This creates three tables with Row Level Security (RLS):

| Table           | Purpose                                             |
| --------------- | --------------------------------------------------- |
| `hosts`         | Registered machines (name, fingerprint, push token) |
| `commands`      | Intercepted commands pending approval               |
| `grace_periods` | Active auto-bypass tokens                           |

All tables have RLS policies ensuring users can only access their own data.

#### 3. Enable Realtime

The migrations automatically add `commands` and `grace_periods` to the Supabase Realtime publication. Verify in your dashboard under Database > Replication that both tables are listed.

#### 4. Deploy Edge Functions

The `push_notification` Edge Function sends push notifications via Expo when commands are inserted:

```bash theme={null}
npx supabase functions deploy push_notification
```

The function requires these environment variables (set automatically in Supabase):

* `SUPABASE_URL`
* `SUPABASE_SERVICE_ROLE_KEY`

#### 5. Enable Anonymous Auth

YesPaPa uses Supabase anonymous auth so the daemon can authenticate without user accounts:

1. Go to Authentication > Providers in your Supabase dashboard
2. Enable **Anonymous Sign-ins**

#### 6. Configure YesPaPa to Use Your Supabase Server

During `yespapa init`, when prompted for the remote server:

```
Remote server URL [https://remote.yespapa.io]: https://your-project.supabase.co
Remote server key [default]: your-anon-key-here
```

Or if already initialized, update the config directly:

```bash theme={null}
# The daemon reads these from ~/.yespapa/yespapa.db config table
# You can reinitialize with: yespapa uninstall && yespapa init
```

### Schema Reference

#### `hosts` Table

```sql theme={null}
create table public.hosts (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users not null default auth.uid(),
  host_name text not null,
  host_fingerprint text unique not null,
  push_token text,
  last_seen_at timestamptz default now(),
  created_at timestamptz default now() not null
);
```

#### `commands` Table

```sql theme={null}
create table public.commands (
  id text primary key,
  host_id uuid references public.hosts(id) on delete cascade not null,
  command_display text not null,
  justification text,
  status text not null default 'pending'
    check (status in ('pending', 'approved', 'denied')),
  totp_code text,
  denial_message text,
  timeout_seconds integer default 0,
  created_at timestamptz default now() not null,
  resolved_at timestamptz
);
```

#### `grace_periods` Table

```sql theme={null}
create table public.grace_periods (
  id text primary key,
  host_id uuid references public.hosts(id) on delete cascade not null,
  scope text not null,
  expires_at timestamptz not null,
  hmac_signature text not null,
  created_at timestamptz default now() not null
);
```

### RLS Policies

All tables use the same pattern — users can only access rows they own:

```sql theme={null}
-- hosts: direct ownership
create policy "users_own_hosts" on public.hosts
  for all using (auth.uid() = user_id);

-- commands: owned via host
create policy "users_own_commands" on public.commands
  for all using (
    host_id in (select id from public.hosts where user_id = auth.uid())
  );

-- grace_periods: owned via host
create policy "users_own_grace" on public.grace_periods
  for all using (
    host_id in (select id from public.hosts where user_id = auth.uid())
  );
```

### Supabase Troubleshooting

#### "Remote connection failed" during init

* Verify the URL is correct and includes `https://`
* Check that the anon key is the `anon` key, not the `service_role` key
* Ensure anonymous auth is enabled in your Supabase project

#### Push notifications not arriving

* Verify the Edge Function is deployed: `npx supabase functions list`
* Check Edge Function logs: `npx supabase functions logs push_notification`
* Ensure the mobile app has a valid push token (check `hosts.push_token` in the database)

#### Commands stuck as "pending"

* Check Realtime is enabled for the `commands` table
* Verify the daemon is connected: `yespapa status` should show "Remote: configured"
* Check daemon logs: `cat ~/.yespapa/daemon.log`

***

## Security Considerations

These apply regardless of which backend you use.

### Zero-Trust Architecture

Even when self-hosting, the remote server is **never trusted** for security decisions:

* TOTP codes are validated **locally** by the daemon, not by the server
* Grace tokens are HMAC-signed with the TOTP seed, which **never leaves the machine**
* The server is a relay — it forwards approval requests and responses, but cannot forge approvals
* If the server is compromised, the worst case is denial of service (commands stuck pending). Attackers cannot approve commands

### Recommended Hardening

1. **Use TLS** — Always use HTTPS in production. For the Docker server, place it behind a reverse proxy (nginx, Caddy, Traefik) with TLS termination
2. **Set a strong JWT secret** — For the Docker server, use a randomly generated string of at least 32 characters
3. **Restrict network access** — Limit who can reach the server port. Use firewall rules or bind to a private network
4. **Set up monitoring** — Watch for unusual patterns in the commands table
5. **Back up your database** — Especially host registrations and push tokens
6. **Rotate secrets** — Periodically rotate JWT secrets (Docker) or API keys (Supabase) and update client configs

## Alternative Backends

The Docker server (`server/` directory) is the reference self-hosted implementation. It provides the full remote server protocol using Express, WebSocket, SQLite, and JWT authentication. For most self-hosting scenarios, this is all you need.

If you need a different stack, the remote server protocol is intentionally simple — a REST API with WebSocket subscriptions. You could reimplement it with:

* **Self-hosted Supabase** — Run the full Supabase stack on your own infrastructure using the migrations in `supabase/`
* **PostgreSQL + PostgREST + pg\_notify** — Same REST+realtime pattern without Supabase
* **Firebase** — Firestore + Cloud Functions + FCM
* **Custom server** — Any language/framework that provides REST + WebSocket + push notifications

The daemon needs:

1. A REST API for CRUD on `hosts`, `commands`, `grace_periods`
2. Real-time subscriptions for `commands` and `grace_periods` changes
3. A push notification endpoint (Expo Push API compatible)
4. Token-based authentication (JWT or equivalent)
