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.
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
Quick Start
Pull the image from DockerHub and run it:
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:
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:
The server starts on port 8080 by default. Verify it is running:
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:
Then update docker-compose.yml:
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:
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:
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:
- Set
EXPO_PUSH_ENABLED=true in docker-compose.yml
- 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+):
cd server
npm install
npm run build
JWT_SECRET=your-secret-here npm start
Updating
To update to a newer version:
docker pull skyaiops/yespapa-server:latest
docker compose up -d
Or pin a specific version:
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 project (free tier works)
- The Supabase CLI (
npx supabase)
Setup Steps
1. Create a Supabase Project
Go to 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:
# 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:
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:
- Go to Authentication > Providers in your Supabase dashboard
- Enable Anonymous Sign-ins
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:
# The daemon reads these from ~/.yespapa/yespapa.db config table
# You can reinitialize with: yespapa uninstall && yespapa init
Schema Reference
hosts Table
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
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
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:
-- 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
- Use TLS — Always use HTTPS in production. For the Docker server, place it behind a reverse proxy (nginx, Caddy, Traefik) with TLS termination
- Set a strong JWT secret — For the Docker server, use a randomly generated string of at least 32 characters
- Restrict network access — Limit who can reach the server port. Use firewall rules or bind to a private network
- Set up monitoring — Watch for unusual patterns in the commands table
- Back up your database — Especially host registrations and push tokens
- 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:
- A REST API for CRUD on
hosts, commands, grace_periods
- Real-time subscriptions for
commands and grace_periods changes
- A push notification endpoint (Expo Push API compatible)
- Token-based authentication (JWT or equivalent)