Skip to main content
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.

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:
Or use Docker Compose. Create a docker-compose.yml:
Then start it:
The server starts on port 8080 by default. Verify it is running:

Configuration

Environment variables are set in docker-compose.yml: Generate a strong JWT secret:
Then update docker-compose.yml:

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:

Connecting YesPaPa to Your Docker Server

During yespapa init, provide your server URL and select the selfhosted server type:
If your server is behind a reverse proxy with TLS, use the https:// URL instead. If already initialized, you can reinitialize:

API Endpoints

The Docker server exposes these endpoints:

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+):

Updating

To update to a newer version:
Or pin a specific version:

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:

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:
This creates three tables with Row Level Security (RLS): 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:
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:
Or if already initialized, update the config directly:

Schema Reference

hosts Table

commands Table

grace_periods Table

RLS Policies

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

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
  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)