mirror of
https://github.com/HeyPuter/puter.git
synced 2026-07-28 18:01:55 +00:00
selfhosting: p3 cleanup docs + small fixes again (#2904)
* fix: Perms and configs * rm broken editor for now * selfhosting cleanup
This commit is contained in:
+151
-116
@@ -1,34 +1,72 @@
|
||||
{
|
||||
// Comprehensive template — every key the backend or shipped extensions read.
|
||||
// Copy to `config.json` and trim what you don't need; unset keys fall back to
|
||||
// documented defaults (see src/backend/types.ts for per-field comments).
|
||||
// Comprehensive template — every key the OSS backend or shipped (non-prod)
|
||||
// extensions read. Copy to `config.json` and trim what you don't need;
|
||||
// unset keys fall back to documented defaults. See
|
||||
// `src/backend/types.ts` for the per-field source of truth.
|
||||
//
|
||||
// Each setting lives at exactly one canonical key — there are no fallback
|
||||
// aliases. Values shown are illustrative, not production secrets.
|
||||
|
||||
//
|
||||
// Keys consumed only by closed-source / hosted-prod extensions (clickhouse,
|
||||
// cacheUpdateHandler, pages, prodMeteringAndBilling, …) are intentionally
|
||||
// omitted.
|
||||
|
||||
// ── Environment / identity ──────────────────────────────────────────
|
||||
"config_name": "template",
|
||||
// `dev` opens a browser on boot, skips blocked-email checks, and runs the
|
||||
// dev-time webpack watcher; `prod` serves pre-built bundles.
|
||||
"env": "dev",
|
||||
"version": "0.0.0",
|
||||
// Stable identity for this server node — used by pager alerts and
|
||||
// graceful-shutdown coordination.
|
||||
"serverId": "node-1",
|
||||
|
||||
// ── Networking / URLs ───────────────────────────────────────────────
|
||||
// Port Puter listens on internally.
|
||||
"port": 4100,
|
||||
// Externally-visible port (set this when behind a reverse proxy on 80/443).
|
||||
"pub_port": 4100,
|
||||
"protocol": "http",
|
||||
"domain": "puter.localhost",
|
||||
// Fully-qualified externally-visible URL. Computed from protocol/domain/
|
||||
// pub_port if unset.
|
||||
"origin": "http://puter.localhost:4100",
|
||||
// Public base URL for the API subdomain (used to build signed URLs and
|
||||
// surfaced to the client by the `installedApps` and `whoami` extensions).
|
||||
"api_base_url": "http://api.puter.localhost:4100",
|
||||
// Subdomains Puter routes on. Wildcard DNS (`*.<domain>`) must point at
|
||||
// this server for site/app hosting to work.
|
||||
"static_hosting_domain": "site.puter.localhost",
|
||||
"static_hosting_domain_alt": "host.puter.localhost",
|
||||
"private_app_hosting_domain": "app.puter.localhost",
|
||||
"private_app_hosting_domain_alt": "dev.puter.localhost",
|
||||
// Host-header / domain handling. Defaults below are the dev-friendly
|
||||
// settings; tighten for any public install.
|
||||
"allow_all_host_values": true,
|
||||
"allow_no_host_header": true,
|
||||
"allow_nipio_domains": false,
|
||||
"custom_domains_enabled": false,
|
||||
"enable_ip_validation": false,
|
||||
// Express `trust proxy` setting — set to the number of reverse-proxy
|
||||
// hops in front of the server (1 = nginx/Cloudflare, 2 = CF→ALB→app),
|
||||
// or to a CIDR / IP / list. `false` (safe default) makes `req.ip` the
|
||||
// direct socket peer. NEVER set to `true` in prod — it trusts every hop
|
||||
// and makes X-Forwarded-For forgeable.
|
||||
"trust_proxy": false,
|
||||
"no_browser_launch": false,
|
||||
|
||||
// ── Dev watcher (devWatcher extension) ──────────────────────────────
|
||||
// Rebuilds GUI + puter.js on file changes when running from source.
|
||||
// Ignored when `env: "prod"` unless `devwatch.enabled: true`.
|
||||
"no_devwatch": false,
|
||||
"devwatch": {
|
||||
// Delay after watcher startup before boot continues. Lets webpack
|
||||
// emit its first build so the homepage doesn't 404 on bundle.min.js.
|
||||
"ready_delay_ms": 5000
|
||||
},
|
||||
|
||||
// ── Auth / session ──────────────────────────────────────────────────
|
||||
// ALWAYS replace these for any public install — `openssl rand -hex 64`.
|
||||
"jwt_secret": "change-me",
|
||||
"url_signature_secret": "change-me",
|
||||
"cookie_name": "puter_auth_token",
|
||||
@@ -41,11 +79,13 @@
|
||||
},
|
||||
"oidc": {
|
||||
"providers": {
|
||||
// Google uses OIDC discovery — only ids are required.
|
||||
"google": {
|
||||
"client_id": "",
|
||||
"client_secret": "",
|
||||
"scopes": "openid email profile"
|
||||
},
|
||||
// Custom OIDC providers must also supply the three endpoints.
|
||||
"custom-oidc": {
|
||||
"client_id": "",
|
||||
"client_secret": "",
|
||||
@@ -56,10 +96,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ── Groups / provisioning ───────────────────────────────────────────
|
||||
// UIDs of the persistent groups new users are auto-enrolled in.
|
||||
"default_user_group": "78b1b1dd-c959-44d2-b02c-8735671f9997",
|
||||
"default_temp_group": "b7220104-7905-4985-b996-649fdcdb3c8f",
|
||||
// When true, ACL grants read/list on `/<user>/Public` to any actor.
|
||||
"enable_public_folders": true,
|
||||
|
||||
// ── Storage / S3 ────────────────────────────────────────────────────
|
||||
"s3": {
|
||||
// Local fauxqs (in-process S3-compatible) — used in dev and the
|
||||
// bundled-defaults Docker mode. Files land under `dataDir`.
|
||||
"localConfig": {
|
||||
"inMemory": false,
|
||||
"host": "127.0.0.1",
|
||||
@@ -67,22 +115,35 @@
|
||||
"dataDir": "volatile/runtime/fauxqs-data",
|
||||
"s3StorageDir": "volatile/runtime/fauxqs-s3-data"
|
||||
},
|
||||
// For real / external S3, replace `localConfig` above with `s3Config`.
|
||||
"_remote_example": {
|
||||
"s3Config": {
|
||||
"useCredentialChain": false,
|
||||
"endpoint": "https://s3.example.com",
|
||||
// Endpoint used in presigned URLs handed to the browser. Set
|
||||
// this when the server-side endpoint isn't reachable from the
|
||||
// browser (e.g. docker-internal `http://s3:9000`).
|
||||
"publicEndpoint": "",
|
||||
"accessKeyId": "",
|
||||
"secretAccessKey": "",
|
||||
"region": "us-west-2"
|
||||
"region": "us-west-2",
|
||||
// Set true for RustFS / MinIO / fauxqs (path-style URLs).
|
||||
// Real AWS S3 wants virtual-hosted — leave unset / false.
|
||||
"forcePathStyle": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"s3_bucket": "puter-local",
|
||||
"s3_region": "us-west-2",
|
||||
"region": "us-west-2",
|
||||
// Default per-user storage cap (bytes). 100 MB.
|
||||
"storage_capacity": 104857600,
|
||||
"is_storage_limited": false,
|
||||
"available_device_storage": 0,
|
||||
// ── Thumbnails (thumbnails extension) ───────────────────────────────
|
||||
// Optional dedicated S3-compatible bucket for generated thumbnails.
|
||||
// When unset (or `endpoint` empty), the extension falls back to the
|
||||
// main S3 client / bucket above.
|
||||
"thumbnailStore": {
|
||||
"name": "puter-local",
|
||||
"endpoint": "",
|
||||
@@ -91,15 +152,21 @@
|
||||
"secretAccessKey": ""
|
||||
}
|
||||
},
|
||||
|
||||
// ── Database ────────────────────────────────────────────────────────
|
||||
"database": {
|
||||
// `sqlite` for single-node / dev; `mysql` for self-host with MariaDB.
|
||||
"engine": "sqlite",
|
||||
// sqlite — file path on disk
|
||||
"path": "volatile/runtime/puter-database.sqlite",
|
||||
"targetVersion": 0,
|
||||
// mysql — connection details
|
||||
"host": "",
|
||||
"port": 3306,
|
||||
"user": "",
|
||||
"password": "",
|
||||
"database": "",
|
||||
// Optional read-replica pool. Reads route here when populated.
|
||||
"replica": {
|
||||
"host": "",
|
||||
"port": 3306,
|
||||
@@ -107,31 +174,48 @@
|
||||
"password": "",
|
||||
"database": ""
|
||||
}
|
||||
// mysql self-host bootstrap: set `migrationPaths` to apply the bundled
|
||||
// schema on first boot. Idempotent — safe to leave on.
|
||||
// "migrationPaths": ["./src/backend/clients/database/migrations/mysql"]
|
||||
},
|
||||
|
||||
// ── DynamoDB (KV store) ─────────────────────────────────────────────
|
||||
"dynamo": {
|
||||
// Local emulator (dynamodb-local) endpoint. Drop this field for real
|
||||
// AWS DynamoDB.
|
||||
"endpoint": "http://localhost:8000",
|
||||
// Set true when pointing at a local emulator so Puter creates the KV
|
||||
// table on boot. NEVER set against real AWS — provision via IaC.
|
||||
// "bootstrapTables": true,
|
||||
"path": "",
|
||||
// Credentials. NOTE: snake_case here, unlike `s3.s3Config` below.
|
||||
// For dynamodb-local, any non-empty values work.
|
||||
"aws": {
|
||||
"access_key": "",
|
||||
"secret_key": "",
|
||||
"region": "us-west-2"
|
||||
}
|
||||
},
|
||||
|
||||
// ── Redis / Valkey (cache + cross-node rate limit) ──────────────────
|
||||
"redis": {
|
||||
// True → in-process redis-mock (dev / single-node).
|
||||
"useMock": true,
|
||||
// Cluster nodes for ioredis. For a single Valkey/Redis container,
|
||||
// run it in cluster mode (one node, all slots).
|
||||
"startupNodes": [
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"port": 7000
|
||||
}
|
||||
]
|
||||
// Defaults to true (matches prod ElastiCache). Set false for plain-TCP
|
||||
// self-host Valkey/Redis.
|
||||
// "tls": false
|
||||
},
|
||||
"pager": {
|
||||
"pagerduty": {
|
||||
"enabled": false,
|
||||
"routingKey": ""
|
||||
}
|
||||
},
|
||||
|
||||
// ── Email (transactional) ───────────────────────────────────────────
|
||||
// Nodemailer transport — used for password resets, email confirmation, etc.
|
||||
"email": {
|
||||
"from": "\"Puter\" <no-reply@puter.com>",
|
||||
"host": "smtp.example.com",
|
||||
@@ -143,84 +227,58 @@
|
||||
"pass": ""
|
||||
}
|
||||
},
|
||||
"clickhouse": {
|
||||
"url": "http://127.0.0.1:8123",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"request_timeout": 15000,
|
||||
"max_buffer_size": 100000,
|
||||
"batch_size": 500,
|
||||
"flush_interval_ms": 5000
|
||||
},
|
||||
|
||||
// ── Rate limiting ───────────────────────────────────────────────────
|
||||
// `memory` for single-node, `redis` for multi-node (default), `kv` for
|
||||
// dynamo-backed counters.
|
||||
"rate_limit": {
|
||||
"backend": "redis"
|
||||
},
|
||||
|
||||
// ── AI / integration providers ──────────────────────────────────────
|
||||
// All AI drivers (chat, image, video, TTS, OCR, STT) read from here.
|
||||
// Provider id == driver-side identifier. Leave empty / omit to disable.
|
||||
"providers": {
|
||||
"_": "All AI / integration drivers read from here. Provider id = driver-side identifier.",
|
||||
"claude": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"openai-completion": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"gemini": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"groq": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"deepseek": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"mistral": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"xai": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"moonshot": {
|
||||
"apiKey": ""
|
||||
},
|
||||
// ─ Chat / completion ─
|
||||
"claude": { "apiKey": "" },
|
||||
"openai-completion": { "apiKey": "" },
|
||||
"gemini": { "apiKey": "" },
|
||||
"groq": { "apiKey": "" },
|
||||
"deepseek": { "apiKey": "" },
|
||||
"mistral": { "apiKey": "" },
|
||||
"xai": { "apiKey": "" },
|
||||
"moonshot": { "apiKey": "" },
|
||||
"openrouter": {
|
||||
"apiKey": "",
|
||||
"apiBaseUrl": "https://openrouter.ai/api/v1"
|
||||
},
|
||||
"together-ai": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"together-ai": { "apiKey": "" },
|
||||
// Local Ollama. `enabled: false` skips the auto-probe at startup
|
||||
// (otherwise Puter logs ECONNREFUSED on every boot when no Ollama
|
||||
// is running).
|
||||
"ollama": {
|
||||
"enabled": false,
|
||||
"apiBaseUrl": "http://localhost:11434"
|
||||
},
|
||||
"openai-image-generation": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"gemini-image-generation": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"together-image-generation": {
|
||||
"apiKey": ""
|
||||
},
|
||||
|
||||
// ─ Image generation ─
|
||||
"openai-image-generation": { "apiKey": "" },
|
||||
"gemini-image-generation": { "apiKey": "" },
|
||||
"together-image-generation": { "apiKey": "" },
|
||||
"cloudflare-image-generation": {
|
||||
"apiToken": "",
|
||||
"accountId": "",
|
||||
"apiBaseUrl": "https://api.cloudflare.com/client/v4"
|
||||
},
|
||||
"xai-image-generation": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"openai-video-generation": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"together-video-generation": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"gemini-video-generation": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"openai": {
|
||||
"apiKey": ""
|
||||
},
|
||||
"xai-image-generation": { "apiKey": "" },
|
||||
|
||||
// ─ Video generation ─
|
||||
"openai-video-generation": { "apiKey": "" },
|
||||
"together-video-generation": { "apiKey": "" },
|
||||
"gemini-video-generation": { "apiKey": "" },
|
||||
|
||||
// ─ Speech / OCR ─
|
||||
"openai": { "apiKey": "" },
|
||||
"elevenlabs": {
|
||||
"apiKey": "",
|
||||
"apiBaseUrl": "https://api.elevenlabs.io",
|
||||
@@ -237,56 +295,17 @@
|
||||
"secret_key": "",
|
||||
"region": "us-west-2"
|
||||
},
|
||||
"mistral-ocr": {
|
||||
"apiKey": ""
|
||||
}
|
||||
"mistral-ocr": { "apiKey": "" }
|
||||
},
|
||||
"broadcast": {
|
||||
"peers": [
|
||||
{
|
||||
"peerId": "peer-a",
|
||||
"webhook": true,
|
||||
"webhook_url": "https://peer-a.example.com/broadcast/webhook",
|
||||
"webhook_secret": "shared-secret"
|
||||
}
|
||||
],
|
||||
"webhook": {
|
||||
"peerId": "this-node",
|
||||
"secret": "shared-secret"
|
||||
},
|
||||
"webhook_replay_window_seconds": 300,
|
||||
"outbound_flush_ms": 2000
|
||||
},
|
||||
"peers": {
|
||||
"signaller_url": "wss://signaller.example.com",
|
||||
"fallback_ice": [],
|
||||
"turn": {
|
||||
"cloudflare_turn_service_id": "",
|
||||
"cloudflare_turn_api_token": "",
|
||||
"ttl": 86400
|
||||
},
|
||||
"internal_auth_secret": ""
|
||||
},
|
||||
"wisp": {
|
||||
"server": "wss://wisp.example.com"
|
||||
},
|
||||
"workers": {
|
||||
"XAUTHKEY": "",
|
||||
"ACCOUNTID": "",
|
||||
"namespace": "",
|
||||
"internetExposedUrl": "https://api.puter.com",
|
||||
"loggingUrl": ""
|
||||
},
|
||||
"entri": {
|
||||
"applicationId": "",
|
||||
"secret": ""
|
||||
},
|
||||
|
||||
|
||||
// ── GUI / static mounts ─────────────────────────────────────────────
|
||||
"gui_assets_root": "./src/gui",
|
||||
"gui_profile": "development",
|
||||
"builtin_apps": {
|
||||
"dev-center": "./src/dev-center"
|
||||
},
|
||||
// Force the bundled GUI even in dev — set true when running from a
|
||||
// pre-built tree without webpack-dev-server.
|
||||
"use_bundled_gui": false,
|
||||
"gui_bundle": "/dist/bundle.min.js",
|
||||
"gui_css": "/dist/bundle.min.css",
|
||||
@@ -296,22 +315,38 @@
|
||||
"short_description": "Your personal cloud computer",
|
||||
"social_media_image": ""
|
||||
},
|
||||
// Optional roots for native app bundles and custom puter.js builds.
|
||||
"native_apps_root": "",
|
||||
"client_libs_root": "",
|
||||
"puterjs_root": "./src/puter-js/dist",
|
||||
|
||||
// ── Feature flags (whoami extension) ────────────────────────────────
|
||||
// Flat `{ flag_name: boolean }` bag. Server-only by default — flags are
|
||||
// only surfaced to the client if their key is on the allowlist in
|
||||
// `extensions/whoami.ts` (CLIENT_VISIBLE_FEATURE_FLAGS).
|
||||
"feature_flags": {
|
||||
"example_flag": false
|
||||
},
|
||||
|
||||
// ── Misc / safety ───────────────────────────────────────────────────
|
||||
// TLDs / domains rejected at signup (prod only).
|
||||
"blockedEmailDomains": [],
|
||||
"support_email": "support@puter.com",
|
||||
// Worker / subdomain names users can't claim.
|
||||
"reserved_words": [],
|
||||
"max_subdomains_per_user": 10,
|
||||
"server_health": {
|
||||
"db_liveness_latency_fail_ms": 1500,
|
||||
"stale_health_loop_fail_ms": 0
|
||||
},
|
||||
|
||||
// ── Extensions ──────────────────────────────────────────────────────
|
||||
// Directories scanned for extension entrypoints (`*.ts` / subdirs).
|
||||
"extensions": [
|
||||
"./extensions"
|
||||
],
|
||||
|
||||
// ── Metering ────────────────────────────────────────────────────────
|
||||
// When true, all metering checks pass — no per-actor limits enforced.
|
||||
"unlimitedMetering": false
|
||||
}
|
||||
|
||||
@@ -1,816 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
properties: {
|
||||
completionId: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'completionId',
|
||||
notes: [],
|
||||
},
|
||||
allow: {
|
||||
type: 'boolean',
|
||||
mutability: 'mutable',
|
||||
summary: 'whether the operation is allowed',
|
||||
notes: [],
|
||||
},
|
||||
intended_service: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'intended service',
|
||||
notes: [],
|
||||
},
|
||||
parameters: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'parameters',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ai.prompt.complete',
|
||||
description: `
|
||||
This event is emitted for ai prompt complete operations.
|
||||
`,
|
||||
properties: {
|
||||
intended_service: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'intended service',
|
||||
notes: [],
|
||||
},
|
||||
parameters: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'parameters',
|
||||
notes: [],
|
||||
},
|
||||
result: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'result',
|
||||
notes: [],
|
||||
},
|
||||
model_used: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'model used',
|
||||
notes: [],
|
||||
},
|
||||
service_used: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'service used',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ai.prompt.cost-calculated',
|
||||
description: `
|
||||
This event is emitted for ai prompt cost calculated operations.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'ai.prompt.validate',
|
||||
description: `
|
||||
This event is emitted when a validate is being validated.
|
||||
The event can be used to block certain validates from being validated.
|
||||
`,
|
||||
properties: {
|
||||
completionId: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'completionId',
|
||||
notes: [],
|
||||
},
|
||||
allow: {
|
||||
type: 'boolean',
|
||||
mutability: 'mutable',
|
||||
summary: 'whether the operation is allowed',
|
||||
notes: [
|
||||
'If set to false, the ai will be considered invalid.',
|
||||
],
|
||||
},
|
||||
intended_service: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'intended service',
|
||||
notes: [],
|
||||
},
|
||||
parameters: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'parameters',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'app.new-icon',
|
||||
description: `
|
||||
This event is emitted for app new icon operations.
|
||||
`,
|
||||
properties: {
|
||||
data_url: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'data url',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'app.rename',
|
||||
description: `
|
||||
This event is emitted for app rename operations.
|
||||
`,
|
||||
properties: {
|
||||
data_url: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'data url',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'apps.invalidate',
|
||||
description: `
|
||||
This event is emitted when a invalidate is being validated.
|
||||
The event can be used to block certain invalidates from being validated.
|
||||
`,
|
||||
properties: {
|
||||
apps: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'apps',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'captcha.check',
|
||||
description: `
|
||||
This event is emitted for captcha check operations.
|
||||
`,
|
||||
properties: {
|
||||
required: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'required',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'core.email.validate',
|
||||
description: `
|
||||
This event is emitted when an email is being validated.
|
||||
The event can be used to block certain emails from being validated.
|
||||
`,
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'the email being validated',
|
||||
notes: [
|
||||
'The email may have already been cleaned.',
|
||||
],
|
||||
},
|
||||
allow: {
|
||||
type: 'boolean',
|
||||
mutability: 'mutable',
|
||||
summary: 'whether the email is allowed',
|
||||
notes: [
|
||||
'If set to false, the email will be considered invalid.',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'core.fs.create.directory',
|
||||
description: `
|
||||
This event is emitted when a directory is created.
|
||||
`,
|
||||
properties: {
|
||||
node: {
|
||||
type: 'FSNodeContext',
|
||||
mutability: 'no-effect',
|
||||
summary: 'the directory that was created',
|
||||
},
|
||||
context: {
|
||||
type: 'Context',
|
||||
mutability: 'no-effect',
|
||||
summary: 'current context',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'core.request.measured',
|
||||
description: `
|
||||
This event is emitted when a requests incoming and outgoing bytes
|
||||
have been measured.
|
||||
`,
|
||||
example: {
|
||||
language: 'javascript',
|
||||
code: /*javascript*/`
|
||||
extension.on('core.request.measured', data => {
|
||||
const measurements = data.measurements;
|
||||
// measurements = { sz_incoming: integer, sz_outgoing: integer }
|
||||
|
||||
const actor = data.actor; // instance of Actor
|
||||
|
||||
console.log('\x1B[36;1m === MEASUREMENT ===\x1B[0m\n', {
|
||||
actor: data.actor.uid,
|
||||
measurements: data.measurements
|
||||
});
|
||||
});
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'credit.check-available',
|
||||
description: `
|
||||
This event is emitted for credit check available operations.
|
||||
`,
|
||||
properties: {
|
||||
available: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'available',
|
||||
notes: [],
|
||||
},
|
||||
cost_uuid: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'cost uuid',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'credit.funding-update',
|
||||
description: `
|
||||
This event is emitted when a funding-update is updated.
|
||||
`,
|
||||
properties: {
|
||||
available: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'available',
|
||||
notes: [],
|
||||
},
|
||||
cost_uuid: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'cost uuid',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'credit.record-cost',
|
||||
description: `
|
||||
This event is emitted for credit record cost operations.
|
||||
`,
|
||||
properties: {
|
||||
available: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'available',
|
||||
notes: [],
|
||||
},
|
||||
cost_uuid: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'cost uuid',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'driver.create-call-context',
|
||||
description: `
|
||||
This event is emitted when a create-call-context is created.
|
||||
`,
|
||||
properties: {
|
||||
usages: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'usages',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'email.validate',
|
||||
description: `
|
||||
This event is emitted when a validate is being validated.
|
||||
The event can be used to block certain validates from being validated.
|
||||
`,
|
||||
properties: {
|
||||
allow: {
|
||||
type: 'boolean',
|
||||
mutability: 'mutable',
|
||||
summary: 'whether the operation is allowed',
|
||||
notes: [
|
||||
'If set to false, the email will be considered invalid.',
|
||||
],
|
||||
},
|
||||
email: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'email',
|
||||
notes: [
|
||||
'The email may have already been cleaned.',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'fs.create.directory',
|
||||
description: `
|
||||
This event is emitted when a directory is created.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'fs.create.file',
|
||||
description: `
|
||||
This event is emitted when a file is created.
|
||||
`,
|
||||
properties: {
|
||||
context: {
|
||||
type: 'Context',
|
||||
mutability: 'no-effect',
|
||||
summary: 'current context',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'fs.create.shortcut',
|
||||
description: `
|
||||
This event is emitted when a shortcut is created.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'fs.create.symlink',
|
||||
description: `
|
||||
This event is emitted when a symlink is created.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'fs.move.file',
|
||||
description: `
|
||||
This event is emitted for fs move file operations.
|
||||
`,
|
||||
properties: {
|
||||
moved: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'moved',
|
||||
notes: [],
|
||||
},
|
||||
old_path: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'path to the affected resource',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'fs.pending.file',
|
||||
description: `
|
||||
This event is emitted for fs pending file operations.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'fs.storage.progress.copy',
|
||||
description: `
|
||||
This event reports progress of a copy operation.
|
||||
`,
|
||||
properties: {
|
||||
context: {
|
||||
type: 'Context',
|
||||
mutability: 'no-effect',
|
||||
summary: 'current context',
|
||||
notes: [],
|
||||
},
|
||||
meta: {
|
||||
type: 'object',
|
||||
mutability: 'no-effect',
|
||||
summary: 'additional metadata for the operation',
|
||||
notes: [],
|
||||
},
|
||||
item_path: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'path to the affected resource',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'fs.storage.upload-progress',
|
||||
description: `
|
||||
This event reports progress of a upload-progress operation.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'fs.write.file',
|
||||
description: `
|
||||
This event is emitted when a file is updated.
|
||||
`,
|
||||
properties: {
|
||||
context: {
|
||||
type: 'Context',
|
||||
mutability: 'no-effect',
|
||||
summary: 'current context',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'ip.validate',
|
||||
description: `
|
||||
This event is emitted when a validate is being validated.
|
||||
The event can be used to block certain validates from being validated.
|
||||
`,
|
||||
properties: {
|
||||
res: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'res',
|
||||
notes: [],
|
||||
},
|
||||
end_: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'end ',
|
||||
notes: [],
|
||||
},
|
||||
end: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'end',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.fs.write-hash',
|
||||
description: `
|
||||
This event is emitted when a write-hash is updated.
|
||||
`,
|
||||
properties: {
|
||||
uuid: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'uuid',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.item.added',
|
||||
description: `
|
||||
This event is emitted for outer gui item added operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.item.moved',
|
||||
description: `
|
||||
This event is emitted for outer gui item moved operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.item.pending',
|
||||
description: `
|
||||
This event is emitted for outer gui item pending operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.item.updated',
|
||||
description: `
|
||||
This event is emitted when a updated is updated.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.notif.ack',
|
||||
description: `
|
||||
This event is emitted for outer gui notif ack operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.notif.message',
|
||||
description: `
|
||||
This event is emitted for outer gui notif message operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
notification: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'notification',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.notif.persisted',
|
||||
description: `
|
||||
This event is emitted for outer gui notif persisted operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.notif.unreads',
|
||||
description: `
|
||||
This event is emitted for outer gui notif unreads operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.submission.done',
|
||||
description: `
|
||||
This event is emitted for outer gui submission done operations.
|
||||
`,
|
||||
properties: {
|
||||
response: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'response',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'outer.gui.usage.update',
|
||||
description: `
|
||||
This event is emitted when a update is updated.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'outer.thread.notify-subscribers',
|
||||
description: `
|
||||
This event is emitted for outer thread notify subscribers operations.
|
||||
`,
|
||||
properties: {
|
||||
uid: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'uid',
|
||||
notes: [],
|
||||
},
|
||||
action: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'action',
|
||||
notes: [],
|
||||
},
|
||||
data: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'data',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'puter.signup',
|
||||
description: `
|
||||
This event is emitted for puter signup operations.
|
||||
`,
|
||||
properties: {
|
||||
ip: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'ip',
|
||||
notes: [],
|
||||
},
|
||||
user_agent: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'user agent',
|
||||
notes: [],
|
||||
},
|
||||
body: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'body',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'request.measured',
|
||||
description: `
|
||||
This event is emitted for request measured operations.
|
||||
`,
|
||||
properties: {
|
||||
req: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'req',
|
||||
notes: [],
|
||||
},
|
||||
res: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'res',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'request.will-be-handled',
|
||||
description: `
|
||||
This event is emitted for request will be handled operations.
|
||||
`,
|
||||
properties: {
|
||||
res: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'res',
|
||||
notes: [],
|
||||
},
|
||||
end_: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'end ',
|
||||
notes: [],
|
||||
},
|
||||
end: {
|
||||
type: 'any',
|
||||
mutability: 'mutable',
|
||||
summary: 'end',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'template-service.hello',
|
||||
description: `
|
||||
This event is emitted for template-service hello operations.
|
||||
`,
|
||||
},
|
||||
{
|
||||
id: 'usages.query',
|
||||
description: `
|
||||
This event is emitted for usages query operations.
|
||||
`,
|
||||
properties: {
|
||||
usages: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'usages',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'user.email-changed',
|
||||
description: `
|
||||
This event is emitted for user email changed operations.
|
||||
`,
|
||||
properties: {
|
||||
new_email: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'new email',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'user.email-confirmed',
|
||||
description: `
|
||||
This event is emitted for user email confirmed operations.
|
||||
`,
|
||||
properties: {
|
||||
email: {
|
||||
type: 'any',
|
||||
mutability: 'no-effect',
|
||||
summary: 'email',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'user.save_account',
|
||||
description: `
|
||||
This event is emitted for user save_account operations.
|
||||
`,
|
||||
properties: {
|
||||
user: {
|
||||
type: 'User',
|
||||
mutability: 'no-effect',
|
||||
summary: 'user associated with the operation',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'web.socket.connected',
|
||||
description: `
|
||||
This event is emitted for web socket connected operations.
|
||||
`,
|
||||
properties: {
|
||||
user: {
|
||||
type: 'User',
|
||||
mutability: 'mutable',
|
||||
summary: 'user associated with the operation',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'web.socket.user-connected',
|
||||
description: `
|
||||
This event is emitted for web socket user connected operations.
|
||||
`,
|
||||
properties: {
|
||||
user: {
|
||||
type: 'User',
|
||||
mutability: 'mutable',
|
||||
summary: 'user associated with the operation',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'wisp.get-policy',
|
||||
description: `
|
||||
This event is emitted for wisp get policy operations.
|
||||
`,
|
||||
properties: {
|
||||
policy: {
|
||||
type: 'Policy',
|
||||
mutability: 'mutable',
|
||||
summary: 'policy information for the operation',
|
||||
notes: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,761 +0,0 @@
|
||||
#### Property `completionId`
|
||||
|
||||
completionId
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `allow`
|
||||
|
||||
whether the operation is allowed
|
||||
- **Type**: boolean
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `intended_service`
|
||||
|
||||
intended service
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `parameters`
|
||||
|
||||
parameters
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `ai.prompt.complete`
|
||||
|
||||
This event is emitted for ai prompt complete operations.
|
||||
|
||||
#### Property `intended_service`
|
||||
|
||||
intended service
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `parameters`
|
||||
|
||||
parameters
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `result`
|
||||
|
||||
result
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `model_used`
|
||||
|
||||
model used
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `service_used`
|
||||
|
||||
service used
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `ai.prompt.cost-calculated`
|
||||
|
||||
This event is emitted for ai prompt cost calculated operations.
|
||||
|
||||
|
||||
### `ai.prompt.validate`
|
||||
|
||||
This event is emitted when a validate is being validated.
|
||||
The event can be used to block certain validates from being validated.
|
||||
|
||||
#### Property `completionId`
|
||||
|
||||
completionId
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `allow`
|
||||
|
||||
whether the operation is allowed
|
||||
- **Type**: boolean
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
- If set to false, the ai will be considered invalid.
|
||||
|
||||
#### Property `intended_service`
|
||||
|
||||
intended service
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `parameters`
|
||||
|
||||
parameters
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `app.new-icon`
|
||||
|
||||
This event is emitted for app new icon operations.
|
||||
|
||||
#### Property `data_url`
|
||||
|
||||
data url
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `app.rename`
|
||||
|
||||
This event is emitted for app rename operations.
|
||||
|
||||
#### Property `data_url`
|
||||
|
||||
data url
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `apps.invalidate`
|
||||
|
||||
This event is emitted when a invalidate is being validated.
|
||||
The event can be used to block certain invalidates from being validated.
|
||||
|
||||
#### Property `apps`
|
||||
|
||||
apps
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `captcha.check`
|
||||
|
||||
This event is emitted for captcha check operations.
|
||||
|
||||
#### Property `required`
|
||||
|
||||
required
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `core.email.validate`
|
||||
|
||||
This event is emitted when an email is being validated.
|
||||
The event can be used to block certain emails from being validated.
|
||||
|
||||
#### Property `email`
|
||||
|
||||
the email being validated
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
- The email may have already been cleaned.
|
||||
|
||||
#### Property `allow`
|
||||
|
||||
whether the email is allowed
|
||||
- **Type**: boolean
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
- If set to false, the email will be considered invalid.
|
||||
|
||||
|
||||
### `core.fs.create.directory`
|
||||
|
||||
This event is emitted when a directory is created.
|
||||
|
||||
#### Property `node`
|
||||
|
||||
the directory that was created
|
||||
- **Type**: FSNodeContext
|
||||
- **Mutability**: no-effect
|
||||
|
||||
#### Property `context`
|
||||
|
||||
current context
|
||||
- **Type**: Context
|
||||
- **Mutability**: no-effect
|
||||
|
||||
|
||||
### `core.request.measured`
|
||||
|
||||
This event is emitted when a requests incoming and outgoing bytes
|
||||
have been measured.
|
||||
|
||||
#### Example
|
||||
|
||||
```javascript
|
||||
extension.on('core.request.measured', data => {
|
||||
const measurements = data.measurements;
|
||||
// measurements = { sz_incoming: integer, sz_outgoing: integer }
|
||||
|
||||
const actor = data.actor; // instance of Actor
|
||||
|
||||
console.log('[36;1m === MEASUREMENT ===[0m
|
||||
', {
|
||||
actor: data.actor.uid,
|
||||
measurements: data.measurements
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### `credit.check-available`
|
||||
|
||||
This event is emitted for credit check available operations.
|
||||
|
||||
#### Property `available`
|
||||
|
||||
available
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `cost_uuid`
|
||||
|
||||
cost uuid
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `credit.funding-update`
|
||||
|
||||
This event is emitted when a funding-update is updated.
|
||||
|
||||
#### Property `available`
|
||||
|
||||
available
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `cost_uuid`
|
||||
|
||||
cost uuid
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `credit.record-cost`
|
||||
|
||||
This event is emitted for credit record cost operations.
|
||||
|
||||
#### Property `available`
|
||||
|
||||
available
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `cost_uuid`
|
||||
|
||||
cost uuid
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `driver.create-call-context`
|
||||
|
||||
This event is emitted when a create-call-context is created.
|
||||
|
||||
#### Property `usages`
|
||||
|
||||
usages
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `email.validate`
|
||||
|
||||
This event is emitted when a validate is being validated.
|
||||
The event can be used to block certain validates from being validated.
|
||||
|
||||
#### Property `allow`
|
||||
|
||||
whether the operation is allowed
|
||||
- **Type**: boolean
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
- If set to false, the email will be considered invalid.
|
||||
|
||||
#### Property `email`
|
||||
|
||||
email
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
- The email may have already been cleaned.
|
||||
|
||||
|
||||
### `fs.create.directory`
|
||||
|
||||
This event is emitted when a directory is created.
|
||||
|
||||
|
||||
### `fs.create.file`
|
||||
|
||||
This event is emitted when a file is created.
|
||||
|
||||
#### Property `context`
|
||||
|
||||
current context
|
||||
- **Type**: Context
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `fs.create.shortcut`
|
||||
|
||||
This event is emitted when a shortcut is created.
|
||||
|
||||
|
||||
### `fs.create.symlink`
|
||||
|
||||
This event is emitted when a symlink is created.
|
||||
|
||||
|
||||
### `fs.move.file`
|
||||
|
||||
This event is emitted for fs move file operations.
|
||||
|
||||
#### Property `moved`
|
||||
|
||||
moved
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `old_path`
|
||||
|
||||
path to the affected resource
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `fs.pending.file`
|
||||
|
||||
This event is emitted for fs pending file operations.
|
||||
|
||||
|
||||
### `fs.storage.progress.copy`
|
||||
|
||||
This event reports progress of a copy operation.
|
||||
|
||||
#### Property `context`
|
||||
|
||||
current context
|
||||
- **Type**: Context
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `meta`
|
||||
|
||||
additional metadata for the operation
|
||||
- **Type**: object
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `item_path`
|
||||
|
||||
path to the affected resource
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `fs.storage.upload-progress`
|
||||
|
||||
This event reports progress of a upload-progress operation.
|
||||
|
||||
|
||||
### `fs.write.file`
|
||||
|
||||
This event is emitted when a file is updated.
|
||||
|
||||
#### Property `context`
|
||||
|
||||
current context
|
||||
- **Type**: Context
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `ip.validate`
|
||||
|
||||
This event is emitted when a validate is being validated.
|
||||
The event can be used to block certain validates from being validated.
|
||||
|
||||
#### Property `res`
|
||||
|
||||
res
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `end_`
|
||||
|
||||
end
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `end`
|
||||
|
||||
end
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.fs.write-hash`
|
||||
|
||||
This event is emitted when a write-hash is updated.
|
||||
|
||||
#### Property `uuid`
|
||||
|
||||
uuid
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.item.added`
|
||||
|
||||
This event is emitted for outer gui item added operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.item.moved`
|
||||
|
||||
This event is emitted for outer gui item moved operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.item.pending`
|
||||
|
||||
This event is emitted for outer gui item pending operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.item.updated`
|
||||
|
||||
This event is emitted when a updated is updated.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.notif.ack`
|
||||
|
||||
This event is emitted for outer gui notif ack operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.notif.message`
|
||||
|
||||
This event is emitted for outer gui notif message operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `notification`
|
||||
|
||||
notification
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.notif.persisted`
|
||||
|
||||
This event is emitted for outer gui notif persisted operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.notif.unreads`
|
||||
|
||||
This event is emitted for outer gui notif unreads operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.submission.done`
|
||||
|
||||
This event is emitted for outer gui submission done operations.
|
||||
|
||||
#### Property `response`
|
||||
|
||||
response
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `outer.gui.usage.update`
|
||||
|
||||
This event is emitted when a update is updated.
|
||||
|
||||
|
||||
### `outer.thread.notify-subscribers`
|
||||
|
||||
This event is emitted for outer thread notify subscribers operations.
|
||||
|
||||
#### Property `uid`
|
||||
|
||||
uid
|
||||
- **Type**: string
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `action`
|
||||
|
||||
action
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `data`
|
||||
|
||||
data
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `puter.signup`
|
||||
|
||||
This event is emitted for puter signup operations.
|
||||
|
||||
#### Property `ip`
|
||||
|
||||
ip
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `user_agent`
|
||||
|
||||
user agent
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `body`
|
||||
|
||||
body
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `request.measured`
|
||||
|
||||
This event is emitted for request measured operations.
|
||||
|
||||
#### Property `req`
|
||||
|
||||
req
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
#### Property `res`
|
||||
|
||||
res
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `request.will-be-handled`
|
||||
|
||||
This event is emitted for request will be handled operations.
|
||||
|
||||
#### Property `res`
|
||||
|
||||
res
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `end_`
|
||||
|
||||
end
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
#### Property `end`
|
||||
|
||||
end
|
||||
- **Type**: any
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `sns`
|
||||
|
||||
This event is emitted for sns operations.
|
||||
|
||||
#### Property `message`
|
||||
|
||||
message
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `template-service.hello`
|
||||
|
||||
This event is emitted for template-service hello operations.
|
||||
|
||||
|
||||
### `usages.query`
|
||||
|
||||
This event is emitted for usages query operations.
|
||||
|
||||
#### Property `usages`
|
||||
|
||||
usages
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `user.email-changed`
|
||||
|
||||
This event is emitted for user email changed operations.
|
||||
|
||||
#### Property `new_email`
|
||||
|
||||
new email
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `user.email-confirmed`
|
||||
|
||||
This event is emitted for user email confirmed operations.
|
||||
|
||||
#### Property `email`
|
||||
|
||||
email
|
||||
- **Type**: any
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `user.save_account`
|
||||
|
||||
This event is emitted for user save_account operations.
|
||||
|
||||
#### Property `user`
|
||||
|
||||
user associated with the operation
|
||||
- **Type**: User
|
||||
- **Mutability**: no-effect
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `web.socket.connected`
|
||||
|
||||
This event is emitted for web socket connected operations.
|
||||
|
||||
#### Property `user`
|
||||
|
||||
user associated with the operation
|
||||
- **Type**: User
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `web.socket.user-connected`
|
||||
|
||||
This event is emitted for web socket user connected operations.
|
||||
|
||||
#### Property `user`
|
||||
|
||||
user associated with the operation
|
||||
- **Type**: User
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
### `wisp.get-policy`
|
||||
|
||||
This event is emitted for wisp get policy operations.
|
||||
|
||||
#### Property `policy`
|
||||
|
||||
policy information for the operation
|
||||
- **Type**: Policy
|
||||
- **Mutability**: mutable
|
||||
- **Notes**:
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
export default [
|
||||
{
|
||||
id: 'core.email.validate',
|
||||
description: `
|
||||
This event is emitted when an email is being validated.
|
||||
The event can be used to block certain emails from being validated.
|
||||
`,
|
||||
properties: {
|
||||
email: {
|
||||
type: 'string',
|
||||
mutability: 'no-effect',
|
||||
summary: 'the email being validated',
|
||||
notes: [
|
||||
'The email may have already been cleaned.',
|
||||
],
|
||||
},
|
||||
allow: {
|
||||
type: 'boolean',
|
||||
mutability: 'mutable',
|
||||
summary: 'whether the email is allowed',
|
||||
notes: [
|
||||
'If set to false, the email will be considered invalid.',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'core.request.measured',
|
||||
description: `
|
||||
This event is emitted when a requests incoming and outgoing bytes
|
||||
have been measured.
|
||||
`,
|
||||
example: {
|
||||
language: 'javascript',
|
||||
code: /*javascript*/`
|
||||
extension.on('core.request.measured', data => {
|
||||
const measurements = data.measurements;
|
||||
// measurements = { sz_incoming: integer, sz_outgoing: integer }
|
||||
|
||||
const actor = data.actor; // instance of Actor
|
||||
|
||||
console.log('\\x1B[36;1m === MEASUREMENT ===\\x1B[0m\\n', {
|
||||
actor: data.actor.uid,
|
||||
measurements: data.measurements
|
||||
});
|
||||
});
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'core.fs.create.directory',
|
||||
description: `
|
||||
This event is emitted when a directory is created.
|
||||
`,
|
||||
properties: {
|
||||
node: {
|
||||
type: 'FSNodeContext',
|
||||
mutability: 'no-effect',
|
||||
summary: 'the directory that was created',
|
||||
},
|
||||
context: {
|
||||
type: 'Context',
|
||||
mutability: 'no-effect',
|
||||
summary: 'current context',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -112,7 +112,9 @@ cat > puter/config/config.json <<EOF
|
||||
|
||||
"providers": {
|
||||
"ollama": { "enabled": false }
|
||||
}
|
||||
},
|
||||
|
||||
"trust_proxy": 1
|
||||
}
|
||||
EOF
|
||||
```
|
||||
@@ -128,6 +130,7 @@ Why these knobs:
|
||||
- `providers.ollama.enabled: false` — Puter auto-probes a local Ollama at `127.0.0.1:11434` by default; without one running you'd see `ECONNREFUSED` on every boot. To run a bundled Ollama, see [Optional: local LLM (Ollama)](#optional-local-llm-ollama) below.
|
||||
- `s3.s3Config.forcePathStyle: true` — RustFS / MinIO / fauxqs need path-style URLs (`<endpoint>/<bucket>`). Real AWS S3 wants virtual-hosted (`<bucket>.<endpoint>`) — drop this flag (or set `false`) when you swap to real S3.
|
||||
- `s3.s3Config.publicEndpoint` — `endpoint` (`http://s3:9000`) only resolves inside the docker network; presigned upload/download URLs handed to the browser need a host-reachable URL. nginx routes the `s3.<domain>` subdomain to RustFS internally and preserves the Host header end-to-end (required for S3 signature validation), so the browser hits the same port/protocol as the rest of the app — no separate published port, no mixed-content surprises when you turn on TLS. Switch to `https://s3.<your-domain>` once you enable TLS in Step 3. Real AWS S3 doesn't need this — its endpoint is already public; drop the field entirely.
|
||||
- `trust_proxy: 1` — nginx terminates TLS and forwards `X-Forwarded-For`. Without this, `req.ip` is the docker-network address of the nginx container instead of the real client IP, which breaks rate limiting and IP-based audit logs. `1` = one trusted hop (nginx). Bump to `2` if you put Cloudflare in front of nginx; never set `true` (it trusts every hop and makes XFF forgeable).
|
||||
|
||||
> If you ever change `MARIADB_PASSWORD` after first boot, `.env` alone won't update MariaDB — its credentials are baked into `./puter/data/mariadb/` on first init. Either rotate the password inside MariaDB by hand or `docker compose down && rm -rf ./puter/data/mariadb` to start fresh.
|
||||
|
||||
@@ -219,6 +222,100 @@ docker compose -f docker-compose.full.yml logs puter | grep tmp_password
|
||||
|
||||
Change it in Settings after first login.
|
||||
|
||||
## Additional configuration
|
||||
|
||||
All optional. Drop any of the blocks below into `puter/config/config.json` and `docker compose -f docker-compose.full.yml restart puter`. See [config.template.jsonc](../config.template.jsonc) for the full list. Per-key documentation lives in [src/backend/types.ts](../src/backend/types.ts).
|
||||
|
||||
### Email (SMTP)
|
||||
|
||||
Used for password resets, email confirmation, and notifications. Without it those flows silently fail.
|
||||
|
||||
```json
|
||||
"email": {
|
||||
"from": "\"Puter\" <no-reply@puter.example.com>",
|
||||
"host": "smtp.example.com",
|
||||
"port": 587,
|
||||
"secure": false,
|
||||
"auth": { "user": "...", "pass": "..." }
|
||||
}
|
||||
```
|
||||
|
||||
To require email confirmation before login, also set `"strict_email_verification_required": true`.
|
||||
|
||||
### Sign in with Google (or another OIDC provider)
|
||||
|
||||
```json
|
||||
"oidc": {
|
||||
"providers": {
|
||||
"google": {
|
||||
"client_id": "...apps.googleusercontent.com",
|
||||
"client_secret": "...",
|
||||
"scopes": "openid email profile"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add `https://puter.<your-domain>/auth/oidc/callback/login` to the OAuth client's authorized redirect URIs in the Google Cloud Console. For non-Google providers, replace `google` with a custom id and supply `authorization_endpoint` / `token_endpoint` / `userinfo_endpoint` explicitly.
|
||||
|
||||
### AI providers
|
||||
|
||||
Any provider with a key set is auto-enabled. Same shape as `ollama` above:
|
||||
|
||||
```json
|
||||
"providers": {
|
||||
"claude": { "apiKey": "sk-ant-..." },
|
||||
"openai-completion": { "apiKey": "sk-..." },
|
||||
"gemini": { "apiKey": "..." },
|
||||
"openai-image-generation": { "apiKey": "sk-..." }
|
||||
}
|
||||
```
|
||||
|
||||
Full provider list (chat, image, video, TTS, OCR) is in the template.
|
||||
|
||||
### Per-user storage quota
|
||||
|
||||
Default is 100 MB per user.
|
||||
|
||||
```json
|
||||
"storage_capacity": 5368709120, // 5 GB
|
||||
"is_storage_limited": true
|
||||
```
|
||||
|
||||
Set `is_storage_limited: false` for unlimited (bounded by host disk).
|
||||
|
||||
### Captcha on signup / login
|
||||
|
||||
Built-in proof-of-work captcha — no external service needed.
|
||||
|
||||
```json
|
||||
"captcha": { "enabled": true, "difficulty": "medium" }
|
||||
```
|
||||
|
||||
`difficulty` is one of `easy` / `medium` / `hard`.
|
||||
|
||||
### Block disposable email TLDs
|
||||
|
||||
Only enforced when `env: "prod"`.
|
||||
|
||||
```json
|
||||
"blockedEmailDomains": ["mailinator.com", "tempmail.com", "guerrillamail.com"]
|
||||
```
|
||||
|
||||
### Password policy
|
||||
|
||||
```json
|
||||
"min_pass_length": 12
|
||||
```
|
||||
|
||||
### Contact-form recipient
|
||||
|
||||
Where the in-app contact form posts. Defaults to `support@puter.com`.
|
||||
|
||||
```json
|
||||
"support_email": "support@puter.example.com"
|
||||
```
|
||||
|
||||
## Optional: local LLM (Ollama)
|
||||
|
||||
The `ollama` and `ollama-init` services live behind a compose profile so they don't run unless you ask for them. By default, `puter/config/config.json` has `"ollama": { "enabled": false }` — Puter skips the auto-probe entirely. To run a local model:
|
||||
@@ -256,7 +353,7 @@ docker compose -f docker-compose.full.yml up -d --build
|
||||
|
||||
---
|
||||
|
||||
## Re-starting backend
|
||||
## Managing running backend
|
||||
|
||||
```bash
|
||||
# update
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
/*!40014 SET @OLD_FK = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
|
||||
INSERT IGNORE INTO `apps` (`uid`, `owner_user_id`, `icon`, `name`, `title`, `description`, `index_url`, `godmode`, `maximize_on_start`, `background`, `approved_for_listing`, `approved_for_opening_items`, `approved_for_incentive_program`, `tags`, `timestamp`) VALUES ('app-838dfbc4-bf8b-48c2-b47b-c4adc77fab58', 1, 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjIiIGJhc2VQcm9maWxlPSJ0aW55LXBzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij4KCTx0aXRsZT5hcHAtaWNvbi1lZGl0b3Itc3ZnPC90aXRsZT4KCTxkZWZzPgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iZ3JkMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiICB4MT0iNDciIHkxPSIzOS41MTQiIHgyPSIxIiB5Mj0iOC40ODYiPgoJCQk8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM3MTAxZTgiICAvPgoJCQk8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM5MTY3YmUiICAvPgoJCTwvbGluZWFyR3JhZGllbnQ+Cgk8L2RlZnM+Cgk8c3R5bGU+CgkJdHNwYW4geyB3aGl0ZS1zcGFjZTpwcmUgfQoJCS5zaHAwIHsgZmlsbDogdXJsKCNncmQxKSB9IAoJCS5zaHAxIHsgZmlsbDogI2ZmZmZmZiB9IAoJPC9zdHlsZT4KCTxnIGlkPSJMYXllciI+CgkJPHBhdGggaWQ9IkxheWVyIiBjbGFzcz0ic2hwMCIgZD0iTTQ3IDNMNDcgNDVDNDcgNDYuMSA0Ni4xIDQ3IDQ1IDQ3TDMgNDdDMS45IDQ3IDEgNDYuMSAxIDQ1TDEgM0MxIDEuOSAxLjkgMSAzIDFMNDUgMUM0Ni4xIDEgNDcgMS45IDQ3IDNaIiAvPgoJCTxwYXRoIGlkPSJMYXllciIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGFzcz0ic2hwMSIgZD0iTTI4LjYyIDQwTDI4LjYyIDM3LjYxTDMyLjI1IDM3LjIyTDI5Ljg2IDMwTDE3LjUzIDMwTDE1LjE4IDM3LjIyTDE4Ljc2IDM3LjYxTDE4Ljc2IDQwTDguNiA0MEw4LjYgMzcuNjZMMTAuNSAzNy4xN0MxMS4yMSAzNi45OSAxMS40MyAzNi44NiAxMS42IDM2LjMzTDIxLjMzIDhMMjYuNDUgOEwzNi4zNiAzNi4zOEMzNi41MyAzNi45MSAzNi44OCAzNi45OSAzNy40MiAzNy4xM0wzOS40IDM3LjYxTDM5LjQgNDBMMjguNjIgNDBaTTIzLjc2IDExLjQ1TDE4LjU0IDI3TDI4Ljg4IDI3TDIzLjc2IDExLjQ1WiIgLz4KCTwvZz4KPC9zdmc+', 'editor', 'Editor', 'A simple text editor', 'https://editor.puter.com/index.html', 0, 0, 0, 1, 1, 0, NULL, '2020-01-01 00:00:00');
|
||||
-- TEMP: editor app insert removed — broken, will fix later
|
||||
|
||||
INSERT IGNORE INTO `apps` (`uid`, `owner_user_id`, `icon`, `name`, `title`, `description`, `index_url`, `godmode`, `maximize_on_start`, `background`, `approved_for_listing`, `approved_for_opening_items`, `approved_for_incentive_program`, `tags`, `timestamp`) VALUES ('app-7870be61-8dff-4a99-af64-e9ae6811e367', 1, 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjIiIGJhc2VQcm9maWxlPSJ0aW55LXBzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij4KCTx0aXRsZT5hcHAtaWNvbi12aWV3ZXItc3ZnPC90aXRsZT4KCTxkZWZzPgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iZ3JkMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiICB4MT0iNDciIHkxPSIzOS41MTQiIHgyPSIxIiB5Mj0iOC40ODYiPgoJCQk8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMwMzYzYWQiICAvPgoJCQk8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM1Njg0ZjUiICAvPgoJCTwvbGluZWFyR3JhZGllbnQ+Cgk8L2RlZnM+Cgk8c3R5bGU+CgkJdHNwYW4geyB3aGl0ZS1zcGFjZTpwcmUgfQoJCS5zaHAwIHsgZmlsbDogdXJsKCNncmQxKSB9IAoJCS5zaHAxIHsgZmlsbDogI2ZmZDc2NCB9IAoJCS5zaHAyIHsgZmlsbDogI2NiZWFmYiB9IAoJPC9zdHlsZT4KCTxnIGlkPSJMYXllciI+CgkJPHBhdGggaWQ9IlNoYXBlIDEiIGNsYXNzPSJzaHAwIiBkPSJNMSAxTDQ3IDFMNDcgNDdMMSA0N0wxIDFaIiAvPgoJCTxwYXRoIGlkPSJMYXllciIgY2xhc3M9InNocDEiIGQ9Ik0xOCAxOEMxNS43OSAxOCAxNCAxNi4yMSAxNCAxNEMxNCAxMS43OSAxNS43OSAxMCAxOCAxMEMyMC4yMSAxMCAyMiAxMS43OSAyMiAxNEMyMiAxNi4yMSAyMC4yMSAxOCAxOCAxOFoiIC8+CgkJPHBhdGggaWQ9IkxheWVyIiBjbGFzcz0ic2hwMiIgZD0iTTM5Ljg2IDM2LjUxQzM5LjgyIDM2LjU4IDM5Ljc3IDM2LjY1IDM5LjcgMzYuNzFDMzkuNjQgMzYuNzcgMzkuNTcgMzYuODIgMzkuNSAzNi44N0MzOS40MiAzNi45MSAzOS4zNCAzNi45NCAzOS4yNiAzNi45N0MzOS4xNyAzNi45OSAzOS4wOSAzNyAzOSAzN0w5IDM3QzguODIgMzcgOC42NCAzNi45NSA4LjQ5IDM2Ljg2QzguMzMgMzYuNzYgOC4yIDM2LjYzIDguMTIgMzYuNDdDOC4wMyAzNi4zMSA3Ljk5IDM2LjEzIDggMzUuOTVDOC4wMSAzNS43NyA4LjA3IDM1LjYgOC4xNyAzNS40NEwxNC4xNyAyNi40NUMxNC4yNCAyNi4zNCAxNC4zMyAyNi4yNCAxNC40NCAyNi4xN0MxNC41NSAyNi4xIDE0LjY4IDI2LjA0IDE0LjggMjYuMDJDMTQuOTMgMjUuOTkgMTUuMDcgMjUuOTkgMTUuMTkgMjYuMDJDMTUuMzIgMjYuMDQgMTUuNDUgMjYuMSAxNS41NSAyNi4xN0MxNS41NyAyNi4xOCAxNS41OCAyNi4xOSAxNS42IDI2LjJDMTUuNjEgMjYuMjEgMTUuNjIgMjYuMjIgMTUuNjMgMjYuMjNDMTUuNjUgMjYuMjQgMTUuNjYgMjYuMjUgMTUuNjcgMjYuMjZDMTUuNjggMjYuMjcgMTUuNyAyNi4yOCAxNS43MSAyNi4yOUwyMC44NiAzMS40NUwyOS4xOCAxOS40M0MyOS4yMyAxOS4zNiAyOS4yOCAxOS4zIDI5LjM1IDE5LjI0QzI5LjQxIDE5LjE5IDI5LjQ4IDE5LjE0IDI5LjU2IDE5LjFDMjkuNjMgMTkuMDYgMjkuNzEgMTkuMDQgMjkuNzkgMTkuMDJDMjkuODggMTkgMjkuOTYgMTkgMzAuMDUgMTlDMzAuMTMgMTkgMzAuMjEgMTkuMDIgMzAuMjkgMTkuMDRDMzAuMzggMTkuMDcgMzAuNDUgMTkuMSAzMC41MiAxOS4xNUMzMC42IDE5LjE5IDMwLjY2IDE5LjI1IDMwLjcyIDE5LjMxQzMwLjc4IDE5LjM3IDMwLjgzIDE5LjQ0IDMwLjg3IDE5LjUxTDM5Ljg3IDM1LjUxQzM5LjkxIDM1LjU5IDM5Ljk1IDM1LjY3IDM5Ljk3IDM1Ljc1QzM5Ljk5IDM1Ljg0IDQwIDM1LjkyIDQwIDM2LjAxQzQwIDM2LjEgMzkuOTkgMzYuMTggMzkuOTYgMzYuMjdDMzkuOTQgMzYuMzUgMzkuOTEgMzYuNDMgMzkuODYgMzYuNTFaIiAvPgoJPC9nPgo8L3N2Zz4=', 'viewer', 'Viewer', '', 'https://viewer.puter.com/index.html', 0, 1, 0, 1, 0, 0, NULL, '2020-01-01 00:00:00');
|
||||
|
||||
|
||||
@@ -15,33 +15,7 @@
|
||||
-- You should have received a copy of the GNU Affero General Public License
|
||||
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
INSERT INTO `apps` (
|
||||
`uid`,
|
||||
`owner_user_id`,
|
||||
`icon`,
|
||||
`name`,
|
||||
`title`,
|
||||
`description`,
|
||||
`index_url`,
|
||||
`approved_for_listing`,
|
||||
`approved_for_opening_items`,
|
||||
`approved_for_incentive_program`,
|
||||
`timestamp`,
|
||||
`last_review`
|
||||
) VALUES (
|
||||
'app-838dfbc4-bf8b-48c2-b47b-c4adc77fab58',
|
||||
1,
|
||||
'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjIiIGJhc2VQcm9maWxlPSJ0aW55LXBzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0OCA0OCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij4KCTx0aXRsZT5hcHAtaWNvbi1lZGl0b3Itc3ZnPC90aXRsZT4KCTxkZWZzPgoJCTxsaW5lYXJHcmFkaWVudCBpZD0iZ3JkMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiICB4MT0iNDciIHkxPSIzOS41MTQiIHgyPSIxIiB5Mj0iOC40ODYiPgoJCQk8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM3MTAxZTgiICAvPgoJCQk8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM5MTY3YmUiICAvPgoJCTwvbGluZWFyR3JhZGllbnQ+Cgk8L2RlZnM+Cgk8c3R5bGU+CgkJdHNwYW4geyB3aGl0ZS1zcGFjZTpwcmUgfQoJCS5zaHAwIHsgZmlsbDogdXJsKCNncmQxKSB9IAoJCS5zaHAxIHsgZmlsbDogI2ZmZmZmZiB9IAoJPC9zdHlsZT4KCTxnIGlkPSJMYXllciI+CgkJPHBhdGggaWQ9IkxheWVyIiBjbGFzcz0ic2hwMCIgZD0iTTQ3IDNMNDcgNDVDNDcgNDYuMSA0Ni4xIDQ3IDQ1IDQ3TDMgNDdDMS45IDQ3IDEgNDYuMSAxIDQ1TDEgM0MxIDEuOSAxLjkgMSAzIDFMNDUgMUM0Ni4xIDEgNDcgMS45IDQ3IDNaIiAvPgoJCTxwYXRoIGlkPSJMYXllciIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGFzcz0ic2hwMSIgZD0iTTI4LjYyIDQwTDI4LjYyIDM3LjYxTDMyLjI1IDM3LjIyTDI5Ljg2IDMwTDE3LjUzIDMwTDE1LjE4IDM3LjIyTDE4Ljc2IDM3LjYxTDE4Ljc2IDQwTDguNiA0MEw4LjYgMzcuNjZMMTAuNSAzNy4xN0MxMS4yMSAzNi45OSAxMS40MyAzNi44NiAxMS42IDM2LjMzTDIxLjMzIDhMMjYuNDUgOEwzNi4zNiAzNi4zOEMzNi41MyAzNi45MSAzNi44OCAzNi45OSAzNy40MiAzNy4xM0wzOS40IDM3LjYxTDM5LjQgNDBMMjguNjIgNDBaTTIzLjc2IDExLjQ1TDE4LjU0IDI3TDI4Ljg4IDI3TDIzLjc2IDExLjQ1WiIgLz4KCTwvZz4KPC9zdmc+',
|
||||
'editor',
|
||||
'Editor',
|
||||
'A simple text editor',
|
||||
'https://editor.puter.com/index.html',
|
||||
1, 1, 0,
|
||||
'2020-01-01 00:00:00',
|
||||
NULL
|
||||
);
|
||||
|
||||
|
||||
-- TEMP: editor app insert removed — broken, will fix later
|
||||
|
||||
INSERT INTO `apps` (
|
||||
`id`, `uid`, `owner_user_id`, `icon`, `name`, `title`, `description`, `godmode`, `maximize_on_start`, `index_url`, `approved_for_listing`, `approved_for_opening_items`, `approved_for_incentive_program`, `timestamp`, `last_review`, `tags`, `app_owner`
|
||||
|
||||
@@ -469,22 +469,6 @@ export class PermissionService extends PuterService {
|
||||
* `hardcoded-permissions.js`, merged with any runtime grants registered
|
||||
* through `registerSystemGrantForEveryone` / `registerSystemGrantForUsers`.
|
||||
*/
|
||||
/**
|
||||
* Hardcoded user-group permissions, granted by `system` (the only
|
||||
* issuer). DB-free: the default groups are static fixtures we never
|
||||
* assign at runtime via DB, so we infer membership from the actor:
|
||||
* - `username === 'admin'` → admin group
|
||||
* - `email_confirmed === true` → `default_user_group`
|
||||
* - otherwise → `default_temp_group`
|
||||
*
|
||||
* The admin username + group UID are matched verbatim against
|
||||
* `DefaultUserService` and the seed migration; if either is renamed,
|
||||
* update both ends.
|
||||
*
|
||||
* Permissions for non-default groups (custom operator-managed
|
||||
* groups) still flow through `#scanUserGroup`, which reads
|
||||
* `user_to_group_permissions` directly.
|
||||
*/
|
||||
async #scanHcUserGroupUser(
|
||||
actor: Actor,
|
||||
options: string[],
|
||||
@@ -493,50 +477,78 @@ export class PermissionService extends PuterService {
|
||||
if (actor.app || actor.accessToken) return;
|
||||
if (!actor.user?.id) return;
|
||||
|
||||
const userGroupUid = this.config.default_user_group;
|
||||
const tempGroupUid = this.config.default_temp_group;
|
||||
const isAdmin = actor.user.username === 'admin';
|
||||
const inferredGroupUid = isAdmin
|
||||
? 'ca342a5e-b13d-4dee-9048-58b11a57cc55' // admin group
|
||||
: actor.user.email_confirmed
|
||||
? userGroupUid
|
||||
: tempGroupUid;
|
||||
if (!inferredGroupUid) return;
|
||||
const memberGroups = await this.stores.group.listGroupsWithMember(
|
||||
actor.user.id,
|
||||
);
|
||||
if (memberGroups.length === 0) return;
|
||||
|
||||
const hcSystem =
|
||||
(
|
||||
hardcoded_user_group_permissions as Record<
|
||||
string,
|
||||
Record<string, Record<string, unknown>>
|
||||
>
|
||||
).system ?? {};
|
||||
const groupByUid: Record<string, { id: number; uid: string }> = {};
|
||||
for (const g of memberGroups) {
|
||||
groupByUid[g.uid] = { id: g.id, uid: g.uid };
|
||||
}
|
||||
|
||||
// Hardcoded grants + runtime `registerSystemGrant*` additions.
|
||||
// Runtime grants always shadow the static map for the same key.
|
||||
const groupPerms: Record<string, unknown> = {
|
||||
...(hcSystem[inferredGroupUid] ?? {}),
|
||||
...(this.systemGrantsByGroupUid[inferredGroupUid] ?? {}),
|
||||
};
|
||||
if (Object.keys(groupPerms).length === 0) return;
|
||||
// Compose the effective issuer → group → permission → data map by
|
||||
// merging the imported hardcoded data with runtime-registered system
|
||||
// grants. Runtime grants are always attributed to the `system` issuer.
|
||||
const hcMap = hardcoded_user_group_permissions as Record<
|
||||
string,
|
||||
Record<string, Record<string, unknown>>
|
||||
>;
|
||||
const hasRuntimeGrants =
|
||||
Object.keys(this.systemGrantsByGroupUid).length > 0;
|
||||
const byIssuer: Record<
|
||||
string,
|
||||
Record<string, Record<string, unknown>>
|
||||
> = hasRuntimeGrants
|
||||
? { ...hcMap, system: { ...(hcMap.system ?? {}) } }
|
||||
: hcMap;
|
||||
if (hasRuntimeGrants) {
|
||||
for (const [gUid, perms] of Object.entries(
|
||||
this.systemGrantsByGroupUid,
|
||||
)) {
|
||||
byIssuer.system[gUid] = {
|
||||
...(byIssuer.system[gUid] ?? {}),
|
||||
...perms,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (const permission of options) {
|
||||
if (!Object.prototype.hasOwnProperty.call(groupPerms, permission)) {
|
||||
continue;
|
||||
for (const issuerUsername of Object.keys(byIssuer)) {
|
||||
const issuerUser =
|
||||
await this.stores.user.getByUsername(issuerUsername);
|
||||
if (!issuerUser) continue;
|
||||
const issuerActor = this.#userToActor(issuerUser);
|
||||
const issuerGroups = byIssuer[issuerUsername];
|
||||
|
||||
for (const groupUid of Object.keys(issuerGroups)) {
|
||||
if (!groupByUid[groupUid]) continue;
|
||||
const issuerGroupPerms = issuerGroups[groupUid];
|
||||
|
||||
for (const permission of options) {
|
||||
if (
|
||||
!Object.prototype.hasOwnProperty.call(
|
||||
issuerGroupPerms,
|
||||
permission,
|
||||
)
|
||||
)
|
||||
continue;
|
||||
const issuerReading = await this.scan(
|
||||
issuerActor,
|
||||
permission,
|
||||
);
|
||||
reading.push({
|
||||
$: 'path',
|
||||
via: 'hc-user-group',
|
||||
has_terminal: readingHasTerminal(issuerReading),
|
||||
permission,
|
||||
data: issuerGroupPerms[permission],
|
||||
holder_username: actor.user.username,
|
||||
issuer_username: issuerUsername,
|
||||
reading: issuerReading,
|
||||
group_id: groupByUid[groupUid].id,
|
||||
});
|
||||
}
|
||||
}
|
||||
reading.push({
|
||||
$: 'path',
|
||||
via: 'hc-user-group',
|
||||
// `system` is the issuer; `isSystemActor` short-circuits
|
||||
// any scan to grant, so the chain is terminal by
|
||||
// definition — no recursive verify needed.
|
||||
has_terminal: true,
|
||||
permission,
|
||||
data: groupPerms[permission],
|
||||
holder_username: actor.user.username,
|
||||
issuer_username: 'system',
|
||||
reading: null,
|
||||
vgroup_id: inferredGroupUid,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user