moshcode 0.24.0
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/LICENSE +21 -0
- package/README.md +580 -0
- package/bin/moshcode.mjs +674 -0
- package/bin/moshscript.mjs +29 -0
- package/examples/alive.mosh +6 -0
- package/examples/scripting-the-cli.mosh +21 -0
- package/examples/team-secrets.mosh +20 -0
- package/examples/templates/bun-caddy-sqlite/.env.example +14 -0
- package/examples/templates/bun-caddy-sqlite/Caddyfile +18 -0
- package/examples/templates/bun-caddy-sqlite/README.md +97 -0
- package/examples/templates/bun-caddy-sqlite/deploy/moshcode-dns.service +39 -0
- package/examples/templates/bun-caddy-sqlite/deploy/moshpit-service.service +38 -0
- package/examples/templates/bun-caddy-sqlite/package.json +15 -0
- package/examples/templates/bun-caddy-sqlite/src/db.ts +47 -0
- package/examples/templates/bun-caddy-sqlite/src/server.ts +44 -0
- package/examples/templates/bun-caddy-sqlite/template.json +10 -0
- package/examples/templates/caddy-proxy/Caddyfile +36 -0
- package/examples/templates/caddy-proxy/README.md +104 -0
- package/examples/templates/caddy-proxy/deploy/moshcode-dns.service +39 -0
- package/examples/templates/caddy-proxy/template.json +8 -0
- package/examples/templates/caddy-static/Caddyfile +16 -0
- package/examples/templates/caddy-static/README.md +90 -0
- package/examples/templates/caddy-static/deploy/moshcode-dns.service +39 -0
- package/examples/templates/caddy-static/site/index.html +11 -0
- package/examples/templates/caddy-static/template.json +8 -0
- package/install.sh +194 -0
- package/package.json +28 -0
- package/prd/0000-template.md +49 -0
- package/prd/0001-wrap-ugig-and-coinpay-clis.md +121 -0
- package/prd/0002-separate-agent-and-raw-engine-launches.md +113 -0
- package/prd/0003-cross-engine-mcp-and-skill-installation.md +165 -0
- package/prd/0004-moshscript-run-programmable-moshcode.md +344 -0
- package/prd/0005-hosted-moshpit-resolver.md +192 -0
- package/prd/0006-help.md +359 -0
- package/prd/0007-profullstack-site-init.md +1183 -0
- package/prd/README.md +26 -0
- package/src/ads.mjs +58 -0
- package/src/auth.mjs +193 -0
- package/src/cli-schema.mjs +533 -0
- package/src/cli.mjs +118 -0
- package/src/commands.mjs +259 -0
- package/src/completion.mjs +594 -0
- package/src/console.mjs +244 -0
- package/src/dns-system.mjs +404 -0
- package/src/dns.mjs +2872 -0
- package/src/doh-server.mjs +256 -0
- package/src/doh.mjs +218 -0
- package/src/engines.mjs +385 -0
- package/src/escalate.mjs +85 -0
- package/src/help.mjs +443 -0
- package/src/integrations.mjs +265 -0
- package/src/mcp-catalog.mjs +50 -0
- package/src/mcp.mjs +155 -0
- package/src/mirror.mjs +187 -0
- package/src/notify.mjs +86 -0
- package/src/open-url.mjs +34 -0
- package/src/parking-http.mjs +65 -0
- package/src/pins.mjs +190 -0
- package/src/pit-url.mjs +13 -0
- package/src/prd.mjs +341 -0
- package/src/pty.mjs +176 -0
- package/src/pwd.mjs +103 -0
- package/src/registry.mjs +37 -0
- package/src/release-install.mjs +191 -0
- package/src/runtime.mjs +161 -0
- package/src/selfupdate.mjs +215 -0
- package/src/serve.mjs +502 -0
- package/src/skills.mjs +93 -0
- package/src/tabs.mjs +144 -0
- package/src/templates.mjs +456 -0
- package/src/tools.mjs +231 -0
- package/src/trade.mjs +137 -0
- package/src/trust.mjs +712 -0
- package/src/tui.mjs +736 -0
- package/src/ui.mjs +49 -0
- package/src/uninstall.mjs +113 -0
- package/src/upgrade.mjs +217 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
---
|
|
2
|
+
openprd: "0.2"
|
|
3
|
+
id: "0007"
|
|
4
|
+
title: "Generate batteries-included Profullstack sites for Moshpit names"
|
|
5
|
+
status: Draft
|
|
6
|
+
authors:
|
|
7
|
+
- anthony@profullstack.com
|
|
8
|
+
created: 2026-08-03
|
|
9
|
+
updated: 2026-08-03
|
|
10
|
+
repo: https://github.com/moshcoder/moshcode
|
|
11
|
+
discussion:
|
|
12
|
+
implementation:
|
|
13
|
+
tags:
|
|
14
|
+
- moshpit
|
|
15
|
+
- profullstack
|
|
16
|
+
- template
|
|
17
|
+
- site-init
|
|
18
|
+
- bun
|
|
19
|
+
- solidstart
|
|
20
|
+
- turso
|
|
21
|
+
- sqlite
|
|
22
|
+
- coinpay
|
|
23
|
+
- oauth
|
|
24
|
+
- blog
|
|
25
|
+
- rss
|
|
26
|
+
supersedes:
|
|
27
|
+
superseded-by:
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Problem
|
|
31
|
+
|
|
32
|
+
Moshcode can currently copy basic hosting templates and install web-server configuration for a Moshpit name, but a copied template is not yet a complete application. A developer still has to choose and wire a frontend framework, SSR, client navigation, an API, a database, authentication, migrations, blog routes, RSS, scheduling, deployment units, and secrets.
|
|
33
|
+
|
|
34
|
+
That work is repetitive and prevents a newly claimed Moshpit name from becoming a useful site immediately.
|
|
35
|
+
|
|
36
|
+
The Moshpit network also has unusual hosting constraints:
|
|
37
|
+
|
|
38
|
+
- Moshpit names are outside the public DNS root.
|
|
39
|
+
- Resolver-based visitors normally reach a name over plain HTTP.
|
|
40
|
+
- The public gateway strips cookies and `Authorization`.
|
|
41
|
+
- Pin-verified TLS is available only to Moshpit-aware clients and currently requires nginx.
|
|
42
|
+
- A normal OAuth callback and durable browser session therefore cannot safely depend on the plain Moshpit origin or the public gateway.
|
|
43
|
+
|
|
44
|
+
Moshcode needs a trusted, batteries-included application initializer that preserves the existing safety rule that arbitrary templates are copied but never executed.
|
|
45
|
+
|
|
46
|
+
## Goals
|
|
47
|
+
|
|
48
|
+
1. Let a user turn a claimed Moshpit name into a working full-stack application with one guided command.
|
|
49
|
+
2. Generate one isomorphic TypeScript application whose route and component code is reused for initial SSR and subsequent SPA navigation.
|
|
50
|
+
3. Include a complete versioned JSON API, CoinPay OAuth/OIDC login, durable sessions, role-based blog administration, API tokens, a blog, and an RSS feed.
|
|
51
|
+
4. Use Bun for the generated application and SolidStart/SolidJS for SSR, hydration, routing, and shared UI code.
|
|
52
|
+
5. Use the same application data layer against either a local SQLite-compatible file or a hosted Turso libSQL database.
|
|
53
|
+
6. Prefer hosted Turso automatically when the Turso CLI is installed and authenticated; otherwise use local SQLite without blocking setup.
|
|
54
|
+
7. Make the blog optional but enabled by default.
|
|
55
|
+
8. Make the auto-blog pipeline optional but enabled by default, while avoiding surprise paid AI usage.
|
|
56
|
+
9. Preserve the existing `moshcode template install` no-execution security boundary.
|
|
57
|
+
10. Produce a deterministic, testable, resumable installer suitable for humans, CI, and coding agents.
|
|
58
|
+
|
|
59
|
+
## Non-Goals
|
|
60
|
+
|
|
61
|
+
- Replacing `moshcode template install` with executable third-party templates.
|
|
62
|
+
- Building a general plugin marketplace or arbitrary template hook system.
|
|
63
|
+
- Making ordinary browsers trust Moshpit pin-verified TLS without a Moshpit-aware client.
|
|
64
|
+
- Using the public Moshpit gateway for authenticated sessions or write APIs.
|
|
65
|
+
- Providing local username/password authentication in v1.
|
|
66
|
+
- Shipping a general-purpose CMS comparable to WordPress.
|
|
67
|
+
- Adding a heavyweight ORM, separate backend project, separate frontend project, monorepo, React, Next.js, HTMX, or Hono JSX.
|
|
68
|
+
- Automatically purchasing or configuring a public DNS domain.
|
|
69
|
+
- Automatically spending money on an AI model.
|
|
70
|
+
- Supporting multi-region active-active writes to one local SQLite file.
|
|
71
|
+
- Solving every deployment target in v1; the supported host is a Linux server using systemd plus nginx or Caddy.
|
|
72
|
+
|
|
73
|
+
## Users
|
|
74
|
+
|
|
75
|
+
### Primary user: Moshpit name owner
|
|
76
|
+
|
|
77
|
+
A developer who has claimed a name such as `foo.whatever` and wants a real application rather than a static placeholder.
|
|
78
|
+
|
|
79
|
+
### Secondary user: Coding agent
|
|
80
|
+
|
|
81
|
+
An automated coding engine that needs a predictable project layout, explicit configuration, stable API contracts, migrations, tests, and clear acceptance criteria.
|
|
82
|
+
|
|
83
|
+
### Secondary user: Site visitor
|
|
84
|
+
|
|
85
|
+
A person using a Moshpit resolver, the public gateway, or the site's separate secure public origin to read pages and blog posts.
|
|
86
|
+
|
|
87
|
+
### Secondary user: Site administrator
|
|
88
|
+
|
|
89
|
+
The verified CoinPay user authorized to create, edit, schedule, publish, and delete blog posts and run the auto-blog pipeline.
|
|
90
|
+
|
|
91
|
+
## Product Decision
|
|
92
|
+
|
|
93
|
+
The bundled starter is named **`profullstack`**.
|
|
94
|
+
|
|
95
|
+
Two existing concepts remain distinct:
|
|
96
|
+
|
|
97
|
+
1. `moshcode template install profullstack` copies files only. It never executes template code, installs packages, provisions a database, contacts CoinPay, writes secrets, changes system configuration, or starts services.
|
|
98
|
+
2. `moshcode site init <name>` is a trusted workflow implemented inside Moshcode. It copies the bundled `profullstack` template and performs explicitly approved provisioning steps.
|
|
99
|
+
|
|
100
|
+
The existing `moshcode site <name>` behavior remains backward compatible and continues to plan or install web-server configuration.
|
|
101
|
+
|
|
102
|
+
## Requirements
|
|
103
|
+
|
|
104
|
+
### Command and compatibility
|
|
105
|
+
|
|
106
|
+
- **R1 [P0]** Add `moshcode site init <name>` as the guided application initializer.
|
|
107
|
+
- **R2 [P0]** Preserve the existing behavior of `moshcode site <name>`, `moshcode site <name> --install`, and all existing `site` flags.
|
|
108
|
+
- **R3 [P0]** Add `profullstack` to `examples/templates/` and to `moshcode template list`.
|
|
109
|
+
- **R4 [P0]** Keep `moshcode template install profullstack` copy-only. No file copied from any template may be executed by the installer.
|
|
110
|
+
- **R5 [P0]** Implement provisioning in trusted Moshcode source code, not in a `postinstall`, shell hook, package script, or executable template manifest.
|
|
111
|
+
- **R6 [P0]** Support interactive TTY use and deterministic non-interactive use.
|
|
112
|
+
- **R7 [P0]** Support `--dry-run`; it must display file operations, external commands, configuration choices, and privileged changes without writing files, creating cloud resources, registering OAuth clients, installing dependencies, or changing services.
|
|
113
|
+
- **R8 [P0]** Support `--json` for machine-readable results. JSON mode must write no human commentary to stdout.
|
|
114
|
+
- **R9 [P0]** Detect conflicts before writing any project files. Without `--force`, any conflict aborts the file-copy phase before changes are made.
|
|
115
|
+
- **R10 [P0]** Write a non-secret `.moshcode/site.json` state file so interrupted setup can be inspected and safely resumed.
|
|
116
|
+
- **R11 [P1]** Add `moshcode site init <name> --resume` and make plain re-execution detect and offer to resume an incomplete initialization.
|
|
117
|
+
- **R12 [P1]** Add shell completion and CLI schema entries for every new command and option.
|
|
118
|
+
|
|
119
|
+
### CLI contract
|
|
120
|
+
|
|
121
|
+
The initializer must accept:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
moshcode site init <name>
|
|
125
|
+
|
|
126
|
+
--into <directory>
|
|
127
|
+
--template <name> default: profullstack
|
|
128
|
+
--db auto|turso|sqlite default: auto
|
|
129
|
+
--turso-db <database-name>
|
|
130
|
+
--turso-group <group-name>
|
|
131
|
+
--auth coinpay|none default: coinpay
|
|
132
|
+
--auth-origin <https-url>
|
|
133
|
+
--owner-email <email>
|
|
134
|
+
--blog | --no-blog default: --blog
|
|
135
|
+
--autoblog | --no-autoblog default: --autoblog
|
|
136
|
+
--autoblog-source <source> repeatable: queue, release, ai
|
|
137
|
+
--port <1-65535> default: 3000
|
|
138
|
+
--install permit package install and system changes
|
|
139
|
+
--reload reload/enable services after installation
|
|
140
|
+
--tls request existing Moshpit pin-TLS support
|
|
141
|
+
--yes, -y accept safe prompts
|
|
142
|
+
--force overwrite conflicting project files
|
|
143
|
+
--dry-run
|
|
144
|
+
--json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Rules:
|
|
148
|
+
|
|
149
|
+
- **R13 [P0]** `--yes` may accept safe application defaults but must not imply `--install`, `--reload`, OAuth client registration, cloud resource creation after an explicit failure, or destructive cleanup.
|
|
150
|
+
- **R14 [P0]** `--install` authorizes dependency installation and system-file writes but does not imply `--reload`.
|
|
151
|
+
- **R15 [P0]** `--reload` requires `--install`.
|
|
152
|
+
- **R16 [P0]** In a non-TTY, every value that cannot be derived safely must come from a flag or environment variable. The command must fail with a precise missing-input error rather than hang.
|
|
153
|
+
- **R17 [P0]** Secrets must never appear in command output, dry-run output, JSON output, process titles, or `.moshcode/site.json`.
|
|
154
|
+
|
|
155
|
+
### Generated application stack
|
|
156
|
+
|
|
157
|
+
- **R18 [P0]** The generated runtime and package manager are Bun.
|
|
158
|
+
- **R19 [P0]** The generated web framework is SolidStart with SolidJS and Solid Router.
|
|
159
|
+
- **R20 [P0]** Initial page requests are server rendered.
|
|
160
|
+
- **R21 [P0]** The browser hydrates the same TSX routes and components and performs subsequent internal navigation as an SPA.
|
|
161
|
+
- **R22 [P0]** Shared UI code must not be duplicated into separate SSR and SPA implementations.
|
|
162
|
+
- **R23 [P0]** Database, OAuth, session, secret, scheduler, and privileged mutation code must remain server-only.
|
|
163
|
+
- **R24 [P0]** SolidStart server functions and shared domain modules are used for application UI data access; SSR routes must not make HTTP calls back into their own public API.
|
|
164
|
+
- **R25 [P0]** Public API routes and UI server functions must call the same server-side service layer.
|
|
165
|
+
- **R26 [P0]** The generated project uses strict TypeScript and includes a no-emit typecheck command.
|
|
166
|
+
- **R27 [P0]** Exact tested dependency versions are committed in `bun.lock`. No prerelease framework version is selected automatically.
|
|
167
|
+
- **R28 [P0]** The app binds to `127.0.0.1` by default. nginx or Caddy is the public client.
|
|
168
|
+
- **R29 [P0]** The app must boot with local SQLite and no external services after `bun install`, migration, and seed.
|
|
169
|
+
|
|
170
|
+
### Required project layout
|
|
171
|
+
|
|
172
|
+
The generated project must follow this minimum layout:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
profullstack-site/
|
|
176
|
+
├── src/
|
|
177
|
+
│ ├── app.tsx
|
|
178
|
+
│ ├── app.css
|
|
179
|
+
│ ├── components/
|
|
180
|
+
│ │ ├── AppShell.tsx
|
|
181
|
+
│ │ ├── BlogCard.tsx
|
|
182
|
+
│ │ ├── BlogEditor.tsx
|
|
183
|
+
│ │ ├── LoginButton.tsx
|
|
184
|
+
│ │ └── UserMenu.tsx
|
|
185
|
+
│ ├── domain/
|
|
186
|
+
│ │ ├── api.ts
|
|
187
|
+
│ │ ├── auth.ts
|
|
188
|
+
│ │ ├── posts.ts
|
|
189
|
+
│ │ └── validation.ts
|
|
190
|
+
│ ├── routes/
|
|
191
|
+
│ │ ├── index.tsx
|
|
192
|
+
│ │ ├── account.tsx
|
|
193
|
+
│ │ ├── login.tsx
|
|
194
|
+
│ │ ├── setup.tsx
|
|
195
|
+
│ │ ├── blog/
|
|
196
|
+
│ │ │ ├── index.tsx
|
|
197
|
+
│ │ │ ├── [slug].tsx
|
|
198
|
+
│ │ │ └── feed.xml.ts
|
|
199
|
+
│ │ ├── admin/
|
|
200
|
+
│ │ │ └── blog/
|
|
201
|
+
│ │ │ ├── index.tsx
|
|
202
|
+
│ │ │ └── [id].tsx
|
|
203
|
+
│ │ ├── auth/
|
|
204
|
+
│ │ │ └── coinpay/
|
|
205
|
+
│ │ │ ├── index.ts
|
|
206
|
+
│ │ │ ├── callback.ts
|
|
207
|
+
│ │ │ └── logout.ts
|
|
208
|
+
│ │ └── api/
|
|
209
|
+
│ │ └── v1/
|
|
210
|
+
│ │ ├── index.ts
|
|
211
|
+
│ │ ├── health.ts
|
|
212
|
+
│ │ ├── session.ts
|
|
213
|
+
│ │ ├── openapi.json.ts
|
|
214
|
+
│ │ ├── posts/
|
|
215
|
+
│ │ │ ├── index.ts
|
|
216
|
+
│ │ │ └── [slug].ts
|
|
217
|
+
│ │ ├── account/
|
|
218
|
+
│ │ │ └── tokens.ts
|
|
219
|
+
│ │ └── admin/
|
|
220
|
+
│ │ ├── posts.ts
|
|
221
|
+
│ │ └── autoblog.ts
|
|
222
|
+
│ └── server/
|
|
223
|
+
│ ├── config.ts
|
|
224
|
+
│ ├── db.ts
|
|
225
|
+
│ ├── migrate.ts
|
|
226
|
+
│ ├── posts.ts
|
|
227
|
+
│ ├── rss.ts
|
|
228
|
+
│ ├── security.ts
|
|
229
|
+
│ ├── auth/
|
|
230
|
+
│ │ ├── coinpay.ts
|
|
231
|
+
│ │ ├── oauth-transactions.ts
|
|
232
|
+
│ │ ├── sessions.ts
|
|
233
|
+
│ │ └── api-tokens.ts
|
|
234
|
+
│ └── autoblog/
|
|
235
|
+
│ ├── index.ts
|
|
236
|
+
│ ├── queue.ts
|
|
237
|
+
│ ├── release.ts
|
|
238
|
+
│ └── ai.ts
|
|
239
|
+
├── content/
|
|
240
|
+
│ └── queue/
|
|
241
|
+
├── migrations/
|
|
242
|
+
│ └── 0001_initial.sql
|
|
243
|
+
├── scripts/
|
|
244
|
+
│ ├── migrate.ts
|
|
245
|
+
│ ├── seed.ts
|
|
246
|
+
│ └── autoblog.ts
|
|
247
|
+
├── test/
|
|
248
|
+
├── public/
|
|
249
|
+
│ └── assets/
|
|
250
|
+
├── deploy/
|
|
251
|
+
│ ├── profullstack.service
|
|
252
|
+
│ ├── profullstack-autoblog.service
|
|
253
|
+
│ └── profullstack-autoblog.timer
|
|
254
|
+
├── .env.example
|
|
255
|
+
├── .gitignore
|
|
256
|
+
├── app.config.ts
|
|
257
|
+
├── bun.lock
|
|
258
|
+
├── Caddyfile
|
|
259
|
+
├── package.json
|
|
260
|
+
├── README.md
|
|
261
|
+
├── template.json
|
|
262
|
+
└── tsconfig.json
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
A small implementation may combine files, but it must preserve the conceptual boundaries between shared domain code, shared UI code, server-only services, public API routes, and migrations.
|
|
266
|
+
|
|
267
|
+
### Database selection and provisioning
|
|
268
|
+
|
|
269
|
+
- **R30 [P0]** Use `@libsql/client` so one data-access API works with a local SQLite-compatible file and a hosted Turso libSQL database.
|
|
270
|
+
- **R31 [P0]** Do not add an ORM in v1. Use parameterized raw SQL and small repository/service functions.
|
|
271
|
+
- **R32 [P0]** Default database mode is `auto`.
|
|
272
|
+
- **R33 [P0]** In `auto` mode, look for `turso` on `PATH`.
|
|
273
|
+
- **R34 [P0]** When the Turso CLI is present, run `turso auth whoami`.
|
|
274
|
+
- **R35 [P0]** When the Turso CLI is present and authenticated, offer to create or use a hosted database. The interactive default is hosted Turso.
|
|
275
|
+
- **R36 [P0]** When the Turso CLI is absent, unauthenticated, declined, or unavailable, `auto` mode must fall back to local SQLite and continue.
|
|
276
|
+
- **R37 [P0]** Explicit `--db turso` must fail on provisioning failure and must never silently switch to local SQLite.
|
|
277
|
+
- **R38 [P0]** Explicit `--db sqlite` must never contact Turso.
|
|
278
|
+
- **R39 [P0]** In a non-TTY, `--db auto` uses Turso only when the CLI is already authenticated and all required choices are deterministic; otherwise it uses local SQLite.
|
|
279
|
+
- **R40 [P0]** Create hosted libSQL with:
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
turso db create <database-name> --wait
|
|
283
|
+
turso db show <database-name> --url
|
|
284
|
+
turso db tokens create <database-name> --expiration never
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- **R41 [P0]** If an account has multiple Turso groups and no group is supplied, prompt interactively or fail non-interactively with a list of valid choices.
|
|
288
|
+
- **R42 [P0]** Sanitize the Moshpit name into a valid database name and append a short stable hash when needed to avoid collisions.
|
|
289
|
+
- **R43 [P0]** Store hosted credentials only in `.env`, with file mode `0600`.
|
|
290
|
+
- **R44 [P0]** Never commit `.env`, `data/*.db`, SQLite journal/WAL files, or generated tokens.
|
|
291
|
+
- **R45 [P0]** Local configuration is:
|
|
292
|
+
|
|
293
|
+
```env
|
|
294
|
+
DATABASE_URL=file:./data/app.db
|
|
295
|
+
TURSO_AUTH_TOKEN=
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
- **R46 [P0]** Hosted configuration is:
|
|
299
|
+
|
|
300
|
+
```env
|
|
301
|
+
DATABASE_URL=libsql://<database-name>-<organization>.turso.io
|
|
302
|
+
TURSO_AUTH_TOKEN=<redacted>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
- **R47 [P0]** The application creates the parent directory for a local database before opening the file.
|
|
306
|
+
- **R48 [P0]** The migration runner is idempotent, transactional where SQLite allows, records applied migration filename and checksum, and refuses a changed checksum for an already-applied migration.
|
|
307
|
+
- **R49 [P0]** If a cloud database is created and a later step fails, do not destroy it automatically. Report its name and the exact resume or cleanup command.
|
|
308
|
+
- **R50 [P1]** Add a documented database-token rotation procedure.
|
|
309
|
+
|
|
310
|
+
### Initial database schema
|
|
311
|
+
|
|
312
|
+
The first migration must create at least:
|
|
313
|
+
|
|
314
|
+
```text
|
|
315
|
+
schema_migrations
|
|
316
|
+
users
|
|
317
|
+
sessions
|
|
318
|
+
oauth_transactions
|
|
319
|
+
api_tokens
|
|
320
|
+
posts
|
|
321
|
+
autoblog_runs
|
|
322
|
+
settings
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Minimum fields and constraints:
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
schema_migrations
|
|
329
|
+
filename TEXT PRIMARY KEY
|
|
330
|
+
checksum TEXT NOT NULL
|
|
331
|
+
applied_at INTEGER NOT NULL
|
|
332
|
+
|
|
333
|
+
users
|
|
334
|
+
id TEXT PRIMARY KEY
|
|
335
|
+
coinpay_sub TEXT NOT NULL UNIQUE
|
|
336
|
+
email TEXT
|
|
337
|
+
email_verified INTEGER NOT NULL DEFAULT 0
|
|
338
|
+
display_name TEXT
|
|
339
|
+
avatar_url TEXT
|
|
340
|
+
role TEXT NOT NULL DEFAULT 'user'
|
|
341
|
+
created_at INTEGER NOT NULL
|
|
342
|
+
updated_at INTEGER NOT NULL
|
|
343
|
+
last_login_at INTEGER
|
|
344
|
+
|
|
345
|
+
sessions
|
|
346
|
+
id TEXT PRIMARY KEY
|
|
347
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE
|
|
348
|
+
token_hash TEXT NOT NULL UNIQUE
|
|
349
|
+
created_at INTEGER NOT NULL
|
|
350
|
+
expires_at INTEGER NOT NULL
|
|
351
|
+
last_seen_at INTEGER NOT NULL
|
|
352
|
+
user_agent_hash TEXT
|
|
353
|
+
ip_prefix_hash TEXT
|
|
354
|
+
|
|
355
|
+
oauth_transactions
|
|
356
|
+
id TEXT PRIMARY KEY
|
|
357
|
+
state_hash TEXT NOT NULL UNIQUE
|
|
358
|
+
pkce_verifier_ciphertext TEXT NOT NULL
|
|
359
|
+
return_to TEXT NOT NULL
|
|
360
|
+
created_at INTEGER NOT NULL
|
|
361
|
+
expires_at INTEGER NOT NULL
|
|
362
|
+
consumed_at INTEGER
|
|
363
|
+
|
|
364
|
+
api_tokens
|
|
365
|
+
id TEXT PRIMARY KEY
|
|
366
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE
|
|
367
|
+
name TEXT NOT NULL
|
|
368
|
+
token_prefix TEXT NOT NULL
|
|
369
|
+
token_hash TEXT NOT NULL UNIQUE
|
|
370
|
+
scopes TEXT NOT NULL
|
|
371
|
+
created_at INTEGER NOT NULL
|
|
372
|
+
expires_at INTEGER
|
|
373
|
+
last_used_at INTEGER
|
|
374
|
+
revoked_at INTEGER
|
|
375
|
+
|
|
376
|
+
posts
|
|
377
|
+
id TEXT PRIMARY KEY
|
|
378
|
+
slug TEXT NOT NULL UNIQUE
|
|
379
|
+
title TEXT NOT NULL
|
|
380
|
+
excerpt TEXT
|
|
381
|
+
body_markdown TEXT NOT NULL
|
|
382
|
+
body_html TEXT NOT NULL
|
|
383
|
+
status TEXT NOT NULL
|
|
384
|
+
source TEXT NOT NULL
|
|
385
|
+
author_user_id TEXT REFERENCES users(id)
|
|
386
|
+
scheduled_at INTEGER
|
|
387
|
+
published_at INTEGER
|
|
388
|
+
created_at INTEGER NOT NULL
|
|
389
|
+
updated_at INTEGER NOT NULL
|
|
390
|
+
|
|
391
|
+
autoblog_runs
|
|
392
|
+
id TEXT PRIMARY KEY
|
|
393
|
+
source TEXT NOT NULL
|
|
394
|
+
status TEXT NOT NULL
|
|
395
|
+
input_ref TEXT
|
|
396
|
+
post_id TEXT REFERENCES posts(id)
|
|
397
|
+
error_code TEXT
|
|
398
|
+
error_message TEXT
|
|
399
|
+
started_at INTEGER NOT NULL
|
|
400
|
+
completed_at INTEGER
|
|
401
|
+
|
|
402
|
+
settings
|
|
403
|
+
key TEXT PRIMARY KEY
|
|
404
|
+
value_json TEXT NOT NULL
|
|
405
|
+
updated_at INTEGER NOT NULL
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Required indexes:
|
|
409
|
+
|
|
410
|
+
```text
|
|
411
|
+
posts(status, published_at)
|
|
412
|
+
posts(status, scheduled_at)
|
|
413
|
+
sessions(expires_at)
|
|
414
|
+
oauth_transactions(expires_at)
|
|
415
|
+
api_tokens(user_id, revoked_at)
|
|
416
|
+
autoblog_runs(started_at)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Allowed role values in v1 are `owner`, `editor`, and `user`.
|
|
420
|
+
|
|
421
|
+
Allowed post states in v1 are `draft`, `scheduled`, `published`, and `archived`.
|
|
422
|
+
|
|
423
|
+
### CoinPay OAuth/OIDC
|
|
424
|
+
|
|
425
|
+
- **R51 [P0]** Authentication provider is CoinPay OAuth 2.0/OIDC authorization code flow with S256 PKCE.
|
|
426
|
+
- **R52 [P0]** Default scopes are `openid profile email`.
|
|
427
|
+
- **R53 [P1]** Optional scopes are `did` and `wallet:read`; they are never requested by default.
|
|
428
|
+
- **R54 [P0]** The installer supports:
|
|
429
|
+
1. automatic client registration using a user-supplied CoinPay API token;
|
|
430
|
+
2. entry of an existing client ID and client secret; or
|
|
431
|
+
3. configure-later mode.
|
|
432
|
+
- **R55 [P0]** CoinPay secrets are entered without terminal echo and are written only to `.env`.
|
|
433
|
+
- **R56 [P0]** Automatic registration calls the authenticated CoinPay client-registration API with the final HTTPS callback URI and requested scopes.
|
|
434
|
+
- **R57 [P0]** If automatic registration succeeds, the client secret is persisted immediately because it may be shown only once.
|
|
435
|
+
- **R58 [P0]** If auth is selected but credentials are absent, the application still boots and `/api/v1/health` reports `auth.configured: false`; login UI must explain the exact missing configuration.
|
|
436
|
+
- **R59 [P0]** OAuth transactions contain a cryptographically random state value, a PKCE verifier, an allowed post-login return path, creation time, expiry, and single-use marker.
|
|
437
|
+
- **R60 [P0]** Store only a hash of the state token. Protect the PKCE verifier at rest with authenticated encryption derived from `SESSION_SECRET`.
|
|
438
|
+
- **R61 [P0]** OAuth transactions expire after ten minutes and are consumed atomically.
|
|
439
|
+
- **R62 [P0]** The callback rejects missing, expired, reused, or mismatched state before exchanging the code.
|
|
440
|
+
- **R63 [P0]** The authorization code exchange and UserInfo request occur only on the server.
|
|
441
|
+
- **R64 [P0]** The `sub` returned by the verified CoinPay identity is the stable user key. Email is not the primary key.
|
|
442
|
+
- **R65 [P0]** The implementation must not hand-roll JWT or JOSE verification. It must use a maintained library and the canonical CoinPay verification metadata confirmed before release.
|
|
443
|
+
- **R66 [P0]** Access, refresh, and ID tokens are not persisted for login-only use after identity establishment. Persist provider tokens only if a future feature explicitly needs delegated CoinPay API access.
|
|
444
|
+
- **R67 [P0]** Logout destroys the local session. Provider-token revocation is performed only if CoinPay exposes and documents a revocation endpoint.
|
|
445
|
+
- **R68 [P0]** Authentication errors return safe user messages and structured internal error codes without exposing provider responses, client secrets, codes, state, or tokens.
|
|
446
|
+
- **R69 [P0]** The installer asks for `OWNER_EMAIL`. The first CoinPay user whose verified email matches it is assigned `owner`.
|
|
447
|
+
- **R70 [P0]** If no owner email is configured, no user becomes owner automatically. The generated README must document the safe owner-bootstrap command.
|
|
448
|
+
- **R71 [P1]** Add a local administrative CLI script that can assign or revoke roles by CoinPay `sub` or verified email while running on the host.
|
|
449
|
+
|
|
450
|
+
#### Configurable CoinPay endpoints
|
|
451
|
+
|
|
452
|
+
Until CoinPay's public OAuth prompt and API documentation use one canonical endpoint set, the template must keep endpoint URLs configurable:
|
|
453
|
+
|
|
454
|
+
```env
|
|
455
|
+
COINPAY_BASE_URL=https://coinpayportal.com
|
|
456
|
+
COINPAY_AUTHORIZE_PATH=/api/oauth/authorize
|
|
457
|
+
COINPAY_TOKEN_PATH=/api/oauth/token
|
|
458
|
+
COINPAY_USERINFO_PATH=/api/oauth/userinfo
|
|
459
|
+
COINPAY_CLIENTS_PATH=/api/oauth/clients
|
|
460
|
+
COINPAY_JWKS_PATH=/api/oauth/jwks
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Release is blocked until an integration test passes against the canonical production endpoint set and the chosen ID-token verification method is confirmed.
|
|
464
|
+
|
|
465
|
+
### Session and API authentication
|
|
466
|
+
|
|
467
|
+
- **R72 [P0]** Generate `SESSION_SECRET` from at least 32 cryptographically random bytes.
|
|
468
|
+
- **R73 [P0]** Browser sessions use an opaque random token; store only its cryptographic hash in the database.
|
|
469
|
+
- **R74 [P0]** On a normal CA-valid HTTPS auth origin, the session cookie is `HttpOnly`, `Secure`, `SameSite=Lax`, host-only, and uses a `__Host-` prefix.
|
|
470
|
+
- **R75 [P0]** Rotate the session token after login and privilege changes.
|
|
471
|
+
- **R76 [P0]** Default session lifetime is 30 days with idle activity tracking and server-side revocation.
|
|
472
|
+
- **R77 [P0]** State-changing browser requests require same-origin validation and CSRF protection appropriate to SolidStart actions.
|
|
473
|
+
- **R78 [P0]** User-created API tokens are prefixed, shown once, stored as hashes, scoped, revocable, and accepted only over a CA-valid HTTPS origin.
|
|
474
|
+
- **R79 [P0]** Default API-token scopes are read-only. Write scopes require an explicit selection.
|
|
475
|
+
- **R80 [P0]** Authentication middleware must distinguish browser sessions from bearer API tokens and produce a normalized internal principal.
|
|
476
|
+
- **R81 [P0]** Admin blog mutations require `owner` or `editor`.
|
|
477
|
+
- **R82 [P0]** Owner-only operations include role management, OAuth configuration status, and destructive site settings.
|
|
478
|
+
- **R83 [P0]** Return `401` for unauthenticated requests and `403` for authenticated principals lacking permission.
|
|
479
|
+
|
|
480
|
+
### Moshpit origin and secure auth origin
|
|
481
|
+
|
|
482
|
+
- **R84 [P0]** The generated app supports two logical origins served by the same application:
|
|
483
|
+
|
|
484
|
+
```env
|
|
485
|
+
MOSHPIT_ORIGIN=http://foo.whatever
|
|
486
|
+
AUTH_ORIGIN=https://foo.apps.example.com
|
|
487
|
+
PUBLIC_CANONICAL_ORIGIN=https://foo.apps.example.com
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
- **R85 [P0]** `AUTH_ORIGIN` must be a normal, CA-valid HTTPS URL for stock-browser CoinPay OAuth and authenticated sessions.
|
|
491
|
+
- **R86 [P0]** The plain Moshpit origin may serve public pages, public blog posts, RSS, health, and read-only API routes.
|
|
492
|
+
- **R87 [P0]** The plain Moshpit origin must not issue login sessions, accept bearer tokens, render account data, expose admin UI, or accept state-changing requests.
|
|
493
|
+
- **R88 [P0]** A login link opened on the Moshpit origin redirects to the equivalent route on `AUTH_ORIGIN`.
|
|
494
|
+
- **R89 [P0]** The public gateway is treated as anonymous and read-only because it strips cookies and `Authorization`.
|
|
495
|
+
- **R90 [P0]** Host/origin policy must be enforced on the server, not only hidden in the frontend.
|
|
496
|
+
- **R91 [P0]** `PUBLIC_CANONICAL_ORIGIN` supplies absolute public links in RSS, Open Graph metadata, canonical tags, and API documentation.
|
|
497
|
+
- **R92 [P1]** When `moshcode site <name> --tls` is used with nginx, the generated app may advertise the pin-verified Moshpit HTTPS origin to compatible clients, but it is not the default OAuth callback in v1.
|
|
498
|
+
- **R93 [P1]** Add platform support for automatically allocating a CA-valid companion hostname under a Moshcode-owned public domain. Until that service exists, prompt for `AUTH_ORIGIN`.
|
|
499
|
+
|
|
500
|
+
### Public JSON API
|
|
501
|
+
|
|
502
|
+
All API endpoints are versioned beneath `/api/v1`.
|
|
503
|
+
|
|
504
|
+
Public endpoints:
|
|
505
|
+
|
|
506
|
+
```text
|
|
507
|
+
GET /api/v1
|
|
508
|
+
GET /api/v1/health
|
|
509
|
+
GET /api/v1/session
|
|
510
|
+
GET /api/v1/posts
|
|
511
|
+
GET /api/v1/posts/:slug
|
|
512
|
+
GET /api/v1/openapi.json
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Authenticated account endpoints:
|
|
516
|
+
|
|
517
|
+
```text
|
|
518
|
+
GET /api/v1/account/tokens
|
|
519
|
+
POST /api/v1/account/tokens
|
|
520
|
+
DELETE /api/v1/account/tokens/:id
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Administrative endpoints:
|
|
524
|
+
|
|
525
|
+
```text
|
|
526
|
+
POST /api/v1/admin/posts
|
|
527
|
+
PATCH /api/v1/admin/posts/:id
|
|
528
|
+
DELETE /api/v1/admin/posts/:id
|
|
529
|
+
POST /api/v1/admin/autoblog/run
|
|
530
|
+
GET /api/v1/admin/autoblog/runs
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
- **R94 [P0]** Use JSON request and response bodies except endpoints that explicitly return HTML, redirects, or XML.
|
|
534
|
+
- **R95 [P0]** Successful list responses use:
|
|
535
|
+
|
|
536
|
+
```json
|
|
537
|
+
{
|
|
538
|
+
"data": [],
|
|
539
|
+
"meta": {
|
|
540
|
+
"limit": 20,
|
|
541
|
+
"cursor": null,
|
|
542
|
+
"next_cursor": null
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
- **R96 [P0]** Errors use:
|
|
548
|
+
|
|
549
|
+
```json
|
|
550
|
+
{
|
|
551
|
+
"error": {
|
|
552
|
+
"code": "machine_readable_code",
|
|
553
|
+
"message": "Safe human-readable message",
|
|
554
|
+
"request_id": "opaque-id"
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
- **R97 [P0]** Validate every path, query, header, and body input using shared schemas.
|
|
560
|
+
- **R98 [P0]** Pagination is cursor-based, stable, and bounded. Default limit is 20; maximum is 100.
|
|
561
|
+
- **R99 [P0]** Public post responses include only published posts whose `published_at` is not in the future.
|
|
562
|
+
- **R100 [P0]** OpenAPI output documents auth methods, schemas, errors, and every v1 endpoint.
|
|
563
|
+
- **R101 [P0]** CORS is disabled by default except same-origin requests. Additional allowed origins require explicit configuration.
|
|
564
|
+
- **R102 [P0]** API responses include a request ID, `X-Content-Type-Options: nosniff`, and appropriate cache headers.
|
|
565
|
+
- **R103 [P0]** Read-only public endpoints may be cached; session, token, and admin endpoints must be `no-store`.
|
|
566
|
+
- **R104 [P1]** Add conditional GET support (`ETag` and/or `Last-Modified`) to post list and detail responses.
|
|
567
|
+
|
|
568
|
+
### Blog
|
|
569
|
+
|
|
570
|
+
- **R105 [P0]** Blog is installed and enabled by default.
|
|
571
|
+
- **R106 [P0]** `--no-blog` keeps the code in the template but sets `BLOG_ENABLED=false`, removes blog navigation, disables auto-blog, and returns `404` from blog and feed routes.
|
|
572
|
+
- **R107 [P0]** Public routes are:
|
|
573
|
+
|
|
574
|
+
```text
|
|
575
|
+
/blog
|
|
576
|
+
/blog/:slug
|
|
577
|
+
/blog/feed.xml
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
- **R108 [P0]** Admin route is `/admin/blog` with create, edit, preview, schedule, publish, archive, and delete actions.
|
|
581
|
+
- **R109 [P0]** Blog pages use the same Solid components during SSR and hydrated SPA navigation.
|
|
582
|
+
- **R110 [P0]** Posts are authored in Markdown.
|
|
583
|
+
- **R111 [P0]** Markdown is rendered and sanitized on the server with maintained open-source libraries. Unsanitized HTML must never be rendered.
|
|
584
|
+
- **R112 [P0]** Slugs are normalized, unique, stable after publication unless explicitly changed, and validated against reserved routes.
|
|
585
|
+
- **R113 [P0]** A changed published slug records or generates a permanent redirect from the old slug.
|
|
586
|
+
- **R114 [P0]** The installer seeds one published welcome post so `/blog`, a post detail page, the posts API, and RSS are immediately non-empty.
|
|
587
|
+
- **R115 [P0]** Draft and scheduled posts are never exposed to anonymous visitors.
|
|
588
|
+
- **R116 [P0]** The admin editor uses progressive forms/actions that work after hydration and report validation errors without losing content.
|
|
589
|
+
- **R117 [P1]** Support tags and tag-filtered blog pages.
|
|
590
|
+
- **R118 [P1]** Support image attachments through a configurable object-storage adapter; v1 does not require this.
|
|
591
|
+
|
|
592
|
+
### RSS
|
|
593
|
+
|
|
594
|
+
- **R119 [P0]** `/blog/feed.xml` returns RSS 2.0 as `application/rss+xml; charset=utf-8`.
|
|
595
|
+
- **R120 [P0]** The feed includes an Atom self-link, channel title, description, canonical site link, language, last build date, stable GUIDs, publication dates, excerpts, and sanitized full HTML in `content:encoded`.
|
|
596
|
+
- **R121 [P0]** Every XML field and URL is escaped correctly.
|
|
597
|
+
- **R122 [P0]** Only currently published posts appear.
|
|
598
|
+
- **R123 [P0]** Feed links use `PUBLIC_CANONICAL_ORIGIN`, not an untrusted request `Host` header.
|
|
599
|
+
- **R124 [P0]** The feed supports `ETag`, `Last-Modified`, and `304 Not Modified`.
|
|
600
|
+
- **R125 [P0]** RSS generation is covered by an XML-parser test, not only string snapshots.
|
|
601
|
+
- **R126 [P0]** When blog is disabled, the feed returns `404`.
|
|
602
|
+
|
|
603
|
+
### Auto-blog
|
|
604
|
+
|
|
605
|
+
- **R127 [P0]** Auto-blog is enabled by default when the blog is enabled.
|
|
606
|
+
- **R128 [P0]** `--no-autoblog` sets `AUTOBLOG_ENABLED=false` and does not install or enable the timer.
|
|
607
|
+
- **R129 [P0]** Auto-blog has a provider-neutral pipeline and records every attempted run.
|
|
608
|
+
- **R130 [P0]** Default sources are `queue` and `release`.
|
|
609
|
+
- **R131 [P0]** Queue source imports Markdown files from `content/queue/` exactly once.
|
|
610
|
+
- **R132 [P0]** Release source can turn a local changelog or supplied release JSON into a post without requiring an AI provider.
|
|
611
|
+
- **R133 [P0]** AI source is included but inactive until an endpoint, model, and credential are explicitly configured.
|
|
612
|
+
- **R134 [P0]** Enabling auto-blog must not itself make paid model calls.
|
|
613
|
+
- **R135 [P0]** The baseline AI adapter uses an OpenAI-compatible HTTP API through `fetch` and does not require a vendor SDK.
|
|
614
|
+
- **R136 [P0]** AI output is validated, rendered, sanitized, and subject to the same post rules as manually authored content.
|
|
615
|
+
- **R137 [P0]** Default auto-publish behavior is:
|
|
616
|
+
- queue and release sources: publish;
|
|
617
|
+
- AI source: create a draft until `AUTOBLOG_AI_AUTOPUBLISH=true`.
|
|
618
|
+
- **R138 [P0]** A systemd oneshot service and timer run the pipeline on a configurable schedule. Default schedule is daily at 09:00 server local time.
|
|
619
|
+
- **R139 [P0]** The job uses a database lock/lease so concurrent timer, API, or manual runs cannot process the same input twice.
|
|
620
|
+
- **R140 [P0]** Each run records source, status, input reference, resulting post, timestamps, and a safe error.
|
|
621
|
+
- **R141 [P0]** Manual execution is available through `bun run autoblog` and the authorized admin API.
|
|
622
|
+
- **R142 [P0]** A failed item does not abort unrelated queued items.
|
|
623
|
+
- **R143 [P0]** Re-running a source is idempotent based on a stable input hash.
|
|
624
|
+
- **R144 [P1]** Add GitHub release ingestion through a webhook or scheduled fetch.
|
|
625
|
+
- **R145 [P1]** Add OpenRouter, OpenAI, Anthropic, and local Ollama presets while retaining the generic OpenAI-compatible adapter.
|
|
626
|
+
|
|
627
|
+
### Configuration
|
|
628
|
+
|
|
629
|
+
The generated `.env.example` must include:
|
|
630
|
+
|
|
631
|
+
```env
|
|
632
|
+
APP_NAME=foo.whatever
|
|
633
|
+
HOST=127.0.0.1
|
|
634
|
+
PORT=3000
|
|
635
|
+
NODE_ENV=development
|
|
636
|
+
|
|
637
|
+
MOSHPIT_ORIGIN=http://foo.whatever
|
|
638
|
+
AUTH_ORIGIN=https://foo.apps.example.com
|
|
639
|
+
PUBLIC_CANONICAL_ORIGIN=https://foo.apps.example.com
|
|
640
|
+
ALLOWED_ORIGINS=https://foo.apps.example.com
|
|
641
|
+
|
|
642
|
+
DATABASE_URL=file:./data/app.db
|
|
643
|
+
TURSO_AUTH_TOKEN=
|
|
644
|
+
|
|
645
|
+
COINPAY_BASE_URL=https://coinpayportal.com
|
|
646
|
+
COINPAY_AUTHORIZE_PATH=/api/oauth/authorize
|
|
647
|
+
COINPAY_TOKEN_PATH=/api/oauth/token
|
|
648
|
+
COINPAY_USERINFO_PATH=/api/oauth/userinfo
|
|
649
|
+
COINPAY_CLIENTS_PATH=/api/oauth/clients
|
|
650
|
+
COINPAY_JWKS_PATH=/api/oauth/jwks
|
|
651
|
+
COINPAY_CLIENT_ID=
|
|
652
|
+
COINPAY_CLIENT_SECRET=
|
|
653
|
+
COINPAY_SCOPES="openid profile email"
|
|
654
|
+
|
|
655
|
+
SESSION_SECRET=
|
|
656
|
+
SESSION_TTL_DAYS=30
|
|
657
|
+
OWNER_EMAIL=
|
|
658
|
+
|
|
659
|
+
BLOG_ENABLED=true
|
|
660
|
+
BLOG_TITLE=foo.whatever
|
|
661
|
+
BLOG_DESCRIPTION="Updates from foo.whatever"
|
|
662
|
+
BLOG_LANGUAGE=en-us
|
|
663
|
+
BLOG_POSTS_PER_PAGE=20
|
|
664
|
+
|
|
665
|
+
AUTOBLOG_ENABLED=true
|
|
666
|
+
AUTOBLOG_SOURCES=queue,release
|
|
667
|
+
AUTOBLOG_AI_AUTOPUBLISH=false
|
|
668
|
+
AUTOBLOG_OPENAI_BASE_URL=
|
|
669
|
+
AUTOBLOG_OPENAI_API_KEY=
|
|
670
|
+
AUTOBLOG_OPENAI_MODEL=
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
- **R146 [P0]** Configuration is parsed once at startup and validated before the server accepts requests.
|
|
674
|
+
- **R147 [P0]** Missing required production values produce one actionable startup report listing all invalid fields.
|
|
675
|
+
- **R148 [P0]** Public environment values and server secrets are defined in separate typed configuration objects.
|
|
676
|
+
- **R149 [P0]** No secret is serialized into SSR payloads or browser bundles.
|
|
677
|
+
- **R150 [P0]** The health endpoint reports feature configuration as booleans and modes, never secret values.
|
|
678
|
+
|
|
679
|
+
### Installer flow
|
|
680
|
+
|
|
681
|
+
Interactive default flow:
|
|
682
|
+
|
|
683
|
+
```text
|
|
684
|
+
$ moshcode site init foo.whatever
|
|
685
|
+
|
|
686
|
+
Creating a Profullstack application for foo.whatever
|
|
687
|
+
|
|
688
|
+
Project directory: ./foo.whatever
|
|
689
|
+
Runtime: Bun
|
|
690
|
+
UI: SolidStart
|
|
691
|
+
API: /api/v1
|
|
692
|
+
Blog: /blog
|
|
693
|
+
RSS: /blog/feed.xml
|
|
694
|
+
|
|
695
|
+
✓ Bun found
|
|
696
|
+
|
|
697
|
+
✓ Turso CLI found
|
|
698
|
+
✓ Turso login found
|
|
699
|
+
|
|
700
|
+
Use a hosted Turso database? [Y/n]
|
|
701
|
+
Database name [foo-whatever]:
|
|
702
|
+
Create database now? [Y/n]
|
|
703
|
+
|
|
704
|
+
✓ Database created
|
|
705
|
+
✓ Database URL retrieved
|
|
706
|
+
✓ Database token created
|
|
707
|
+
✓ Credentials written to .env (0600)
|
|
708
|
+
|
|
709
|
+
Enable blog? [Y/n]
|
|
710
|
+
Enable auto-blog? [Y/n]
|
|
711
|
+
Auto-blog sources [queue,release]:
|
|
712
|
+
|
|
713
|
+
Configure CoinPay login now? [Y/n]
|
|
714
|
+
Secure HTTPS origin:
|
|
715
|
+
Owner CoinPay email:
|
|
716
|
+
CoinPay setup:
|
|
717
|
+
1. Register a new OAuth client with an API token
|
|
718
|
+
2. Enter an existing client ID and secret
|
|
719
|
+
3. Configure later
|
|
720
|
+
|
|
721
|
+
Install Bun dependencies? [Y/n]
|
|
722
|
+
Run migrations and seed data? [Y/n]
|
|
723
|
+
Build production bundle? [Y/n]
|
|
724
|
+
Install systemd and web-server configuration? [y/N]
|
|
725
|
+
Reload and enable services now? [y/N]
|
|
726
|
+
|
|
727
|
+
✓ Application initialized
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
- **R151 [P0]** Prompt order must avoid requesting secrets before the final callback URI and database choice are known.
|
|
731
|
+
- **R152 [P0]** Every external command's exit code and stderr are checked.
|
|
732
|
+
- **R153 [P0]** The installer stops at the first failed dependent step and prints completed steps plus the exact resume command.
|
|
733
|
+
- **R154 [P0]** Sensitive prompts use no-echo input.
|
|
734
|
+
- **R155 [P0]** The final summary contains:
|
|
735
|
+
- project directory;
|
|
736
|
+
- database mode and resource name;
|
|
737
|
+
- auth configured/not configured;
|
|
738
|
+
- public and secure origins;
|
|
739
|
+
- enabled features;
|
|
740
|
+
- service names;
|
|
741
|
+
- local development command;
|
|
742
|
+
- production start command;
|
|
743
|
+
- remaining manual actions.
|
|
744
|
+
- **R156 [P0]** The final summary never prints a full database token, OAuth client secret, session secret, API token, authorization code, or state.
|
|
745
|
+
- **R157 [P0]** If Bun is missing, offer the official Bun installation path interactively. Without approval, generate the project and print the install command; do not silently run a remote shell script.
|
|
746
|
+
- **R158 [P0]** If Turso is missing, local SQLite fallback is immediate; do not require Turso installation.
|
|
747
|
+
- **R159 [P0]** If CoinPay automatic registration is unavailable, provide the exact callback URI and scopes needed for manual dashboard registration.
|
|
748
|
+
|
|
749
|
+
### Safe template rendering
|
|
750
|
+
|
|
751
|
+
- **R160 [P0]** The initializer may replace documented inert tokens such as `__APP_NAME__`, `__APP_SLUG__`, `__MOSHPIT_ORIGIN__`, `__AUTH_ORIGIN__`, and `__PORT__`.
|
|
752
|
+
- **R161 [P0]** Only trusted initializer code performs token replacement.
|
|
753
|
+
- **R162 [P0]** Template values are escaped for the target format; a Moshpit name must not be able to inject TypeScript, JSON, shell, systemd, nginx, or Caddy syntax.
|
|
754
|
+
- **R163 [P0]** No arbitrary expression evaluation, JavaScript evaluation, shell interpolation, or user-provided template engine is allowed.
|
|
755
|
+
- **R164 [P0]** Generated systemd unit names and paths use sanitized identifiers and absolute paths.
|
|
756
|
+
|
|
757
|
+
### Deployment
|
|
758
|
+
|
|
759
|
+
- **R165 [P0]** The generated application includes a systemd service that runs the built SolidStart Bun server from an unprivileged user and restarts on failure.
|
|
760
|
+
- **R166 [P0]** The service binds to `127.0.0.1:<port>`.
|
|
761
|
+
- **R167 [P0]** The service loads secrets from an environment file with restrictive permissions.
|
|
762
|
+
- **R168 [P0]** The service uses a dedicated writable data directory and reasonable systemd hardening that does not break Bun or SQLite.
|
|
763
|
+
- **R169 [P0]** The initializer can call the existing trusted `moshcode site <name> --proxy <port>` planning/install path rather than reimplementing web-server detection.
|
|
764
|
+
- **R170 [P0]** Caddy serves the HTTP Moshpit origin and reverse-proxies to Bun.
|
|
765
|
+
- **R171 [P0]** nginx may serve both plain HTTP and registry pin-verified TLS using the existing `--tls` behavior.
|
|
766
|
+
- **R172 [P0]** Port 80 never redirects unconditionally to pin-verified port 443.
|
|
767
|
+
- **R173 [P0]** A separate CA-valid `AUTH_ORIGIN` reverse proxy is documented and generated when its hostname points to the server.
|
|
768
|
+
- **R174 [P0]** `--install` may write project service units and web-server configuration only after showing a plan.
|
|
769
|
+
- **R175 [P0]** `--reload` validates configuration before reloading and must not take unrelated sites down on invalid generated configuration.
|
|
770
|
+
- **R176 [P0]** Auto-blog timer installation is skipped when blog or auto-blog is disabled.
|
|
771
|
+
- **R177 [P1]** Add Railway deployment documentation and an external scheduler alternative to systemd.
|
|
772
|
+
|
|
773
|
+
### Security and privacy
|
|
774
|
+
|
|
775
|
+
- **R178 [P0]** Use parameterized SQL everywhere.
|
|
776
|
+
- **R179 [P0]** Normalize and validate all redirect targets; post-login `return_to` values must be local paths from an allowlist and must not permit open redirects.
|
|
777
|
+
- **R180 [P0]** Enforce maximum request-body sizes for JSON and forms.
|
|
778
|
+
- **R181 [P0]** Add conservative rate limits for OAuth initiation/callback, session creation, token creation, admin mutations, and auto-blog execution.
|
|
779
|
+
- **R182 [P0]** Do not trust proxy headers unless the request came from the configured local reverse proxy.
|
|
780
|
+
- **R183 [P0]** Escape HTML by default and sanitize rendered Markdown.
|
|
781
|
+
- **R184 [P0]** Add a Content Security Policy compatible with SolidStart and the site's own assets.
|
|
782
|
+
- **R185 [P0]** Do not store raw IP addresses by default. If abuse controls use client metadata, store a keyed or salted coarse-prefix hash with a retention policy.
|
|
783
|
+
- **R186 [P0]** Redact secrets and tokens from logs.
|
|
784
|
+
- **R187 [P0]** Log structured request IDs, route, status, duration, and safe error codes.
|
|
785
|
+
- **R188 [P0]** Refuse authenticated writes when the request arrives through the plain Moshpit origin, gateway, or an unapproved host.
|
|
786
|
+
- **R189 [P0]** Dependency and lockfile scanning must run in CI.
|
|
787
|
+
- **R190 [P0]** The generated README must clearly label plain Moshpit HTTP as public/non-sensitive and `AUTH_ORIGIN` as the only stock-browser authenticated origin.
|
|
788
|
+
|
|
789
|
+
### Observability and health
|
|
790
|
+
|
|
791
|
+
- **R191 [P0]** `GET /api/v1/health` returns at least:
|
|
792
|
+
|
|
793
|
+
```json
|
|
794
|
+
{
|
|
795
|
+
"ok": true,
|
|
796
|
+
"version": "0.1.0",
|
|
797
|
+
"runtime": "bun",
|
|
798
|
+
"database": {
|
|
799
|
+
"mode": "sqlite",
|
|
800
|
+
"reachable": true,
|
|
801
|
+
"migrations_current": true
|
|
802
|
+
},
|
|
803
|
+
"auth": {
|
|
804
|
+
"provider": "coinpay",
|
|
805
|
+
"configured": false
|
|
806
|
+
},
|
|
807
|
+
"blog": {
|
|
808
|
+
"enabled": true
|
|
809
|
+
},
|
|
810
|
+
"autoblog": {
|
|
811
|
+
"enabled": true,
|
|
812
|
+
"sources": ["queue", "release"]
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
- **R192 [P0]** Health must return a non-2xx status when the database is unreachable or required migrations are missing.
|
|
818
|
+
- **R193 [P0]** A liveness route must avoid expensive external calls.
|
|
819
|
+
- **R194 [P1]** Add a separate readiness route when deployment targets need it.
|
|
820
|
+
- **R195 [P0]** Auto-blog run failures are visible in admin UI and API without exposing model prompts, credentials, or provider-sensitive payloads.
|
|
821
|
+
|
|
822
|
+
### Documentation
|
|
823
|
+
|
|
824
|
+
- **R196 [P0]** Add `docs/profullstack-template.md`.
|
|
825
|
+
- **R197 [P0]** Update `docs/hosting-a-moshpit-name.md` to explain:
|
|
826
|
+
- copy-only templates;
|
|
827
|
+
- trusted `site init`;
|
|
828
|
+
- plain HTTP;
|
|
829
|
+
- pin-verified TLS;
|
|
830
|
+
- gateway stripping;
|
|
831
|
+
- the need for a CA-valid auth origin.
|
|
832
|
+
- **R198 [P0]** Update the root README's template and site sections.
|
|
833
|
+
- **R199 [P0]** Generated README includes local development, Turso setup, CoinPay client setup, owner bootstrap, migrations, blog management, RSS, auto-blog, API examples, deployment, backup, restore, and token rotation.
|
|
834
|
+
- **R200 [P0]** API examples use `curl` and never include real secrets.
|
|
835
|
+
- **R201 [P0]** Add a troubleshooting matrix for resolver, web server, Bun service, database, OAuth callback, session cookie, RSS, and timer failures.
|
|
836
|
+
|
|
837
|
+
## UX Notes
|
|
838
|
+
|
|
839
|
+
### First-run principles
|
|
840
|
+
|
|
841
|
+
- Prefer useful defaults.
|
|
842
|
+
- Show the user the architecture before asking questions.
|
|
843
|
+
- Ask one decision at a time.
|
|
844
|
+
- Do not ask for a value that can be safely derived.
|
|
845
|
+
- Do not hide fallback behavior.
|
|
846
|
+
- Never print secrets back to the terminal.
|
|
847
|
+
- Every cloud or privileged action must be visible before execution.
|
|
848
|
+
- A failed optional integration must not prevent a local application from running.
|
|
849
|
+
|
|
850
|
+
### Database states
|
|
851
|
+
|
|
852
|
+
| State | Interactive behavior | Non-interactive `--db auto` |
|
|
853
|
+
|---|---|---|
|
|
854
|
+
| Turso absent | Explain local fallback; continue | Use SQLite |
|
|
855
|
+
| Turso present, logged out | Offer login; fallback if declined | Use SQLite |
|
|
856
|
+
| Turso present, logged in | Hosted Turso is default choice | Use Turso only when group/name are deterministic |
|
|
857
|
+
| Explicit `--db turso` fails | Stop with actionable error | Stop with actionable error |
|
|
858
|
+
| Explicit `--db sqlite` | Never contact Turso | Never contact Turso |
|
|
859
|
+
|
|
860
|
+
### Auth states
|
|
861
|
+
|
|
862
|
+
| State | Application behavior |
|
|
863
|
+
|---|---|
|
|
864
|
+
| CoinPay configured + HTTPS auth origin | Login works |
|
|
865
|
+
| CoinPay selected but credentials missing | App boots; login page shows setup instructions |
|
|
866
|
+
| Plain Moshpit origin | Public/read-only; login redirects to secure origin |
|
|
867
|
+
| Public gateway | Anonymous/read-only |
|
|
868
|
+
| Secure origin with bad/missing host | Reject request |
|
|
869
|
+
| Callback state expired/reused | Reject and offer restart login |
|
|
870
|
+
|
|
871
|
+
### Blog states
|
|
872
|
+
|
|
873
|
+
| Setting | `/blog` | `/blog/feed.xml` | Admin | Timer |
|
|
874
|
+
|---|---|---|---|---|
|
|
875
|
+
| Blog on, auto-blog on | Enabled | Enabled | Enabled | Enabled |
|
|
876
|
+
| Blog on, auto-blog off | Enabled | Enabled | Enabled | Disabled |
|
|
877
|
+
| Blog off | 404 | 404 | 404 | Disabled |
|
|
878
|
+
|
|
879
|
+
### Code-reuse rule
|
|
880
|
+
|
|
881
|
+
The required rendering lifecycle is:
|
|
882
|
+
|
|
883
|
+
```text
|
|
884
|
+
same Solid route/component code
|
|
885
|
+
│
|
|
886
|
+
├── initial request: rendered by Bun/SolidStart
|
|
887
|
+
│
|
|
888
|
+
└── after hydration: reused by Solid Router as SPA UI
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
No feature may ship separate React-like client and server component trees for the same page.
|
|
892
|
+
|
|
893
|
+
## Acceptance Criteria
|
|
894
|
+
|
|
895
|
+
### A. Local zero-cloud setup
|
|
896
|
+
|
|
897
|
+
Given a machine with Bun but no Turso CLI, when the user runs:
|
|
898
|
+
|
|
899
|
+
```sh
|
|
900
|
+
moshcode site init foo.whatever --db auto --auth none --yes
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
then:
|
|
904
|
+
|
|
905
|
+
1. a project is generated without contacting Turso;
|
|
906
|
+
2. `DATABASE_URL=file:./data/app.db` is configured;
|
|
907
|
+
3. migrations apply;
|
|
908
|
+
4. a welcome post is seeded;
|
|
909
|
+
5. `bun run dev` starts successfully;
|
|
910
|
+
6. `/`, `/blog`, `/blog/<welcome-slug>`, `/blog/feed.xml`, `/api/v1/health`, and `/api/v1/posts` return valid responses;
|
|
911
|
+
7. internal navigation hydrates and continues without full-page reloads;
|
|
912
|
+
8. no secret or database file is tracked by Git.
|
|
913
|
+
|
|
914
|
+
### B. Hosted Turso setup
|
|
915
|
+
|
|
916
|
+
Given an authenticated Turso CLI, when the user accepts the hosted default, then:
|
|
917
|
+
|
|
918
|
+
1. exactly one database is created or selected;
|
|
919
|
+
2. the database URL and token are retrieved;
|
|
920
|
+
3. `.env` is mode `0600`;
|
|
921
|
+
4. secrets are redacted from output;
|
|
922
|
+
5. migrations and seed run against the hosted database;
|
|
923
|
+
6. re-running the initializer does not create a duplicate database.
|
|
924
|
+
|
|
925
|
+
### C. Explicit Turso failure
|
|
926
|
+
|
|
927
|
+
Given `--db turso`, when Turso provisioning fails, then:
|
|
928
|
+
|
|
929
|
+
1. setup exits non-zero;
|
|
930
|
+
2. it does not switch to SQLite;
|
|
931
|
+
3. it reports completed resources and the resume command;
|
|
932
|
+
4. it does not destroy a created database automatically.
|
|
933
|
+
|
|
934
|
+
### D. CoinPay login
|
|
935
|
+
|
|
936
|
+
Given a CA-valid `AUTH_ORIGIN` and valid CoinPay client credentials, then:
|
|
937
|
+
|
|
938
|
+
1. login initiates authorization code flow with S256 PKCE and state;
|
|
939
|
+
2. callback validates state before token exchange;
|
|
940
|
+
3. a verified CoinPay identity creates or updates one local user keyed by `sub`;
|
|
941
|
+
4. the matching verified owner email receives the `owner` role;
|
|
942
|
+
5. a hashed opaque session is stored;
|
|
943
|
+
6. a secure host-only cookie is set;
|
|
944
|
+
7. logout revokes the local session;
|
|
945
|
+
8. the Moshpit HTTP origin never receives or accepts the authenticated cookie.
|
|
946
|
+
|
|
947
|
+
### E. Blog and RSS
|
|
948
|
+
|
|
949
|
+
Given the seeded welcome post, then:
|
|
950
|
+
|
|
951
|
+
1. it appears on `/blog`;
|
|
952
|
+
2. it has a server-rendered detail page;
|
|
953
|
+
3. it appears in `/api/v1/posts`;
|
|
954
|
+
4. it appears in valid RSS;
|
|
955
|
+
5. draft and future scheduled posts do not appear publicly;
|
|
956
|
+
6. RSS supports conditional requests.
|
|
957
|
+
|
|
958
|
+
### F. Auto-blog
|
|
959
|
+
|
|
960
|
+
Given the default queue source, when a unique Markdown file is placed in `content/queue/` and the job runs twice, then:
|
|
961
|
+
|
|
962
|
+
1. one post is created and published;
|
|
963
|
+
2. one successful run is recorded;
|
|
964
|
+
3. the second execution does not create a duplicate;
|
|
965
|
+
4. no AI provider is called.
|
|
966
|
+
|
|
967
|
+
### G. Template safety
|
|
968
|
+
|
|
969
|
+
Given a remote template containing a `postinstall`, executable script, symlink, or malicious manifest, then `moshcode template install` continues to copy only permitted regular files and does not execute anything.
|
|
970
|
+
|
|
971
|
+
Given the bundled `profullstack` template, `moshcode site init` performs only operations explicitly implemented in trusted Moshcode source.
|
|
972
|
+
|
|
973
|
+
### H. Dry run
|
|
974
|
+
|
|
975
|
+
Given any valid initializer command with `--dry-run`, then:
|
|
976
|
+
|
|
977
|
+
1. no files are written;
|
|
978
|
+
2. no cloud resources are created;
|
|
979
|
+
3. no package installer is run;
|
|
980
|
+
4. no OAuth client is registered;
|
|
981
|
+
5. no system service or web server is changed;
|
|
982
|
+
6. all planned actions are displayed with secrets redacted.
|
|
983
|
+
|
|
984
|
+
## Testing Requirements
|
|
985
|
+
|
|
986
|
+
### Moshcode CLI tests
|
|
987
|
+
|
|
988
|
+
- Argument parsing for all flags, aliases, conflicts, and missing values.
|
|
989
|
+
- Backward compatibility for existing `moshcode site <name>` behavior.
|
|
990
|
+
- TTY and non-TTY decision matrix.
|
|
991
|
+
- Turso absent, logged-out, logged-in, multi-group, create success, partial failure, and explicit failure.
|
|
992
|
+
- Secret redaction in text, JSON, error, and dry-run output.
|
|
993
|
+
- File-conflict atomicity.
|
|
994
|
+
- Resume and idempotency.
|
|
995
|
+
- Safe template-token rendering against injection payloads.
|
|
996
|
+
- No execution through template installation.
|
|
997
|
+
- Privileged plan generation without applying it.
|
|
998
|
+
- Mock CoinPay registration success and failure.
|
|
999
|
+
|
|
1000
|
+
### Generated application tests
|
|
1001
|
+
|
|
1002
|
+
- Strict TypeScript typecheck.
|
|
1003
|
+
- Migration idempotency and checksum mismatch.
|
|
1004
|
+
- Local SQLite integration tests.
|
|
1005
|
+
- Mocked remote libSQL client tests.
|
|
1006
|
+
- SSR route output.
|
|
1007
|
+
- Hydration and client-side navigation smoke test.
|
|
1008
|
+
- Shared service behavior used by UI and API.
|
|
1009
|
+
- API validation, pagination, error envelopes, auth, roles, and caching.
|
|
1010
|
+
- OAuth state, expiry, reuse, PKCE, callback, and session rotation.
|
|
1011
|
+
- Host/origin gating.
|
|
1012
|
+
- Session and API-token hashing/revocation.
|
|
1013
|
+
- Markdown sanitization with malicious payloads.
|
|
1014
|
+
- Blog state visibility.
|
|
1015
|
+
- RSS parse and conditional requests.
|
|
1016
|
+
- Auto-blog idempotency, locking, and provider-disabled behavior.
|
|
1017
|
+
- Health behavior with missing migrations or database outage.
|
|
1018
|
+
|
|
1019
|
+
### CI matrix
|
|
1020
|
+
|
|
1021
|
+
At minimum:
|
|
1022
|
+
|
|
1023
|
+
```text
|
|
1024
|
+
Moshcode CLI: Node.js 18 and current LTS
|
|
1025
|
+
Generated app: current supported Bun release
|
|
1026
|
+
Database: local file mode
|
|
1027
|
+
OS: Ubuntu latest LTS
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
A nightly or release-gate integration job may test real Turso and CoinPay using isolated credentials. Pull-request CI must use mocks and must not create external resources.
|
|
1031
|
+
|
|
1032
|
+
## Success Metrics
|
|
1033
|
+
|
|
1034
|
+
1. A user with Bun can generate and run the local site in one command sequence without editing source code.
|
|
1035
|
+
2. At least 90% of successful interactive initializations reach a passing health endpoint without manual debugging.
|
|
1036
|
+
3. Local SQLite fallback succeeds whenever Turso is unavailable in `auto` mode.
|
|
1037
|
+
4. No installer output or committed file contains generated secrets.
|
|
1038
|
+
5. SSR and hydrated SPA navigation are both covered by automated tests.
|
|
1039
|
+
6. A newly generated default site has a working blog and RSS feed immediately.
|
|
1040
|
+
7. Default auto-blog processing creates no paid AI requests.
|
|
1041
|
+
8. Existing template and site command tests remain green.
|
|
1042
|
+
9. The generated application has no separate frontend/backend repositories and no duplicate page component implementations.
|
|
1043
|
+
10. A coding agent can implement or extend a site using the generated README, OpenAPI document, tests, and this PRD without reverse-engineering installer behavior.
|
|
1044
|
+
|
|
1045
|
+
## Rollout Plan
|
|
1046
|
+
|
|
1047
|
+
### Phase 1: Template and local application
|
|
1048
|
+
|
|
1049
|
+
- Add the `profullstack` bundled files.
|
|
1050
|
+
- Implement SolidStart SSR/hydration, local SQLite, migrations, API, blog, RSS, seed, and tests.
|
|
1051
|
+
- Keep auth configurable but disabled in local smoke tests.
|
|
1052
|
+
- Add copy-only template listing and install coverage.
|
|
1053
|
+
|
|
1054
|
+
### Phase 2: Trusted initializer and Turso
|
|
1055
|
+
|
|
1056
|
+
- Add `site init` parsing, state file, dry run, rendering, conflict handling, Bun detection, Turso detection, provisioning, fallback, migration, seed, and build.
|
|
1057
|
+
- Add text and JSON output.
|
|
1058
|
+
- Add resume/idempotency tests.
|
|
1059
|
+
|
|
1060
|
+
### Phase 3: CoinPay authentication
|
|
1061
|
+
|
|
1062
|
+
- Confirm canonical CoinPay endpoints and ID-token verification method.
|
|
1063
|
+
- Implement client registration options, PKCE/state, callback, user/session creation, owner bootstrap, host/origin enforcement, and account/API-token UI.
|
|
1064
|
+
- Add mocked and production-gated integration tests.
|
|
1065
|
+
|
|
1066
|
+
### Phase 4: Deployment and auto-blog
|
|
1067
|
+
|
|
1068
|
+
- Integrate existing `moshcode site` proxy planning.
|
|
1069
|
+
- Add systemd service/timer units and installation workflow.
|
|
1070
|
+
- Add queue/release auto-blog, locking, run history, admin controls, and optional OpenAI-compatible adapter.
|
|
1071
|
+
- Update hosting documentation.
|
|
1072
|
+
|
|
1073
|
+
### Phase 5: Companion secure origin
|
|
1074
|
+
|
|
1075
|
+
- Document manual CA-valid companion origin setup.
|
|
1076
|
+
- Add automated `apps.moshcode.sh` allocation only when a supporting Moshcode platform API exists.
|
|
1077
|
+
- Do not block phases 1–4 on automatic hostname allocation.
|
|
1078
|
+
|
|
1079
|
+
## Repository Changes
|
|
1080
|
+
|
|
1081
|
+
Expected Moshcode changes:
|
|
1082
|
+
|
|
1083
|
+
```text
|
|
1084
|
+
bin/moshcode.mjs
|
|
1085
|
+
src/serve.mjs
|
|
1086
|
+
src/site-init.mjs
|
|
1087
|
+
src/site-init/
|
|
1088
|
+
args.mjs
|
|
1089
|
+
plan.mjs
|
|
1090
|
+
render.mjs
|
|
1091
|
+
state.mjs
|
|
1092
|
+
turso.mjs
|
|
1093
|
+
coinpay.mjs
|
|
1094
|
+
install.mjs
|
|
1095
|
+
src/cli-schema.mjs
|
|
1096
|
+
src/completion.mjs
|
|
1097
|
+
src/help.mjs
|
|
1098
|
+
examples/templates/profullstack/**
|
|
1099
|
+
test/site-init.test.mjs
|
|
1100
|
+
test/site-init-turso.test.mjs
|
|
1101
|
+
test/site-init-coinpay.test.mjs
|
|
1102
|
+
test/site-init-render.test.mjs
|
|
1103
|
+
docs/profullstack-template.md
|
|
1104
|
+
docs/hosting-a-moshpit-name.md
|
|
1105
|
+
README.md
|
|
1106
|
+
prd/README.md
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
The implementation may split modules differently, but database provisioning, CoinPay provisioning, rendering, state/resume, and privileged install planning must remain independently testable.
|
|
1110
|
+
|
|
1111
|
+
## Risks & Open Questions
|
|
1112
|
+
|
|
1113
|
+
1. **CoinPay documentation consistency:** Current CoinPay documentation surfaces different OAuth/JWKS paths in different integration pages. The canonical authorization, token, UserInfo, JWKS, issuer, audience, signing algorithm, and revocation behavior must be confirmed before release.
|
|
1114
|
+
2. **Secure companion origin:** CoinPay OAuth for stock browsers requires a CA-valid HTTPS callback. Automatic `apps.moshcode.sh` allocation needs a platform API that may not yet exist.
|
|
1115
|
+
3. **Pinned Moshpit TLS:** It is useful for Moshpit-aware clients but cannot be treated as universally trusted by ordinary browsers or external OAuth providers.
|
|
1116
|
+
4. **Turso product modes:** Turso supports both its newer database engine and libSQL. This PRD intentionally selects hosted libSQL because `@libsql/client` also supports the local file mode. Revisit when one SDK provides equally simple local-file and hosted-new-engine behavior.
|
|
1117
|
+
5. **SolidStart release selection:** Pin the latest tested stable release at implementation time; do not automatically adopt a prerelease in a production starter.
|
|
1118
|
+
6. **Auto-blog definition:** Queue and release sources are fully defined for v1. More autonomous topic selection, crawling, editorial policy, citations, and duplicate-content controls need a later PRD.
|
|
1119
|
+
7. **HTML sanitization:** The exact Markdown and sanitization libraries must be selected based on maintained status, Bun compatibility, and security review.
|
|
1120
|
+
8. **Systemd hardening:** Restrictive unit settings must be tested with Bun, local SQLite writes, build assets, DNS, and outbound CoinPay/Turso requests.
|
|
1121
|
+
9. **Multiple sites on one host:** Port selection, system user strategy, service naming, data directories, and Turso database names must remain collision-safe.
|
|
1122
|
+
10. **Backups:** Local SQLite backup and hosted Turso recovery procedures must be documented before declaring the template production-ready.
|
|
1123
|
+
11. **Owner recovery:** A lost owner account needs a host-local recovery path that does not expose a network setup token.
|
|
1124
|
+
12. **API stability:** `/api/v1` is a compatibility promise. Breaking response changes require `/api/v2` or an explicit deprecation process.
|
|
1125
|
+
|
|
1126
|
+
## Decision Summary
|
|
1127
|
+
|
|
1128
|
+
The default generated site is:
|
|
1129
|
+
|
|
1130
|
+
```text
|
|
1131
|
+
Bun
|
|
1132
|
+
SolidStart + SolidJS
|
|
1133
|
+
SSR first request
|
|
1134
|
+
hydrated SPA navigation
|
|
1135
|
+
shared TSX routes/components
|
|
1136
|
+
SolidStart server functions
|
|
1137
|
+
versioned JSON API
|
|
1138
|
+
@libsql/client
|
|
1139
|
+
local SQLite fallback
|
|
1140
|
+
hosted Turso libSQL when available
|
|
1141
|
+
CoinPay OAuth/OIDC
|
|
1142
|
+
hashed database sessions
|
|
1143
|
+
scoped API tokens
|
|
1144
|
+
/blog
|
|
1145
|
+
/blog/feed.xml
|
|
1146
|
+
default-on queue/release auto-blog
|
|
1147
|
+
systemd
|
|
1148
|
+
nginx or Caddy
|
|
1149
|
+
plain public Moshpit origin
|
|
1150
|
+
separate CA-valid authenticated origin
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
The primary experience is:
|
|
1154
|
+
|
|
1155
|
+
```sh
|
|
1156
|
+
moshcode site init foo.whatever
|
|
1157
|
+
cd foo.whatever
|
|
1158
|
+
bun run dev
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
The existing template security boundary remains:
|
|
1162
|
+
|
|
1163
|
+
```text
|
|
1164
|
+
template install = copy files only
|
|
1165
|
+
site init = trusted, explicit provisioning
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
## References
|
|
1169
|
+
|
|
1170
|
+
- Moshcode bundled templates: https://github.com/moshcoder/moshcode/tree/main/examples/templates
|
|
1171
|
+
- Moshcode template safety implementation: https://github.com/moshcoder/moshcode/blob/main/src/templates.mjs
|
|
1172
|
+
- Moshcode site/hosting implementation: https://github.com/moshcoder/moshcode/blob/main/src/serve.mjs
|
|
1173
|
+
- Moshpit hosting constraints: https://github.com/moshcoder/moshcode/blob/main/docs/hosting-a-moshpit-name.md
|
|
1174
|
+
- Moshcode OpenPRD template: https://github.com/moshcoder/moshcode/blob/main/prd/0000-template.md
|
|
1175
|
+
- SolidStart configuration: https://docs.solidjs.com/solid-start/reference/config/define-config
|
|
1176
|
+
- SolidStart data fetching and server functions: https://docs.solidjs.com/solid-start/guides/data-fetching
|
|
1177
|
+
- SolidStart authentication guidance: https://docs.solidjs.com/solid-start/advanced/auth
|
|
1178
|
+
- Turso TypeScript quickstart: https://docs.turso.tech/sdk/ts/quickstart
|
|
1179
|
+
- Turso CLI database creation: https://docs.turso.tech/cli/db/create
|
|
1180
|
+
- Turso CLI authentication status: https://docs.turso.tech/cli/auth/whoami
|
|
1181
|
+
- Turso database token creation: https://docs.turso.tech/cli/db/tokens/create
|
|
1182
|
+
- CoinPay OAuth/OIDC documentation: https://coinpayportal.com/docs
|
|
1183
|
+
- CoinPay OAuth integration prompt: https://coinpayportal.com/docs/prompts/OAUTH
|