@rizom/brain 0.2.0-alpha.6 → 0.2.0-alpha.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/brain.js +4763 -26510
- package/dist/deploy.d.ts +17 -26
- package/dist/deploy.js +25 -25
- package/dist/deploy.js.map +4 -4
- package/dist/entities.d.ts +561 -0
- package/dist/entities.js +172 -0
- package/dist/entities.js.map +242 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +11 -0
- package/dist/interfaces.d.ts +923 -0
- package/dist/interfaces.js +172 -0
- package/dist/interfaces.js.map +209 -0
- package/dist/plugins.d.ts +1195 -0
- package/dist/plugins.js +178 -0
- package/dist/plugins.js.map +294 -0
- package/dist/seed-content/relay/README.md +28 -371
- package/dist/seed-content/relay/anchor-profile/anchor-profile.md +8 -11
- package/dist/seed-content/relay/brain-character/brain-character.md +5 -4
- package/dist/seed-content/relay/site-content/about/about.md +20 -0
- package/dist/seed-content/relay/site-content/home/diagram.md +184 -0
- package/dist/seed-content/relay/site-info/site-info.md +11 -1
- package/dist/services.d.ts +601 -0
- package/dist/services.js +172 -0
- package/dist/services.js.map +242 -0
- package/dist/site.d.ts +56 -157
- package/dist/site.js +323 -463
- package/dist/site.js.map +131 -249
- package/dist/templates.d.ts +302 -0
- package/dist/templates.js +172 -0
- package/dist/templates.js.map +206 -0
- package/dist/themes.d.ts +5 -44
- package/dist/themes.js +91 -8
- package/dist/themes.js.map +2 -2
- package/package.json +35 -4
- package/templates/deploy/scripts/provision-server.ts +109 -0
- package/templates/deploy/scripts/update-dns.ts +65 -0
- package/templates/deploy/scripts/write-ssh-key.ts +17 -0
- package/tsconfig.instance.json +31 -0
package/dist/themes.d.ts
CHANGED
|
@@ -1,48 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Compose a complete theme by prepending shared base utilities.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* edge cases of the `@brains/*` type graph. See
|
|
7
|
-
* `docs/plans/external-plugin-api.md` for the replacement plan.
|
|
8
|
-
*
|
|
9
|
-
* **Sync rules:**
|
|
10
|
-
* - When `composeTheme`'s signature changes in `@brains/theme-base`,
|
|
11
|
-
* update this file in the same commit.
|
|
12
|
-
* - The runtime side (`../entries/themes.ts`) re-exports the real
|
|
13
|
-
* implementation from `@brains/theme-base`. The .js bundle
|
|
14
|
-
* produced by `scripts/build.ts` is what consumers execute.
|
|
15
|
-
* This .d.ts file is what their tsc sees.
|
|
4
|
+
* Base utilities live in @layer theme-base; theme-specific styles
|
|
5
|
+
* should use @layer theme-override to guarantee correct cascade order.
|
|
16
6
|
*/
|
|
7
|
+
declare function composeTheme(themeCSS: string): string;
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
* Prepend the shared base theme utilities to a raw theme CSS string
|
|
20
|
-
* and return the combined result.
|
|
21
|
-
*
|
|
22
|
-
* The base utilities layer contains:
|
|
23
|
-
*
|
|
24
|
-
* - Palette tokens (`--palette-*`) for neutral colors, status colors,
|
|
25
|
-
* and universal UI signals
|
|
26
|
-
* - `@theme inline` declarations that expose the semantic color
|
|
27
|
-
* tokens (`--color-brand`, `--color-bg`, `--color-text`, etc.) to
|
|
28
|
-
* Tailwind v4's JIT so utilities like `bg-brand`, `text-brand`,
|
|
29
|
-
* `border-brand`, `focus-visible:ring-brand` auto-generate
|
|
30
|
-
* - Layer ordering (`@layer theme-base, theme-override`) so theme
|
|
31
|
-
* overrides cascade correctly regardless of concat order
|
|
32
|
-
* - Universal gradient, status, selection, and warning utilities
|
|
33
|
-
* - Prose color slots for `@tailwindcss/typography`
|
|
34
|
-
*
|
|
35
|
-
* The framework resolver uses this helper when it loads a raw theme
|
|
36
|
-
* package or inline theme CSS. Advanced consumers can use the same
|
|
37
|
-
* helper when they need a fully composed CSS string outside the
|
|
38
|
-
* resolver.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* import { composeTheme } from "@rizom/brain/themes";
|
|
43
|
-
* import themeCSS from "./theme.css" with { type: "text" };
|
|
44
|
-
*
|
|
45
|
-
* const fullThemeCSS = composeTheme(themeCSS);
|
|
46
|
-
* ```
|
|
47
|
-
*/
|
|
48
|
-
export function composeTheme(themeCSS: string): string;
|
|
9
|
+
export { composeTheme };
|
package/dist/themes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
2
|
+
var e=`/* ============================================
|
|
3
3
|
THEME BASE - Shared utilities for all themes
|
|
4
4
|
============================================
|
|
5
5
|
This file contains CSS that is IDENTICAL across all themes.
|
|
@@ -45,11 +45,17 @@ var t=`/* ============================================
|
|
|
45
45
|
/* Selection colors */
|
|
46
46
|
--color-selection: var(--color-selection);
|
|
47
47
|
|
|
48
|
+
/* Rule colors \u2014 subtle hairlines for dividers, entry borders, footer top */
|
|
49
|
+
--color-rule: var(--color-rule);
|
|
50
|
+
--color-rule-strong: var(--color-rule-strong);
|
|
51
|
+
|
|
48
52
|
/* Monospace font \u2014 used by ui-library widgets, code blocks, etc.
|
|
49
53
|
Defined by every theme (or left empty for ranger). Brand-specific
|
|
50
54
|
fonts (\`font-display\`, \`font-body\`, \`font-label\`, \`font-nav\`)
|
|
51
55
|
and the typography scale live in the brand theme that uses them
|
|
52
56
|
so non-brand themes don't ship broken utilities. */
|
|
57
|
+
--font-sans: var(--font-sans);
|
|
58
|
+
--font-heading: var(--font-heading);
|
|
53
59
|
--font-mono: var(--font-mono);
|
|
54
60
|
|
|
55
61
|
/* Typography scale \u2014 each entry bundles font-size + line-height +
|
|
@@ -150,6 +156,7 @@ var t=`/* ============================================
|
|
|
150
156
|
.text-theme-muted { color: var(--color-text-muted); }
|
|
151
157
|
.text-theme-light { color: var(--color-text-light); }
|
|
152
158
|
.text-theme-inverse { color: var(--color-text-inverse); }
|
|
159
|
+
.text-theme-on-dark { color: var(--color-text-on-dark, var(--color-text-inverse)); }
|
|
153
160
|
.text-heading { color: var(--color-heading); }
|
|
154
161
|
.text-selection { color: var(--color-selection); }
|
|
155
162
|
.border-theme { border-color: var(--color-border); }
|
|
@@ -186,6 +193,82 @@ var t=`/* ============================================
|
|
|
186
193
|
background-color: var(--color-selection-bg);
|
|
187
194
|
color: var(--color-selection-text);
|
|
188
195
|
}
|
|
196
|
+
|
|
197
|
+
/* Sticky-footer hygiene for site layouts that use flex-grow mains. */
|
|
198
|
+
body {
|
|
199
|
+
min-height: 100dvh;
|
|
200
|
+
display: flex;
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* Reusable editorial decoration hooks. Themes provide colors through
|
|
205
|
+
semantic tokens; these classes only define layering and gradients. */
|
|
206
|
+
.hero-decor,
|
|
207
|
+
.cta-decor {
|
|
208
|
+
position: relative;
|
|
209
|
+
overflow: hidden;
|
|
210
|
+
isolation: isolate;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.hero-decor > *,
|
|
214
|
+
.cta-decor > * {
|
|
215
|
+
position: relative;
|
|
216
|
+
z-index: 1;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.hero-decor::before,
|
|
220
|
+
.hero-decor::after,
|
|
221
|
+
.cta-decor::before,
|
|
222
|
+
.cta-decor::after {
|
|
223
|
+
content: "";
|
|
224
|
+
position: absolute;
|
|
225
|
+
pointer-events: none;
|
|
226
|
+
z-index: 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.hero-decor::before {
|
|
230
|
+
inset: -30% -20% auto;
|
|
231
|
+
height: 120%;
|
|
232
|
+
background:
|
|
233
|
+
radial-gradient(circle at 50% 20%, rgb(from var(--color-accent) r g b / 0.5), transparent 28%),
|
|
234
|
+
radial-gradient(circle at 68% 38%, rgb(from var(--color-brand-dark) r g b / 0.35), transparent 36%);
|
|
235
|
+
filter: blur(8px);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.hero-decor::after,
|
|
239
|
+
.cta-decor::before {
|
|
240
|
+
inset: 0;
|
|
241
|
+
opacity: 0.12;
|
|
242
|
+
mix-blend-mode: soft-light;
|
|
243
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.65'/%3E%3C/svg%3E");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.cta-decor::after {
|
|
247
|
+
width: min(48rem, 70vw);
|
|
248
|
+
aspect-ratio: 1;
|
|
249
|
+
right: -18rem;
|
|
250
|
+
bottom: -28rem;
|
|
251
|
+
border-radius: 9999px;
|
|
252
|
+
background: radial-gradient(circle, rgb(from var(--color-brand) r g b / 0.42), transparent 66%);
|
|
253
|
+
filter: blur(6px);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.pulse-sparkle {
|
|
257
|
+
animation: hero-pulse 2.8s ease-in-out infinite;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@keyframes hero-pulse {
|
|
261
|
+
0%, 100% {
|
|
262
|
+
opacity: 0.55;
|
|
263
|
+
transform: scale(1);
|
|
264
|
+
box-shadow: 0 0 0 0 rgb(from var(--color-accent) r g b / 0.28);
|
|
265
|
+
}
|
|
266
|
+
50% {
|
|
267
|
+
opacity: 1;
|
|
268
|
+
transform: scale(1.18);
|
|
269
|
+
box-shadow: 0 0 0 10px rgb(from var(--color-accent) r g b / 0);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
189
272
|
}
|
|
190
273
|
|
|
191
274
|
/* ============================================
|
|
@@ -268,21 +351,21 @@ var t=`/* ============================================
|
|
|
268
351
|
============================================ */
|
|
269
352
|
@layer theme-base {
|
|
270
353
|
[data-theme="dark"] .sun-icon {
|
|
271
|
-
display:
|
|
354
|
+
display: none;
|
|
272
355
|
}
|
|
273
356
|
[data-theme="dark"] .moon-icon {
|
|
274
|
-
display:
|
|
357
|
+
display: block;
|
|
275
358
|
}
|
|
276
359
|
[data-theme="light"] .sun-icon {
|
|
277
|
-
display:
|
|
360
|
+
display: block;
|
|
278
361
|
}
|
|
279
362
|
[data-theme="light"] .moon-icon {
|
|
280
|
-
display:
|
|
363
|
+
display: none;
|
|
281
364
|
}
|
|
282
365
|
}
|
|
283
|
-
`;function o(
|
|
366
|
+
`;function o(t){return e+`
|
|
284
367
|
|
|
285
|
-
`+
|
|
368
|
+
`+t}export{o as composeTheme};
|
|
286
369
|
|
|
287
|
-
//# debugId=
|
|
370
|
+
//# debugId=11AD1BC3215EB2DC64756E2164756E21
|
|
288
371
|
//# sourceMappingURL=themes.js.map
|
package/dist/themes.js.map
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"// Types for CSS imports are defined in types.d.ts\n/// <reference types=\"./types.d.ts\" />\n\nimport themeBaseCSS from \"./theme-base.css\" with { type: \"text\" };\n\n/**\n * Compose a complete theme by prepending shared base utilities.\n *\n * Base utilities live in @layer theme-base; theme-specific styles\n * should use @layer theme-override to guarantee correct cascade order.\n */\nexport function composeTheme(themeCSS: string): string {\n return themeBaseCSS + \"\\n\\n\" + themeCSS;\n}\n\nexport default themeBaseCSS;\nexport { themeBaseCSS };\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWO,SAAS,CAAY,CAAC,EAA0B,CACrD,OAAO,EAAe;AAAA;AAAA,EAAS",
|
|
8
|
+
"debugId": "11AD1BC3215EB2DC64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rizom/brain",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.61",
|
|
4
4
|
"description": "Brain runtime + CLI — scaffold, run, and manage AI brain instances",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"brain": "./dist/brain.js"
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
10
14
|
"./cli": "./dist/brain.js",
|
|
15
|
+
"./plugins": {
|
|
16
|
+
"types": "./dist/plugins.d.ts",
|
|
17
|
+
"import": "./dist/plugins.js"
|
|
18
|
+
},
|
|
19
|
+
"./entities": {
|
|
20
|
+
"types": "./dist/entities.d.ts",
|
|
21
|
+
"import": "./dist/entities.js"
|
|
22
|
+
},
|
|
23
|
+
"./services": {
|
|
24
|
+
"types": "./dist/services.d.ts",
|
|
25
|
+
"import": "./dist/services.js"
|
|
26
|
+
},
|
|
27
|
+
"./interfaces": {
|
|
28
|
+
"types": "./dist/interfaces.d.ts",
|
|
29
|
+
"import": "./dist/interfaces.js"
|
|
30
|
+
},
|
|
31
|
+
"./templates": {
|
|
32
|
+
"types": "./dist/templates.d.ts",
|
|
33
|
+
"import": "./dist/templates.js"
|
|
34
|
+
},
|
|
11
35
|
"./site": {
|
|
12
36
|
"types": "./dist/site.d.ts",
|
|
13
37
|
"import": "./dist/site.js"
|
|
@@ -19,14 +43,18 @@
|
|
|
19
43
|
"./deploy": {
|
|
20
44
|
"types": "./dist/deploy.d.ts",
|
|
21
45
|
"import": "./dist/deploy.js"
|
|
22
|
-
}
|
|
46
|
+
},
|
|
47
|
+
"./tsconfig.instance.json": "./tsconfig.instance.json"
|
|
23
48
|
},
|
|
24
49
|
"files": [
|
|
25
|
-
"dist"
|
|
50
|
+
"dist",
|
|
51
|
+
"templates",
|
|
52
|
+
"tsconfig.instance.json"
|
|
26
53
|
],
|
|
27
54
|
"scripts": {
|
|
28
55
|
"build": "bun scripts/build.ts",
|
|
29
56
|
"prepublishOnly": "bun scripts/build.ts",
|
|
57
|
+
"dev:start": "bun dist/brain.js start",
|
|
30
58
|
"typecheck": "tsc --noEmit",
|
|
31
59
|
"test": "bun test",
|
|
32
60
|
"lint": "eslint . --ext .ts"
|
|
@@ -42,6 +70,7 @@
|
|
|
42
70
|
"tailwindcss": "^4.1.11"
|
|
43
71
|
},
|
|
44
72
|
"optionalDependencies": {
|
|
73
|
+
"@bitwarden/sdk-napi": "^1.0.0",
|
|
45
74
|
"@libsql/client": "^0.15.7",
|
|
46
75
|
"@tailwindcss/oxide": "^4.1.4",
|
|
47
76
|
"better-sqlite3": "^11.8.1",
|
|
@@ -51,6 +80,7 @@
|
|
|
51
80
|
},
|
|
52
81
|
"devDependencies": {
|
|
53
82
|
"@brains/app": "workspace:*",
|
|
83
|
+
"@brains/deploy-templates": "workspace:*",
|
|
54
84
|
"@brains/eslint-config": "workspace:*",
|
|
55
85
|
"@brains/mcp-service": "workspace:*",
|
|
56
86
|
"@brains/plugins": "workspace:*",
|
|
@@ -61,12 +91,13 @@
|
|
|
61
91
|
"@brains/site-default": "workspace:*",
|
|
62
92
|
"@brains/site-personal": "workspace:*",
|
|
63
93
|
"@brains/site-professional": "workspace:*",
|
|
64
|
-
"@brains/site-rizom": "workspace:*",
|
|
65
94
|
"@brains/theme-default": "workspace:*",
|
|
66
95
|
"@brains/theme-rizom": "workspace:*",
|
|
67
96
|
"@brains/typescript-config": "workspace:*",
|
|
68
97
|
"@brains/utils": "workspace:*",
|
|
69
98
|
"@types/bun": "latest",
|
|
99
|
+
"rollup": "^4.60.2",
|
|
100
|
+
"rollup-plugin-dts": "^6.4.1",
|
|
70
101
|
"typescript": "^5.3.3"
|
|
71
102
|
},
|
|
72
103
|
"publishConfig": {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readJsonResponse,
|
|
3
|
+
requireEnv,
|
|
4
|
+
writeGitHubOutput,
|
|
5
|
+
writeGitHubEnv,
|
|
6
|
+
} from "./helpers";
|
|
7
|
+
|
|
8
|
+
const token = requireEnv("HCLOUD_TOKEN");
|
|
9
|
+
const instanceName = requireEnv("INSTANCE_NAME");
|
|
10
|
+
const sshKeyName = requireEnv("HCLOUD_SSH_KEY_NAME");
|
|
11
|
+
const serverType = requireEnv("HCLOUD_SERVER_TYPE");
|
|
12
|
+
const location = requireEnv("HCLOUD_LOCATION");
|
|
13
|
+
|
|
14
|
+
const headers: Record<string, string> = {
|
|
15
|
+
Authorization: `Bearer ${token}`,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
};
|
|
18
|
+
const baseUrl = "https://api.hetzner.cloud/v1";
|
|
19
|
+
const labelSelector = `brain=${instanceName}`;
|
|
20
|
+
const MAX_POLLS = 30;
|
|
21
|
+
const POLL_INTERVAL_MS = 10_000;
|
|
22
|
+
|
|
23
|
+
interface HetznerServer {
|
|
24
|
+
id: number;
|
|
25
|
+
status: string;
|
|
26
|
+
public_net?: { ipv4?: { ip?: string } };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sleep(ms: number): Promise<void> {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function listServers(): Promise<HetznerServer[]> {
|
|
34
|
+
const url = `${baseUrl}/servers?label_selector=${encodeURIComponent(labelSelector)}`;
|
|
35
|
+
const response = await fetch(url, { headers });
|
|
36
|
+
const payload = (await readJsonResponse(
|
|
37
|
+
response,
|
|
38
|
+
"Hetzner server lookup",
|
|
39
|
+
)) as {
|
|
40
|
+
servers?: HetznerServer[];
|
|
41
|
+
};
|
|
42
|
+
if (!response.ok || !payload.servers) {
|
|
43
|
+
throw new Error(`Hetzner server lookup failed: ${JSON.stringify(payload)}`);
|
|
44
|
+
}
|
|
45
|
+
return payload.servers;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function createServer(): Promise<HetznerServer> {
|
|
49
|
+
const response = await fetch(`${baseUrl}/servers`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers,
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
name: instanceName,
|
|
54
|
+
server_type: serverType,
|
|
55
|
+
image: "ubuntu-22.04",
|
|
56
|
+
location,
|
|
57
|
+
ssh_keys: [sshKeyName],
|
|
58
|
+
labels: { brain: instanceName },
|
|
59
|
+
}),
|
|
60
|
+
});
|
|
61
|
+
const payload = (await readJsonResponse(
|
|
62
|
+
response,
|
|
63
|
+
"Hetzner server create",
|
|
64
|
+
)) as {
|
|
65
|
+
server?: HetznerServer;
|
|
66
|
+
};
|
|
67
|
+
if (!response.ok || !payload.server) {
|
|
68
|
+
throw new Error(`Hetzner server create failed: ${JSON.stringify(payload)}`);
|
|
69
|
+
}
|
|
70
|
+
return payload.server;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function getServer(id: number): Promise<HetznerServer> {
|
|
74
|
+
const response = await fetch(`${baseUrl}/servers/${id}`, { headers });
|
|
75
|
+
const payload = (await readJsonResponse(response, "Hetzner server poll")) as {
|
|
76
|
+
server?: HetznerServer;
|
|
77
|
+
};
|
|
78
|
+
if (!response.ok || !payload.server) {
|
|
79
|
+
throw new Error(`Hetzner server poll failed: ${JSON.stringify(payload)}`);
|
|
80
|
+
}
|
|
81
|
+
return payload.server;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let server: HetznerServer | undefined = (await listServers())[0];
|
|
85
|
+
server ??= await createServer();
|
|
86
|
+
|
|
87
|
+
let polls = 0;
|
|
88
|
+
while (server.status !== "running" || !server.public_net?.ipv4?.ip) {
|
|
89
|
+
if (++polls > MAX_POLLS) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Server ${server.id} did not become ready after ${(MAX_POLLS * POLL_INTERVAL_MS) / 1000}s (status: ${server.status})`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (server.status === "error") {
|
|
95
|
+
throw new Error(`Server ${server.id} entered error state`);
|
|
96
|
+
}
|
|
97
|
+
console.log(
|
|
98
|
+
`Waiting for server ${server.id} (status: ${server.status}, poll ${polls}/${MAX_POLLS})...`,
|
|
99
|
+
);
|
|
100
|
+
await sleep(POLL_INTERVAL_MS);
|
|
101
|
+
server = await getServer(server.id);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const serverIp = server.public_net.ipv4.ip;
|
|
105
|
+
if (!serverIp) {
|
|
106
|
+
throw new Error(`Server ${server.id} running but has no IPv4 address`);
|
|
107
|
+
}
|
|
108
|
+
writeGitHubOutput("server_ip", serverIp);
|
|
109
|
+
writeGitHubEnv("SERVER_IP", serverIp);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readJsonResponse, requireEnv } from "./helpers";
|
|
2
|
+
|
|
3
|
+
const token = requireEnv("CF_API_TOKEN");
|
|
4
|
+
const zoneId = requireEnv("CF_ZONE_ID");
|
|
5
|
+
const domain = requireEnv("BRAIN_DOMAIN");
|
|
6
|
+
const serverIp = requireEnv("SERVER_IP");
|
|
7
|
+
|
|
8
|
+
const headers: Record<string, string> = {
|
|
9
|
+
Authorization: `Bearer ${token}`,
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
};
|
|
12
|
+
const baseUrl = "https://api.cloudflare.com/client/v4";
|
|
13
|
+
|
|
14
|
+
interface CloudflareResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
result?: Array<{ id: string }>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function findRecordId(
|
|
20
|
+
name: string,
|
|
21
|
+
type: "A" | "CNAME",
|
|
22
|
+
): Promise<string | undefined> {
|
|
23
|
+
const lookupUrl = `${baseUrl}/zones/${zoneId}/dns_records?type=${type}&name=${encodeURIComponent(name)}`;
|
|
24
|
+
const lookup = await fetch(lookupUrl, { headers });
|
|
25
|
+
const payload = (await readJsonResponse(
|
|
26
|
+
lookup,
|
|
27
|
+
"Cloudflare DNS lookup",
|
|
28
|
+
)) as CloudflareResult;
|
|
29
|
+
if (!lookup.ok || !payload.success) {
|
|
30
|
+
throw new Error(`Cloudflare DNS lookup failed: ${JSON.stringify(payload)}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return payload.result?.[0]?.id;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function upsertRecord(name: string): Promise<void> {
|
|
37
|
+
// Prefer an existing A record. If the hostname currently has a CNAME,
|
|
38
|
+
// replace that CNAME in-place so deploys can claim legacy www aliases.
|
|
39
|
+
const existing =
|
|
40
|
+
(await findRecordId(name, "A")) ?? (await findRecordId(name, "CNAME"));
|
|
41
|
+
const url = existing
|
|
42
|
+
? `${baseUrl}/zones/${zoneId}/dns_records/${existing}`
|
|
43
|
+
: `${baseUrl}/zones/${zoneId}/dns_records`;
|
|
44
|
+
|
|
45
|
+
const response = await fetch(url, {
|
|
46
|
+
method: existing ? "PUT" : "POST",
|
|
47
|
+
headers,
|
|
48
|
+
body: JSON.stringify({
|
|
49
|
+
type: "A",
|
|
50
|
+
name,
|
|
51
|
+
content: serverIp,
|
|
52
|
+
ttl: 1,
|
|
53
|
+
proxied: true,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
const result = (await readJsonResponse(
|
|
57
|
+
response,
|
|
58
|
+
"Cloudflare DNS upsert",
|
|
59
|
+
)) as CloudflareResult;
|
|
60
|
+
if (!response.ok || !result.success) {
|
|
61
|
+
throw new Error(`Cloudflare DNS upsert failed: ${JSON.stringify(result)}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await upsertRecord(domain);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { requireEnv } from "./helpers";
|
|
4
|
+
|
|
5
|
+
const privateKey = requireEnv("KAMAL_SSH_PRIVATE_KEY");
|
|
6
|
+
|
|
7
|
+
let normalized = privateKey.replace(/\r\n/g, "\n").replace(/\\n/g, "\n");
|
|
8
|
+
if (!normalized.endsWith("\n")) {
|
|
9
|
+
normalized += "\n";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const sshDir = join(process.env["HOME"] ?? "/root", ".ssh");
|
|
13
|
+
mkdirSync(sshDir, { recursive: true });
|
|
14
|
+
writeFileSync(join(sshDir, "id_ed25519"), normalized, {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
mode: 0o600,
|
|
17
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"display": "Rizom Brain Instance",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"forceConsistentCasingInFileNames": true,
|
|
7
|
+
"isolatedModules": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"noImplicitAny": true,
|
|
13
|
+
"exactOptionalPropertyTypes": true,
|
|
14
|
+
"noImplicitReturns": true,
|
|
15
|
+
"noFallthroughCasesInSwitch": true,
|
|
16
|
+
"noUncheckedIndexedAccess": true,
|
|
17
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
18
|
+
"allowUnreachableCode": false,
|
|
19
|
+
"allowUnusedLabels": false,
|
|
20
|
+
"noImplicitOverride": true,
|
|
21
|
+
"preserveWatchOutput": true,
|
|
22
|
+
"skipLibCheck": true,
|
|
23
|
+
"strict": true,
|
|
24
|
+
"target": "ES2022",
|
|
25
|
+
"module": "ESNext",
|
|
26
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
27
|
+
"jsx": "react-jsx",
|
|
28
|
+
"jsxImportSource": "preact",
|
|
29
|
+
"noEmit": true
|
|
30
|
+
}
|
|
31
|
+
}
|