diff --git a/.agents/workflows/deploy-vps.md b/.agents/workflows/deploy-vps.md index 57fef60e..2dda3454 100644 --- a/.agents/workflows/deploy-vps.md +++ b/.agents/workflows/deploy-vps.md @@ -4,73 +4,81 @@ description: Deploy the latest OmniRoute code to the Akamai VPS (69.164.221.35) # Deploy to VPS Workflow -Deploy OmniRoute to the production VPS using `npm install -g` + PM2. +Deploy OmniRoute to the production VPS using `npm pack + scp` + PM2. **VPS:** `69.164.221.35` (Akamai, Ubuntu 24.04, 1GB RAM + 2.5GB swap) **Local VPS:** `192.168.0.15` (same setup) **Process manager:** PM2 (`omniroute`) **Port:** `20128` +**PM2 entry:** `/usr/lib/node_modules/omniroute/app/server.js` > [!IMPORTANT] > PM2 runs from the global npm package at `/usr/lib/node_modules/omniroute`. -> **DO NOT** use git clone or local copies. The `npm install -g` command handles -> building, publishing, and installing the standalone app in one step. +> The Next.js standalone build is at `app/server.js` inside that directory. +> The npm registry rejects packages > 100MB, so deployment uses **npm pack + scp**. ## Steps -### 1. Publish to npm +### 1. Build + pack locally -Ensure the version in `package.json` is bumped and the package is published: +Run the full build (includes hash-strip patch) and create the .tgz: + +// turbo ```bash -npm publish +cd /home/diegosouzapw/dev/proxys/9router && npm run build:cli && npm pack --ignore-scripts ``` -### 2. Install on VPS and restart PM2 +### 2. Copy to both VPS and install // turbo-all ```bash -ssh root@69.164.221.35 "npm install -g omniroute@latest && pm2 restart omniroute && pm2 save && echo '✅ Deploy complete!'" +scp omniroute-*.tgz root@69.164.221.35:/tmp/ && scp omniroute-*.tgz root@192.168.0.15:/tmp/ ``` -For the local VPS: +```bash +ssh root@69.164.221.35 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && pm2 restart omniroute && pm2 save && echo '✅ Akamai done'" +``` ```bash -ssh root@192.168.0.15 "npm install -g omniroute@latest && pm2 restart omniroute && pm2 save && echo '✅ Deploy complete!'" +ssh root@192.168.0.15 "npm install -g /tmp/omniroute-*.tgz --ignore-scripts && pm2 restart omniroute && pm2 save && echo '✅ Local done'" ``` ### 3. Verify the deployment ```bash -ssh root@69.164.221.35 "pm2 list && cat \$(npm root -g)/omniroute/package.json | grep version | head -1 && curl -s -o /dev/null -w 'HTTP %{http_code}' http://localhost:20128/" +ssh root@69.164.221.35 "pm2 list && cat \$(npm root -g)/omniroute/app/package.json | grep version | head -1 && curl -s -o /dev/null -w 'HTTP %{http_code}' http://localhost:20128/" ``` -Expected: PM2 shows `online`, version matches published, HTTP returns `307` (redirect to login). +Expected: PM2 shows `online`, version matches, HTTP returns `307`. ## How it works -1. `npm publish` builds Next.js standalone + bundles everything into the npm package -2. `npm install -g omniroute@latest` downloads and installs to `/usr/lib/node_modules/omniroute/` -3. PM2 is registered to run `npm start` from that directory (cwd: `/usr/lib/node_modules/omniroute`) -4. `pm2 restart omniroute` picks up the new code immediately +1. `npm run build:cli` builds Next.js standalone → `app/` and strips Turbopack hashed require() calls from chunks +2. `npm pack --ignore-scripts` packages without re-running the build +3. `scp` transfers the .tgz to each VPS (~286MB) +4. `npm install -g /tmp/omniroute-*.tgz --ignore-scripts` installs pre-built package +5. PM2 runs `app/server.js` from `/usr/lib/node_modules/omniroute` -## PM2 Setup (one-time) - -If PM2 needs to be reconfigured from scratch: +## PM2 Setup (one-time — if reconfiguring from scratch) ```bash ssh root@ " - cd /usr/lib/node_modules/omniroute && - PORT=20128 pm2 start app/server.js --name omniroute --env PORT=20128 && - pm2 save && - pm2 startup + pm2 delete omniroute ; + cp /opt/omniroute-app/.env /usr/lib/node_modules/omniroute/.env && + PORT=20128 pm2 start /usr/lib/node_modules/omniroute/app/server.js --name omniroute --cwd /usr/lib/node_modules/omniroute/app && + pm2 save && pm2 startup " ``` +> [!NOTE] +> Copy `.env` from the old installation first. For Akamai it was at `/opt/omniroute-app/.env`, +> for the local VPS it was at `/root/omniroute-fresh/.env`. + ## Notes -- The `.env` file is at `/usr/lib/node_modules/omniroute/.env`. Back it up before major npm updates. -- PM2 is configured with `pm2 startup` to auto-restart on reboot. -- Nginx proxies `omniroute.online` → `localhost:20128`. -- The VPS has only 1GB RAM — builds happen locally via `npm publish`, not on the VPS. +- `.env` should be placed at `/usr/lib/node_modules/omniroute/app/.env` +- PM2 is configured with `pm2 startup` to auto-restart on reboot +- Nginx proxies `omniroute.online` → `localhost:20128` +- The VPS has only 1GB RAM — builds happen locally, never on the VPS diff --git a/scripts/prepublish.mjs b/scripts/prepublish.mjs index 94a588c6..434d7212 100644 --- a/scripts/prepublish.mjs +++ b/scripts/prepublish.mjs @@ -142,6 +142,62 @@ if (sanitisedCount > 0) { console.log(" ℹ️ No hardcoded paths found to sanitise"); } +// ── Step 5.6: Strip Turbopack hashed externals from compiled chunks ───────── +// Even when Turbopack is disabled at build time, some instrumentation chunks +// may still emit require('package-<16hexchars>') instead of require('package'). +// These hashed names don't exist in node_modules and cause MODULE_NOT_FOUND at +// runtime. We strip the hex suffix from all .js files in app/.next/server/ +// to ensure all require() calls use the real package names. +{ + const serverOutput = join(APP_DIR, ".next", "server"); + const HASH_RE = /(['"\\])([a-z@][a-z0-9@./_-]+-[0-9a-f]{16})\1/g; + let patchedFiles = 0; + let patchedMatches = 0; + const walkDir = (dir) => { + let entries = []; + try { + entries = readdirSync(dir); + } catch { + return; + } + for (const entry of entries) { + const full = join(dir, entry); + try { + const st = statSync(full); + if (st.isDirectory()) { + walkDir(full); + continue; + } + if (!entry.endsWith(".js")) continue; + const src = readFileSync(full, "utf8"); + let count = 0; + const patched = src.replace(HASH_RE, (_, q, name) => { + const base = name.replace(/-[0-9a-f]{16}$/, ""); + count++; + return `${q}${base}${q}`; + }); + if (count > 0) { + writeFileSync(full, patched); + patchedFiles++; + patchedMatches += count; + } + } catch { + /* skip unreadable files */ + } + } + }; + if (existsSync(serverOutput)) { + walkDir(serverOutput); + if (patchedMatches > 0) { + console.log( + ` 🔧 Hash-strip: patched ${patchedMatches} hashed require() in ${patchedFiles} server chunk file(s)` + ); + } else { + console.log(" ✅ Hash-strip: no hashed externals found in compiled chunks."); + } + } +} + // ── Step 6: Copy static assets ───────────────────────────── const staticSrc = join(ROOT, ".next", "static"); const staticDest = join(APP_DIR, ".next", "static");