Update TTL for Testing
This commit is contained in:
parent
815c4423ce
commit
8d2469ddb6
|
|
@ -9,7 +9,7 @@ import { isSuperAdmin } from '../roles';
|
|||
|
||||
const router = Router();
|
||||
|
||||
const ACCESS_TOKEN_TTL = '1h';
|
||||
const ACCESS_TOKEN_TTL = '10s';
|
||||
const COOKIE_DOMAIN = process.env.COOKIE_DOMAIN ?? '.rokojori.com';
|
||||
const RESET_BASE_URL = process.env.RESET_BASE_URL ?? 'https://account.rokojori.com';
|
||||
const ACCOUNT_BASE_URL = process.env.RESET_BASE_URL ?? 'https://account.rokojori.com';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,440 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Add a Subdomain — rokojori</title>
|
||||
<link rel="stylesheet" href="./_assets_/styles.css">
|
||||
<link rel="stylesheet" href="./_assets_/nav.css">
|
||||
<style>
|
||||
.vars-card {
|
||||
background: var(--surface);
|
||||
border: 2px solid var(--accent);
|
||||
border-radius: 8px;
|
||||
padding: calc(var(--font-size) * 1.25) calc(var(--font-size) * 1.5);
|
||||
margin-bottom: calc(var(--font-size) * 2);
|
||||
}
|
||||
.vars-card h3 { font-size: calc(var(--font-size) * 1); font-weight: 600; color: var(--text); margin-bottom: 0.25rem; }
|
||||
.vars-card p { font-size: calc(var(--font-size) * 0.85); color: var(--muted); margin-bottom: calc(var(--font-size) * 0.75); }
|
||||
.vars-grid { display: flex; gap: 1.5rem; flex-wrap: wrap; }
|
||||
.vars-grid label { display: flex; flex-direction: column; gap: 0.3rem; font-size: calc(var(--font-size) * 0.78); color: var(--muted); }
|
||||
.vars-grid input {
|
||||
background: #0a0c14;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--accent);
|
||||
padding: 0.4rem 0.7rem;
|
||||
font-family: ui-monospace, "Cascadia Code", monospace;
|
||||
font-size: calc(var(--font-size) * 0.88);
|
||||
width: 200px;
|
||||
}
|
||||
.vars-grid input:focus { outline: none; border-color: var(--accent); }
|
||||
.v-sub, .v-port, .v-svc { color: #fff; font-weight: 600; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<header>
|
||||
<p class="date">Guide</p>
|
||||
<h1>Add a Subdomain</h1>
|
||||
<p class="subtitle">
|
||||
Step-by-step checklist for deploying a new Node.js service as a
|
||||
<code>subdomain.rokojori.com</code> on Server A.
|
||||
Fill in the variables below — all commands update automatically.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- ─── Variables ────────────────────────────────────────────── -->
|
||||
<div class="vars-card">
|
||||
<h3>Variables</h3>
|
||||
<p>Fill these in once and every command in the guide updates.</p>
|
||||
<div class="vars-grid">
|
||||
<label>
|
||||
Subdomain
|
||||
<input id="v-subdomain" type="text" placeholder="styles" spellcheck="false">
|
||||
</label>
|
||||
<label>
|
||||
Port
|
||||
<input id="v-port" type="text" placeholder="3002" spellcheck="false">
|
||||
</label>
|
||||
<label>
|
||||
Service name (systemd)
|
||||
<input id="v-service" type="text" placeholder="styles-rokojori" spellcheck="false">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── 0. CORS ──────────────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>0 — Ensure compatibility</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>When do you need CORS?</h3>
|
||||
<p>
|
||||
If any other rokojori site loads resources from this service (fonts, CSS, JSON,
|
||||
images) via a browser request — <code>@import</code>, <code>fetch</code>,
|
||||
<code><img src></code> etc. — the browser enforces the
|
||||
<strong style="color:var(--text)">same-origin policy</strong> and blocks the
|
||||
response unless the server sends an <code>Access-Control-Allow-Origin</code>
|
||||
header. Services that only serve their own pages and API calls to their own
|
||||
frontend do not need CORS.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Do not use <code>*</code></h3>
|
||||
<p>
|
||||
<code>Access-Control-Allow-Origin: *</code> allows any website on the internet
|
||||
to load your resources. For public CDN assets that is acceptable, but for
|
||||
rokojori services restrict it to known origins.
|
||||
</p>
|
||||
<p style="margin-top:0.75rem">
|
||||
Because the header cannot carry a wildcard pattern like
|
||||
<code>*.rokojori.com</code>, the server must check the incoming
|
||||
<code>Origin</code> header against an allowlist and echo back the matched
|
||||
origin. Unrecognised origins receive no header and the browser blocks them.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Pattern — allowlist middleware</h3>
|
||||
<p>Add this to <code>source/server/index.ts</code> before the routes that need it:</p>
|
||||
<pre><code>const CORS_ALLOWED: Array<string | RegExp> = [
|
||||
/^https?:\/\/([\w-]+\.)?rokojori\.com$/, // all *.rokojori.com subdomains
|
||||
// 'https://someother.site', // add extra origins here
|
||||
];
|
||||
|
||||
function allowCors(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
next: express.NextFunction
|
||||
): void
|
||||
{
|
||||
const origin = req.headers.origin;
|
||||
if ( origin && CORS_ALLOWED.some( rule =>
|
||||
typeof rule === 'string' ? rule === origin : rule.test( origin )
|
||||
) )
|
||||
{
|
||||
res.set( 'Access-Control-Allow-Origin', origin );
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
// Apply before the routes that serve cross-origin resources:
|
||||
app.use( '/your-public-route', allowCors );</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>What the regex matches</h3>
|
||||
<p>
|
||||
<code>/^https?:\/\/([\w-]+\.)?rokojori\.com$/</code> accepts:
|
||||
<code>https://rokojori.com</code>,
|
||||
<code>https://roject.rokojori.com</code>,
|
||||
<code>https://styles.rokojori.com</code>, and so on.
|
||||
HTTP is included so local dev with a mapped host still works.
|
||||
Extend <code>CORS_ALLOWED</code> with additional strings or regexes for
|
||||
any external sites that need access.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 1. Gitea repo ─────────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>1 — Create the repository</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>New repo on Gitea</h3>
|
||||
<p>
|
||||
Log in to <strong style="color:var(--text)">community.rokojori.com</strong>,
|
||||
click <em>New Repository</em>, name it (e.g. <code><span class="v-sub">subdomain</span></code>),
|
||||
set it to private, and copy the clone URL.
|
||||
Do not initialise with a README — the local project is already set up.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 2. .gitignore, commit, push ──────────────────────────── -->
|
||||
<section>
|
||||
<h2>2 — .gitignore, commit, push</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>.gitignore</h3>
|
||||
<p>Create this at the project root before the first commit:</p>
|
||||
<pre><code>node_modules/
|
||||
dist/
|
||||
build/
|
||||
.env
|
||||
storage/</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Initial commit and push</h3>
|
||||
<pre><code>git init
|
||||
git add .
|
||||
git commit -m "init"
|
||||
git remote add origin https://community.rokojori.com/Rokojori/<span class="v-sub">subdomain</span>.git
|
||||
git push -u origin main</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 3. DNS on IONOS ───────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>3 — DNS record on IONOS</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Add A record</h3>
|
||||
<p>
|
||||
Log in to IONOS → Domains & SSL → <strong style="color:var(--text)">rokojori.com</strong>
|
||||
→ DNS. Add a new record:
|
||||
</p>
|
||||
<pre><code>Type: A
|
||||
Name: <span class="v-sub">subdomain</span>
|
||||
Value: <Server A IP>
|
||||
TTL: 300</code></pre>
|
||||
<p style="margin-top:0.75rem">
|
||||
Wait a few minutes for propagation before running certbot.
|
||||
Verify with <code>ping <span class="v-sub">subdomain</span>.rokojori.com</code> from any machine.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 4. Clone on server ────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>4 — Clone on the server</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>SSH and clone</h3>
|
||||
<pre><code>ssh root@<Server A IP>
|
||||
|
||||
cd /opt
|
||||
git clone https://community.rokojori.com/Rokojori/<span class="v-sub">subdomain</span>.git <span class="v-sub">subdomain</span>
|
||||
cd <span class="v-sub">subdomain</span></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 5. Install / build / configure ───────────────────────── -->
|
||||
<section>
|
||||
<h2>5 — Install, build, configure</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Install dependencies</h3>
|
||||
<pre><code>npm install</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Copy pages (if the project has source/pages/)</h3>
|
||||
<pre><code>node scripts/copy-pages.js</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Create .env</h3>
|
||||
<p>
|
||||
Copy the contents from your local <code>.env</code> and paste them on the server.
|
||||
Update any values that differ in production (port, secrets, URLs).
|
||||
</p>
|
||||
<pre><code>nano .env</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Create storage directories (if needed)</h3>
|
||||
<pre><code># example for styles.rokojori.com
|
||||
mkdir -p storage/fonts</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Quick smoke test</h3>
|
||||
<p>
|
||||
Run the server once manually to confirm it starts. Kill it with
|
||||
<code>Ctrl+C</code> before moving to the systemd step — leave it
|
||||
running and the port will conflict.
|
||||
</p>
|
||||
<pre><code>npm run dev
|
||||
# → service running on http://localhost:<span class="v-port">PORT</span>
|
||||
# Ctrl+C to stop</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 6. nginx HTTP config ──────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>6 — nginx config (HTTP only)</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Create the site config</h3>
|
||||
<pre><code>nano /etc/nginx/sites-available/<span class="v-sub">subdomain</span>.rokojori.com</code></pre>
|
||||
<pre><code>server {
|
||||
listen 80;
|
||||
server_name <span class="v-sub">subdomain</span>.rokojori.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:<span class="v-port">PORT</span>;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Enable and reload</h3>
|
||||
<pre><code>ln -s /etc/nginx/sites-available/<span class="v-sub">subdomain</span>.rokojori.com \
|
||||
/etc/nginx/sites-enabled/<span class="v-sub">subdomain</span>.rokojori.com
|
||||
|
||||
nginx -t
|
||||
systemctl reload nginx</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 7. Let's Encrypt ──────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>7 — Let's Encrypt certificate</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Run certbot</h3>
|
||||
<p>
|
||||
The <code>--nginx</code> plugin handles the ACME HTTP-01 challenge
|
||||
through nginx directly — the app does not need to be running.
|
||||
When prompted whether to redirect HTTP to HTTPS, choose
|
||||
<strong style="color:var(--text)">Redirect</strong> (option 2).
|
||||
</p>
|
||||
<pre><code>certbot --nginx -d <span class="v-sub">subdomain</span>.rokojori.com</code></pre>
|
||||
<p style="margin-top:0.75rem">
|
||||
Certbot modifies the nginx config in-place, adding TLS directives and
|
||||
an HTTP→HTTPS redirect block. It also installs a systemd timer for
|
||||
automatic renewal — no extra setup needed.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 8. nginx HTTPS ────────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>8 — Verify nginx HTTPS config</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Check and reload</h3>
|
||||
<p>
|
||||
Certbot updates the config automatically, but always verify before reloading:
|
||||
</p>
|
||||
<pre><code>nginx -t
|
||||
systemctl reload nginx</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Confirm TLS is live</h3>
|
||||
<pre><code>curl -I https://<span class="v-sub">subdomain</span>.rokojori.com</code></pre>
|
||||
<p style="margin-top:0.5rem">
|
||||
Expect <code>HTTP/2 200</code> (or 502 if the app is not yet running — that is fine at this stage).
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 9. systemd service ────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>9 — systemd service</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Create the unit file</h3>
|
||||
<pre><code>nano /etc/systemd/system/<span class="v-svc">service-name</span>.service</code></pre>
|
||||
<pre><code>[Unit]
|
||||
Description=<span class="v-svc">service-name</span>
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/<span class="v-sub">subdomain</span>
|
||||
ExecStart=/usr/bin/npx ts-node --project tsconfig.ts-node.json source/server/index.ts
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target</code></pre>
|
||||
<p style="margin-top:0.75rem">
|
||||
The <code>.env</code> file is loaded by <code>import 'dotenv/config'</code>
|
||||
inside the app, so no <code>EnvironmentFile=</code> is needed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Enable and start</h3>
|
||||
<pre><code>systemctl daemon-reload
|
||||
systemctl enable <span class="v-svc">service-name</span>
|
||||
systemctl start <span class="v-svc">service-name</span>
|
||||
systemctl status <span class="v-svc">service-name</span></code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Check logs</h3>
|
||||
<pre><code>journalctl -u <span class="v-svc">service-name</span> -f</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── Quick reference ───────────────────────────────────────── -->
|
||||
<section>
|
||||
<h2>Quick reference</h2>
|
||||
|
||||
<div class="card">
|
||||
<h3>Deploy an update</h3>
|
||||
<pre><code>cd /opt/<span class="v-sub">subdomain</span>
|
||||
git pull
|
||||
npm install
|
||||
node scripts/copy-pages.js # if applicable
|
||||
systemctl restart <span class="v-svc">service-name</span></code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Common commands</h3>
|
||||
<pre><code>systemctl restart <span class="v-svc">service-name</span> # restart app
|
||||
systemctl status <span class="v-svc">service-name</span> # current state
|
||||
journalctl -u <span class="v-svc">service-name</span> -n 100 # last 100 log lines
|
||||
nginx -t # validate nginx config
|
||||
systemctl reload nginx # apply nginx changes</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Certificate renewal</h3>
|
||||
<p>
|
||||
Certbot installs a systemd timer (<code>certbot.timer</code>) that
|
||||
renews certificates automatically before expiry. Check its status with:
|
||||
</p>
|
||||
<pre><code>systemctl status certbot.timer</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
rokojori-auth — add-subdomain guide
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const subInput = document.getElementById('v-subdomain');
|
||||
const portInput = document.getElementById('v-port');
|
||||
const svcInput = document.getElementById('v-service');
|
||||
|
||||
function update()
|
||||
{
|
||||
const sub = subInput.value.trim() || 'subdomain';
|
||||
const port = portInput.value.trim() || 'PORT';
|
||||
const svc = svcInput.value.trim() || 'service-name';
|
||||
document.querySelectorAll('.v-sub').forEach( el => el.textContent = sub );
|
||||
document.querySelectorAll('.v-port').forEach( el => el.textContent = port );
|
||||
document.querySelectorAll('.v-svc').forEach( el => el.textContent = svc );
|
||||
}
|
||||
|
||||
subInput.addEventListener( 'input', update );
|
||||
portInput.addEventListener( 'input', update );
|
||||
svcInput.addEventListener( 'input', update );
|
||||
</script>
|
||||
|
||||
<script>var NAV_ROOT = './';</script>
|
||||
<script src="./_assets_/nav-data.js"></script>
|
||||
<script src="./_assets_/nav.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue