- This is a scan and preview only — nothing has been signed. Closing
- empty accounts and burning junk (which require signing in your
- wallet) comes next.
-
-
- )}
+
+
+
+
+
);
}
diff --git a/apps/web/src/components/Features.tsx b/apps/web/src/components/Features.tsx
new file mode 100644
index 0000000..8d18c53
--- /dev/null
+++ b/apps/web/src/components/Features.tsx
@@ -0,0 +1,68 @@
+/**
+ * Feature / status cards. HONEST about what is live vs coming soon — coming-soon
+ * items carry a clear badge and must not imply they work today.
+ * Server component (static content, no hooks).
+ */
+type Status = "live" | "partial" | "soon";
+
+const FEATURES: ReadonlyArray<{
+ title: string;
+ body: string;
+ status: Status;
+ statusLabel: string;
+}> = [
+ {
+ title: "Scan & classify",
+ body: "Read your SPL token accounts and conservatively classify every one. Unknown means skip.",
+ status: "live",
+ statusLabel: "Live",
+ },
+ {
+ title: "Reclaim ATA rent",
+ body: "The scan already estimates recoverable rent from empty accounts. One-click close to send that SOL back to you is next.",
+ status: "partial",
+ statusLabel: "Scan live · close soon",
+ },
+ {
+ title: "Burn dead tokens",
+ body: "Burn worthless leftover balances to zero, then close the emptied accounts to recover their rent.",
+ status: "soon",
+ statusLabel: "Phase 3",
+ },
+ {
+ title: "Prometheus AI meme rebirth",
+ body: "Turn burned remnants into an AI-generated meme Spawn for human-reviewed launch. Entertainment, not a promise of value.",
+ status: "soon",
+ statusLabel: "Coming soon",
+ },
+];
+
+function badgeClass(status: Status): string {
+ if (status === "live") return "badge badge--live";
+ if (status === "partial") return "badge badge--partial";
+ return "badge badge--soon";
+}
+
+export function Features() {
+ return (
+
+
+ This is a scan and preview only — nothing has been signed. Closing
+ empty accounts and burning junk (which require signing in your
+ wallet) comes next.
+
+
+ )}
+
+ );
+}
diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs
index b2351f8..415f1fe 100644
--- a/ecosystem.config.cjs
+++ b/ecosystem.config.cjs
@@ -1,74 +1,82 @@
-// PYRE / Prometheus Protocol — PM2 ecosystem (process manager) config
-//
-// ⚠️ INERT / NOT YET RUNNABLE: the apps are NOT implemented yet. This file
-// defines how the three PYRE processes WILL run once apps/web, apps/api,
-// and apps/worker are built/deployed. Starting it now will fail because
-// the apps (and their builds) do not exist yet.
+// PYRE / Prometheus Protocol — PM2 ecosystem (process manager) config.
//
// Process names match docs/PYRE_MVP_DESIGN.md §12: pyre-web, pyre-api, pyre-worker.
-//
-// Once apps exist, start with:
-// pm2 start ecosystem.config.cjs
-// pm2 save # persist process list so `pm2 resurrect` works on boot
+// Start (from repo root): pm2 start ecosystem.config.cjs --only pyre-api,pyre-web
+// Persist for boot: pm2 save (systemd unit infra/systemd/pm2-pyre.service
+// runs `pm2 resurrect` on boot)
//
// PM2 is installed at user level: ~/.local/share/pnpm/bin/pm2
// Logs go to /home/pyre/pyre/logs/ (rotated by infra/logrotate/pyre).
+// Each process is capped at 400M (8GB VPS shared with postgres/redis/nginx).
//
-// Note on memory: the 8GB VPS is shared with postgres, redis, nginx, etc.,
-// so each process is capped at 400M via max_memory_restart.
+// NOTE: api/worker run their TypeScript directly via `node --import tsx` (no
+// separate build step; tsx resolves the workspace `@pyre/*` source packages).
+// The web app runs Next.js in production mode and requires a prior `next build`.
+
+const REPO = __dirname;
module.exports = {
apps: [
{
// Next.js frontend (production) — port 3000 per .env.example (WEB_PORT).
+ // Requires `pnpm --filter @pyre/web build` first.
name: "pyre-web",
- cwd: "apps/web",
- script: "pnpm",
- args: "start", // runs `next start` (requires a prior `pnpm build`)
+ cwd: `${REPO}/apps/web`,
+ script: "node_modules/next/dist/bin/next",
+ args: "start",
instances: 1,
+ exec_mode: "fork",
autorestart: true,
- max_memory_restart: "400M",
+ max_memory_restart: "500M",
env: {
NODE_ENV: "production",
PORT: 3000,
+ HOSTNAME: "127.0.0.1",
},
- out_file: "/home/pyre/pyre/logs/pyre-web-out.log",
- error_file: "/home/pyre/pyre/logs/pyre-web-err.log",
+ out_file: `${REPO}/logs/pyre-web-out.log`,
+ error_file: `${REPO}/logs/pyre-web-err.log`,
},
{
// Fastify HTTP API — port 4000 per .env.example (API_PORT).
- // Runs the compiled server. Until a build exists you can temporarily
- // swap to a dev runner: script: "pnpm", args: "dev"
+ // Runs TS directly via tsx (resolves workspace @pyre/* source).
name: "pyre-api",
- cwd: "apps/api",
- script: "node",
- args: "dist/index.js",
+ cwd: `${REPO}/apps/api`,
+ script: "src/index.ts",
+ interpreter: "node",
+ interpreter_args: "--import tsx",
instances: 1,
+ exec_mode: "fork",
autorestart: true,
max_memory_restart: "400M",
env: {
NODE_ENV: "production",
PORT: 4000,
+ HOST: "127.0.0.1",
+ WEB_PUBLIC_URL: "https://feedthepyre.com",
+ // Public RPC by default — override with a Helius/Triton/QuickNode URL
+ // (set SOLANA_RPC_URL in the environment) to avoid mainnet rate limits.
+ SOLANA_RPC_URL: "https://api.mainnet-beta.solana.com",
},
- out_file: "/home/pyre/pyre/logs/pyre-api-out.log",
- error_file: "/home/pyre/pyre/logs/pyre-api-err.log",
+ out_file: `${REPO}/logs/pyre-api-out.log`,
+ error_file: `${REPO}/logs/pyre-api-err.log`,
},
{
- // BullMQ background worker (no HTTP port).
- // Runs the compiled worker. Until a build exists you can temporarily
- // swap to a dev runner: script: "pnpm", args: "dev"
+ // BullMQ background worker (no HTTP port). Not started by default in
+ // Phase 1 (no jobs implemented yet). Runs TS via tsx when enabled.
name: "pyre-worker",
- cwd: "apps/worker",
- script: "node",
- args: "dist/index.js",
+ cwd: `${REPO}/apps/worker`,
+ script: "src/index.ts",
+ interpreter: "node",
+ interpreter_args: "--import tsx",
instances: 1,
+ exec_mode: "fork",
autorestart: true,
max_memory_restart: "400M",
env: {
NODE_ENV: "production",
},
- out_file: "/home/pyre/pyre/logs/pyre-worker-out.log",
- error_file: "/home/pyre/pyre/logs/pyre-worker-err.log",
+ out_file: `${REPO}/logs/pyre-worker-out.log`,
+ error_file: `${REPO}/logs/pyre-worker-err.log`,
},
],
};
diff --git a/infra/nginx/feedthepyre.com.conf b/infra/nginx/feedthepyre.com.conf
index a3fbb11..4c1e03c 100644
--- a/infra/nginx/feedthepyre.com.conf
+++ b/infra/nginx/feedthepyre.com.conf
@@ -1,109 +1,63 @@
-# ============================================================================
-# PYRE / Prometheus Protocol — nginx virtual host for feedthepyre.com
-# ----------------------------------------------------------------------------
-# Install path: /etc/nginx/sites-available/feedthepyre.com
-# (the provision script symlinks this into sites-enabled/)
+# =============================================================================
+# nginx vhost — feedthepyre.com
+# =============================================================================
+# Serves the PYRE app at /, the API at /api, and the dev status tracker at
+# /status. Installed to /etc/nginx/sites-available/feedthepyre.com by
+# scripts/deploy-web.sh; certbot --nginx mirrors this server to a 443 block and
+# adds the HTTP->HTTPS redirect.
#
-# TLS: Managed by certbot. Run `certbot --nginx` AFTER this config is
-# installed — it will inject the listen 443 ssl server block,
-# the ssl_certificate / ssl_certificate_key lines, and the
-# HTTP->HTTPS redirect automatically. Do NOT hand-edit those in.
-#
-# App ports (see docs/PYRE_MVP_DESIGN.md §11 and .env.example):
-# web (Next.js) -> 127.0.0.1:3000 (WEB_PORT)
-# api (Fastify) -> 127.0.0.1:4000 (API_PORT)
-#
-# Current behaviour: serves the static status dashboard from
-# /var/www/feedthepyre/status. The reverse-proxy blocks below
-# are commented out until the apps are deployed.
-# ============================================================================
+# Upstreams (pm2): web = 127.0.0.1:3000 (Next.js), api = 127.0.0.1:4000 (Fastify)
+# =============================================================================
server {
listen 80;
listen [::]:80;
server_name feedthepyre.com www.feedthepyre.com;
- # --- Static status site (current site root) -----------------------------
- root /var/www/feedthepyre/status;
- index index.html;
-
- # --- Logging ------------------------------------------------------------
access_log /var/log/nginx/feedthepyre.access.log;
error_log /var/log/nginx/feedthepyre.error.log;
- # --- ACME HTTP-01 challenge --------------------------------------------
- # Explicit so certbot's HTTP-01 validation works even before its --nginx
- # tweaks are applied. ^~ ensures this wins over the regex/proxy locations.
+ gzip on;
+ gzip_proxied any;
+ gzip_types text/plain text/css application/json application/javascript
+ application/xml image/svg+xml;
+
+ client_max_body_size 1m;
+
+ # Let's Encrypt HTTP-01 (kept so cert renewals work).
location ^~ /.well-known/acme-challenge/ {
root /var/www/feedthepyre/status;
allow all;
}
- # --- Basic hardening ----------------------------------------------------
- # gzip for text-ish content types.
- gzip on;
- gzip_comp_level 5;
- gzip_min_length 256;
- gzip_proxied any;
- gzip_vary on;
- gzip_types
- text/plain
- text/css
- text/xml
- text/javascript
- application/javascript
- application/json
- application/xml
- application/rss+xml
- image/svg+xml;
-
- # NOTE: `server_tokens off;` is intentionally NOT set here — it belongs in
- # the http{} block of /etc/nginx/nginx.conf so it applies globally. Set it
- # there once rather than duplicating it per-vhost.
-
- # --- Site root ----------------------------------------------------------
- # Serve the static status dashboard for now.
- #
- # LATER: when apps/web (Next.js) is deployed, switch this location from the
- # static status page to a reverse proxy. Replace the try_files body with:
- #
- # proxy_pass http://127.0.0.1:3000;
- # proxy_http_version 1.1;
- # proxy_set_header Host $host;
- # proxy_set_header X-Real-IP $remote_addr;
- # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- # proxy_set_header X-Forwarded-Proto $scheme;
- # proxy_set_header Upgrade $http_upgrade;
- # proxy_set_header Connection $connection_upgrade;
- #
- location / {
- try_files $uri $uri/ /index.html;
+ # --- Dev status tracker (static) -> /status -----------------------------
+ location = /status { return 301 /status/; }
+ location /status/ {
+ alias /var/www/feedthepyre/status/;
+ index index.html;
+ try_files $uri $uri/ /status/index.html;
}
- # ------------------------------------------------------------------------
- # REVERSE-PROXY BLOCKS — enable when apps are running
- # ------------------------------------------------------------------------
- # Uncomment the /api/ block below once apps/api (Fastify, port 4000) is up.
- # The trailing slash on proxy_pass strips the /api/ prefix so the backend
- # sees /scan, /receipt, etc.
- #
- # location /api/ {
- # proxy_pass http://127.0.0.1:4000/;
- # proxy_http_version 1.1;
- # proxy_set_header Host $host;
- # proxy_set_header X-Real-IP $remote_addr;
- # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- # proxy_set_header X-Forwarded-Proto $scheme;
- # proxy_set_header Upgrade $http_upgrade;
- # proxy_set_header Connection $connection_upgrade;
- # }
- #
- # The websocket Upgrade/Connection headers above rely on a $connection_upgrade
- # map. Add this once in the http{} block of /etc/nginx/nginx.conf:
- #
- # map $http_upgrade $connection_upgrade {
- # default upgrade;
- # '' close;
- # }
- # ------------------------------------------------------------------------
+ # --- API (Fastify) ------------------------------------------------------
+ # No trailing slash on proxy_pass: the /api/ prefix is preserved, so the
+ # backend receives /api/scan (its actual route).
+ location /api/ {
+ proxy_pass http://127.0.0.1:4000;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_read_timeout 60s;
+ }
+
+ # --- Web app (Next.js) --------------------------------------------------
+ location / {
+ proxy_pass http://127.0.0.1:3000;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
}
diff --git a/scripts/deploy-web.sh b/scripts/deploy-web.sh
new file mode 100755
index 0000000..8b8de4b
--- /dev/null
+++ b/scripts/deploy-web.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+# =============================================================================
+# Cut nginx over to serve the PYRE app at / (and the dev tracker at /status).
+# =============================================================================
+# Prereqs: the app is built + running under pm2 (pyre-web:3000, pyre-api:4000),
+# and Phase 0 provisioning already obtained a TLS cert for feedthepyre.com.
+#
+# Run as root: sudo bash scripts/deploy-web.sh
+# Idempotent + re-runnable.
+# =============================================================================
+set -euo pipefail
+
+DOMAIN="feedthepyre.com"
+WWW_DOMAIN="www.feedthepyre.com"
+REPO_DIR="/home/pyre/pyre"
+CERTBOT_EMAIL="${CERTBOT_EMAIL:-a31s15.roguewave@gmail.com}"
+VHOST_SRC="${REPO_DIR}/infra/nginx/${DOMAIN}.conf"
+VHOST_AVAIL="/etc/nginx/sites-available/${DOMAIN}"
+VHOST_ENABLED="/etc/nginx/sites-enabled/${DOMAIN}"
+
+if [[ "${EUID}" -ne 0 ]]; then
+ echo "Must run as root: sudo bash ${0}" >&2
+ exit 1
+fi
+
+echo "==> Installing nginx vhost (app at / , tracker at /status , api at /api)"
+install -m 0644 "${VHOST_SRC}" "${VHOST_AVAIL}"
+ln -sfn "${VHOST_AVAIL}" "${VHOST_ENABLED}"
+
+echo "==> nginx -t"
+nginx -t
+systemctl reload nginx
+
+echo "==> Re-applying TLS (certbot mirrors the server to a 443 block; idempotent)"
+certbot --nginx -d "${DOMAIN}" -d "${WWW_DOMAIN}" \
+ --non-interactive --agree-tos -m "${CERTBOT_EMAIL}" \
+ --redirect --keep-until-expiring || {
+ echo "[WARN] certbot did not complete; HTTP is live, re-run once DNS/cert is ready." >&2
+ }
+systemctl reload nginx
+
+echo "Done."
+echo " https://${DOMAIN}/ -> PYRE app (pm2 pyre-web:3000)"
+echo " https://${DOMAIN}/api/scan -> API (pm2 pyre-api:4000)"
+echo " https://${DOMAIN}/status/ -> dev status tracker (static)"