### Install and Run Development Server Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Commands to install project dependencies for the web workspace and start the development server. After running, access the application at http://localhost:5173. ```bash npm install --workspace @rpow/web npm --workspace @rpow/web run dev # Open http://localhost:5173 — should see header + tagline. Ctrl-C. ``` -------------------------------- ### Local Development Setup Source: https://github.com/frkrueger/rpow/blob/main/README.md Installs dependencies, builds shared packages, and runs tests for local development. ```bash docker run --rm -d --name rpow-pg -e POSTGRES_PASSWORD=p -p 55432:5432 postgres:16 npm install npm run build --workspace @rpow/shared npm test ``` -------------------------------- ### Install and Test Server Package Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Commands to install the server package dependencies and run its tests. This is used to confirm the initial setup and identify any immediate issues, such as missing modules. ```bash npm install --workspace @rpow/server npm --workspace @rpow/server test ``` -------------------------------- ### Install and Enable USDC Indexer Service Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/specs/2026-05-11-amm-usdc-bridge-design.md Copy the systemd service file, reload the daemon, enable and start the service, and follow its logs. This installs the rpow-usdc-indexer unit. ```bash sudo cp ops/systemd/rpow-usdc-indexer.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now rpow-usdc-indexer sudo journalctl -u rpow-usdc-indexer -f ``` -------------------------------- ### Install Dependencies and Build Trivia Frontend Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-trivia-frontend.md Installs all project dependencies and then builds the web-trivia workspace. Assumes you are in the repository root for the install step. ```bash cd /Users/fredkrueger/rpow && npm install cd apps/web-trivia && npm run build ``` -------------------------------- ### Install and Enable rpow-server Systemd Service Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Copies the systemd unit file to the VPS, reloads the systemd daemon, and enables the rpow-server service to start on boot. ```bash local$ scp /Users/fredkrueger/rpow/ops/systemd/rpow-server.service ubuntu@15.204.254.192:/tmp/ && \ ssh ubuntu@15.204.254.192 'sudo install -m 644 /tmp/rpow-server.service /etc/systemd/system/rpow-server.service && \ sudo systemctl daemon-reload && \ sudo systemctl enable rpow-server' ``` -------------------------------- ### Start VPS Application Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/specs/2026-05-07-fly-to-vps-migration-design.md Starts the rpow-server application on the VPS, making it live but not yet accessible via DNS. ```bash systemctl start rpow-server ``` -------------------------------- ### Freelottery Entry Start Integration Tests Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-12-freelottery-slice-2.md Write failing integration tests for the `/start` endpoint. This includes setup for test applications, user login, and the start function itself. ```typescript import { describe, it, expect, afterEach, vi } from 'vitest'; import { makeTestApp } from './helpers.js'; import { signSession, SESSION_COOKIE } from '../src/session.js'; import * as xVerify from '../src/gladiator/xVerify.js'; async function login(ctx: Awaited>, email: string, opts?: { xHandle?: string }) { await ctx.pool.query( `INSERT INTO users (email) VALUES ($1) ON CONFLICT (email) DO NOTHING`, [email], ); if (opts?.xHandle) { await ctx.pool.query( `UPDATE users SET x_handle = $1, x_handle_verified_at = now(), x_avatar_url = $2 WHERE email = $3`, [opts.xHandle, `https://unavatar.io/twitter/${opts.xHandle}`, email], ); } const token = signSession({ email }, 'x'.repeat(32), 3600); return `${SESSION_COOKIE}=${token}`; } async function start(ctx: Awaited>, cookie: string) { return ctx.app.inject({ method: 'POST', url: '/api/freelottery/entry/start', headers: { cookie, 'content-type': 'application/json' }, payload: {}, }); } async function verify(ctx: Awaited>, cookie: string, tweet_url: string) { return ctx.app.inject({ method: 'POST', url: '/api/freelottery/entry/verify', headers: { cookie, 'content-type': 'application/json' }, payload: { tweet_url }, }); } const TODAY = new Date().toISOString().slice(0, 10); describe('POST /api/freelottery/entry/start', () => { let cleanup: (() => Promise) | null = null; afterEach(async () => { vi.restoreAllMocks(); if (cleanup) await cleanup(); cleanup = null; }); it('401 unauthenticated', async () => { const ctx = await makeTestApp({ freelotteryStartUtcDate: TODAY }); cleanup = ctx.cleanup; const res = await ctx.app.inject({ method: 'POST', url: '/api/freelottery/entry/start', headers: { 'content-type': 'application/json' }, payload: {}, }); expect(res.statusCode).toBe(401); }); it('404 when feature is disabled (no start date)', async () => { const ctx = await makeTestApp(); cleanup = ctx.cleanup; const cookie = await login(ctx, 'a@b.com', { xHandle: 'alice' }); const res = await start(ctx, cookie); expect(res.statusCode).toBe(404); expect(res.json().error).toBe('FEATURE_DISABLED'); }); it('409 BIND_REQUIRED when user has no x_handle', async () => { const ctx = await makeTestApp({ freelotteryStartUtcDate: TODAY }); cleanup = ctx.cleanup; const cookie = await login(ctx, 'a@b.com'); const res = await start(ctx, cookie); expect(res.statusCode).toBe(409); expect(res.json().error).toBe('BIND_REQUIRED'); }); it('200 returns code, tweet_intent_url, expires_at, day_utc and upserts the code row', async () => { const ctx = await makeTestApp({ freelotteryStartUtcDate: TODAY }); cleanup = ctx.cleanup; const cookie = await login(ctx, 'a@b.com', { xHandle: 'alice' }); const res = await start(ctx, cookie); expect(res.statusCode).toBe(200); const body = res.json(); expect(body.code).toMatch(/^\d{6}$/); expect(body.tweet_intent_url).toMatch(/^https:\/\/twitter\.com\/intent\/tweet\?text=/); expect(body.day_utc).toMatch(/^\d{4}-\d{2}-\d{2}$/); expect(body.expires_at).toMatch(/^\d{4}-\d{2}-\d{2}T19:00:00\.000Z$/); // Row exists in DB. const { rows } = await ctx.pool.query( `SELECT code FROM freelottery_codes WHERE account_email = 'a@b.com' AND day_utc = $1`, [body.day_utc], ); expect(rows[0]?.code).toBe(body.code); }); it('409 ALREADY_ENTERED when user already has an entry for today', async () => { const ctx = await makeTestApp({ freelotteryStartUtcDate: TODAY }); cleanup = ctx.cleanup; const cookie = await login(ctx, 'a@b.com', { xHandle: 'alice' }); // Pre-seed an entry for today. await ctx.pool.query( `INSERT INTO freelottery_entries (account_email, day_utc, x_handle, tweet_url, ticket_count, balance_base_units_at_entry) VALUES ('a@b.com', $1, 'alice', 'https://twitter.com/alice/status/1', 1, 0)`, [TODAY], ); const res = await start(ctx, cookie); expect(res.statusCode).toBe(409); expect(res.json().error).toBe('ALREADY_ENTERED'); }); }); ``` -------------------------------- ### Install Backup Script on VPS Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Copies the local backup script to the VPS and installs it as `/usr/local/bin/rpow-backup` with appropriate permissions. ```bash local$ scp /Users/fredkrueger/rpow/ops/backup.sh ubuntu@15.204.254.192:/tmp/ && \ ssh ubuntu@15.204.254.192 'sudo install -m 750 -o root -g root /tmp/backup.sh /usr/local/bin/rpow-backup' ``` -------------------------------- ### Start RPOW2 Web Client Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Starts the RPOW2 web client in development mode. ```bash npm --workspace @rpow/web run dev ``` -------------------------------- ### Running PostgreSQL with Docker Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Command to start a PostgreSQL container for testing purposes. Ensure Docker is installed and running. ```bash docker run --rm -d --name rpow-pg -e POSTGRES_PASSWORD=p -p 55432:5432 postgres:16 sleep 3 TEST_DATABASE_URL=postgres://postgres:p@localhost:55432/postgres npm --workspace @rpow/server test -- db.test.ts docker stop rpow-pg ``` -------------------------------- ### Install and Build Solana Bridge Package Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-08-srpow-wrap.md Commands to install all workspace dependencies and build the `@rpow/solana-bridge` package. ```bash npm install --workspaces --include-workspace-root --ignore-scripts npm run build --workspace @rpow/solana-bridge ``` -------------------------------- ### Install Solana Dependencies Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-08-srpow-wrap.md Installs necessary Solana web3.js, spl-token, and base58 libraries for the web application. Use this command to add these packages to your project. ```bash npm install --workspace apps/web --save \ @solana/web3.js@^1.91.0 @solana/spl-token@^0.4.6 bs58@^6.0.0 --ignore-scripts ``` -------------------------------- ### Start RPOW Development Server Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-10-api-keys.md Starts the RPOW development server locally. Requires Postgres to be running. ```bash # In one shell: start dev server npm run dev --workspace @rpow/server ``` -------------------------------- ### Install PostgreSQL 16 Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Installs PostgreSQL 16 and its client package using apt-get. This command assumes the PGDG repository has already been added. ```bash local$ ssh ubuntu@15.204.254.192 'sudo apt-get install -y postgresql-16 postgresql-client-16' ``` -------------------------------- ### Start Trivia Frontend Dev Server Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-trivia-frontend.md Starts the development server for the web-trivia workspace in the background. Ensure you are in the repository root. ```bash cd /Users/fredkrueger/rpow && npm run dev --workspace @rpow/web-trivia & ``` -------------------------------- ### Install Dependencies with npm Workspaces Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-12-rpow-chatrooms-slice-1.md Command to install all project dependencies, including those for workspaces, ensuring the new web-chat app is recognized and its packages are installed. ```bash npm install --workspaces --include-workspace-root ``` -------------------------------- ### Install Root Dependencies and Commit Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Command to install root development dependencies and commit the initial workspace skeleton to version control. ```bash cd /Users/fredkrueger/rpow npm install git add . git commit -m "chore: workspace skeleton + tsconfig" ``` -------------------------------- ### Install and Build Shared Package Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Commands to install the shared package using npm and then build it to verify that the output files are generated correctly. Expects 'packages/shared/dist/protocol.js' and 'dist/protocol.d.ts' to exist. ```bash npm install --workspace @rpow/shared npm --workspace @rpow/shared run build ``` -------------------------------- ### Start RPOW Backup Service Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Starts the RPOW backup service on the remote VPS. Use this to confirm nightly backups are running correctly after the cutover. ```bash local$ ssh ubuntu@15.204.254.192 'sudo systemctl start rpow-backup.service' ``` -------------------------------- ### Open Trivia DB API Request Example Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/specs/2026-05-11-trivia-pvp-design.md Example of a GET request to the Open Trivia DB API to fetch 50 multiple-choice questions. ```http GET https://opentdb.com/api.php?amount=50&type=multiple ``` -------------------------------- ### Server Entrypoint with Database Initialization Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Loads environment variables, creates a PostgreSQL pool, runs database migrations, builds the Fastify application with the pool, and starts the server. Ensure environment variables like `DATABASE_URL` and `PORT` are set. ```typescript import { parseEnv } from './env.js'; import { createPool, runMigrations } from './db.js'; import { buildApp } from './buildApp.js'; const env = parseEnv(); const pool = createPool(env.DATABASE_URL); await runMigrations(pool); const app = await buildApp({ pool }); await app.listen({ host: '0.0.0.0', port: env.PORT }); app.log.info(`rpow2 server listening on :${env.PORT}`); ``` -------------------------------- ### Start Development Server (Web) Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-send-return-url.md Run the web development server to test the React components. Ensure this is running before proceeding with other steps. ```bash cd apps/web && npm run dev ``` -------------------------------- ### Run Full Build Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-12-freelottery-slice-1.md Compile the server and build all web applications. Verify that the server compiles successfully and all web apps build to their respective 'dist/' folders without errors. ```bash npm run build ``` -------------------------------- ### Install Systemd Timer and Service on VPS Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Copies the systemd service and timer files to the VPS and enables/starts the timer. This command assumes the files are already present locally. ```bash local$ scp /Users/fredkrueger/rpow/ops/systemd/rpow-backup.service \ /Users/fredkrueger/rpow/ops/systemd/rpow-backup.timer \ ubuntu@15.204.254.192:/tmp/ && \ ssh ubuntu@15.204.254.192 'sudo install -m 644 /tmp/rpow-backup.service /etc/systemd/system/ && \ sudo install -m 644 /tmp/rpow-backup.timer /etc/systemd/system/ && \ sudo systemctl daemon-reload && \ sudo systemctl enable --now rpow-backup.timer' ``` -------------------------------- ### Initialize SRPOW Mint Keys Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/specs/2026-05-08-srpow-wrap-design.md Run this script locally to initialize keys for creating the SRPOW mint. Record the BRIDGE_PUBKEY and BRIDGE_KEYPAIR_BASE58 for subsequent steps. ```bash create-srpow-mint.ts --init-keys ``` -------------------------------- ### GET /api/trivia/matches/:id: Returns active match for challenger Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-trivia-slice-3.md Use this endpoint to retrieve details of a specific trivia match by its ID. This example shows the response for the challenger, including the match state and whether they have answered. ```typescript it('returns ACTIVE state for the challenger', async () => { const ctx = await makeTestApp(); cleanup = ctx.cleanup; const { matchId, challengerCookie } = await setupMatch(ctx); const res = await ctx.app.inject({ method: 'GET', url: `/api/trivia/matches/${matchId}`, headers: { cookie: challengerCookie }, }); expect(res.statusCode).toBe(200); const m = res.json().match; expect(m.state).toBe('ACTIVE'); expect(m.question).toBe('capital of France?'); expect(m.offerer_choice_idx).toBeNull(); expect(m.challenger_choice_idx).toBeNull(); }); ``` -------------------------------- ### Local Development Setup for rpow2 Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Instructions for setting up the local development environment for rpow2, including Docker, Node.js, and running the server and web applications with low difficulty for testing. ```bash docker run --rm -d --name rpow-pg -e POSTGRES_PASSWORD=p -p 55432:5432 postgres:16 npm install npm run build --workspace @rpow/shared npm test ``` ```bash DATABASE_URL=postgres://postgres:p@localhost:55432/postgres \ RESEND_API_KEY=re_test EMAIL_FROM='rpow2 ' \ SESSION_SECRET=$(openssl rand -hex 32) \ MAGIC_LINK_BASE_URL=http://localhost:8080 WEB_ORIGIN=http://localhost:5173 \ DIFFICULTY_BITS=20 DIFFICULTY_FLOOR=8 \ RPOW_TEST_INBOX=true \ $(node -e 'const {generateKeypair}=require("./apps/server/dist/signing.js"); const k=generateKeypair(); console.log("RPOW_SIGNING_PRIVATE_KEY_HEX="+k.privateHex+" RPOW_SIGNING_PUBLIC_KEY_HEX="+k.publicHex);') \ npm --workspace @rpow/server run dev ``` ```bash npm --workspace @rpow/web run dev ``` -------------------------------- ### Freelottery Status Endpoint Implementation Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-12-freelottery-slice-1.md Implements the GET /api/freelottery/status endpoint to provide current freelottery schedule information. It calculates and returns details like start date, total days, prize units, draw hour, current day index, next draw time, and whether the lottery has ended. ```typescript import type { FastifyInstance } from 'fastify'; import { getDayUtc, dayIndex, nextDrawAt, hasEnded } from '../../freelottery/schedule.js'; export async function statusRoutes(app: FastifyInstance) { app.get('/api/freelottery/status', async () => { const cfg = app.config; const sched = { startUtcDate: cfg.freelotteryStartUtcDate, totalDays: cfg.freelotteryTotalDays, drawHourUtc: cfg.freelotteryDrawHourUtc, }; const now = new Date(); const currentDayUtc = getDayUtc(now, sched); return { enabled: !!cfg.freelotteryStartUtcDate, startUtcDate: cfg.freelotteryStartUtcDate ?? null, totalDays: cfg.freelotteryTotalDays, prizeBaseUnits: cfg.freelotteryPrizeBaseUnits.toString(), drawHourUtc: cfg.freelotteryDrawHourUtc, dayIndex: currentDayUtc ? dayIndex(currentDayUtc, sched) : null, currentDayUtc: currentDayUtc ?? null, nextDrawAt: nextDrawAt(now, sched)?.toISOString() ?? null, ended: hasEnded(now, sched), }; }); } ``` -------------------------------- ### Install Node.js 22 Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Installs Node.js using apt-get after the NodeSource repository has been added. This command installs the 'nodejs' package, which includes npm. ```bash local$ ssh ubuntu@15.204.254.192 'sudo apt-get install -y nodejs' ``` -------------------------------- ### Server Entry Point Implementation Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md The main entry point for the server application. It imports buildApp, sets the port from environment variables or defaults to 8080, starts the Fastify server, and logs a startup message. ```typescript import { buildApp } from './buildApp.js'; const port = Number(process.env.PORT ?? 8080); const app = await buildApp(); await app.listen({ host: '0.0.0.0', port }); app.log.info(`rpow2 server listening on :${port}`); ``` -------------------------------- ### Start Postgres and RPOW2 Server Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Launches a PostgreSQL database container and then builds and runs the RPOW2 server with development configurations. This includes generating signing keys and setting various environment variables for database connection, email, session secrets, and difficulty. ```bash docker run --rm -d --name rpow-pg -e POSTGRES_PASSWORD=p -p 55432:5432 postgres:16 sleep 3 # Generate a server keypair for the dev environment: node -e 'const {generateKeypair}=require("./apps/server/dist/signing.js"); const k=generateKeypair(); console.log("RPOW_SIGNING_PRIVATE_KEY_HEX="+k.privateHex+"\nRPOW_SIGNING_PUBLIC_KEY_HEX="+k.publicHex);' > .keys.env # Build server first so the keygen works (npm run build) npm run build --workspace @rpow/server # Now start with low difficulty to actually feel mining at desk speed DATABASE_URL=postgres://postgres:p@localhost:55432/postgres \ RESEND_API_KEY=re_test EMAIL_FROM='rpow2 ' \ SESSION_SECRET=$(openssl rand -hex 32) \ MAGIC_LINK_BASE_URL=http://localhost:8080 \ DIFFICULTY_BITS=20 DIFFICULTY_FLOOR=8 \ WEB_ORIGIN=http://localhost:5173 \ $(cat .keys.env | xargs) \ npm --workspace @rpow/server run dev ``` -------------------------------- ### Start Development Server (Server) Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-send-return-url.md Run the server-side development server to enable API endpoints like /send. This is necessary for the web application to function correctly. ```bash cd apps/server && npm run dev ``` -------------------------------- ### Server Startup Sequence Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/specs/2026-05-08-srpow-wrap-design.md Details the server startup sequence, including reconciliation of pending wraps before listening. ```typescript - `apps/server/src/server.ts` — call `reconcilePendingWraps()` after migrations, before `listen`. ``` -------------------------------- ### Install Playwright and Dependencies Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Installs Playwright as a development dependency for the web workspace and downloads the Chromium browser with its dependencies. ```bash npm install -D @playwright/test --workspace @rpow/web npx playwright install --with-deps chromium ``` -------------------------------- ### Create Fly App, Set Secrets, and Deploy Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Creates a new Fly.io app, sets necessary secrets including database URL, API keys, and signing keys, then deploys the application. ```bash flyctl apps create rpow2-server flyctl secrets set \ DATABASE_URL="$NEON_URL" \ RESEND_API_KEY="$RESEND_KEY" \ EMAIL_FROM='rpow2 ' \ SESSION_SECRET=$(openssl rand -hex 32) \ RPOW_SIGNING_PRIVATE_KEY_HEX="$PRIV" \ RPOW_SIGNING_PUBLIC_KEY_HEX="$PUB" \ DIFFICULTY_BITS=28 \ DIFFICULTY_FLOOR=20 \ --app rpow2-server flyctl deploy --app rpow2-server flyctl status --app rpow2-server ``` -------------------------------- ### Deploy Bundle and Configure Directory Source: https://github.com/frkrueger/rpow/blob/main/ops/server/LONGSHOT_DEPLOY.md Deploy the built web application bundle to the server and ensure the target directory has the correct ownership. This involves creating the directory if it doesn't exist and setting ownership to the ubuntu user. ```bash ssh ubuntu@15.204.254.192 "sudo mkdir -p /opt/rpow-longshot && sudo chown -R ubuntu:ubuntu /opt/rpow-longshot" rsync -avz --delete apps/web-longshot/dist/ ubuntu@15.204.254.192:/opt/rpow-longshot/dist/ ``` -------------------------------- ### Environment Variable Example Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md An example of an environment variable for the web application, specifying the base URL for API requests. ```env VITE_API_BASE_URL=http://localhost:8080 ``` -------------------------------- ### Verify PostgreSQL 16 installation and service Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Confirms that the PostgreSQL service is active and checks the installed version of the psql client. ```bash local$ ssh ubuntu@15.204.254.192 'sudo systemctl is-active postgresql && psql --version' ``` -------------------------------- ### Create Test Application Instance Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Sets up a test application with a unique database schema, runs migrations, and initializes the mailer and core application logic. Includes a cleanup function to remove the schema and close the pool. ```typescript import { createPool, runMigrations } from '../src/db.js'; import type { Pool } from 'pg'; import { randomBytes } from 'node:crypto'; import { FakeMailer } from '../src/mailer.js'; import { buildApp } from '../src/buildApp.js'; export async function makeTestApp(): Promise<{ app: Awaited>; pool: Pool; mailer: FakeMailer; cleanup: () => Promise; }> { const url = process.env.TEST_DATABASE_URL; if (!url) throw new Error('TEST_DATABASE_URL required'); const pool = createPool(url); const schema = `t_${randomBytes(4).toString('hex')}`; await pool.query(`CREATE SCHEMA ${schema}`); await pool.query(`SET search_path TO ${schema}`); await runMigrations(pool); const mailer = new FakeMailer(); const app = await buildApp({ pool, mailer, test: true, config: { sessionSecret: 'x'.repeat(32), magicLinkBaseUrl: 'http://test', difficultyBits: 8, difficultyFloor: 4, signingPrivateKeyHex: '11'.repeat(32), signingPublicKeyHex: '22'.repeat(32), webOrigin: 'http://web.test', }, }); return { app, pool, mailer, cleanup: async () => { await app.close(); await pool.query(`DROP SCHEMA ${schema} CASCADE`); await pool.end(); }, }; } ``` -------------------------------- ### Implement GET /ledger route Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Implements the GET /ledger route to fetch and return token and user statistics from the database. ```typescript import type { FastifyInstance } from 'fastify'; export async function ledgerRoutes(app: FastifyInstance) { app.get('/ledger', async () => { const [{ rows: minted }, { rows: transferred }, { rows: circ }, { rows: users }] = await Promise.all([ app.pool.query<{ n: number }>(`SELECT count(*)::int AS n FROM tokens WHERE parent_token_id IS NULL`), app.pool.query<{ n: number }>(`SELECT coalesce(sum(amount),0)::int AS n FROM transfers`), app.pool.query<{ n: number }>(`SELECT count(*)::int AS n FROM tokens WHERE state='VALID'`), app.pool.query<{ n: number }>(`SELECT count(*)::int AS n FROM users`), ]); return { total_minted: minted[0]!.n, total_transferred: transferred[0]!.n, circulating_supply: circ[0]!.n, current_difficulty_bits: Math.max(app.config.difficultyFloor, app.config.difficultyBits), user_count: users[0]!.n, }; }); } ``` -------------------------------- ### Build Server and Shared Workspace on VPS Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Installs npm dependencies using 'npm ci' and builds the '@rpow/shared' and '@rpow/server' workspaces on the VPS. Requires npm and Node.js to be installed. ```bash local$ ssh ubuntu@15.204.254.192 'cd /opt/rpow/repo && \ sudo -u rpow npm ci --workspaces --include-workspace-root --ignore-scripts && \ sudo -u rpow npm run build --workspace @rpow/shared && \ sudo -u rpow npm run build --workspace @rpow/server' ``` -------------------------------- ### Create rpow Directory and Server Environment File on VPS Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Creates the /etc/rpow directory with appropriate permissions and populates the server.env file with production configuration variables. Ensure .env.vps is sourced locally. ```bash local$ . /Users/fredkrueger/rpow/.env.vps && \ ssh ubuntu@15.204.254.192 "sudo install -d -m 750 -o root -g rpow /etc/rpow && \ sudo tee /etc/rpow/server.env >/dev/null < { const pubDer = Buffer.concat([ Buffer.from('302a300506032b6570032100', 'hex'), Buffer.from(app.config.signingPublicKeyHex, 'hex'), ]); const b64 = pubDer.toString('base64').match(/.{1,64}/g)!.join('\n'); const pem = `-----BEGIN PUBLIC KEY-----\n${b64}\n-----END PUBLIC KEY-----\n`; reply.header('content-type', 'application/x-pem-file').send(pem); }); ``` -------------------------------- ### Deploy Frontend Builds Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-lobby-favorites.md Deploys the production build of the web-gladiator frontend to Netlify. Requires the site ID to be looked up separately. Assumes local builds are already complete. ```bash cd /Users/fredkrueger/rpow/apps/web-gladiator netlify deploy --prod --dir=/Users/fredkrueger/rpow/apps/web-gladiator/dist --site=$(python3 -c "import json; d=json.load(open('/Users/fredkrueger/Library/Preferences/netlify/config.json')); print('see gladiator site')") --no-build ``` -------------------------------- ### Test GET /matches/recent (Public Access) Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-trivia-slice-2.md Tests the public accessibility of the GET /matches/recent endpoint. Expects a 200 status code. ```typescript it('public', async () => { const ctx = await makeTestApp(); cleanup = ctx.cleanup; const res = await ctx.app.inject({ method: 'GET', url: '/api/trivia/matches/recent' }); expect(res.statusCode).toBe(200); }); ``` -------------------------------- ### Install and Run Restore Drill on VPS Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Installs the restore drill script on the VPS and then executes it. The expected outcome is a 'Restore drill OK.' message, confirming the end-to-end restore process. ```bash local$ scp /Users/fredkrueger/rpow/ops/restore-test.sh ubuntu@15.204.254.192:/tmp/ && \ ssh ubuntu@15.204.254.192 'sudo install -m 750 -o root -g root /tmp/restore-test.sh /usr/local/bin/rpow-restore-test && \ sudo /usr/local/bin/rpow-restore-test' ``` -------------------------------- ### Test GET /.well-known/rpow-pubkey.pem endpoint Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Tests the public GET /.well-known/rpow-pubkey.pem endpoint to ensure it returns the configured public key in PEM format. ```typescript import { describe, it, expect, afterEach } from 'vitest'; import { makeTestApp } from './helpers.js'; describe('GET /.well-known/rpow-pubkey.pem', () => { let cleanup: (() => Promise) | null = null; afterEach(async () => { if (cleanup) await cleanup(); cleanup = null; }); it('returns the configured public key as PEM', async () => { const ctx = await makeTestApp(); cleanup = ctx.cleanup; const res = await ctx.app.inject({ method: 'GET', url: '/.well-known/rpow-pubkey.pem' }); expect(res.statusCode).toBe(200); expect(res.headers['content-type']).toMatch(/application\/x-pem-file/); expect(res.body).toMatch(/-----BEGIN PUBLIC KEY-----/); }); }); ``` -------------------------------- ### Test GET /ledger endpoint Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-rpow2-server-web-v1.md Tests the public GET /ledger endpoint to ensure it returns correct counter data without authentication. ```typescript import { describe, it, expect, afterEach } from 'vitest'; import { makeTestApp } from './helpers.js'; describe('GET /ledger', () => { let cleanup: (() => Promise) | null = null; afterEach(async () => { if (cleanup) await cleanup(); cleanup = null; }); it('public, no auth, returns counters', async () => { const ctx = await makeTestApp(); cleanup = ctx.cleanup; const res = await ctx.app.inject({ method: 'GET', url: '/ledger' }); expect(res.statusCode).toBe(200); const body = res.json(); expect(body).toMatchObject({ total_minted: 0, total_transferred: 0, circulating_supply: 0, current_difficulty_bits: 8, user_count: 0, }); }); }); ``` -------------------------------- ### Test GET /matches/history (No Session) Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-trivia-slice-2.md Tests the GET /matches/history endpoint without an authentication session. Expects a 401 Unauthorized status code. ```typescript it('401 without session', async () => { const ctx = await makeTestApp(); cleanup = ctx.cleanup; const res = await ctx.app.inject({ method: 'GET', url: '/api/trivia/matches/history' }); expect(res.statusCode).toBe(401); }); ``` -------------------------------- ### Connect to Neon database and query counts Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-07-fly-to-vps-migration.md Establishes a connection to the Neon database using the DATABASE_URL from the .env.vps file and queries the counts of users, tokens, and transfers. The results should be recorded for later verification. ```bash local$ set -a && . /Users/fredkrueger/rpow/.env.vps && set +a && \ psql "$DATABASE_URL" -c 'SELECT count(*) AS users FROM users;' \ -c 'SELECT count(*) AS tokens FROM tokens;' \ -c 'SELECT count(*) AS transfers FROM transfers;' ``` -------------------------------- ### Commit Changes for GET /me Endpoint Source: https://github.com/frkrueger/rpow/blob/main/docs/superpowers/plans/2026-05-11-trivia-slice-2.md Stages and commits the TypeScript files for the new GET /me endpoint and its corresponding test file. ```bash git add apps/server/src/routes/trivia/me.ts apps/server/tests/triviaMe.test.ts git commit -m "feat(trivia): slice 2 — GET /me (profile + open session + career W/L)" ```