Showcase HTTPS (0.36.0): auto per-user wildcard TLS
- server: certbot DNS-01 GoDaddy hooks + showcase-cert.sh issues *.<user>.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)
This commit is contained in:
parent
0ecf037b5a
commit
adbac1b9f0
@ -4,7 +4,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#0e0a14" />
|
<meta name="theme-color" content="#0e0a14" />
|
||||||
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
<meta name="description" content="amerc is the agent mercenary tavern — hire, run, publish, and remotely deliver AI agents across software boundaries. No infrastructure, just skills." />
|
||||||
<meta name="version" content="0.35.0-account" />
|
<meta name="version" content="0.36.0-https" />
|
||||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
<!-- Open Graph / social share -->
|
<!-- Open Graph / social share -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "amerc-site",
|
"name": "amerc-site",
|
||||||
"version": "0.35.0-account",
|
"version": "0.36.0-https",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import http from 'node:http';
|
|||||||
import { DatabaseSync } from 'node:sqlite';
|
import { DatabaseSync } from 'node:sqlite';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import { spawn } from 'node:child_process';
|
||||||
import { existsSync, mkdirSync, createReadStream } from 'node:fs';
|
import { existsSync, mkdirSync, createReadStream } from 'node:fs';
|
||||||
import { dirname, join, basename } from 'node:path';
|
import { dirname, join, basename } from 'node:path';
|
||||||
|
|
||||||
@ -496,8 +497,10 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const host = `${name}.${handle}.amerc.ai`;
|
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' });
|
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);
|
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());
|
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 ((m = path.match(/^\/showcase\/(\d+)$/)) && method === 'DELETE') {
|
||||||
if (id?.kind !== 'user') return send(res, 401, { ok: false, error: 'login required' });
|
if (id?.kind !== 'user') return send(res, 401, { ok: false, error: 'login required' });
|
||||||
|
|||||||
@ -50,6 +50,7 @@ function Showcase({ handle }) {
|
|||||||
{created && (
|
{created && (
|
||||||
<div className="mn-created">
|
<div className="mn-created">
|
||||||
<p>✅ <b>{created.host}</b> reserved · DNS {created.dns}</p>
|
<p>✅ <b>{created.host}</b> reserved · DNS {created.dns}</p>
|
||||||
|
{created.tls && <p className="ap-hint">🔒 {created.tls}</p>}
|
||||||
<label className="ap-field">public URL<code className="ap-token" onClick={() => copy(created.url)}>{created.url} ⧉</code></label>
|
<label className="ap-field">public URL<code className="ap-token" onClick={() => copy(created.url)}>{created.url} ⧉</code></label>
|
||||||
<label className="ap-field">broker WS (kebab connects here)<code className="ap-token" onClick={() => copy(created.edgeWs)}>{created.edgeWs} ⧉</code></label>
|
<label className="ap-field">broker WS (kebab connects here)<code className="ap-token" onClick={() => copy(created.edgeWs)}>{created.edgeWs} ⧉</code></label>
|
||||||
<label className="ap-field">edge token (bind {created.host})<code className="ap-token" onClick={() => copy(created.edgeToken)}>{created.edgeToken.slice(0, 28)}… ⧉</code></label>
|
<label className="ap-field">edge token (bind {created.host})<code className="ap-token" onClick={() => copy(created.edgeToken)}>{created.edgeToken.slice(0, 28)}… ⧉</code></label>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import Account from './Account.jsx';
|
|||||||
import TavernGame from './TavernGame.jsx';
|
import TavernGame from './TavernGame.jsx';
|
||||||
import { api } from './api.js';
|
import { api } from './api.js';
|
||||||
|
|
||||||
const RELEASE = '0.35.0-account';
|
const RELEASE = '0.36.0-https';
|
||||||
|
|
||||||
const NAV = [
|
const NAV = [
|
||||||
{ id: 'home', label: 'Tavern' },
|
{ id: 'home', label: 'Tavern' },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user