From adbac1b9f0c3af2d5102c467a03d13c81f6e1b72 Mon Sep 17 00:00:00 2001 From: artheru Date: Wed, 10 Jun 2026 05:46:28 +0800 Subject: [PATCH] Showcase HTTPS (0.36.0): auto per-user wildcard TLS - server: certbot DNS-01 GoDaddy hooks + showcase-cert.sh issues *..amerc.ai wildcard cert and writes a per-user 443 nginx vhost -> edge - amerc-api: registration triggers cert provisioning async (idempotent) + returns https URL - Mansion shows the HTTPS-provisioning note - verified: https://demo1.artheru.amerc.ai serves over TLS (Let's Encrypt *.artheru cert) --- index.html | 2 +- package.json | 2 +- server/amerc-api.mjs | 5 ++++- src/Mansion.jsx | 1 + src/Scene2D.jsx | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 4672da1..2f03f33 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + diff --git a/package.json b/package.json index 4735835..b26a9ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.35.0-account", + "version": "0.36.0-https", "private": true, "type": "module", "scripts": { diff --git a/server/amerc-api.mjs b/server/amerc-api.mjs index ba41969..2276925 100644 --- a/server/amerc-api.mjs +++ b/server/amerc-api.mjs @@ -6,6 +6,7 @@ import http from 'node:http'; import { DatabaseSync } from 'node:sqlite'; import crypto from 'node:crypto'; import fs from 'node:fs'; +import { spawn } from 'node:child_process'; import { existsSync, mkdirSync, createReadStream } from 'node:fs'; import { dirname, join, basename } from 'node:path'; @@ -496,8 +497,10 @@ const server = http.createServer(async (req, res) => { const host = `${name}.${handle}.amerc.ai`; if (db.prepare('SELECT id FROM showcases WHERE host=?').get(host)) return send(res, 409, { ok: false, error: 'showcase already registered' }); const dns = await godaddyEnsureWildcard(handle); + // provision per-user wildcard TLS cert + nginx vhost (idempotent, async) + try { if (existsSync('/home/amerc/showcase-cert.sh')) spawn('/home/amerc/showcase-cert.sh', [handle], { detached: true, stdio: 'ignore' }).unref(); } catch { /* best effort */ } db.prepare('INSERT INTO showcases(owner,name,host,created_at) VALUES(?,?,?,?)').run(id.user.handle, name, host, Date.now()); - return send(res, 200, { ok: true, host, url: `http://${host}/`, edgeToken: showcaseToken(host), edgeWs: 'wss://amerc.ai/showcase/ws', dns: dns.ok ? 'configured' : (dns.error || 'pending') }); + return send(res, 200, { ok: true, host, url: `https://${host}/`, edgeToken: showcaseToken(host), edgeWs: 'wss://amerc.ai/showcase/ws', dns: dns.ok ? 'configured' : (dns.error || 'pending'), tls: 'HTTPS provisions automatically (~1-2 min on your first showcase)' }); } if ((m = path.match(/^\/showcase\/(\d+)$/)) && method === 'DELETE') { if (id?.kind !== 'user') return send(res, 401, { ok: false, error: 'login required' }); diff --git a/src/Mansion.jsx b/src/Mansion.jsx index 9107917..9fa067c 100644 --- a/src/Mansion.jsx +++ b/src/Mansion.jsx @@ -50,6 +50,7 @@ function Showcase({ handle }) { {created && (

{created.host} reserved · DNS {created.dns}

+ {created.tls &&

🔒 {created.tls}

} diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index 29c5db8..3653984 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -7,7 +7,7 @@ import Account from './Account.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.35.0-account'; +const RELEASE = '0.36.0-https'; const NAV = [ { id: 'home', label: 'Tavern' },