mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 17:56:34 +00:00
chore: remove accidentally committed doc files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dba06b63db
commit
6a74b96570
2 changed files with 0 additions and 236 deletions
203
BSB_LEARNINGS.md
203
BSB_LEARNINGS.md
|
|
@ -1,203 +0,0 @@
|
||||||
# BSB Framework — Practical Learnings
|
|
||||||
|
|
||||||
Lessons learned building BetterFrame on BSB v9. Intended for updating
|
|
||||||
bsbcode.dev/llms.txt so future LLM sessions don't repeat these mistakes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Container Runtime
|
|
||||||
|
|
||||||
### Base Image
|
|
||||||
- Image: `betterweb/service-base:9` (Docker Hub)
|
|
||||||
- Alpine-based (use `apk` not `apt-get`)
|
|
||||||
- Entrypoint: `/root/entrypoint.sh` — runs as root, drops to unprivileged user
|
|
||||||
- **DO NOT** set `USER` in child Dockerfiles — BSB entrypoint handles privilege dropping
|
|
||||||
- **DO NOT** override `CMD` or `ENTRYPOINT` — BSB handles startup
|
|
||||||
|
|
||||||
### Production Environment
|
|
||||||
- Set `ENV NODE_ENV=production` and `ENV BSB_LIVE=true` in Dockerfile
|
|
||||||
- Without `BSB_LIVE=true`, BSB warns about non-production and tries to write sec-config.yaml
|
|
||||||
|
|
||||||
### Plugin Discovery
|
|
||||||
- BSB searches: `/home/bsb/node_modules/<package>/lib/plugins/<type>-<name>/index.js`
|
|
||||||
- Also searches: `/mnt/bsb-plugins/node_modules/...` but this is a VOLUME — build-time COPY gets shadowed by empty anonymous volume at runtime
|
|
||||||
- **USE `/home/bsb/node_modules/`** for build-time plugin installation
|
|
||||||
- Working directory is `/home/bsb`
|
|
||||||
|
|
||||||
### Plugin Registration in Config
|
|
||||||
```yaml
|
|
||||||
services:
|
|
||||||
service-my-plugin:
|
|
||||||
package: my-npm-package-name # <-- REQUIRED for container mode
|
|
||||||
plugin: service-my-plugin
|
|
||||||
enabled: true
|
|
||||||
config: {}
|
|
||||||
```
|
|
||||||
Without `package:`, BSB only finds built-in plugins (config-default, events-default, log-default).
|
|
||||||
|
|
||||||
The `package` value must match the `name` field in the plugin's `package.json`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Config Schema
|
|
||||||
|
|
||||||
### Schema Extractor Limitations
|
|
||||||
- `bsb-plugin-cli build` extracts config schemas from TypeScript source **statically**
|
|
||||||
- **Cannot resolve cross-file imports** — imported schema variables show as "not defined"
|
|
||||||
- Workaround: inline anyvali schema definitions in each plugin's ConfigSchema
|
|
||||||
- Type-only imports (`import type { ... }`) are fine — only runtime value imports break extraction
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD — schema extractor can't resolve this:
|
|
||||||
import { dbConfigSchema } from "../../shared/db/config.js";
|
|
||||||
const ConfigSchema = av.object({ db: dbConfigSchema, ... });
|
|
||||||
|
|
||||||
// GOOD — inline the schema:
|
|
||||||
const ConfigSchema = av.object({
|
|
||||||
db: av.object({
|
|
||||||
driver: av.enum_(["sqlite", "postgres"] as const).default("postgres"),
|
|
||||||
host: av.string().default("postgres"),
|
|
||||||
// ...
|
|
||||||
}, { unknownKeys: "strip" }),
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nested Config Objects
|
|
||||||
- Config supports nested `av.object()` — use for grouping related fields:
|
|
||||||
```yaml
|
|
||||||
config:
|
|
||||||
db:
|
|
||||||
driver: postgres
|
|
||||||
host: postgres
|
|
||||||
host: 0.0.0.0
|
|
||||||
port: 18080
|
|
||||||
```
|
|
||||||
- Access via `this.config.db.driver`, `this.config.host`
|
|
||||||
|
|
||||||
### Config Defaults
|
|
||||||
- BSB does NOT apply anyvali schema defaults for keys missing from sec-config.yaml
|
|
||||||
- Always declare config values explicitly in sec-config.yaml
|
|
||||||
- Defaults in the schema are documentation, not runtime fallbacks
|
|
||||||
|
|
||||||
### No Environment Variable Access
|
|
||||||
- Application code should **never** read `process.env`
|
|
||||||
- All config comes from sec-config.yaml via `this.config.*`
|
|
||||||
- For Docker/Coolify: use build-time `envsubst` on a template to generate sec-config.yaml
|
|
||||||
- sec-config.yaml should be baked into the image or bind-mounted — not generated at runtime
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Plugin Architecture
|
|
||||||
|
|
||||||
### What Should Be a Plugin
|
|
||||||
- Services with own port, lifecycle, independent scaling
|
|
||||||
- Examples: HTTP server, WebSocket server, API server
|
|
||||||
|
|
||||||
### What Should NOT Be a Plugin
|
|
||||||
- Database access layer (just a library — no port, no lifecycle)
|
|
||||||
- Shared utilities, crypto helpers, auth logic
|
|
||||||
- Anything that's just a function other plugins call
|
|
||||||
|
|
||||||
### Cross-Plugin Communication
|
|
||||||
- Plugins should have **zero runtime imports** from other plugins
|
|
||||||
- Type-only imports (`import type { ... }`) are acceptable
|
|
||||||
- Communication between plugins: BSB event bus (emitBroadcast, emitReturnableEvent)
|
|
||||||
- Shared code goes in `src/shared/` — imported by multiple plugins as a library
|
|
||||||
|
|
||||||
### Plugin Init Order
|
|
||||||
- `initAfterPlugins` / `initBeforePlugins` control order
|
|
||||||
- Not needed when plugins are independent (each inits own DB, etc.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build System
|
|
||||||
|
|
||||||
### Build Command
|
|
||||||
```bash
|
|
||||||
cross-env NODE_OPTIONS="--import tsx" bsb-plugin-cli build
|
|
||||||
```
|
|
||||||
- BSB build needs `tsx` for schema extraction from TypeScript source
|
|
||||||
- Build output: `lib/` directory with compiled JS + `bsb-plugin.json`
|
|
||||||
|
|
||||||
### bsb-plugin.json
|
|
||||||
- Auto-generated by build, lists all discovered plugins
|
|
||||||
- Must match the plugins present in `src/plugins/`
|
|
||||||
- If a plugin is removed, this file must be regenerated
|
|
||||||
|
|
||||||
### Dev Mode
|
|
||||||
```bash
|
|
||||||
cross-env NODE_OPTIONS="--import tsx" bsb-plugin-cli dev
|
|
||||||
```
|
|
||||||
- Hot reload, runs TypeScript directly
|
|
||||||
- Creates `.bsbdevwatch` file for include/exclude patterns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
### Log Message Format
|
|
||||||
- `obs.log.info("text {tag}", { tag: value })` — structured logging
|
|
||||||
- Message strings **MUST be string literals** (BSB SmartLogMeta extracts placeholders from literal type)
|
|
||||||
- String concatenation (`"a " + "b"`) widens to `string` and breaks SmartLogMeta placeholder extraction
|
|
||||||
- Template literals in log messages work for the value, not the message key
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// BAD:
|
|
||||||
obs.log.info("connecting to " + url, {});
|
|
||||||
|
|
||||||
// GOOD:
|
|
||||||
obs.log.info("connecting to {url}", { url });
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Docker Deployment Pattern
|
|
||||||
|
|
||||||
### Dockerfile Structure
|
|
||||||
```dockerfile
|
|
||||||
# Builder — compile TypeScript + native deps
|
|
||||||
FROM node:24-trixie-slim AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
# ... npm ci, npm run build ...
|
|
||||||
|
|
||||||
# Runtime — BSB container
|
|
||||||
FROM betterweb/service-base:9
|
|
||||||
|
|
||||||
# Install extras (Alpine)
|
|
||||||
RUN apk add --no-cache gettext ffmpeg
|
|
||||||
|
|
||||||
# Copy built plugin into BSB's node_modules
|
|
||||||
COPY --from=builder /app/server/package.json /home/bsb/node_modules/<pkg>/package.json
|
|
||||||
COPY --from=builder /app/server/bsb-plugin.json /home/bsb/node_modules/<pkg>/bsb-plugin.json
|
|
||||||
COPY --from=builder /app/server/lib /home/bsb/node_modules/<pkg>/lib
|
|
||||||
COPY --from=builder /app/node_modules /home/bsb/node_modules/<pkg>/node_modules
|
|
||||||
|
|
||||||
# Generate config from template
|
|
||||||
COPY sec-config.template.yaml /tmp/sec-config.template.yaml
|
|
||||||
RUN envsubst < /tmp/sec-config.template.yaml > /home/bsb/sec-config.yaml \
|
|
||||||
&& chmod 444 /home/bsb/sec-config.yaml \
|
|
||||||
&& rm /tmp/sec-config.template.yaml
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
ENV BSB_LIVE=true
|
|
||||||
```
|
|
||||||
|
|
||||||
### sec-config.template.yaml Pattern
|
|
||||||
- Template with `${VAR}` placeholders (NOT `${VAR:-default}` — envsubst doesn't support defaults)
|
|
||||||
- Defaults come from Dockerfile `ARG` declarations
|
|
||||||
- Secrets set as Coolify build args (not in git)
|
|
||||||
- Template committed to public repo (safe — no secrets)
|
|
||||||
- Generated sec-config.yaml baked into image at build time
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Common Pitfalls
|
|
||||||
|
|
||||||
1. **`betterweb/service-base:node` is v8, `:9` is v9** — use the correct tag
|
|
||||||
2. **VOLUME declarations in base image shadow build-time COPY** — don't write to `/mnt/bsb-plugins`
|
|
||||||
3. **`USER node` blocks BSB entrypoint** — entrypoint is at `/root/entrypoint.sh`, needs root access
|
|
||||||
4. **Schema extractor can't follow imports** — inline schemas in plugin ConfigSchema
|
|
||||||
5. **`package:` required in sec-config** — without it, BSB won't find external plugins
|
|
||||||
6. **Alpine = apk, not apt-get** — base image is Alpine Linux
|
|
||||||
7. **sec-config.yaml must be writable in dev, read-only in prod** — BSB_LIVE=true skips write attempts
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# Multi-Tenant Architecture
|
|
||||||
|
|
||||||
## Current Design
|
|
||||||
|
|
||||||
- **Single admin user** — global admin, full access to all tenants
|
|
||||||
- **No per-tenant logins** — one admin manages everything
|
|
||||||
- **Tenant = data isolation boundary** — each tenant gets its own PG schema
|
|
||||||
- **Admin switches tenants** via dropdown in topbar (session-stored)
|
|
||||||
- **User management deferred** — if/when we want per-tenant user logins, that's a separate feature
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
1. `PUBLIC_MIGRATIONS` create `tenants` + `global_admins` tables in `public` schema
|
|
||||||
2. Each tenant gets a PG schema: `tenant_<slug>` (e.g. `tenant_acme`)
|
|
||||||
3. `TENANT_MIGRATIONS` run inside each tenant schema (full table set per tenant)
|
|
||||||
4. Admin creates tenants from the admin UI
|
|
||||||
5. Middleware sets `search_path = tenant_<slug>` per request based on selected tenant
|
|
||||||
6. All repo queries automatically scope to the active tenant's schema
|
|
||||||
|
|
||||||
## What's NOT Happening
|
|
||||||
|
|
||||||
- No per-tenant admin users (single global admin for now)
|
|
||||||
- No tenant-specific auth (global session, tenant is just a context switch)
|
|
||||||
- No tenant billing/limits enforcement (max_kiosks/max_cameras columns exist but unenforced)
|
|
||||||
- No tenant API keys (all API keys are global)
|
|
||||||
|
|
||||||
## Future: Per-Tenant Users
|
|
||||||
|
|
||||||
When needed, add:
|
|
||||||
- Per-tenant `users` table (already in TENANT_MIGRATIONS)
|
|
||||||
- Login scoped to tenant (tenant slug in login URL or selection)
|
|
||||||
- Role-based access per tenant
|
|
||||||
- Separate from global admin
|
|
||||||
Loading…
Reference in a new issue