Files
mesh-controller/cmd/mesh-controller/testdata/config-moved/mesh.json
T

3661 lines
155 KiB
JSON

{
"layers": [
{
"module": "baserow",
"node": "anchor",
"values": {
"email": "operator@example.test"
}
},
{
"module": "bazarr",
"node": "anchor",
"values": {
"pgid": 2000,
"puid": 1001
}
},
{
"module": "bookshelf",
"node": "anchor",
"values": {
"downloads": {
"jackett-api": {
"adopt-hosts": [
"jackett"
]
},
"nzbget-api": {
"name": "NZBGet"
},
"qbittorrent-api": {
"name": "qBitTorrent"
}
},
"pgid": 1000,
"puid": 1000
}
},
{
"module": "claude-code",
"node": "anchor",
"values": {
"role": "the home server \u2014 media (Plex and the *arr apps), home automation, IoT and app databases"
}
},
{
"module": "hostname",
"node": "anchor",
"values": {
"hostname": "Ace"
}
},
{
"module": "influxdb",
"node": "anchor",
"values": {
"bucket": "zurag",
"org": "zurag"
}
},
{
"module": "kometa",
"node": "anchor",
"values": {
"libraries": {
"Formula 1": {
"metadata_files": [
{
"url": "https://f1-circuits.zurag.be/kometa/formula-1-metadata.yml"
}
]
}
},
"pgid": 1000,
"puid": 1000
}
},
{
"module": "lidarr",
"node": "anchor",
"values": {
"downloads": {
"jackett-api": {
"adopt-hosts": [
"indexers.zurag.be",
"jackett"
]
},
"nzbget-api": {
"name": "NZBGet"
},
"qbittorrent-api": {
"name": "qBitTorrent"
}
},
"pgid": 2000,
"puid": 1001
}
},
{
"module": "nfs-server",
"node": "anchor",
"values": {
"grants": "media=shanks:rw",
"shares": "media=/storage/media"
}
},
{
"module": "nodered",
"node": "anchor",
"values": {
"mqtt": {
"brokers": [
"2b0aece9c5f3b307"
]
},
"timeZone": "Europe/Brussels"
}
},
{
"module": "nzbget",
"node": "anchor",
"values": {
"max-download-speed": 10,
"options": {
"UMask": "0000"
},
"pgid": 2000,
"puid": 1001,
"username": "luffy"
}
},
{
"module": "ombi",
"node": "anchor",
"values": {
"pgid": 2000,
"puid": 1001
}
},
{
"module": "plex",
"node": "anchor",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "qbittorrent",
"node": "anchor",
"values": {
"max-download-speed": 10,
"pgid": 2000,
"puid": 1001,
"username": "luffy"
}
},
{
"module": "radarr",
"node": "anchor",
"values": {
"downloads": {
"jackett-api": {
"adopt-hosts": [
"jackett"
]
},
"nzbget-api": {
"name": "NZBGet"
},
"qbittorrent-api": {
"name": "qBitTorrent"
}
},
"pgid": 2000,
"puid": 1001
}
},
{
"module": "searxng",
"node": "anchor",
"values": {
"search": {
"autocomplete": "",
"suspended_times": {
"SearxEngineAccessDenied": 86400,
"SearxEngineCaptcha": 86400,
"SearxEngineTooManyRequests": 3600
}
},
"server": {
"base_url": "https://searxng.zurag.be/",
"method": "POST"
}
}
},
{
"module": "sonarr",
"node": "anchor",
"values": {
"downloads": {
"jackett-api": {
"adopt-hosts": [
"jackett"
]
},
"nzbget-api": {
"name": "NZBGet"
},
"qbittorrent-api": {
"name": "qBitTorrent"
}
},
"pgid": 2000,
"puid": 1001
}
},
{
"module": "supabase",
"node": "anchor",
"values": {
"pooler": {
"max-client-connections": 100,
"pool-size": 20,
"tenant": "zurag"
}
}
},
{
"module": "tautulli",
"node": "anchor",
"values": {
"pgid": 2000,
"puid": 1001
}
},
{
"module": "unifi",
"node": "anchor",
"values": {
"username": "luffy"
}
},
{
"module": "claude-code",
"node": "workstation",
"values": {
"role": "the operator's laptop \u2014 SaaS integrations and mesh control; travels, often behind a corporate VPN client"
}
},
{
"module": "dunst",
"node": "workstation",
"values": {
"font-size": 13,
"width": 500
}
},
{
"module": "hostname",
"node": "workstation",
"values": {
"hostname": "g14"
}
},
{
"module": "power",
"node": "workstation",
"values": {
"handle-lid-switch": "suspend",
"handle-lid-switch-docked": "suspend",
"handle-lid-switch-external-power": "suspend",
"handle-power-key": "suspend"
}
},
{
"module": "artifact-store-tools",
"node": "",
"values": {
"older-store-names": "registry-api.novox.be"
}
},
{
"module": "bazarr",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "betterbird",
"node": "",
"values": {
"accounts": {
"gmail": {
"address": "operator@example.test",
"from-name": "The Operator",
"incoming": {
"host": "imap.gmail.com",
"kind": "imap",
"port": 993,
"security": "tls",
"sign-in": "oauth",
"user": "operator@example.test"
},
"outgoing": {
"host": "smtp.gmail.com",
"port": 465,
"security": "tls",
"sign-in": "oauth",
"user": "operator@example.test"
}
},
"novox": {
"address": "operator@example.test",
"from-name": "The Operator",
"incoming": {
"host": "mail.novox.be",
"kind": "imap",
"port": 143,
"security": "starttls",
"sign-in": "password",
"user": "operator@example.test"
},
"label": "operator@mail.novox.be",
"outgoing": {
"host": "mail.novox.be",
"port": 587,
"security": "starttls",
"sign-in": "password",
"user": "operator@example.test"
}
},
"novox2": {
"address": "operator@example.test",
"from-name": "The Operator - MH",
"incoming": {
"host": "mail.novox.be",
"kind": "imap",
"port": 993,
"security": "tls",
"sign-in": "password",
"user": "operator@example.test"
},
"outgoing": {
"host": "mail.novox.be",
"port": 465,
"security": "tls",
"sign-in": "password",
"user": "operator@example.test"
}
}
},
"prefs": {
"calendar.alarms.show": true,
"calendar.alarms.showmissed": false,
"calendar.item.promptDelete": false,
"calendar.timezone.useSystemTimezone": true,
"calendar.view.dayendhour": 20,
"calendar.view.daystarthour": 9,
"mail.biff.play_sound": false,
"mail.biff.show_alert": false,
"mail.biff.show_tray_icon": false,
"mail.biff.use_system_alert": true,
"mail.chat.enabled": false,
"mail.dark-reader.enabled": false,
"mail.pane_config.dynamic": 2,
"mail.pane_config.multiline_all": true,
"mail.threadpane.listview": 0,
"mail.uidensity": 0,
"mail.uifontsize": 14,
"mail.warn_on_shift_delete": false,
"mailnews.default_view_flags": 1,
"mailnews.message_display.disable_remote_image": true,
"privacy.donottrackheader.enabled": true
}
}
},
{
"module": "bookshelf",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "claude-code",
"node": "",
"values": {
"managed_settings": {
"spinnerTipsEnabled": false
}
}
},
{
"module": "jackett",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "lab",
"node": "",
"values": {
"forge": "http://novox.internal:20000"
}
},
{
"module": "lidarr",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "mailu",
"node": "",
"values": {
"domain": "novox.be",
"proxy-address": "192.0.2.10",
"sitename": "Novox",
"website": "https://novox.be"
}
},
{
"module": "messenger",
"node": "",
"values": {
"time-zone": "Europe/Brussels"
}
},
{
"module": "nzbget",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "ombi",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "plex",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "power",
"node": "",
"values": {
"handle-lid-switch": "suspend",
"handle-lid-switch-docked": "ignore",
"handle-lid-switch-external-power": "suspend",
"handle-power-key": "poweroff"
}
},
{
"module": "qbittorrent",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "radarr",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "records",
"node": "",
"values": {
"repository": "novox/hq"
}
},
{
"module": "sonarr",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "tautulli",
"node": "",
"values": {
"pgid": 1000,
"puid": 1000
}
},
{
"module": "xdg",
"node": "",
"values": {
"desktop-dir": "$HOME/Desktop",
"publicshare-dir": "$HOME/Public",
"videos-dir": "$HOME/Videos"
}
},
{
"module": "claude-code",
"node": "laptop",
"values": {
"role": "the control node \u2014 the controller, the bus, the store, git hosting, CI/CD and public web apps"
}
},
{
"module": "desk-channel",
"node": "laptop",
"values": {
"desktop-machines": [
"g14",
"shanks"
]
}
},
{
"module": "hostname",
"node": "laptop",
"values": {
"hostname": "novox-be"
}
},
{
"module": "kernel",
"node": "laptop",
"values": {
"transparent-huge-pages": "madvise"
}
},
{
"module": "keycloak",
"node": "laptop",
"values": {
"issuer": "https://keycloak.novox.be/realms/Novox"
}
},
{
"module": "messenger",
"node": "laptop",
"values": {
"desktop-machines": [
"g14",
"shanks"
]
}
},
{
"module": "zsh",
"node": "laptop",
"values": {
"execute": "withhold"
}
},
{
"module": "claude-code",
"node": "desktop",
"values": {
"role": "the operator's interactive desktop \u2014 full mesh control"
}
},
{
"module": "hostname",
"node": "desktop",
"values": {
"hostname": "Shanks"
}
},
{
"module": "mounts",
"node": "desktop",
"values": {
"shares": "media=/storage/media",
"sources": "recalbox=smb://root@192.0.2.10/Share@/mnt/recalbox"
}
},
{
"module": "xdg",
"node": "desktop",
"values": {
"desktop-dir": "$HOME/",
"publicshare-dir": "$HOME/",
"videos-dir": "$HOME/"
}
}
],
"modules": [
{
"config": {
"email": {
"default": "",
"description": "The email address of the Baserow account the module's tools sign in as; unset, the code falls back to its environment.",
"trusted": true,
"type": "string"
},
"host": {
"default": "${bound:route:name}",
"description": "The Host header the module's tools send to Baserow, a host name; the file's default is the name the module's route was given.",
"trusted": true,
"type": "string"
},
"url": {
"default": "",
"description": "The address the module's tools reach Baserow at, as a URL; unset, the code falls back to its environment, else this machine's Baserow on port 80.",
"trusted": true,
"type": "string"
}
},
"module": "baserow",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "data",
"mode": "0755",
"type": "directory"
},
{
"content": "DATABASE_HOST=STAND-IN\nDATABASE_PORT=STAND-IN\nDATABASE_NAME=STAND-IN\nDATABASE_USER=STAND-IN\nDATABASE_PASSWORD_FILE=/run/secrets/database\nDISABLE_EMBEDDED_PSQL=true\nBASEROW_PUBLIC_URL=https://STAND-IN\n",
"id": "server-env",
"mode": "0600",
"path": "${dir:state}/server.env",
"type": "file"
},
{
"content": "{\n \"password\": \"STAND-IN\",\n \"host\": \"STAND-IN\"\n}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"protected": [
"password"
],
"takes-config": [
"host",
"email",
"url"
],
"type": "file"
}
],
"version": "1"
},
{
"config": {
"agents": {
"default": {},
"description": "The agents offered to every session on this machine, by name, each a text as one line or a list of its lines; empty adds none.",
"trusted": true,
"type": "object"
},
"commands": {
"default": {},
"description": "The slash commands offered to every session on this machine, by name, each a text as one line or a list of its lines; empty adds none.",
"trusted": true,
"type": "object"
},
"instructions": {
"default": {},
"description": "The instructions every session on this machine reads, by name, each a text as one line or a list of its lines; empty adds none.",
"trusted": true,
"type": "object"
},
"managed_settings": {
"default": {},
"description": "Keys of Claude Code's managed settings every session on this machine obeys, such as spinnerTipsEnabled, laid over the mesh's own; empty adds none. Falls back to agent-managed-settings, the mesh's or this node's.",
"predefined": "agent-managed-settings",
"trusted": true,
"type": "object"
},
"mcp_servers": {
"default": {},
"description": "The tool servers every session on this machine is given beside the mesh's own, by name, each as Claude Code's managed MCP configuration takes it; empty adds none.",
"trusted": true,
"type": "object"
},
"output_styles": {
"default": {},
"description": "The output styles offered to every session on this machine, by name, each a text as one line or a list of its lines; empty adds none.",
"trusted": true,
"type": "object"
},
"role": {
"default": "",
"description": "What this machine is for, in a sentence, written into the instructions every Claude Code session on it reads; empty says nothing.",
"trusted": true,
"type": "string"
},
"skills": {
"default": {},
"description": "The skills offered to every session on this machine, by name, each its files by path, a text as one line or a list of its lines; a skill without its SKILL.md is left out; empty adds none.",
"trusted": true,
"type": "object"
}
},
"module": "claude-code",
"resources": [
{
"id": "managed",
"mode": "0755",
"path": "/etc/claude-code",
"type": "directory"
},
{
"content": "#!/bin/sh\n# The mesh's (module claude-code, novox/hq ADR 0266): start the agent as the account agents run as on this\n# machine. Written whole at every push: an edit here is overwritten.\nagent='STAND-IN'\nif [ \"$(id -un)\" = \"$agent\" ]; then\n\texec claude \"$@\"\nfi\n# Another account, the operator's, becomes the agent's through its own sudo: the person is the authority. The\n# operator's override of the guard travels only when it is set in this shell, as it would reach claude.\nif [ -n \"$MESH_GUARD_OVERRIDE\" ]; then\n\texec sudo -iu \"$agent\" env MESH_GUARD_OVERRIDE=\"$MESH_GUARD_OVERRIDE\" claude \"$@\"\nfi\nexec sudo -iu \"$agent\" claude \"$@\"\n",
"id": "start",
"mode": "0755",
"path": "/usr/local/bin/claude-agent",
"type": "file"
},
{
"content": "STAND-IN\n",
"id": "agent-account-name",
"mode": "0644",
"path": "/etc/claude-code/agent-account",
"type": "file"
},
{
"id": "agent-home",
"mode": "0700",
"path": "STAND-IN/.claude",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"content": "{\n \"node\": \"STAND-IN\",\n \"console\": \"http://127.0.0.1:STAND-IN/mcp\"\n}\n",
"id": "facts",
"mode": "0600",
"path": "${dir:state}/facts.json",
"type": "file"
},
{
"content": "{\n \"role\": \"\",\n \"mcp_servers\": {},\n \"managed_settings\": {},\n \"instructions\": {},\n \"output_styles\": {},\n \"skills\": {},\n \"commands\": {},\n \"agents\": {}\n}\n",
"id": "settings",
"merge": "json",
"mode": "0600",
"path": "${dir:state}/settings.json",
"takes-config": [
"role",
"mcp_servers",
"managed_settings",
"instructions",
"output_styles",
"skills",
"commands",
"agents"
],
"type": "file"
}
],
"version": "1"
},
{
"config": {
"url": {
"default": "",
"description": "The address the module's tools reach Grafana at, as a URL; unset, the code falls back to its environment, else this machine's Grafana on its port.",
"trusted": true,
"type": "string"
},
"user": {
"default": "admin",
"description": "The Grafana account the module's tools sign in as with the vault's password; the file's default is admin.",
"trusted": true,
"type": "string"
}
},
"module": "grafana",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "data",
"mode": "0700",
"type": "directory"
},
{
"content": "STAND-IN",
"id": "admin-secret",
"mode": "0400",
"path": "${dir:state}/admin.secret",
"type": "file"
},
{
"content": "STAND-IN",
"id": "oidc-secret",
"mode": "0400",
"path": "${dir:state}/oidc-client.secret",
"type": "file"
},
{
"content": "GF_SERVER_ROOT_URL=https://STAND-IN\nGF_AUTH_GENERIC_OAUTH_ENABLED=true\nGF_AUTH_GENERIC_OAUTH_NAME=Keycloak\nGF_AUTH_GENERIC_OAUTH_CLIENT_ID=STAND-IN\nGF_AUTH_GENERIC_OAUTH_CLIENT_SECRET__FILE=/run/secrets/oidc-client\nGF_AUTH_GENERIC_OAUTH_SCOPES=openid email profile roles\nGF_AUTH_GENERIC_OAUTH_AUTH_URL=STAND-INSTAND-IN\nGF_AUTH_GENERIC_OAUTH_TOKEN_URL=STAND-INSTAND-IN\nGF_AUTH_GENERIC_OAUTH_API_URL=STAND-INSTAND-IN\nGF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(roles[*], 'admin') && 'Admin' || contains(realm_access.roles[*], 'admin') && 'Admin' || 'Viewer'\nGF_AUTH_GENERIC_OAUTH_USE_PKCE=true\nGF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP=true\nGF_AUTH_GENERIC_OAUTH_ALLOW_ASSIGN_GRAFANA_ADMIN=true\n",
"id": "oidc-env",
"mode": "0644",
"path": "${dir:state}/oidc.env",
"type": "file"
},
{
"id": "influxdb-datasources",
"mode": "0755",
"path": "${dir:state}/datasources",
"type": "directory"
},
{
"id": "influxdb-credential",
"mode": "0700",
"path": "${dir:state}/influxdb",
"type": "directory"
},
{
"content": "STAND-IN",
"id": "influxdb-secret",
"mode": "0400",
"path": "${dir:influxdb-credential}/influxdb-api",
"type": "file"
},
{
"content": "apiVersion: 1\n# Written by the mesh from grafana's influxdb-api binding, only while it is bound (novox/hq ADR 0330):\n# without influxdb, grafana runs with no mesh data source. grafana reads it at start. Its own name and\n# uid, so a data source somebody made in the UI is never overwritten, and read-only in the UI because\n# the mesh resets it. The password is read from the file the mesh delivers, never written here.\ndatasources:\n - name: InfluxDB (mesh)\n uid: mesh-influxdb-api\n type: influxdb\n access: proxy\n url: STAND-IN://STAND-IN:STAND-IN\n user: STAND-IN\n isDefault: false\n editable: false\n jsonData:\n dbName: STAND-IN\n httpMode: POST\n secureJsonData:\n password: $__file{/run/secrets/influxdb/influxdb-api}\n",
"id": "influxdb-datasource",
"mode": "0644",
"path": "${dir:influxdb-datasources}/mesh-influxdb.yaml",
"type": "file"
},
{
"content": "{\n \"user\": \"admin\",\n \"password\": \"STAND-IN\"\n}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"protected": [
"password",
"token"
],
"takes-config": [
"user",
"url"
],
"type": "file"
}
],
"version": "1"
},
{
"config": {
"url": {
"default": "",
"description": "The address the module's tools reach Home Assistant at, as a URL; unset, the code falls back to its environment, else this machine's Home Assistant on its port.",
"trusted": true,
"type": "string"
}
},
"module": "home-assistant",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "config",
"mode": "0700",
"type": "directory"
},
{
"id": "written",
"mode": "0700",
"type": "directory"
},
{
"content": "{}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"takes-config": [
"url"
],
"type": "file"
},
{
"content": "MESH_HOMEASSISTANT_URL=http://127.0.0.1:STAND-IN\nMESH_HOMEASSISTANT_TOKEN_FILE=${dir:mesh-state}/token\nMESH_PROVISIONS_DIR=${dir:state}\nMESH_WRITTEN_DIR=${dir:written}\n",
"id": "provisions-env",
"mode": "0600",
"path": "${dir:state}/provisions.env",
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"name": "node-hostname",
"scope": "node",
"serves": [
"entries",
"add",
"remove"
]
}
],
"config": {
"hostname": {
"description": "The machine's host name, written to /etc/hostname: the operator's name for this machine, with no default (novox/hq ADR 0112).",
"required": true,
"trusted": true,
"type": "string"
}
},
"module": "hostname",
"resources": [
{
"content": "# The machine's own names (module hostname, novox/hq ADR 0199, ADR 0223). Every line outside this\n# block is the operator's: kept across every push, changed through the node-hostname verbs add and\n# remove, and given back when this module goes. The mesh's names are not here: the mesh's resolver\n# answers them.\n127.0.0.1\tlocalhost\n::1\tlocalhost\n127.0.1.1\tSTAND-IN\n",
"id": "own",
"mode": "0644",
"path": "/etc/hosts",
"type": "file"
},
{
"content": "${config:hostname}\n",
"id": "name",
"mode": "0644",
"path": "/etc/hostname",
"type": "file"
}
],
"version": "1"
},
{
"config": {
"url": {
"default": "",
"description": "The address the module's tools reach Icecast at, as a URL; unset, the code falls back to its environment, else this machine's Icecast on its port.",
"trusted": true,
"type": "string"
},
"user": {
"default": "",
"description": "The Icecast admin account the module's tools sign in as; unset, the code falls back to its environment, else admin.",
"trusted": true,
"type": "string"
}
},
"module": "icecast",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "logs",
"mode": "0700",
"type": "directory"
},
{
"content": "<icecast>\n <!-- Written by the mesh (modules/icecast). Passwords arrive as secrets rendered into this file,\n never as environment: the image's entrypoint seds ICECAST_* variables into the file only\n when they are set, and none are. -->\n <location>Earth</location>\n <admin>icemaster@localhost</admin>\n <limits>\n <clients>100</clients>\n <sources>2</sources>\n <queue-size>524288</queue-size>\n <client-timeout>30</client-timeout>\n <header-timeout>15</header-timeout>\n <source-timeout>10</source-timeout>\n <burst-on-connect>1</burst-on-connect>\n <burst-size>65535</burst-size>\n </limits>\n <authentication>\n <source-password>STAND-IN</source-password>\n <relay-password>STAND-IN</relay-password>\n <admin-user>admin</admin-user>\n <admin-password>STAND-IN</admin-password>\n </authentication>\n <!-- The name icecast writes into playlists (.m3u/.xspf: http://<hostname>:<port>/<mount>) and\n would announce to YP (none configured). A machine's own name belongs to its assignment, and\n an assignment merges only into JSON; this XML cannot take it, so the neutral default stays. -->\n <hostname>localhost</hostname>\n <listen-socket>\n <port>8000</port>\n </listen-socket>\n <http-headers>\n <header name=\"Access-Control-Allow-Origin\" value=\"*\" />\n </http-headers>\n <fileserve>1</fileserve>\n <paths>\n <basedir>/usr/share/icecast</basedir>\n <logdir>/var/log/icecast</logdir>\n <webroot>/usr/share/icecast/web</webroot>\n <adminroot>/usr/share/icecast/admin</adminroot>\n <alias source=\"/\" destination=\"/status.xsl\"/>\n </paths>\n <logging>\n <accesslog>access.log</accesslog>\n <errorlog>error.log</errorlog>\n <loglevel>3</loglevel>\n <logsize>10000</logsize>\n </logging>\n <security>\n <chroot>0</chroot>\n <!-- Starts as root, reads this 0600 root-owned file, then drops to the image's icecast user\n (uid 100, group icecast 101) before serving. -->\n <changeowner>\n <user>icecast</user>\n <group>icecast</group>\n </changeowner>\n </security>\n</icecast>\n",
"id": "server-conf",
"mode": "0600",
"path": "${dir:state}/icecast.xml",
"type": "file"
},
{
"content": "{}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"takes-config": [
"url",
"user"
],
"type": "file"
}
],
"version": "1"
},
{
"config": {
"bucket": {
"default": "default",
"description": "The bucket influxdb-api names to its consumers and the module's tools write to; default is the one influxdb makes at its first start (server.env), and a machine whose server was set up with another sets its own (novox/hq issue 696, ADR 0348 clause 11).",
"trusted": true,
"type": "string"
},
"org": {
"default": "mesh",
"description": "The organisation influxdb-api names to its consumers and the module's tools write to; mesh is the one influxdb makes at its first start (server.env), and a machine whose server was set up with another sets its own (novox/hq issue 696, ADR 0348 clause 11).",
"trusted": true,
"type": "string"
},
"url": {
"default": "",
"description": "The address the module's tools reach influxdb at, as a URL; empty is this machine's influxdb on its port.",
"trusted": true,
"type": "string"
}
},
"module": "influxdb",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "data",
"mode": "0700",
"type": "directory"
},
{
"id": "config",
"mode": "0700",
"type": "directory"
},
{
"id": "grants",
"mode": "0700",
"type": "directory"
},
{
"content": "DOCKER_INFLUXDB_INIT_MODE=setup\nDOCKER_INFLUXDB_INIT_USERNAME=admin\nDOCKER_INFLUXDB_INIT_PASSWORD_FILE=/run/secrets/admin\nDOCKER_INFLUXDB_INIT_ADMIN_TOKEN_FILE=/run/secrets/admin-token\nDOCKER_INFLUXDB_INIT_ORG=mesh\nDOCKER_INFLUXDB_INIT_BUCKET=default\n",
"id": "server-env",
"mode": "0600",
"path": "${dir:state}/server.env",
"type": "file"
},
{
"content": "{}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"takes-config": [
"bucket",
"org",
"url"
],
"trusted": true,
"type": "file"
}
],
"serves-checked": {
"influxdb-api": {
"bucket": "${config:bucket}",
"org": "${config:org}",
"port": 8086,
"scheme": "http"
}
},
"version": "1"
},
{
"config": {
"url": {
"default": "",
"description": "The address the module's tools reach Letta at, as a URL; unset, the code falls back to its environment, else this machine's Letta on port 8283.",
"trusted": true,
"type": "string"
}
},
"module": "letta",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"content": "LETTA_PG_URI=postgresql://STAND-IN@STAND-IN:STAND-IN/STAND-IN\nPGPASSFILE=/run/secrets/pgpass\nLETTA_SERVER_PASSWORD=STAND-IN\nOPENAI_API_KEY=STAND-IN\nSECURE=true\nTZ=Europe/Brussels\n",
"id": "server-env",
"mode": "0600",
"path": "${dir:state}/server.env",
"type": "file"
},
{
"content": "*:*:*:STAND-IN:STAND-IN\n",
"id": "pgpass",
"mode": "0600",
"path": "${dir:state}/pgpass",
"type": "file"
},
{
"content": "#!/bin/sh\n# Generated by the mesh. Do not edit: module letta writes this file and replaces it at every push.\n#\n# letta 0.6.8 prints secrets it is given to its log (novox/hq issue 268):\n# letta/server/rest_api/app.py prints its server password when it starts in secure mode;\n# startup.sh, alembic/env.py and letta/server/server.py print LETTA_PG_URI whole.\n# The URI carries no password (libpq reads it from PGPASSFILE), and the one print of the server\n# password is rewritten before the server starts. Either one failing refuses the start: a letta\n# that does not start says why here, and one that leaks says nothing.\nset -e\napp=/app/letta/server/rest_api/app.py\nsed -i 's/Using secure mode with password: {random_password}/Using secure mode (the password is not printed)/' \"$app\"\nif grep -q 'print(.*random_password' \"$app\"; then\n echo \"letta: $app still prints the server password; not starting (novox/hq issue 268)\" >&2\n exit 1\nfi\ncase \"$LETTA_PG_URI\" in\n *://*:*@*)\n echo \"letta: LETTA_PG_URI carries a password, and letta prints that URI; not starting (novox/hq issue 268)\" >&2\n exit 1\n ;;\nesac\nexec ./letta/server/startup.sh\n",
"id": "start",
"mode": "0644",
"path": "${dir:state}/start.sh",
"type": "file"
},
{
"content": "{\n \"password\": \"STAND-IN\"\n}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"protected": [
"password"
],
"takes-config": [
"url"
],
"type": "file"
}
],
"version": "1"
},
{
"config": {
"brand": {
"default": "Element",
"description": "Element's brand setting of config.json: the name Element shows itself as.",
"trusted": true,
"type": "string"
},
"bug_report_endpoint_url": {
"default": "",
"description": "Element's bug_report_endpoint_url setting of config.json: where Element sends a bug report, a URL; its default here is empty, and the file's own value (Element's, naming outside servers) stays in the file and is never replaced unless this is set.",
"trusted": true,
"type": "string"
},
"default_server_config": {
"default": {
"m.homeserver": {
"base_url": "https://${bound:route:name-homeserver}"
},
"m.identity_server": {
"base_url": "https://vector.im"
}
},
"description": "Element's default_server_config setting of config.json: the homeserver and identity server Element uses by default, by URL.",
"trusted": true,
"type": "object"
},
"default_server_name": {
"default": "${bound:route:name-homeserver}",
"description": "Element's default_server_name setting of config.json: the homeserver Element signs in to by default, by name; the file's default is the module's own route.",
"trusted": true,
"type": "string"
},
"element_call": {
"default": {
"url": "https://call.element.dev"
},
"description": "Element's element_call setting of config.json: where Element starts a call with Element Call, a URL.",
"trusted": true,
"type": "object"
},
"enable_presence_by_hs_url": {
"default": {
"https://matrix-client.matrix.org": false,
"https://matrix.org": false
},
"description": "Element's enable_presence_by_hs_url setting of config.json: whether Element shows presence, per homeserver URL.",
"trusted": true,
"type": "object"
},
"features": {
"default": {
"feature_rust_crypto": true,
"feature_video_rooms": true
},
"description": "Element's features setting of config.json: Element's feature switches, each on or off.",
"trusted": true,
"type": "object"
},
"integrations_rest_url": {
"default": "https://scalar.vector.im/api",
"description": "Element's integrations_rest_url setting of config.json: the integration manager's API, a URL.",
"trusted": true,
"type": "string"
},
"integrations_ui_url": {
"default": "https://scalar.vector.im/",
"description": "Element's integrations_ui_url setting of config.json: the integration manager's page, a URL.",
"trusted": true,
"type": "string"
},
"integrations_widgets_urls": {
"default": [
"https://scalar.vector.im/_matrix/integrations/v1",
"https://scalar.vector.im/api",
"https://scalar-staging.vector.im/_matrix/integrations/v1",
"https://scalar-staging.vector.im/api",
"https://scalar-staging.riot.im/scalar/api"
],
"description": "Element's integrations_widgets_urls setting of config.json: the URLs whose widgets Element trusts as the integration manager's.",
"trusted": true,
"type": "list"
},
"room_directory": {
"default": {},
"description": "Element's room_directory setting of config.json: the servers whose room directories Element offers; its default here is empty, and the file's own value (Element's, naming outside servers) stays in the file and is never replaced unless this is set.",
"trusted": true,
"type": "object"
},
"show_labs_settings": {
"default": true,
"description": "Element's show_labs_settings setting of config.json: whether Element shows its Labs settings.",
"trusted": true,
"type": "boolean"
},
"terms_and_conditions_links": {
"default": [],
"description": "Element's terms_and_conditions_links setting of config.json: the links Element shows as its terms, each a URL and its text; its default here is empty, and the file's own value (Element's, naming outside servers) stays in the file and is never replaced unless this is set.",
"trusted": true,
"type": "list"
},
"uisi_autorageshake_app": {
"default": "element-auto-uisi",
"description": "Element's uisi_autorageshake_app setting of config.json: the app name Element files an automatic report of an undecryptable message under.",
"trusted": true,
"type": "string"
}
},
"module": "matrix",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "db",
"mode": "0700",
"type": "directory"
},
{
"content": "# Written by the mesh (modules/matrix). Conduit reads this file (CONDUIT_CONFIG); nothing comes\n# from the environment. server_name is the homeserver's permanent identity: every user id, room id\n# and signature in the database carries it, so it is the name this module is served under\n# (STAND-IN) and never changes once a database exists.\n[global]\nserver_name = \"STAND-IN\"\ndatabase_backend = \"rocksdb\"\ndatabase_path = \"/var/lib/matrix-conduit/\"\naddress = \"0.0.0.0\"\nport = 6167\nmax_request_size = 20000000\nallow_registration = false\nallow_federation = true\nallow_check_for_updates = true\ntrusted_servers = [\"matrix.org\"]\n",
"id": "conduit-conf",
"mode": "0644",
"path": "${dir:state}/conduit.toml",
"type": "file"
},
{
"content": "{\n \"default_server_name\": \"STAND-IN\",\n \"default_server_config\": {\n \"m.homeserver\": {\n \"base_url\": \"https://STAND-IN\"\n },\n \"m.identity_server\": {\n \"base_url\": \"https://vector.im\"\n }\n },\n \"brand\": \"Element\",\n \"integrations_ui_url\": \"https://scalar.vector.im/\",\n \"integrations_rest_url\": \"https://scalar.vector.im/api\",\n \"integrations_widgets_urls\": [\n \"https://scalar.vector.im/_matrix/integrations/v1\",\n \"https://scalar.vector.im/api\",\n \"https://scalar-staging.vector.im/_matrix/integrations/v1\",\n \"https://scalar-staging.vector.im/api\",\n \"https://scalar-staging.riot.im/scalar/api\"\n ],\n \"bug_report_endpoint_url\": \"https://element.io/bugreports/submit\",\n \"uisi_autorageshake_app\": \"element-auto-uisi\",\n \"show_labs_settings\": true,\n \"room_directory\": {\n \"servers\": [\n \"STAND-IN\",\n \"matrix.org\",\n \"gitter.im\",\n \"libera.chat\"\n ]\n },\n \"enable_presence_by_hs_url\": {\n \"https://matrix.org\": false,\n \"https://matrix-client.matrix.org\": false\n },\n \"terms_and_conditions_links\": [\n {\n \"url\": \"https://element.io/privacy\",\n \"text\": \"Privacy Policy\"\n },\n {\n \"url\": \"https://element.io/cookie-policy\",\n \"text\": \"Cookie Policy\"\n }\n ],\n \"features\": {\n \"feature_video_rooms\": true,\n \"feature_rust_crypto\": true\n },\n \"element_call\": {\n \"url\": \"https://call.element.dev\"\n }\n}\n",
"id": "element-conf",
"merge": "json",
"mode": "0644",
"path": "${dir:state}/element.json",
"takes-config": [
"default_server_name",
"default_server_config",
"brand",
"integrations_ui_url",
"integrations_rest_url",
"integrations_widgets_urls",
"bug_report_endpoint_url",
"uisi_autorageshake_app",
"show_labs_settings",
"room_directory",
"enable_presence_by_hs_url",
"terms_and_conditions_links",
"features",
"element_call"
],
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"name": "node-nfs-server",
"scope": "node",
"serves": [
"exports",
"clients",
"test",
"reload",
"adopt"
]
}
],
"config": {
"grants": {
"default": "none",
"description": "Which nodes may mount which share, and how: share=node:rw|ro[,node:rw|ro], entries separated by spaces, or none to export to no node whatever a node wants (novox/hq ADR 0263).",
"trusted": true,
"type": "string"
},
"shares": {
"description": "The folders this machine exports, as space-separated name=path; the operator's, with no default (novox/hq ADR 0263).",
"required": true,
"trusted": true,
"type": "string"
}
},
"module": "nfs-server",
"resources": [
{
"content": "# Written by the mesh (module nfs-server, novox/hq ADR 0263). Replaced on every push; a drop-in of\n# the operator's that sorts after this one overrides it, and is theirs.\n#\n# NFS version 4 only: a client needs port 2049 and nothing else, so the module opens nothing more\n# than that, to the private network. Version 3 needs rpcbind and mountd, on ports the mesh does not open.\n[nfsd]\nvers2=n\nvers3=n\nvers4=y\nvers4.0=n\nvers4.1=y\nvers4.2=y\n",
"id": "nfs-conf",
"mode": "0644",
"path": "/etc/nfs.conf.d/50-mesh.conf",
"type": "file"
},
{
"id": "run-dir",
"mode": "0755",
"path": "/run/nfs-server",
"type": "directory"
},
{
"id": "wants-dir-parent",
"mode": "0755",
"path": "/var/lib/nfs-server",
"type": "directory"
},
{
"id": "wants-dir",
"mode": "0755",
"path": "/var/lib/nfs-server/from-bus",
"type": "directory"
},
{
"id": "config-dir",
"mode": "0755",
"path": "/etc/nfs-server",
"type": "directory"
},
{
"content": "# Written by the mesh (module nfs-server, novox/hq ADR 0263) from this machine's assignment.\n# Replaced on every push; change the `shares` setting, never this file.\n#\n# The shares: name=folder, or name=folder:ro, one share per folder. The grants: share=node:rw|ro[,\u2026],\n# which nodes may mount each and how. The module's process exports a share to a node's private address\n# only when it is granted here and that node's mounts module wants it, every client mapped to the\n# folder's owner, and only to addresses inside the range below.\nshares=${config:shares}\ngrants=${config:grants}\nrange=STAND-IN\n",
"id": "config",
"mode": "0644",
"path": "/etc/nfs-server/shares.conf",
"type": "file"
}
],
"version": "1"
},
{
"config": {
"debug-max-length": {
"default": 1000,
"description": "Node-RED's debugMaxLength setting: the most characters a debug message shows, a count.",
"trusted": true,
"type": "number"
},
"diagnostics": {
"default": {
"enabled": true,
"ui": true
},
"description": "Node-RED's diagnostics setting: whether its diagnostics report is on, and shown in the editor.",
"trusted": true,
"type": "object"
},
"editor-theme": {
"default": {
"projects": {
"enabled": false
}
},
"description": "Node-RED's editorTheme setting: the editor's look and features, projects among them.",
"trusted": true,
"type": "object"
},
"export-global-context-keys": {
"default": false,
"description": "Node-RED's exportGlobalContextKeys setting: whether a function node may list the global context's keys.",
"trusted": true,
"type": "boolean"
},
"external-modules": {
"default": {},
"description": "Node-RED's externalModules setting: which modules the editor may install, and from where.",
"trusted": true,
"type": "object"
},
"flow-file": {
"default": "flows.json",
"description": "Node-RED's flowFile setting: the file in the user directory the flows are kept in.",
"trusted": true,
"type": "string"
},
"flow-file-pretty": {
"default": true,
"description": "Node-RED's flowFilePretty setting: whether the flows file is written indented.",
"trusted": true,
"type": "boolean"
},
"function-external-modules": {
"default": true,
"description": "Node-RED's functionExternalModules setting: whether a function node may load modules of its own.",
"trusted": true,
"type": "boolean"
},
"logging": {
"default": {
"console": {
"audit": false,
"level": "info",
"metrics": false
}
},
"description": "Node-RED's logging setting: the console log's level, and whether metrics and audit events are logged.",
"trusted": true,
"type": "object"
},
"mqtt": {
"default": {},
"description": "The broker nodes the module's MQTT step keeps pointed at the mesh's broker, as {\"brokers\": [their ids]}; empty, none.",
"trusted": true,
"type": "object"
},
"mqtt-reconnect-time": {
"default": 15000,
"description": "Node-RED's mqttReconnectTime setting: how long an MQTT node waits before reconnecting, in milliseconds.",
"trusted": true,
"type": "number"
},
"runtime-state": {
"default": {
"enabled": false,
"ui": false
},
"description": "Node-RED's runtimeState setting: whether flows may be started and stopped from the editor.",
"trusted": true,
"type": "object"
},
"serial-reconnect-time": {
"default": 15000,
"description": "Node-RED's serialReconnectTime setting: how long a serial node waits before reconnecting, in milliseconds.",
"trusted": true,
"type": "number"
},
"time-zone": {
"default": "",
"description": "The time zone the flows schedule and format in, an IANA name such as Europe/Brussels; unset, the container's UTC.",
"trusted": true,
"type": "string"
}
},
"module": "nodered",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "data",
"mode": "0700",
"type": "directory"
},
{
"id": "written",
"mode": "0700",
"type": "directory"
},
{
"content": "// Node-RED's settings, written by the mesh from the nodered module. What an assignment may change\n// is settings.json beside this file (merged key by key); the credentials are the mesh's secrets and\n// reach Node-RED only through this file. The flows' own credentials stay encrypted in the user\n// directory under the key Node-RED keeps there (.config.runtime.json), which is data, not this.\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst crypto = require(\"crypto\");\n\nconst ADMIN_PASSWORD = \"STAND-IN\";\nconst API_TOKEN = \"STAND-IN\";\nconst ADMIN = { username: \"admin\", permissions: \"*\" };\n\nconst settings = JSON.parse(fs.readFileSync(path.join(__dirname, \"settings.json\"), \"utf8\"));\n// The mesh's keys, not Node-RED's: endpoints lands in every merged file; timeZone is the\n// assignment's way to set the zone flows schedule and format in; mqtt names the broker nodes the\n// module's MQTT step keeps pointed at the mesh's broker; topics is what nodered asks the broker for.\nif (settings.timeZone) process.env.TZ = settings.timeZone;\ndelete settings.timeZone;\ndelete settings.endpoints;\ndelete settings.mqtt;\ndelete settings.topics;\n\nfunction same(a, b) {\n const x = crypto.createHash(\"sha256\").update(String(a)).digest();\n const y = crypto.createHash(\"sha256\").update(String(b)).digest();\n return crypto.timingSafeEqual(x, y);\n}\n\n// The admin secret is a password, or, accepted from an existing install, the bcrypt hash its\n// settings held, so the password people already use keeps working.\nfunction passwordMatches(given) {\n if (/^\\$2[aby]\\$\\d\\d\\$/.test(ADMIN_PASSWORD)) return require(\"bcryptjs\").compare(String(given), ADMIN_PASSWORD);\n return Promise.resolve(same(given, ADMIN_PASSWORD));\n}\n\nmodule.exports = Object.assign(settings, {\n uiPort: 1880,\n adminAuth: {\n type: \"credentials\",\n users: (username) => Promise.resolve(username === ADMIN.username ? ADMIN : null),\n authenticate: (username, password) =>\n username === ADMIN.username\n ? passwordMatches(password).then((ok) => (ok ? ADMIN : null))\n : Promise.resolve(null),\n // The module's own tools call the admin API with this bearer token.\n tokens: (token) => Promise.resolve(same(token, API_TOKEN) ? { username: \"mesh\", permissions: \"*\" } : null),\n },\n});\n",
"id": "settings-code",
"mode": "0600",
"path": "${dir:state}/settings.js",
"type": "file"
},
{
"content": "{\n \"flowFile\": \"flows.json\",\n \"flowFilePretty\": true,\n \"diagnostics\": { \"enabled\": true, \"ui\": true },\n \"runtimeState\": { \"enabled\": false, \"ui\": false },\n \"logging\": { \"console\": { \"level\": \"info\", \"metrics\": false, \"audit\": false } },\n \"exportGlobalContextKeys\": false,\n \"externalModules\": {},\n \"editorTheme\": { \"projects\": { \"enabled\": false } },\n \"functionExternalModules\": true,\n \"debugMaxLength\": 1000,\n \"mqttReconnectTime\": 15000,\n \"serialReconnectTime\": 15000\n}\n",
"id": "settings",
"merge": "json",
"mode": "0600",
"path": "${dir:state}/settings.json",
"takes-config": [
{
"as": "flowFile",
"key": "flow-file"
},
{
"as": "flowFilePretty",
"key": "flow-file-pretty"
},
"diagnostics",
{
"as": "runtimeState",
"key": "runtime-state"
},
"logging",
{
"as": "exportGlobalContextKeys",
"key": "export-global-context-keys"
},
{
"as": "externalModules",
"key": "external-modules"
},
{
"as": "editorTheme",
"key": "editor-theme"
},
{
"as": "functionExternalModules",
"key": "function-external-modules"
},
{
"as": "debugMaxLength",
"key": "debug-max-length"
},
{
"as": "mqttReconnectTime",
"key": "mqtt-reconnect-time"
},
{
"as": "serialReconnectTime",
"key": "serial-reconnect-time"
},
"mqtt",
{
"as": "timeZone",
"key": "time-zone"
}
],
"type": "file"
},
{
"content": "{\n \"token\": \"STAND-IN\"\n}\n",
"id": "runtime-config",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"type": "file"
},
{
"content": "MESH_NODERED_URL=http://127.0.0.1:STAND-IN\nMESH_NODERED_CONFIG_FILE=${dir:mesh-state}/config.json\nMESH_PROVISIONS_DIR=${dir:state}\nMESH_WRITTEN_DIR=${dir:written}\n",
"id": "mqtt-env",
"mode": "0600",
"path": "${dir:state}/mqtt.env",
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"name": "node-backup",
"scope": "node",
"serves": [
"backed-up",
"now",
"restore"
]
}
],
"config": {
"keep-daily": {
"default": 14,
"description": "How many nightly restore points are kept, a count from 1 to 9999: two weeks undo a mistake noticed within a fortnight. Read by the night, which thins nothing while it does not read (novox/hq issue 416); lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.",
"maximum": 9999,
"minimum": 1,
"trusted": true,
"type": "number"
},
"keep-hand-taken-days": {
"default": 7,
"description": "How many days a restore point taken by hand (node-backup.now, or the mesh's before a module moves) is kept at least, a count from 1 to 9999; it is kept until a later good night of its module exists, and this many days in any case, so the next thinning never takes the one taken against a risky change (novox/hq issue 453). Lowering it lets go of the older ones taken by hand at the next thinning, and that cannot be undone.",
"maximum": 9999,
"minimum": 1,
"trusted": true,
"type": "number"
},
"keep-monthly": {
"default": 6,
"description": "How many monthly restore points are kept behind the weeklies, a count from 1 to 9999, refused by the night like keep-daily; lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.",
"maximum": 9999,
"minimum": 1,
"trusted": true,
"type": "number"
},
"keep-weekly": {
"default": 8,
"description": "How many weekly restore points are kept behind the dailies, a count from 1 to 9999, refused by the night like keep-daily; lowering it deletes the restore points beyond the new count at the next night, and that cannot be undone.",
"maximum": 9999,
"minimum": 1,
"trusted": true,
"type": "number"
}
},
"module": "restic",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "repository",
"mode": "0700",
"type": "directory"
},
{
"content": "# What the modules on this machine back up, composed by the mesh (novox/hq to-be 43). Do not edit.\nSTAND-IN",
"id": "declared",
"mode": "0600",
"path": "${dir:state}/backups.conf",
"type": "file"
},
{
"content": "# The data the modules on this machine declare, composed by the mesh (novox/hq ADR 0233). Do not edit.\nSTAND-IN",
"id": "data-declared",
"mode": "0600",
"path": "${dir:state}/data.conf",
"type": "file"
},
{
"content": "# How many restore points the night keeps, written by the mesh from this machine's settings keep-daily,\n# keep-weekly, keep-monthly (novox/hq issue 416) and keep-hand-taken-days (novox/hq issue 453). Replaced on every push; change the settings, never this file.\ndaily=${config:keep-daily}\nweekly=${config:keep-weekly}\nmonthly=${config:keep-monthly}\nhand=${config:keep-hand-taken-days}\n",
"id": "retention",
"mode": "0644",
"path": "${dir:state}/retention.conf",
"trusted": true,
"type": "file"
}
],
"version": "1"
},
{
"config": {
"brand": {
"default": {},
"description": "SearXNG's brand section of settings.yml, laid over the file's own and SearXNG's defaults: the links the pages show for the instance's source, issues and documentation.",
"trusted": true,
"type": "object"
},
"categories_as_tabs": {
"default": {},
"description": "SearXNG's categories_as_tabs section of settings.yml, laid over the file's own and SearXNG's defaults: the categories shown as tabs above the results.",
"trusted": true,
"type": "object"
},
"engines": {
"default": [],
"description": "SearXNG's engines section of settings.yml, laid over the file's own and SearXNG's defaults: the engines to add or change, each by its name.",
"trusted": true,
"type": "list"
},
"general": {
"default": {},
"description": "SearXNG's general section of settings.yml, laid over the file's own and SearXNG's defaults: the instance's name, contact and debug switches.",
"trusted": true,
"type": "object"
},
"outgoing": {
"default": {},
"description": "SearXNG's outgoing section of settings.yml, laid over the file's own and SearXNG's defaults: how SearXNG reaches the engines: timeouts, connection pool and proxies.",
"trusted": true,
"type": "object"
},
"plugins": {
"default": {},
"description": "SearXNG's plugins section of settings.yml, laid over the file's own and SearXNG's defaults: the plugins to switch on or off, by their id.",
"trusted": true,
"type": "object"
},
"preferences": {
"default": {},
"description": "SearXNG's preferences section of settings.yml, laid over the file's own and SearXNG's defaults: the preferences a user cannot change (lock).",
"trusted": true,
"type": "object"
},
"search": {
"default": {
"formats": [
"html",
"json"
]
},
"description": "SearXNG's search section of settings.yml, laid over the file's own and SearXNG's defaults: safe search, autocomplete, the result formats offered and how long an engine is suspended after an error.",
"trusted": true,
"type": "object"
},
"server": {
"default": {
"base_url": false,
"image_proxy": false,
"limiter": false,
"public_instance": false,
"secret_key": "${secret:secret}"
},
"description": "SearXNG's server section of settings.yml, laid over the file's own and SearXNG's defaults: the public base URL, the request method, the limiter and the image proxy; its secret_key is the vault's.",
"trusted": true,
"type": "object"
},
"ui": {
"default": {},
"description": "SearXNG's ui section of settings.yml, laid over the file's own and SearXNG's defaults: the theme, the default locale and how results are shown.",
"trusted": true,
"type": "object"
}
},
"module": "searxng",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "valkey-data",
"mode": "0700",
"type": "directory"
},
{
"content": "{\n \"use_default_settings\": true,\n \"server\": {\n \"secret_key\": \"STAND-IN\",\n \"base_url\": false,\n \"limiter\": false,\n \"image_proxy\": false,\n \"public_instance\": false\n },\n \"search\": {\n \"formats\": [\"html\", \"json\"]\n },\n \"valkey\": {\n \"url\": \"valkey://valkey:6379/0\"\n }\n}\n",
"id": "settings",
"merge": "json",
"mode": "0600",
"path": "${dir:state}/settings.yml",
"takes-config": [
"general",
"brand",
"search",
"server",
"ui",
"outgoing",
"engines",
"plugins",
"categories_as_tabs",
"preferences"
],
"type": "file"
},
{
"content": "{}\n",
"id": "runtime-config",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"type": "file"
}
],
"version": "1"
},
{
"config": {
"pooler": {
"default": {
"max-client-connections": 100,
"pool-size": 20,
"tenant": "default"
},
"description": "The connection pooler's settings, laid over the file's: its tenant, pool size and the most client connections it takes.",
"trusted": true,
"type": "object"
}
},
"module": "supabase",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "db-init",
"mode": "0755",
"path": "${dir:state}/db-init",
"type": "directory"
},
{
"id": "functions-main-dir",
"mode": "0755",
"path": "${dir:state}/functions-main",
"type": "directory"
},
{
"id": "db-data",
"mode": "0700",
"type": "directory"
},
{
"id": "dumps",
"mode": "0700",
"path": "${dir:state}/dumps",
"type": "directory"
},
{
"id": "db-config",
"mode": "0755",
"type": "directory"
},
{
"id": "storage",
"mode": "0755",
"type": "directory"
},
{
"id": "functions",
"mode": "0755",
"type": "directory"
},
{
"content": "{\n \"pooler\": {\n \"tenant\": \"default\",\n \"pool-size\": 20,\n \"max-client-connections\": 100\n }\n}\n",
"id": "settings",
"merge": "json",
"mode": "0644",
"path": "${dir:state}/settings.json",
"takes-config": [
"pooler"
],
"type": "file"
},
{
"content": "# Written by the mesh (modules/supabase): the gateway's declarative config, credentials rendered in.\n_format_version: '2.1'\n_transform: true\n\n###\n### Consumers / Users\n###\nconsumers:\n - username: DASHBOARD\n - username: anon\n keyauth_credentials:\n - key: \"STAND-IN\"\n - username: service_role\n keyauth_credentials:\n - key: \"STAND-IN\"\n\n###\n### Access Control List\n###\nacls:\n - consumer: anon\n group: anon\n - consumer: service_role\n group: admin\n\n###\n### Dashboard credentials\n###\nbasicauth_credentials:\n - consumer: DASHBOARD\n username: \"STAND-IN\"\n password: \"STAND-IN\"\n\n###\n### API Routes\n###\nservices:\n ## Open Auth routes\n - name: auth-v1-open\n url: http://supabase-auth:9999/verify\n routes:\n - name: auth-v1-open\n strip_path: true\n paths:\n - /auth/v1/verify\n plugins:\n - name: cors\n - name: auth-v1-open-callback\n url: http://supabase-auth:9999/callback\n routes:\n - name: auth-v1-open-callback\n strip_path: true\n paths:\n - /auth/v1/callback\n plugins:\n - name: cors\n - name: auth-v1-open-authorize\n url: http://supabase-auth:9999/authorize\n routes:\n - name: auth-v1-open-authorize\n strip_path: true\n paths:\n - /auth/v1/authorize\n plugins:\n - name: cors\n\n ## Secure Auth routes\n - name: auth-v1\n _comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*'\n url: http://supabase-auth:9999/\n routes:\n - name: auth-v1-all\n strip_path: true\n paths:\n - /auth/v1/\n plugins:\n - name: cors\n - name: key-auth\n config:\n hide_credentials: false\n - name: acl\n config:\n hide_groups_header: true\n allow:\n - admin\n - anon\n\n ## Secure REST routes\n - name: rest-v1\n _comment: 'PostgREST: /rest/v1/* -> http://rest:3000/*'\n url: http://supabase-rest:3000/\n routes:\n - name: rest-v1-all\n strip_path: true\n paths:\n - /rest/v1/\n plugins:\n - name: cors\n - name: key-auth\n config:\n hide_credentials: true\n - name: acl\n config:\n hide_groups_header: true\n allow:\n - admin\n - anon\n\n ## Secure GraphQL routes\n - name: graphql-v1\n _comment: 'PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql'\n url: http://supabase-rest:3000/rpc/graphql\n routes:\n - name: graphql-v1-all\n strip_path: true\n paths:\n - /graphql/v1\n plugins:\n - name: cors\n - name: key-auth\n config:\n hide_credentials: true\n - name: request-transformer\n config:\n add:\n headers:\n - Content-Profile:graphql_public\n - name: acl\n config:\n hide_groups_header: true\n allow:\n - admin\n - anon\n\n ## Secure Realtime routes\n - name: realtime-v1-ws\n _comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'\n url: http://realtime-dev.supabase-realtime:4000/socket\n protocol: ws\n routes:\n - name: realtime-v1-ws\n strip_path: true\n paths:\n - /realtime/v1/\n plugins:\n - name: cors\n - name: key-auth\n config:\n hide_credentials: false\n - name: acl\n config:\n hide_groups_header: true\n allow:\n - admin\n - anon\n - name: realtime-v1-rest\n _comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*'\n url: http://realtime-dev.supabase-realtime:4000/api\n protocol: http\n routes:\n - name: realtime-v1-rest\n strip_path: true\n paths:\n - /realtime/v1/api\n plugins:\n - name: cors\n - name: key-auth\n config:\n hide_credentials: false\n - name: acl\n config:\n hide_groups_header: true\n allow:\n - admin\n - anon\n ## Storage routes: the storage server manages its own auth\n - name: storage-v1\n _comment: 'Storage: /storage/v1/* -> http://storage:5000/*'\n url: http://supabase-storage:5000/\n routes:\n - name: storage-v1-all\n strip_path: true\n paths:\n - /storage/v1/\n plugins:\n - name: cors\n\n ## Edge Functions routes\n - name: functions-v1\n _comment: 'Edge Functions: /functions/v1/* -> http://functions:9000/*'\n url: http://supabase-edge-functions:9000/\n routes:\n - name: functions-v1-all\n strip_path: true\n paths:\n - /functions/v1/\n plugins:\n - name: cors\n\n ## Analytics routes\n - name: analytics-v1\n _comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*'\n url: http://supabase-analytics:4000/\n routes:\n - name: analytics-v1-all\n strip_path: true\n paths:\n - /analytics/v1/\n\n ## Secure Database routes\n - name: meta\n _comment: 'pg-meta: /pg/* -> http://pg-meta:8080/*'\n url: http://supabase-meta:8080/\n routes:\n - name: meta-all\n strip_path: true\n paths:\n - /pg/\n plugins:\n - name: key-auth\n config:\n hide_credentials: false\n - name: acl\n config:\n hide_groups_header: true\n allow:\n - admin\n\n ## Protected Dashboard - catch all remaining routes\n - name: dashboard\n _comment: 'Studio: /* -> http://studio:3000/*'\n url: http://supabase-studio:3000/\n routes:\n - name: dashboard-all\n strip_path: true\n paths:\n - /\n plugins:\n - name: cors\n - name: basic-auth\n config:\n hide_credentials: true\n",
"id": "kong-conf",
"mode": "0600",
"path": "${dir:state}/kong.yml",
"type": "file"
},
{
"content": "GOTRUE_API_HOST=0.0.0.0\nGOTRUE_API_PORT=9999\nAPI_EXTERNAL_URL=https://STAND-IN\nGOTRUE_DB_DRIVER=postgres\nGOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:STAND-IN@supabase-db:5432/postgres\nGOTRUE_SITE_URL=https://STAND-IN\nGOTRUE_URI_ALLOW_LIST=\nGOTRUE_DISABLE_SIGNUP=true\nGOTRUE_JWT_ADMIN_ROLES=service_role\nGOTRUE_JWT_AUD=authenticated\nGOTRUE_JWT_DEFAULT_GROUP_NAME=authenticated\nGOTRUE_JWT_EXP=3600\nGOTRUE_JWT_SECRET=STAND-IN\nGOTRUE_EXTERNAL_EMAIL_ENABLED=false\nGOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false\nGOTRUE_MAILER_AUTOCONFIRM=false\nGOTRUE_SMTP_ADMIN_EMAIL=admin@example.com\nGOTRUE_SMTP_HOST=supabase-mail\nGOTRUE_SMTP_PORT=2500\nGOTRUE_SMTP_USER=fake_mail_user\nGOTRUE_SMTP_PASS=fake_mail_password\nGOTRUE_SMTP_SENDER_NAME=fake_sender\nGOTRUE_MAILER_URLPATHS_INVITE=/auth/v1/verify\nGOTRUE_MAILER_URLPATHS_CONFIRMATION=/auth/v1/verify\nGOTRUE_MAILER_URLPATHS_RECOVERY=/auth/v1/verify\nGOTRUE_MAILER_URLPATHS_EMAIL_CHANGE=/auth/v1/verify\nGOTRUE_EXTERNAL_PHONE_ENABLED=false\nGOTRUE_SMS_AUTOCONFIRM=true\n",
"id": "auth-conf",
"mode": "0600",
"path": "${dir:state}/gotrue.env",
"type": "file"
},
{
"content": "db-uri = \"postgres://authenticator:STAND-IN@supabase-db:5432/postgres\"\ndb-schemas = \"public,storage,graphql_public\"\ndb-anon-role = \"anon\"\njwt-secret = \"STAND-IN\"\ndb-use-legacy-gucs = false\napp.settings.jwt_secret = \"STAND-IN\"\napp.settings.jwt_exp = \"3600\"\n",
"id": "rest-conf",
"mode": "0600",
"path": "${dir:state}/postgrest.conf",
"type": "file"
},
{
"content": "# Written by the mesh (modules/supabase).\n#\n# Every sink hands logflare its API key in the x-api-key header, never as an api_key query parameter:\n# logflare prints a failed request's whole URL, query string included, in its error report, so a\n# key in the URL is a key in its log (novox/hq issue 268). supabase-analytics refuses to start\n# while this file still puts the key in a URL.\napi:\n enabled: true\n address: 0.0.0.0:9001\n\nsources:\n docker_host:\n type: docker_logs\n include_containers:\n - supabase-kong\n - supabase-auth\n - supabase-rest\n - realtime-dev.supabase-realtime\n - supabase-storage\n - supabase-edge-functions\n - supabase-db\n\ntransforms:\n project_logs:\n type: remap\n inputs:\n - docker_host\n source: |-\n .project = \"default\"\n .event_message = del(.message)\n .appname = del(.container_name)\n del(.container_created_at)\n del(.container_id)\n del(.source_type)\n del(.stream)\n del(.label)\n del(.image)\n del(.host)\n del(.stream)\n router:\n type: route\n inputs:\n - project_logs\n route:\n kong: '.appname == \"supabase-kong\"'\n auth: '.appname == \"supabase-auth\"'\n rest: '.appname == \"supabase-rest\"'\n realtime: '.appname == \"realtime-dev.supabase-realtime\"'\n storage: '.appname == \"supabase-storage\"'\n functions: '.appname == \"supabase-edge-functions\"'\n db: '.appname == \"supabase-db\"'\n # Ignores non nginx errors since they are related with kong booting up\n kong_logs:\n type: remap\n inputs:\n - router.kong\n source: |-\n req, err = parse_nginx_log(.event_message, \"combined\")\n if err == null {\n .timestamp = req.timestamp\n .metadata.request.headers.referer = req.referer\n .metadata.request.headers.user_agent = req.agent\n .metadata.request.headers.cf_connecting_ip = req.client\n .metadata.request.method = req.method\n .metadata.request.path = req.path\n .metadata.request.protocol = req.protocol\n .metadata.response.status_code = req.status\n }\n if err != null {\n abort\n }\n # Ignores non nginx errors since they are related with kong booting up\n kong_err:\n type: remap\n inputs:\n - router.kong\n source: |-\n .metadata.request.method = \"GET\"\n .metadata.response.status_code = 200\n parsed, err = parse_nginx_log(.event_message, \"error\")\n if err == null {\n .timestamp = parsed.timestamp\n .severity = parsed.severity\n .metadata.request.host = parsed.host\n .metadata.request.headers.cf_connecting_ip = parsed.client\n url, err = split(parsed.request, \" \")\n if err == null {\n .metadata.request.method = url[0]\n .metadata.request.path = url[1]\n .metadata.request.protocol = url[2]\n }\n }\n if err != null {\n abort\n }\n # Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency.\n auth_logs:\n type: remap\n inputs:\n - router.auth\n source: |-\n parsed, err = parse_json(.event_message)\n if err == null {\n .metadata.timestamp = parsed.time\n .metadata = merge!(.metadata, parsed)\n }\n # PostgREST logs are structured so we separate timestamp from message using regex\n rest_logs:\n type: remap\n inputs:\n - router.rest\n source: |-\n parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')\n if err == null {\n .event_message = parsed.msg\n .timestamp = to_timestamp!(parsed.time)\n .metadata.host = .project\n }\n # Realtime logs are structured so we parse the severity level using regex (ignore time because it has no date)\n realtime_logs:\n type: remap\n inputs:\n - router.realtime\n source: |-\n .metadata.project = del(.project)\n .metadata.external_id = .metadata.project\n parsed, err = parse_regex(.event_message, r'^(?P<time>\\d+:\\d+:\\d+\\.\\d+) \\[(?P<level>\\w+)\\] (?P<msg>.*)$')\n if err == null {\n .event_message = parsed.msg\n .metadata.level = parsed.level\n }\n # Storage logs may contain json objects so we parse them for completeness\n storage_logs:\n type: remap\n inputs:\n - router.storage\n source: |-\n .metadata.project = del(.project)\n .metadata.tenantId = .metadata.project\n parsed, err = parse_json(.event_message)\n if err == null {\n .event_message = parsed.msg\n .metadata.level = parsed.level\n .metadata.timestamp = parsed.time\n .metadata.context[0].host = parsed.hostname\n .metadata.context[0].pid = parsed.pid\n }\n # Postgres logs some messages to stderr which we map to warning severity level\n db_logs:\n type: remap\n inputs:\n - router.db\n source: |-\n .metadata.host = \"db-default\"\n .metadata.parsed.timestamp = .timestamp\n\n parsed, err = parse_regex(.event_message, r'.*(?P<level>INFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)\n\n if err != null || parsed == null {\n .metadata.parsed.error_severity = \"info\"\n }\n if parsed != null {\n .metadata.parsed.error_severity = parsed.level\n }\n if .metadata.parsed.error_severity == \"info\" {\n .metadata.parsed.error_severity = \"log\"\n }\n .metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)\n\nsinks:\n logflare_auth:\n type: 'http'\n inputs:\n - auth_logs\n encoding:\n codec: 'json'\n method: 'post'\n request:\n retry_max_duration_secs: 10\n headers:\n x-api-key: 'STAND-IN'\n uri: 'http://supabase-analytics:4000/api/logs?source_name=gotrue.logs.prod'\n logflare_realtime:\n type: 'http'\n inputs:\n - realtime_logs\n encoding:\n codec: 'json'\n method: 'post'\n request:\n retry_max_duration_secs: 10\n headers:\n x-api-key: 'STAND-IN'\n uri: 'http://supabase-analytics:4000/api/logs?source_name=realtime.logs.prod'\n logflare_rest:\n type: 'http'\n inputs:\n - rest_logs\n encoding:\n codec: 'json'\n method: 'post'\n request:\n retry_max_duration_secs: 10\n headers:\n x-api-key: 'STAND-IN'\n uri: 'http://supabase-analytics:4000/api/logs?source_name=postgREST.logs.prod'\n logflare_db:\n type: 'http'\n inputs:\n - db_logs\n encoding:\n codec: 'json'\n method: 'post'\n request:\n retry_max_duration_secs: 10\n headers:\n x-api-key: 'STAND-IN'\n # We must route the sink through kong because ingesting logs before logflare is fully initialised will\n # lead to broken queries from studio. This works by the assumption that containers are started in the\n # following order: vector > db > logflare > kong\n uri: 'http://supabase-kong:8000/analytics/v1/api/logs?source_name=postgres.logs'\n logflare_functions:\n type: 'http'\n inputs:\n - router.functions\n encoding:\n codec: 'json'\n method: 'post'\n request:\n retry_max_duration_secs: 10\n headers:\n x-api-key: 'STAND-IN'\n uri: 'http://supabase-analytics:4000/api/logs?source_name=deno-relay-logs'\n logflare_storage:\n type: 'http'\n inputs:\n - storage_logs\n encoding:\n codec: 'json'\n method: 'post'\n request:\n retry_max_duration_secs: 10\n headers:\n x-api-key: 'STAND-IN'\n uri: 'http://supabase-analytics:4000/api/logs?source_name=storage.logs.prod.2'\n logflare_kong:\n type: 'http'\n inputs:\n - kong_logs\n - kong_err\n encoding:\n codec: 'json'\n method: 'post'\n request:\n retry_max_duration_secs: 10\n headers:\n x-api-key: 'STAND-IN'\n uri: 'http://supabase-analytics:4000/api/logs?source_name=cloudflare.logs.prod'\n",
"id": "vector-conf",
"mode": "0600",
"path": "${dir:state}/vector.yml",
"type": "file"
},
{
"content": "{:ok, _} = Application.ensure_all_started(:supavisor)\n\n# Written by the mesh (modules/supabase). Per-machine values come from settings.json (the module's\n# one merge:json file); secrets come from the environment the image reads.\nsettings = \"/etc/pooler/settings.json\" |> File.read!() |> Jason.decode!() |> Map.get(\"pooler\", %{})\n\n{:ok, version} =\n case Supavisor.Repo.query!(\"select version()\") do\n %{rows: [[ver]]} -> Supavisor.Helpers.parse_pg_version(ver)\n _ -> nil\n end\n\nparams = %{\n \"external_id\" => to_string(settings[\"tenant\"] || \"default\"),\n \"db_host\" => \"supabase-db\",\n \"db_port\" => System.get_env(\"POSTGRES_PORT\"),\n \"db_database\" => System.get_env(\"POSTGRES_DB\"),\n \"require_user\" => false,\n \"auth_query\" => \"SELECT * FROM pgbouncer.get_auth($1)\",\n \"default_max_clients\" => settings[\"max-client-connections\"] || 100,\n \"default_pool_size\" => settings[\"pool-size\"] || 20,\n \"default_parameter_status\" => %{\"server_version\" => version},\n \"users\" => [%{\n \"db_user\" => \"pgbouncer\",\n \"db_password\" => System.get_env(\"POSTGRES_PASSWORD\"),\n \"mode_type\" => System.get_env(\"POOLER_POOL_MODE\"),\n \"pool_size\" => settings[\"pool-size\"] || 20,\n \"is_manager\" => true\n }]\n}\n\ncase Supavisor.Tenants.get_tenant_by_external_id(params[\"external_id\"]) do\n nil ->\n {:ok, _} = Supavisor.Tenants.create_tenant(params)\n\n # A tenant made elsewhere (HAL named the database host \"db\") is pointed at this module's\n # database container; nothing else it holds is changed.\n %{db_host: host} = tenant when host != \"supabase-db\" ->\n {:ok, _} = Supavisor.Tenants.update_tenant(tenant, %{\"db_host\" => \"supabase-db\"})\n\n _ ->\n :ok\nend\n",
"id": "pooler-conf",
"mode": "0644",
"path": "${dir:state}/pooler.exs",
"type": "file"
},
{
"content": "\\set pgpass `echo \"$POSTGRES_PASSWORD\"`\n\nALTER USER authenticator WITH PASSWORD :'pgpass';\nALTER USER pgbouncer WITH PASSWORD :'pgpass';\nALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';\nALTER USER supabase_functions_admin WITH PASSWORD :'pgpass';\nALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';\n",
"id": "db-init-roles",
"mode": "0600",
"path": "${dir:state}/db-init/roles.sql",
"type": "file"
},
{
"content": "ALTER DATABASE postgres SET \"app.settings.jwt_secret\" TO 'STAND-IN';\nALTER DATABASE postgres SET \"app.settings.jwt_exp\" TO '3600';\n",
"id": "db-init-jwt",
"mode": "0600",
"path": "${dir:state}/db-init/jwt.sql",
"type": "file"
},
{
"content": "\\set pguser `echo \"$POSTGRES_USER\"`\n\ncreate schema if not exists _realtime;\nalter schema _realtime owner to :pguser;\n",
"id": "db-init-realtime",
"mode": "0600",
"path": "${dir:state}/db-init/realtime.sql",
"type": "file"
},
{
"content": "\\set pguser `echo \"$POSTGRES_USER\"`\n\nCREATE DATABASE _supabase WITH OWNER :pguser;\n",
"id": "db-init-supabase",
"mode": "0600",
"path": "${dir:state}/db-init/supabase.sql",
"type": "file"
},
{
"content": "\\set pguser `echo \"$POSTGRES_USER\"`\n\n\\c _supabase\ncreate schema if not exists _analytics;\nalter schema _analytics owner to :pguser;\n\\c postgres\n",
"id": "db-init-logs",
"mode": "0600",
"path": "${dir:state}/db-init/logs.sql",
"type": "file"
},
{
"content": "\\set pguser `echo \"$POSTGRES_USER\"`\n\n\\c _supabase\ncreate schema if not exists _supavisor;\nalter schema _supavisor owner to :pguser;\n\\c postgres\n",
"id": "db-init-pooler",
"mode": "0600",
"path": "${dir:state}/db-init/pooler.sql",
"type": "file"
},
{
"content": "BEGIN;\n -- Create pg_net extension\n CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;\n -- Create supabase_functions schema\n CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;\n GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;\n ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;\n ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;\n ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;\n -- supabase_functions.migrations definition\n CREATE TABLE supabase_functions.migrations (\n version text PRIMARY KEY,\n inserted_at timestamptz NOT NULL DEFAULT NOW()\n );\n -- Initial supabase_functions migration\n INSERT INTO supabase_functions.migrations (version) VALUES ('initial');\n -- supabase_functions.hooks definition\n CREATE TABLE supabase_functions.hooks (\n id bigserial PRIMARY KEY,\n hook_table_id integer NOT NULL,\n hook_name text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT NOW(),\n request_id bigint\n );\n CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);\n CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);\n COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';\n CREATE FUNCTION supabase_functions.http_request()\n RETURNS trigger\n LANGUAGE plpgsql\n AS $function$\n DECLARE\n request_id bigint;\n payload jsonb;\n url text := TG_ARGV[0]::text;\n method text := TG_ARGV[1]::text;\n headers jsonb DEFAULT '{}'::jsonb;\n params jsonb DEFAULT '{}'::jsonb;\n timeout_ms integer DEFAULT 1000;\n BEGIN\n IF url IS NULL OR url = 'null' THEN\n RAISE EXCEPTION 'url argument is missing';\n END IF;\n\n IF method IS NULL OR method = 'null' THEN\n RAISE EXCEPTION 'method argument is missing';\n END IF;\n\n IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN\n headers = '{\"Content-Type\": \"application/json\"}'::jsonb;\n ELSE\n headers = TG_ARGV[2]::jsonb;\n END IF;\n\n IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN\n params = '{}'::jsonb;\n ELSE\n params = TG_ARGV[3]::jsonb;\n END IF;\n\n IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN\n timeout_ms = 1000;\n ELSE\n timeout_ms = TG_ARGV[4]::integer;\n END IF;\n\n CASE\n WHEN method = 'GET' THEN\n SELECT http_get INTO request_id FROM net.http_get(\n url,\n params,\n headers,\n timeout_ms\n );\n WHEN method = 'POST' THEN\n payload = jsonb_build_object(\n 'old_record', OLD,\n 'record', NEW,\n 'type', TG_OP,\n 'table', TG_TABLE_NAME,\n 'schema', TG_TABLE_SCHEMA\n );\n\n SELECT http_post INTO request_id FROM net.http_post(\n url,\n payload,\n params,\n headers,\n timeout_ms\n );\n ELSE\n RAISE EXCEPTION 'method argument % is invalid', method;\n END CASE;\n\n INSERT INTO supabase_functions.hooks\n (hook_table_id, hook_name, request_id)\n VALUES\n (TG_RELID, TG_NAME, request_id);\n\n RETURN NEW;\n END\n $function$;\n -- Supabase super admin\n DO\n $$\n BEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM pg_roles\n WHERE rolname = 'supabase_functions_admin'\n )\n THEN\n CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;\n END IF;\n END\n $$;\n GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;\n GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;\n GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;\n ALTER USER supabase_functions_admin SET search_path = \"supabase_functions\";\n ALTER table \"supabase_functions\".migrations OWNER TO supabase_functions_admin;\n ALTER table \"supabase_functions\".hooks OWNER TO supabase_functions_admin;\n ALTER function \"supabase_functions\".http_request() OWNER TO supabase_functions_admin;\n GRANT supabase_functions_admin TO postgres;\n -- Remove unused supabase_pg_net_admin role\n DO\n $$\n BEGIN\n IF EXISTS (\n SELECT 1\n FROM pg_roles\n WHERE rolname = 'supabase_pg_net_admin'\n )\n THEN\n REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;\n DROP OWNED BY supabase_pg_net_admin;\n DROP ROLE supabase_pg_net_admin;\n END IF;\n END\n $$;\n -- pg_net grants when extension is already enabled\n DO\n $$\n BEGIN\n IF EXISTS (\n SELECT 1\n FROM pg_extension\n WHERE extname = 'pg_net'\n )\n THEN\n GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;\n ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;\n ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;\n ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;\n ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;\n REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;\n REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;\n GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;\n GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;\n END IF;\n END\n $$;\n -- Event trigger for pg_net\n CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()\n RETURNS event_trigger\n LANGUAGE plpgsql\n AS $$\n BEGIN\n IF EXISTS (\n SELECT 1\n FROM pg_event_trigger_ddl_commands() AS ev\n JOIN pg_extension AS ext\n ON ev.objid = ext.oid\n WHERE ext.extname = 'pg_net'\n )\n THEN\n GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;\n ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;\n ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;\n ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;\n ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;\n REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;\n REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;\n GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;\n GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;\n END IF;\n END;\n $$;\n COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';\n DO\n $$\n BEGIN\n IF NOT EXISTS (\n SELECT 1\n FROM pg_event_trigger\n WHERE evtname = 'issue_pg_net_access'\n ) THEN\n CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')\n EXECUTE PROCEDURE extensions.grant_pg_net_access();\n END IF;\n END\n $$;\n INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');\n ALTER function supabase_functions.http_request() SECURITY DEFINER;\n ALTER function supabase_functions.http_request() SET search_path = supabase_functions;\n REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;\n GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;\nCOMMIT;\n",
"id": "db-init-webhooks",
"mode": "0600",
"path": "${dir:state}/db-init/webhooks.sql",
"type": "file"
},
{
"content": "import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'\nimport * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'\n\nconsole.log('main function started')\n\nconst JWT_SECRET = Deno.env.get('JWT_SECRET')\nconst VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'\n\nfunction getAuthToken(req: Request) {\n const authHeader = req.headers.get('authorization')\n if (!authHeader) {\n throw new Error('Missing authorization header')\n }\n const [bearer, token] = authHeader.split(' ')\n if (bearer !== 'Bearer') {\n throw new Error(`Auth header is not 'Bearer {token}'`)\n }\n return token\n}\n\nasync function verifyJWT(jwt: string): Promise<boolean> {\n const encoder = new TextEncoder()\n const secretKey = encoder.encode(JWT_SECRET)\n try {\n await jose.jwtVerify(jwt, secretKey)\n } catch (err) {\n console.error(err)\n return false\n }\n return true\n}\n\nserve(async (req: Request) => {\n if (req.method !== 'OPTIONS' && VERIFY_JWT) {\n try {\n const token = getAuthToken(req)\n const isValidJWT = await verifyJWT(token)\n\n if (!isValidJWT) {\n return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n } catch (e) {\n console.error(e)\n return new Response(JSON.stringify({ msg: e.toString() }), {\n status: 401,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n }\n\n const url = new URL(req.url)\n const { pathname } = url\n const path_parts = pathname.split('/')\n const service_name = path_parts[1]\n\n if (!service_name || service_name === '') {\n const error = { msg: 'missing function name in request' }\n return new Response(JSON.stringify(error), {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n\n const servicePath = `/home/deno/functions/${service_name}`\n console.error(`serving the request with ${servicePath}`)\n\n const memoryLimitMb = 150\n const workerTimeoutMs = 1 * 60 * 1000\n const noModuleCache = false\n const importMapPath = null\n const envVarsObj = Deno.env.toObject()\n const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])\n\n try {\n const worker = await EdgeRuntime.userWorkers.create({\n servicePath,\n memoryLimitMb,\n workerTimeoutMs,\n noModuleCache,\n importMapPath,\n envVars,\n })\n return await worker.fetch(req)\n } catch (e) {\n const error = { msg: e.toString() }\n return new Response(JSON.stringify(error), {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n})\n",
"id": "functions-main",
"mode": "0644",
"path": "${dir:state}/functions-main/index.ts",
"type": "file"
},
{
"content": "PORT=4000\nDB_HOST=supabase-db\nDB_PORT=5432\nDB_USER=supabase_admin\nDB_PASSWORD=STAND-IN\nDB_NAME=postgres\nDB_AFTER_CONNECT_QUERY=SET search_path TO _realtime\nDB_ENC_KEY=supabaserealtime\nAPI_JWT_SECRET=STAND-IN\nSECRET_KEY_BASE=STAND-INSTAND-IN\nERL_AFLAGS=-proto_dist inet_tcp\nDNS_NODES=''\nRLIMIT_NOFILE=10000\nAPP_NAME=realtime\nSEED_SELF_HOST=true\nRUN_JANITOR=true\n",
"id": "realtime-env",
"mode": "0600",
"path": "${dir:state}/realtime-env.env",
"type": "file"
},
{
"content": "ANON_KEY=STAND-IN\nSERVICE_KEY=STAND-IN\nPOSTGREST_URL=http://supabase-rest:3000\nPGRST_JWT_SECRET=STAND-IN\nDATABASE_URL=postgres://supabase_storage_admin:STAND-IN@supabase-db:5432/postgres\nFILE_SIZE_LIMIT=52428800\nSTORAGE_BACKEND=file\nFILE_STORAGE_BACKEND_PATH=/var/lib/storage\nTENANT_ID=stub\nREGION=stub\nGLOBAL_S3_BUCKET=stub\nENABLE_IMAGE_TRANSFORMATION=true\nIMGPROXY_URL=http://supabase-imgproxy:5001\n",
"id": "storage-env",
"mode": "0600",
"path": "${dir:state}/storage-env.env",
"type": "file"
},
{
"content": "PG_META_PORT=8080\nPG_META_DB_HOST=supabase-db\nPG_META_DB_PORT=5432\nPG_META_DB_NAME=postgres\nPG_META_DB_USER=supabase_admin\nPG_META_DB_PASSWORD=STAND-IN\n",
"id": "meta-env",
"mode": "0600",
"path": "${dir:state}/meta-env.env",
"type": "file"
},
{
"content": "JWT_SECRET=STAND-IN\nSUPABASE_URL=http://supabase-kong:8000\nSUPABASE_ANON_KEY=STAND-IN\nSUPABASE_SERVICE_ROLE_KEY=STAND-IN\nSUPABASE_DB_URL=postgresql://postgres:STAND-IN@supabase-db:5432/postgres\nVERIFY_JWT=false\n",
"id": "functions-env",
"mode": "0600",
"path": "${dir:state}/functions-env.env",
"type": "file"
},
{
"content": "LOGFLARE_NODE_HOST=127.0.0.1\nDB_USERNAME=supabase_admin\nDB_DATABASE=_supabase\nDB_HOSTNAME=supabase-db\nDB_PORT=5432\nDB_PASSWORD=STAND-IN\nDB_SCHEMA=_analytics\nLOGFLARE_API_KEY=STAND-IN\nLOGFLARE_SINGLE_TENANT=true\nLOGFLARE_SUPABASE_MODE=true\nLOGFLARE_MIN_CLUSTER_SIZE=1\nPOSTGRES_BACKEND_URL=postgresql://supabase_admin:STAND-IN@supabase-db:5432/_supabase\nPOSTGRES_BACKEND_SCHEMA=_analytics\nLOGFLARE_FEATURE_FLAG_OVERRIDE=multibackend=true\n",
"id": "analytics-env",
"mode": "0600",
"path": "${dir:state}/analytics-env.env",
"type": "file"
},
{
"content": "#!/bin/sh\n# Generated by the mesh. Do not edit: module supabase writes this file and replaces it at every push.\n#\n# Two things logflare 1.4.0 does not do for itself, done here before it starts; either failing\n# stops the start, with the reason as the last line of this container's log.\n#\n# 1. The API key never travels in a URL. logflare prints the whole URL of every request that fails,\n# query string included, in the error report Plug.Cowboy writes (\"Request: POST\n# /api/logs?source_name=...&api_key=...\"). That is an error, so no log level hides it while errors\n# are logged at all (novox/hq issue 268). logflare reads the key from the x-api-key header as well,\n# and the mesh's caller, vector, sends it there; this refuses to start logflare while the vector\n# config it is given still passes the key in a URL: an analytics that does not start says why\n# here, and one that leaks says nothing.\n#\n# 2. Its database says what its environment says. logflare copies LOGFLARE_API_KEY into its default\n# user and POSTGRES_BACKEND_URL into every source's backend once, when it creates them, and never\n# again; reconcile.exs makes both copies match the environment through logflare's own code, after\n# its migrations and before it starts. That is what lets the mesh rotate the key by starting\n# logflare again, and what moves the sources' backend when the database's address or password\n# changes.\nset -e\nconf=/run/logflare/vector.yml\nif [ ! -r \"$conf\" ]; then\n echo \"analytics: $conf is not there to check, so whether the API key travels in a URL is unknown; not starting (novox/hq issue 268)\" >&2\n exit 1\nfi\nif grep -v '^[[:space:]]*#' \"$conf\" | grep -q 'api_key='; then\n echo \"analytics: $conf passes the API key in a URL, and logflare prints a failed request's URL; not starting (novox/hq issue 268)\" >&2\n exit 1\nfi\nreconcile=/run/logflare/reconcile.exs\nif [ ! -r \"$reconcile\" ]; then\n echo \"analytics: $reconcile is not there, so whether logflare's stored key and backends match its environment is unknown; not starting\" >&2\n exit 1\nfi\ncd /opt/app/rel/logflare/bin\n# The tables reconcile.exs reads exist only once the migrations have run; run.sh runs them again,\n# which finds nothing left to do.\n./logflare eval Logflare.Release.migrate\n./logflare eval 'Code.eval_file(\"/run/logflare/reconcile.exs\")'\nexec sh run.sh\n",
"id": "analytics-start",
"mode": "0644",
"path": "${dir:state}/analytics-start.sh",
"type": "file"
},
{
"content": "# Generated by the mesh. Do not edit: module supabase writes this file and replaces it at every push.\n#\n# logflare 1.4.0 copies two of its settings into its own database once and never reads them again:\n# LOGFLARE_API_KEY becomes the default user's api_key when that user is created, and\n# POSTGRES_BACKEND_URL becomes each source's backend when that source is created. A changed value\n# reaches neither: a rotated key is refused, and a moved database is never written to (every\n# source here pointed at db:5432, a name that resolves nowhere, so nothing was stored).\n#\n# Run by the start script before logflare starts, through logflare's own code (its Repo, its Users\n# and Backends contexts and their changesets): it makes both copies say what the environment says.\n# It changes nothing when they already do, says what it changed, never prints the key or a URL's\n# password, and stops the start when it cannot finish.\nfail = fn why ->\n IO.puts(:stderr, \"analytics: #{why}; not starting\")\n System.halt(1)\nend\n\nsay = fn what -> IO.puts(:stderr, \"analytics: #{what}\") end\n\nkey = System.get_env(\"LOGFLARE_API_KEY\")\nurl = System.get_env(\"POSTGRES_BACKEND_URL\")\nschema = System.get_env(\"POSTGRES_BACKEND_SCHEMA\")\n\nif key in [nil, \"\"], do: fail.(\"LOGFLARE_API_KEY is not set, so the key logflare must accept is unknown\")\nif url in [nil, \"\"], do: fail.(\"POSTGRES_BACKEND_URL is not set, so where logflare must store logs is unknown\")\n\nwhere = fn u ->\n case URI.parse(to_string(u)) do\n %URI{host: host, port: port, path: path} when is_binary(host) -> \"#{host}:#{port}#{path}\"\n _ -> \"an unreadable URL\"\n end\nend\n\nApplication.ensure_all_started(:ssl)\n\noutcome =\n Ecto.Migrator.with_repo(Logflare.Repo, fn _repo ->\n # Backends.update_source_backend_config restarts the source's pipeline after saving, and looks\n # it up in this registry to do so; logflare is not running yet, so the registry is empty and\n # there is nothing to restart.\n {:ok, _} = Registry.start_link(keys: :unique, name: Logflare.Backends.SourceRegistry)\n\n case Logflare.SingleTenant.get_default_user() do\n nil ->\n say.(\"no default user yet; logflare creates it from LOGFLARE_API_KEY and POSTGRES_BACKEND_URL as it starts\")\n :ok\n\n user ->\n if user.api_key == key and is_nil(user.old_api_key) do\n say.(\"the default user's API key is LOGFLARE_API_KEY already\")\n else\n # old_api_key is cleared too: logflare's UI can swap it back in, and the key replaced\n # here may be one that leaked.\n case Logflare.Users.update_user_all_fields(user, %{api_key: key, old_api_key: nil}) do\n {:ok, _} -> say.(\"the default user's API key was replaced with LOGFLARE_API_KEY\")\n {:error, changeset} -> fail.(\"the default user's API key could not be replaced: #{inspect(changeset.errors)}\")\n end\n end\n\n results =\n for source <- Logflare.Sources.list_sources_by_user(user),\n backend <- Logflare.Backends.list_source_backends(source),\n backend.type == :postgres do\n if backend.config.url == url and backend.config.schema == schema do\n :same\n else\n case Logflare.Backends.update_source_backend_config(backend, %{url: url, schema: schema}) do\n {:ok, _} ->\n say.(\"source #{source.name} stored logs at #{where.(backend.config.url)} schema #{inspect(backend.config.schema)}; now at #{where.(url)} schema #{inspect(schema)}\")\n\n {:error, changeset} ->\n fail.(\"source #{source.name}'s backend could not be pointed at #{where.(url)}: #{inspect(changeset.errors)}\")\n end\n end\n end\n\n say.(\"#{Enum.count(results, &(&1 == :same))} source backend(s) already at #{where.(url)}\")\n\n :ok\n end\n end)\n\ncase outcome do\n {:ok, :ok, _} -> :ok\n other -> fail.(\"logflare's database could not be reconciled: #{inspect(other)}\")\nend\n",
"id": "analytics-reconcile",
"mode": "0644",
"path": "${dir:state}/analytics-reconcile.exs",
"type": "file"
},
{
"content": "# Next.js listens on the address HOSTNAME names, and docker sets HOSTNAME to the container's id,\n# so studio answered only on its network address while its image's health check asks localhost:\n# unhealthy for ever while working. Every address, as the upstream compose file has it.\nHOSTNAME=::\nSTUDIO_PG_META_URL=http://supabase-meta:8080\nPOSTGRES_PASSWORD=STAND-IN\nDEFAULT_ORGANIZATION_NAME=Default Organization\nDEFAULT_PROJECT_NAME=Default Project\nOPENAI_API_KEY=STAND-IN\nSUPABASE_URL=http://supabase-kong:8000\nSUPABASE_PUBLIC_URL=https://STAND-IN\nSUPABASE_ANON_KEY=STAND-IN\nSUPABASE_SERVICE_KEY=STAND-IN\nAUTH_JWT_SECRET=STAND-IN\nLOGFLARE_API_KEY=STAND-IN\nLOGFLARE_URL=http://supabase-analytics:4000\nNEXT_PUBLIC_ENABLE_LOGS=true\nNEXT_ANALYTICS_BACKEND_PROVIDER=postgres\n",
"id": "studio-env",
"mode": "0600",
"path": "${dir:state}/studio-env.env",
"type": "file"
},
{
"content": "PORT=4000\nPOSTGRES_PORT=5432\nPOSTGRES_DB=postgres\nPOSTGRES_PASSWORD=STAND-IN\nDATABASE_URL=ecto://supabase_admin:STAND-IN@supabase-db:5432/_supabase\nCLUSTER_POSTGRES=true\nSECRET_KEY_BASE=STAND-INSTAND-IN\nVAULT_ENC_KEY=STAND-IN\nAPI_JWT_SECRET=STAND-IN\nMETRICS_JWT_SECRET=STAND-IN\nREGION=local\nERL_AFLAGS=-proto_dist inet_tcp\nRELEASE_NODE=supavisor@127.0.0.1\nPOOLER_POOL_MODE=transaction\n",
"id": "pooler-env",
"mode": "0600",
"path": "${dir:state}/pooler-env.env",
"type": "file"
}
],
"version": "1"
},
{
"config": {
"site": {
"default": "default",
"description": "The UniFi site the module's tools act on, by its id; the file's default is default.",
"trusted": true,
"type": "string"
},
"url": {
"default": "",
"description": "The address the module's tools reach the UniFi controller at, as a URL; unset, the code falls back to its environment, else this machine's controller on its HTTPS port.",
"trusted": true,
"type": "string"
},
"username": {
"default": "",
"description": "The UniFi account the module's tools sign in as with the vault's password; unset, the code falls back to its environment.",
"trusted": true,
"type": "string"
}
},
"module": "unifi",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "data",
"mode": "0700",
"type": "directory"
},
{
"content": "{\n \"site\": \"default\",\n \"password\": \"STAND-IN\"\n}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"protected": [
"password"
],
"takes-config": [
"site",
"url",
"username"
],
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"name": "node-notifier",
"scope": "node",
"serves": [
"send",
"history"
]
}
],
"config": {
"font-size": {
"default": 10,
"description": "The size of a notification's text, in points of Inter: 10 reads well on a screen of about 100 DPI, and a denser screen needs a larger size.",
"maximum": 72,
"minimum": 6,
"predefined": "font-size",
"type": "number"
},
"width": {
"default": 250,
"description": "The width of a notification, in pixels: 250 fits a title of about forty characters at 10 points, and a larger font needs a wider notification.",
"maximum": 4000,
"minimum": 50,
"type": "number"
}
},
"module": "dunst",
"resources": [
{
"id": "configuration-dir",
"mode": "0755",
"path": "STAND-IN/.config/dunst",
"type": "directory"
},
{
"id": "dropins",
"mode": "0755",
"path": "STAND-IN/.config/dunst/dunstrc.d",
"type": "directory"
},
{
"content": "# dunst, the notifier (module dunst, novox/hq ADR 0208). Owned by the mesh: this file is replaced\n# at every push. Adopted from the laptop's file of 2026-10-04 (the two workstations differed in\n# position, transparency and corner radius; the laptop's square, opaque, top-right one matches the\n# rest of the desktop). Only what differs from dunst's defaults, and what the desktop relies on.\n#\n# Other modules' rules go in ~/.config/dunst/dunstrc.d/*.conf, which dunst reads after this file,\n# so a drop-in outranks it. dunst is started by D-Bus on the first notification: nothing starts it.\n# When the mesh rewrites this file (a font-size or width setting changed), it tells a running dunst\n# to read it again (its unit's reload, dunstctl reload); what is on screen stays.\n\n[global]\n monitor = 0\n follow = none\n\n # Geometry\n width = ${config:width}\n height = (0, 300)\n origin = top-right\n offset = (10, 50)\n notification_limit = 20\n\n progress_bar = true\n progress_bar_height = 10\n progress_bar_frame_width = 1\n progress_bar_min_width = 150\n progress_bar_max_width = 300\n\n indicate_hidden = yes\n transparency = 0\n separator_height = 2\n padding = 8\n horizontal_padding = 8\n text_icon_padding = 0\n frame_width = 3\n frame_color = \"#de5200\"\n gap_size = 0\n separator_color = frame\n sort = yes\n corner_radius = 0\n\n # Text: the interface face (research 026/04)\n font = Inter ${config:font-size}\n line_height = 0\n markup = full\n format = \"<b>%s</b>\\n%b\"\n alignment = left\n vertical_alignment = center\n show_age_threshold = 60\n ellipsize = middle\n ignore_newline = no\n stack_duplicates = true\n hide_duplicate_count = false\n show_indicators = yes\n\n # Icons, from the desktop's icon theme\n enable_recursive_icon_lookup = true\n icon_theme = Adwaita\n icon_position = left\n min_icon_size = 32\n max_icon_size = 128\n\n # History\n sticky_history = yes\n history_length = 20\n\n # The context menu is rofi (installed by the rofi module); -no-custom keeps typed text from becoming a choice. Not `dmenu`, the node-launcher's command in\n # ~/.local/bin: dunst runs in the account's service manager, whose PATH does not hold that\n # directory (seen on the laptop on 2026-10-10), so the menu never opened. Links open in the\n # desktop's default browser.\n dmenu = rofi -dmenu -no-custom -p dunst\n browser = /usr/bin/xdg-open\n always_run_script = true\n\n title = Dunst\n class = Dunst\n ignore_dbusclose = false\n\n # A tap answers (the touchpads have no middle button). do_action runs the notification's only\n # action, or opens the context menu when it has several; without actions it opens its one link,\n # if it has one. The notification then closes; a menu dismissed with Escape leaves it shown.\n mouse_left_click = do_action, close_current\n mouse_middle_click = do_action, close_current\n mouse_right_click = close_all\n\n[urgency_low]\n background = \"#000000\"\n foreground = \"#ffffff\"\n timeout = 10\n\n[urgency_normal]\n background = \"#000000\"\n foreground = \"#ffffff\"\n timeout = 10\n\n[urgency_critical]\n background = \"#000000\"\n foreground = \"#ffffff\"\n frame_color = \"#ff0000\"\n timeout = 0\n\n# What is shown once is never kept (the review of 2026-10-09): a notification marked secret (a link's\n# code, category mesh.secret) and any transient one leave no entry in the history, which\n# node-notifier.history serves to every caller of the mesh's verbs.\n[mesh-secret]\n category = \"mesh.secret\"\n history_ignore = yes\n\n[transient-history-ignore]\n match_transient = yes\n history_ignore = yes\n",
"id": "configuration",
"mode": "0644",
"path": "STAND-IN/.config/dunst/dunstrc",
"trusted": false,
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"name": "node-power",
"scope": "node"
}
],
"config": {
"handle-lid-switch": {
"description": "What logind does when the lid is closed on battery: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"required": true,
"trusted": true,
"type": "string"
},
"handle-lid-switch-docked": {
"description": "What logind does when the lid is closed while docked or with another screen attached: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"required": true,
"trusted": true,
"type": "string"
},
"handle-lid-switch-external-power": {
"description": "What logind does when the lid is closed on external power: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"required": true,
"trusted": true,
"type": "string"
},
"handle-power-key": {
"description": "What logind does when the power key is pressed: one of logind's actions, such as suspend, ignore or poweroff (logind.conf(5)); the operator's choice for this machine, with no default.",
"enum": [
"ignore",
"poweroff",
"reboot",
"halt",
"kexec",
"suspend",
"hibernate",
"hybrid-sleep",
"suspend-then-hibernate",
"lock",
"factory-reset",
"sleep"
],
"required": true,
"trusted": true,
"type": "string"
}
},
"module": "power",
"resources": [
{
"id": "config-dir",
"mode": "0755",
"path": "/etc/mesh-power",
"type": "directory"
},
{
"id": "moments-dir",
"mode": "0755",
"path": "/etc/mesh-power/moments",
"type": "directory"
},
{
"content": "# What every module on this machine runs at the moment after-boot, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\nSTAND-INSTAND-INSTAND-IN",
"id": "moment-after-boot",
"mode": "0644",
"path": "/etc/mesh-power/moments/after-boot",
"type": "file"
},
{
"content": "# What every module on this machine runs at the moment before-sleep, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\nSTAND-INSTAND-INSTAND-IN",
"id": "moment-before-sleep",
"mode": "0644",
"path": "/etc/mesh-power/moments/before-sleep",
"type": "file"
},
{
"content": "# What every module on this machine runs at the moment after-wake, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\nSTAND-INSTAND-INSTAND-IN",
"id": "moment-after-wake",
"mode": "0644",
"path": "/etc/mesh-power/moments/after-wake",
"type": "file"
},
{
"content": "# What every module on this machine runs at the moment before-shutdown, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\nSTAND-INSTAND-INSTAND-IN",
"id": "moment-before-shutdown",
"mode": "0644",
"path": "/etc/mesh-power/moments/before-shutdown",
"type": "file"
},
{
"content": "# What every module on this machine runs at the moment on-mains, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\nSTAND-INSTAND-INSTAND-IN",
"id": "moment-on-mains",
"mode": "0644",
"path": "/etc/mesh-power/moments/on-mains",
"type": "file"
},
{
"content": "# What every module on this machine runs at the moment on-battery, as the mesh placed it (module power,\n# novox/hq ADR 0211). Replaced on every push: code is added by the module it belongs to, as a\n# contribution for this moment. Each module's piece runs on its own, as root, bounded in time.\nSTAND-INSTAND-INSTAND-IN",
"id": "moment-on-battery",
"mode": "0644",
"path": "/etc/mesh-power/moments/on-battery",
"type": "file"
},
{
"id": "logind-drop-ins",
"mode": "0755",
"path": "/etc/systemd/logind.conf.d",
"type": "directory"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# The power key and the lid, from this module's settings: the mesh's, then this machine's.\n[Login]\nHandlePowerKey=${config:handle-power-key}\nHandleLidSwitch=${config:handle-lid-switch}\nHandleLidSwitchExternalPower=${config:handle-lid-switch-external-power}\nHandleLidSwitchDocked=${config:handle-lid-switch-docked}\n",
"id": "logind-power",
"mode": "0644",
"path": "/etc/systemd/logind.conf.d/power.conf",
"type": "file"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nDescription=Every module's code after boot (mesh power)\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/usr/local/lib/mesh-power/bin/power-moment after-boot\n\n[Install]\nWantedBy=multi-user.target\n",
"id": "after-boot-unit",
"mode": "0644",
"path": "/etc/systemd/system/mesh-power-after-boot.service",
"type": "file"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Started at boot and left active; stopping it is the shutdown. Ordered after the network, so at\n# shutdown it stops, and runs every module's code, while the network is still up.\n[Unit]\nDescription=Every module's code before shutdown (mesh power)\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=/bin/true\nExecStop=/usr/local/lib/mesh-power/bin/power-moment before-shutdown\nTimeoutStopSec=120\n\n[Install]\nWantedBy=multi-user.target\n",
"id": "before-shutdown-unit",
"mode": "0644",
"path": "/etc/systemd/system/mesh-power-before-shutdown.service",
"type": "file"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Pulled in by sleep.target through this module's drop-in on it, never enabled: a one-shot waiting\n# for a sleep is not a service whose state the mesh can declare.\n[Unit]\nDescription=Every module's code before sleep (mesh power)\nBefore=sleep.target\n\n[Service]\nType=oneshot\nExecStart=/usr/local/lib/mesh-power/bin/power-moment before-sleep\n",
"id": "before-sleep-unit",
"mode": "0644",
"path": "/etc/systemd/system/mesh-power-before-sleep.service",
"type": "file"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Pulled in by the four sleep targets through this module's drop-ins on them, and ordered after\n# them, so it runs once the machine is awake again. Never enabled.\n[Unit]\nDescription=Every module's code after waking (mesh power)\nAfter=suspend.target hibernate.target hybrid-sleep.target suspend-then-hibernate.target\n\n[Service]\nType=oneshot\nExecStart=/usr/local/lib/mesh-power/bin/power-moment after-wake\n",
"id": "after-wake-unit",
"mode": "0644",
"path": "/etc/systemd/system/mesh-power-after-wake.service",
"type": "file"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n#\n# Started by the module's udev rule when a power supply changes; runs on-mains or on-battery once per\n# change of source.\n[Unit]\nDescription=Every module's code for the power source (mesh power)\n\n[Service]\nType=oneshot\nExecStart=/usr/local/lib/mesh-power/bin/power-moment supply\n",
"id": "supply-unit",
"mode": "0644",
"path": "/etc/systemd/system/mesh-power-supply.service",
"type": "file"
},
{
"id": "sleep-drop-ins",
"mode": "0755",
"path": "/etc/systemd/system/sleep.target.d",
"type": "directory"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-before-sleep.service\n",
"id": "sleep-wants",
"mode": "0644",
"path": "/etc/systemd/system/sleep.target.d/mesh-power.conf",
"type": "file"
},
{
"id": "suspend-drop-ins",
"mode": "0755",
"path": "/etc/systemd/system/suspend.target.d",
"type": "directory"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n",
"id": "suspend-wants",
"mode": "0644",
"path": "/etc/systemd/system/suspend.target.d/mesh-power.conf",
"type": "file"
},
{
"id": "hibernate-drop-ins",
"mode": "0755",
"path": "/etc/systemd/system/hibernate.target.d",
"type": "directory"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n",
"id": "hibernate-wants",
"mode": "0644",
"path": "/etc/systemd/system/hibernate.target.d/mesh-power.conf",
"type": "file"
},
{
"id": "hybrid-sleep-drop-ins",
"mode": "0755",
"path": "/etc/systemd/system/hybrid-sleep.target.d",
"type": "directory"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n",
"id": "hybrid-sleep-wants",
"mode": "0644",
"path": "/etc/systemd/system/hybrid-sleep.target.d/mesh-power.conf",
"type": "file"
},
{
"id": "suspend-then-hibernate-drop-ins",
"mode": "0755",
"path": "/etc/systemd/system/suspend-then-hibernate.target.d",
"type": "directory"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n[Unit]\nWants=mesh-power-after-wake.service\n",
"id": "suspend-then-hibernate-wants",
"mode": "0644",
"path": "/etc/systemd/system/suspend-then-hibernate.target.d/mesh-power.conf",
"type": "file"
},
{
"content": "# Written by the mesh (module power, novox/hq ADR 0211). Replaced on every push.\n# Only the charger's change can switch the machine between mains and battery. A battery reports its\n# level many times a minute, and USB-C supplies come and go; matching them started the supply unit\n# every second or two on the laptop.\nSUBSYSTEM==\"power_supply\", ACTION==\"change\", ATTR{type}==\"Mains\", RUN+=\"/usr/bin/systemctl --no-block start mesh-power-supply.service\"\n",
"id": "udev-rule",
"mode": "0644",
"path": "/etc/udev/rules.d/90-mesh-power.rules",
"type": "file"
},
{
"id": "draw-dir",
"mode": "0755",
"path": "/run/mesh-power",
"type": "directory"
}
],
"version": "1"
},
{
"config": {
"older-store-names": {
"default": "none",
"description": "The names the artifact store had before, as host names or host:port separated by spaces, or none when it had no other. artifact_store_references reads every definition under these as well as under the store's current name, so an image a definition pins under one is said to be named by a definition (novox/hq issue 323).",
"pattern": "none|[A-Za-z0-9.:-]+( [A-Za-z0-9.:-]+)*",
"type": "string"
}
},
"module": "artifact-store-tools",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"content": "# The names the artifact store had before, from the setting older-store-names, written by the mesh.\n# artifact_store_references reads every definition under these too (novox/hq issue 323).\n# Replaced on every push; change the setting, never this file.\nolder=${config:older-store-names}\n",
"id": "store-names",
"mode": "0600",
"path": "${dir:state}/store-names",
"trusted": false,
"type": "file"
}
],
"version": "1"
},
{
"config": {
"accounts": {
"default": {},
"description": "The mail accounts Betterbird is set up with, each by a name of the operator's: its address, its sender's name and its incoming and outgoing servers; no password, token or cookie (novox/hq ADR 0329 \u00a72).",
"trusted": true,
"type": "object"
},
"address-books": {
"default": {},
"description": "The address books Betterbird uses, each by a name of the operator's: where it is and how it is reached, and the profile's key to adopt on a node.",
"trusted": true,
"type": "object"
},
"calendars": {
"default": {},
"description": "The calendars Betterbird shows, each by a name of the operator's: where it is and how it is reached, and the profile's key to adopt on a node.",
"trusted": true,
"type": "object"
},
"filters": {
"default": {},
"description": "The message filters of each account, by account: what each matches and what it does with the mail.",
"trusted": true,
"type": "object"
},
"identities": {
"default": {},
"description": "The identities beside an account's own, each by a name of the operator's: the address and name it sends as, its signature as a list of lines, and the profile's key to adopt on a node.",
"trusted": true,
"type": "object"
},
"prefs": {
"default": {},
"description": "Betterbird's preferences the operator sets, by their own names (mailnews.*, calendar.* and the like), laid into prefs.json; an account's, a server's and a tag's are refused, as accounts, the four kinds and tags own them (novox/hq ADR 0329 \u00a76).",
"trusted": true,
"type": "object"
},
"tags": {
"default": {},
"description": "The tags Betterbird offers, each by its own key: its name, colour and order; trusted since issue 751, so a change to it is the operator's to approve.",
"trusted": true,
"type": "object"
}
},
"module": "betterbird",
"resources": [
{
"id": "configuration-dir",
"mode": "0755",
"path": "STAND-IN/.config/betterbird",
"type": "directory"
},
{
"content": "{\n \"prefs\": {}\n}\n",
"id": "prefs",
"merge": "json",
"mode": "0644",
"path": "STAND-IN/.config/betterbird/prefs.json",
"takes-config": [
"prefs"
],
"trusted": true,
"type": "file"
},
{
"content": "{\n \"accounts\": {}\n}\n",
"id": "accounts",
"merge": "json",
"mode": "0600",
"path": "STAND-IN/.config/betterbird/accounts.json",
"takes-config": [
"accounts"
],
"trusted": true,
"type": "file"
},
{
"content": "{\n \"identities\": {},\n \"calendars\": {},\n \"address-books\": {},\n \"filters\": {}\n}\n",
"id": "configuration",
"merge": "json",
"mode": "0600",
"path": "STAND-IN/.config/betterbird/configuration.json",
"takes-config": [
"identities",
"calendars",
"address-books",
"filters"
],
"trusted": true,
"type": "file"
},
{
"content": "{\n \"tags\": {}\n}\n",
"id": "tags",
"merge": "json",
"mode": "0644",
"path": "STAND-IN/.config/betterbird/tags.json",
"takes-config": [
"tags"
],
"trusted": true,
"type": "file"
},
{
"content": "#!/bin/sh\n# Betterbird's start, written by the mesh (module betterbird, novox/hq ADR 0316). Replaced at every push.\n#\n# Before Betterbird starts, the module's tools place its user.js in the profile (the settings prefs, accounts, identities, calendars,\n# address-books and tags) and write what Betterbird rewrites itself: the account lists and each declared account's filters. They do\n# it only while no Betterbird of this account runs, because Betterbird rewrites its saved preferences when it quits; a\n# second start while one runs only hands over to it. What the preparation could not do it says here, and\n# betterbird_check says it too. Betterbird starts either way: its configuration never holds the mail back.\n/usr/local/lib/mesh-betterbird/betterbird-tools prepare || echo \"betterbird: the mesh's preparation did not complete; betterbird_check says why\" >&2\nexec /opt/betterbird/betterbird \"$@\"\n",
"id": "start",
"mode": "0755",
"path": "/usr/local/bin/betterbird",
"type": "file"
},
{
"content": "[Desktop Entry]\nVersion=1.0\nType=Application\nName=Betterbird\nGenericName=Mail Client\nComment=Send and receive mail with Betterbird\nExec=/usr/local/bin/betterbird %u\nIcon=/opt/betterbird/chrome/icons/default/default128.png\nTerminal=false\nCategories=Network;Email;\nMimeType=message/rfc822;x-scheme-handler/mailto;application/x-xpinstall;application/x-extension-ics;text/calendar;text/vcard;text/x-vcard;x-scheme-handler/webcal;x-scheme-handler/webcals;x-scheme-handler/mid;\nStartupNotify=true\nStartupWMClass=eu.betterbird.Betterbird\nKeywords=Email;E-mail;Calendar;Thunderbird;\nActions=ComposeMessage;OpenAddressBook;\n# Written by the mesh (module betterbird, novox/hq ADR 0316 \u00a73), in place of the community package's entry:\n# it starts Betterbird through the module's start, and names the icon in the installation, with no link.\n\n[Desktop Action ComposeMessage]\nName=Write new message\nExec=/usr/local/bin/betterbird -compose\n\n[Desktop Action OpenAddressBook]\nName=Open address book\nExec=/usr/local/bin/betterbird -addressbook\n",
"id": "launcher",
"mode": "0644",
"path": "/usr/local/share/applications/eu.betterbird.Betterbird.desktop",
"type": "file"
}
],
"version": "1"
},
{
"config": {
"forge": {
"description": "The forge the lab's machines take their packages from, as its address: the operator's, with no default (novox/hq ADR 0112).",
"required": true,
"trusted": true,
"type": "string"
}
},
"module": "lab",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "work",
"mode": "0700",
"path": "/var/lib/mesh-lab-runs",
"type": "directory"
},
{
"content": "MESH_LAB_FORGE=${config:forge}\nMESH_LAB_FORGE_BINDING=${dir:state}/package-registry.json\nMESH_LAB_FORGE_SECRET=${dir:state}/package-registry.secret\n",
"id": "runtime-env",
"mode": "0600",
"path": "${dir:state}/lab.env",
"type": "file"
}
],
"version": "1"
},
{
"config": {
"container": {
"default": "",
"description": "The name of the container holding Mailu's mailboxes, which the module's tools read mail in; empty is mailu-imap.",
"trusted": true,
"type": "string"
},
"domain": {
"description": "The mail domain Mailu serves, such as example.org: its first mail domain, the postmaster's and the one consumers send as; the operator's, with no default (novox/hq ADR 0112).",
"required": true,
"trusted": true,
"type": "string"
},
"proxy-address": {
"description": "The address of the reverse proxy in front of Mailu, whose X-Real-IP header Mailu believes; the operator's, with no default.",
"required": true,
"trusted": true,
"type": "string"
},
"sitename": {
"description": "The name Mailu's web pages show for the mail service; the operator's, with no default.",
"required": true,
"trusted": true,
"type": "string"
},
"url": {
"default": "",
"description": "The address the module's tools reach Mailu's API at, as a URL; empty leaves them to MESH_MAILU_URL.",
"trusted": true,
"type": "string"
},
"website": {
"description": "The address of the website Mailu's web pages link to, as a URL; the operator's, with no default.",
"required": true,
"trusted": true,
"type": "string"
}
},
"module": "mailu",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "grants",
"mode": "0700",
"type": "directory"
},
{
"id": "data-automx",
"mode": "0700",
"type": "directory"
},
{
"content": "ADMIN_ADDRESS=mailu-admin\nANTISPAM_ADDRESS=mailu-antispam\nANTIVIRUS_ADDRESS=mailu-antivirus\nIMAP_ADDRESS=mailu-imap\nSMTP_ADDRESS=mailu-smtp\nFRONT_ADDRESS=mailu-front\nWEBMAIL_ADDRESS=mailu-webmail\nWEBDAV_ADDRESS=mailu-webdav\nREDIS_ADDRESS=mailu-redis\nPORTS=25,80,443,465,993,995,4190,110,143,587\nDOMAIN=${config:domain}\nHOSTNAMES=STAND-IN\nPOSTMASTER=admin\nSITENAME=${config:sitename}\nWEBSITE=${config:website}\nTLS_FLAVOR=letsencrypt\nSUBNET=192.168.203.0/24\nCOMPOSE_PROJECT_NAME=mailu\nANTIVIRUS=clamav\nWEBMAIL=roundcube\nWEBDAV=radicale\nFETCHMAIL_ENABLED=True\nFETCHMAIL_DELAY=600\nADMIN=true\nWEB_ADMIN=/admin\nWEB_WEBMAIL=/webmail\nWEBROOT_REDIRECT=/webmail\nAPI=true\nWEB_API=/api\nAUTH_RATELIMIT_IP=6000/hour\nAUTH_RATELIMIT_USER=1000/day\nCREDENTIAL_ROUNDS=12\nPASSWORD_SCHEME=PBKDF2\nDISABLE_STATISTICS=True\nMESSAGE_SIZE_LIMIT=50000000\nMESSAGE_RATELIMIT=200/day\nRECIPIENT_DELIMITER=+\nPOSTFIX_MYNETWORKS=127.0.0.0/8 [::1]/128\nRELAYNETS=\nRELAYHOST=\nREJECT_UNLISTED_RECIPIENT=\nDB_FLAVOR=postgresql\nINITIAL_ADMIN_ACCOUNT=admin\nINITIAL_ADMIN_DOMAIN=${config:domain}\nINITIAL_ADMIN_MODE=ifmissing\nSMTP_PORT=25\nSMTPS_PORT=465\nSUBMISSION_PORT=587\nPOP3_PORT=110\nPOP3S_PORT=995\nIMAP_PORT=143\nIMAPS_PORT=993\nHTTP_PORT=7080\nHTTPS_PORT=7443\nAUTOMX_PORT=4243\nAMX_SMTP_ADDRESS=STAND-IN\nAMX_SMTP_PORT=587\nAMX_IMAP_ADDRESS=STAND-IN\nAMX_IMAP_PORT=143\nAMX_MAIL_DOMAINS=${config:domain}\nDMARC_RUA=admin\nDMARC_RUF=admin\nLETSENCRYPT_SHORTCHAIN=True\nTZ=Etc/UTC\nLOG_LEVEL=INFO\nWELCOME=false\nREAL_IP_HEADER=X-Real-IP\nREAL_IP_FROM=${config:proxy-address}\nCOMPRESSION=\nCOMPRESS_LEVEL=\nCOMPRESSION_LEVEL=\nBIND_ADDRESS4=127.0.0.1\nBIND_ADDRESS6=::1\nMAILU_VERSION=1.9\nDOCKER_ORG=mailu\nDOCKER_PREFIX=\nWELCOME_SUBJECT=Welcome to your new email account\nWELCOME_BODY=Welcome to your new email account, if you can read this, then it is configured properly!\n",
"id": "config-env",
"mode": "0644",
"path": "${dir:state}/mailu.env",
"type": "file"
},
{
"content": "SECRET_KEY=STAND-IN\n",
"id": "secret-env",
"mode": "0600",
"path": "${dir:state}/secret.env",
"type": "file"
},
{
"content": "DB_FLAVOR=postgresql\nDB_HOST=STAND-IN:STAND-IN\nDB_USER=STAND-IN\nDB_NAME=STAND-IN\nDB_PW=STAND-IN\n",
"id": "database-env",
"mode": "0600",
"path": "${dir:state}/database.env",
"type": "file"
},
{
"content": "INITIAL_ADMIN_PW=STAND-IN\nAPI_TOKEN=STAND-IN\n",
"id": "admin-env",
"mode": "0600",
"path": "${dir:state}/admin.env",
"type": "file"
},
{
"id": "data-certs",
"mode": "0700",
"type": "directory"
},
{
"id": "data-data",
"mode": "0700",
"type": "directory"
},
{
"id": "data-dkim",
"mode": "0700",
"type": "directory"
},
{
"id": "data-mail",
"mode": "0700",
"type": "directory"
},
{
"id": "data-mailqueue",
"mode": "0755",
"type": "directory"
},
{
"id": "data-filter",
"mode": "0700",
"type": "directory"
},
{
"id": "data-clamav",
"mode": "0700",
"type": "directory"
},
{
"id": "data-redis",
"mode": "0700",
"type": "directory"
},
{
"id": "data-webmail",
"mode": "0700",
"type": "directory"
},
{
"id": "data-dav",
"mode": "0700",
"type": "directory"
},
{
"id": "data-fetchmail",
"mode": "0700",
"type": "directory"
},
{
"id": "data-overrides-nginx",
"mode": "0700",
"type": "directory"
},
{
"id": "data-overrides-dovecot",
"mode": "0700",
"type": "directory"
},
{
"id": "data-overrides-postfix",
"mode": "0700",
"type": "directory"
},
{
"id": "data-overrides-rspamd",
"mode": "0700",
"type": "directory"
},
{
"id": "data-overrides-roundcube",
"mode": "0700",
"type": "directory"
},
{
"content": "{}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"takes-config": [
"domain",
"proxy-address",
"sitename",
"website",
"url",
"container"
],
"type": "file"
}
],
"serves-checked": {
"smtp": {
"domain": "${config:domain}",
"port": 587
}
},
"version": "1"
},
{
"claims": [
{
"name": "operator-channel",
"scope": "mesh",
"serves": [
"open",
"history",
"notify",
"asks"
]
}
],
"config": {
"desktop-machines": {
"default": [],
"description": "The machines whose desktop showed the operator's messages, by node name. Nothing in this module reads it since the desk channel took the desktop over; it is taken so the file stays as a layer that still sets it makes it, until that layer drops it.",
"trusted": true,
"type": "list"
},
"only-what-needs-me": {
"default": [],
"description": "The kinds of channel set to say only what needs the operator: a walk that delivered with nothing held and nothing open is not told there (ADR 0308 \u00a74); empty is none.",
"trusted": true,
"type": "list"
},
"time-zone": {
"default": "",
"description": "The time zone every time a message says is said in, an IANA name such as Europe/Brussels; empty is this machine's own zone.",
"trusted": true,
"type": "string"
}
},
"module": "messenger",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"content": "{\n \"time-zone\": \"\",\n \"only-what-needs-me\": []\n}\n",
"id": "settings",
"merge": "json",
"mode": "0600",
"path": "${dir:state}/settings.json",
"takes-config": [
"time-zone",
"desktop-machines",
"only-what-needs-me"
],
"type": "file"
}
],
"version": "1"
},
{
"config": {
"repository": {
"description": "The repository the records module keeps a checkout of and serves the record from, as owner/name on the mesh's forge; the operator's, with no default (novox/hq ADR 0112).",
"pattern": "[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+",
"required": true,
"trusted": true,
"type": "string"
}
},
"module": "records",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "checkout",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"content": "{}\n",
"id": "config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"takes-config": [
"repository"
],
"type": "file"
},
{
"content": "STAND-IN://STAND-IN:STAND-IN\n",
"id": "origin",
"mode": "0600",
"path": "${dir:mesh-state}/origin",
"type": "file"
}
],
"version": "1"
},
{
"config": {
"desktop-dir": {
"description": "The desktop folder, as user-dirs.dirs writes it: \"$HOME/<folder>\", or \"$HOME/\" when the folder is not wanted; it differs between the workstations, so each says its own.",
"required": true,
"trusted": true,
"type": "string"
},
"publicshare-dir": {
"description": "The public share folder, as user-dirs.dirs writes it: \"$HOME/<folder>\", or \"$HOME/\" when the folder is not wanted; it differs between the workstations, so each says its own.",
"required": true,
"trusted": true,
"type": "string"
},
"videos-dir": {
"description": "The videos folder, as user-dirs.dirs writes it: \"$HOME/<folder>\", or \"$HOME/\" when the folder is not wanted; it differs between the workstations, so each says its own.",
"required": true,
"trusted": true,
"type": "string"
}
},
"module": "xdg",
"resources": [
{
"content": "# The account's folders (module xdg, novox/hq ADR 0182), written by the mesh and replaced at every\n# push. Adopted from the workstations' own files of 2026-10-05. A folder that differs between the\n# machines is a setting of this module; every other one is the same everywhere. PROJECTS names the\n# folder the operator's repositories are in (it pointed at the home, or was absent).\n#\n# xdg-user-dirs-update never rewrites this file: user-dirs.conf beside it says enabled=False. Each\n# line is xdg-user-dirs' only format, XDG_xxx_DIR=\"$HOME/yyy\" or XDG_xxx_DIR=\"/yyy\". \"$HOME/\" is\n# the home itself: the folder is not wanted. xdg_user_dirs says what each one resolves to.\nXDG_DESKTOP_DIR=\"${config:desktop-dir}\"\nXDG_DOWNLOAD_DIR=\"$HOME/Downloads\"\nXDG_TEMPLATES_DIR=\"$HOME/Templates\"\nXDG_PUBLICSHARE_DIR=\"${config:publicshare-dir}\"\nXDG_DOCUMENTS_DIR=\"$HOME/Documents\"\nXDG_MUSIC_DIR=\"$HOME/Music\"\nXDG_PICTURES_DIR=\"$HOME/Pictures\"\nXDG_VIDEOS_DIR=\"${config:videos-dir}\"\nXDG_PROJECTS_DIR=\"$HOME/projects\"\n",
"id": "user-dirs",
"mode": "0600",
"path": "STAND-IN/.config/user-dirs.dirs",
"type": "file"
},
{
"content": "en_US\n",
"id": "user-dirs-locale",
"mode": "0644",
"path": "STAND-IN/.config/user-dirs.locale",
"type": "file"
},
{
"content": "# xdg-user-dirs-update for this account (module xdg, novox/hq ADR 0182), written by the mesh and\n# replaced at every push.\n#\n# The update runs at every login (its XDG autostart entry, which the session's dex starts). Enabled,\n# it reassigns a folder it cannot find to the home and adds a line for every folder its defaults\n# know, rewriting user-dirs.dirs, which is the mesh's. Disabled, it does nothing, even with --force.\n# The folders are this module's to say.\nenabled=False\nfilename_encoding=UTF-8\n",
"id": "user-dirs-update",
"mode": "0644",
"path": "STAND-IN/.config/user-dirs.conf",
"type": "file"
},
{
"content": "# The machine's default applications (module xdg, novox/hq ADR 0182), written by the mesh and\n# replaced at every push.\n#\n# This is the lowest list an application consults. The account's own ~/.config/mimeapps.list comes\n# first: every program that offers \"always open with\" writes there, and so does xdg_default. That\n# file is the person's and is never written by the mesh, so a choice made there always wins; this\n# list answers only what the person has not chosen. Adopted from the workstations' own lists of\n# 2026-10-05, without the entries naming an application neither workstation has installed (see the\n# module's README).\n[Default Applications]\napplication/pdf=firefox.desktop;\napplication/x-extension-htm=firefox.desktop;\napplication/x-extension-shtml=firefox.desktop;\napplication/x-extension-xht=firefox.desktop;\napplication/x-extension-xhtml=firefox.desktop;\napplication/xhtml+xml=firefox.desktop;\ntext/html=firefox.desktop;\nx-scheme-handler/chrome=firefox.desktop;\nx-scheme-handler/ftp=firefox.desktop;\nx-scheme-handler/http=firefox.desktop;\nx-scheme-handler/https=firefox.desktop;\ntext/markdown=org.kde.ghostwriter.desktop;\ninode/directory=thunar.desktop;\nx-scheme-handler/abc=plexamp.desktop;\nx-scheme-handler/slack=slack.desktop;\nx-scheme-handler/tg=org.telegram.desktop.desktop;\n\n[Added Associations]\nvideo/mp4=mpv.desktop;mplayer.desktop;vlc.desktop;\n",
"id": "default-applications",
"mode": "0644",
"path": "/etc/xdg/mimeapps.list",
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"capabilities": [
"deliver",
"silent",
"loud",
"edit",
"private",
"choice",
"exact-render"
],
"kind": "desktop",
"name": "channel",
"scope": "mesh"
},
{
"kind": "desktop",
"name": "intake",
"scope": "mesh"
}
],
"config": {
"desktop-machines": {
"default": [],
"description": "The machines, by node name, whose desktop shows the operator's questions and notifications, in the order they are tried; empty shows them on none.",
"trusted": true,
"type": "list"
}
},
"module": "desk-channel",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"content": "{\n \"desktop-machines\": []\n}\n",
"id": "settings",
"merge": "json",
"mode": "0600",
"path": "${dir:state}/settings.json",
"takes-config": [
"desktop-machines"
],
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"name": "npm-package-registry",
"scope": "mesh"
},
{
"name": "git",
"scope": "mesh"
}
],
"config": {
"url": {
"default": "",
"description": "The address the module's tools reach the forge at, as a URL; unset, the code falls back to its environment, else this machine's gitea on its port.",
"trusted": true,
"type": "string"
}
},
"module": "gitea",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "runtime-state",
"mode": "0700",
"path": "${dir:mesh-state}/state",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "grants",
"mode": "0700",
"type": "directory"
},
{
"content": "GITEA__security__INTERNAL_TOKEN=STAND-IN\nGITEA__database__DB_TYPE=postgres\nGITEA__database__HOST=STAND-IN:STAND-IN\nGITEA__database__NAME=STAND-IN\nGITEA__database__USER=STAND-IN\nGITEA__database__PASSWD=STAND-IN\n",
"id": "server-env",
"mode": "0600",
"path": "${dir:state}/server.env",
"type": "file"
},
{
"id": "data",
"mode": "0700",
"type": "directory"
},
{
"content": "https://STAND-IN\n",
"id": "origins",
"mode": "0644",
"path": "${dir:mesh-state}/origins",
"type": "file"
},
{
"content": "{}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"takes-config": [
"url"
],
"type": "file"
}
],
"serves-checked": {
"git": {
"port": 3000,
"scheme": "http"
},
"npm-package-registry": {
"npm-path": "/api/packages/novox/npm/",
"port": 3000,
"public-root": "https://${bound:route:name-web}/api/packages/novox/npm/",
"scheme": "http"
}
},
"version": "1"
},
{
"config": {
"issuer": {
"description": "The issuer Keycloak's tokens name and oidc consumers are told, as the realm's URL, such as https://keycloak.example.org/realms/Example; the operator's, with no default (novox/hq ADR 0112, ADR 0155).",
"required": true,
"trusted": true,
"type": "string"
},
"realm": {
"default": "",
"description": "The realm the module's provider makes its consumers' clients in; empty is master.",
"trusted": true,
"type": "string"
},
"url": {
"default": "",
"description": "The address the module's provider reaches Keycloak's admin API at, as a URL; empty is this machine's Keycloak on its port.",
"trusted": true,
"type": "string"
},
"user": {
"default": "",
"description": "The Keycloak administrator the module's provider signs in as; empty is admin.",
"trusted": true,
"type": "string"
}
},
"module": "keycloak",
"resources": [
{
"id": "mesh-state",
"mode": "0700",
"place": "mesh",
"type": "directory"
},
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "grants",
"mode": "0700",
"type": "directory"
},
{
"content": "KEYCLOAK_ADMIN=admin\nKEYCLOAK_ADMIN_PASSWORD=STAND-IN\n",
"id": "admin-env",
"mode": "0600",
"path": "${dir:state}/admin.env",
"type": "file"
},
{
"content": "KC_DB_URL=jdbc:postgresql://STAND-IN:STAND-IN/STAND-IN\nKC_DB_USERNAME=STAND-IN\nKC_DB_PASSWORD=STAND-IN\n",
"id": "database-env",
"mode": "0600",
"path": "${dir:state}/database.env",
"type": "file"
},
{
"content": "KC_HOSTNAME=https://STAND-IN\n",
"id": "hostname",
"mode": "0644",
"path": "${dir:state}/hostname.env",
"type": "file"
},
{
"content": "{}\n",
"id": "runtime-config",
"merge": "json",
"mode": "0600",
"path": "${dir:mesh-state}/config.json",
"takes-config": [
"url",
"user",
"realm",
"issuer"
],
"type": "file"
}
],
"serves-checked": {
"oidc-client": {
"authorization-path": "/protocol/openid-connect/auth",
"issuer": "${config:issuer}",
"token-path": "/protocol/openid-connect/token",
"userinfo-path": "/protocol/openid-connect/userinfo"
}
},
"version": "1"
},
{
"claims": [
{
"name": "node-login-shell",
"scope": "node",
"serves": [
"execute"
]
}
],
"config": {
"execute": {
"default": "serve",
"description": "Whether this machine offers execute, which runs any command as the operator account and so can become root without a person: serve, or withhold on a machine that does not offer it (novox/hq ADR 0268), such as the control-node until a call to it needs a person's approval.",
"enum": [
"serve",
"withhold"
],
"trusted": true,
"type": "string"
}
},
"module": "zsh",
"resources": [
{
"content": "# The mesh's block (module zsh, novox/hq ADR 0203, ADR 0204): the account's environment, read by\n# every zsh \u2014 a login, a script, and node-login-shell's execute. Replaced at every push; everything\n# outside it is yours.\n# PATH keeps each directory once, wherever it was added: the session, a nested shell or a\n# sourced file. Without it every nested shell prepended the same entries again.\ntypeset -U path PATH\nif [[ -r \"STAND-IN/.config/mesh/environment.sh\" ]]; then\n source \"STAND-IN/.config/mesh/environment.sh\"\nfi\n",
"id": "env",
"mode": "0644",
"path": "STAND-IN/.zshenv",
"type": "file"
},
{
"content": "# The mesh's block (module zsh, novox/hq ADR 0204): the defaults every machine shares, and the code\n# other modules contribute in three slots. It is replaced at every push. Everything below it is\n# yours, kept as you wrote it, and runs after it, so your lines win.\nSTAND-IN\n# History, kept across sessions and shared between open shells. Without these zsh saves none:\n# SAVEHIST defaults to 0 (novox/hq to-be 41, found on review 2026-10-04).\nHISTFILE=\"$HOME/.zsh_history\"\nHISTSIZE=100000\nSAVEHIST=100000\nsetopt EXTENDED_HISTORY SHARE_HISTORY HIST_IGNORE_DUPS HIST_IGNORE_SPACE HIST_REDUCE_BLANKS\n\n# The completion system. Nothing initialised it, so only zsh's basic completion ran. Its cache\n# goes under ~/.cache rather than beside ~/.zshrc.\n[[ -d \"$HOME/.cache/zsh\" ]] || mkdir -p \"$HOME/.cache/zsh\"\nautoload -Uz compinit && compinit -d \"$HOME/.cache/zsh/zcompdump\"\nzstyle ':completion:*' menu select\n\n# Terminal title: host, directory, git branch\nfunction set_terminal_title() {\n local git_branch=\"\"\n if git rev-parse --is-inside-work-tree &>/dev/null; then\n git_branch=\" ($(git branch --show-current 2>/dev/null))\"\n fi\n print -Pn \"\\e]2;%m: %~${git_branch}\\a\"\n}\nprecmd_functions+=(set_terminal_title)\n\n# Keybindings\nbindkey \"^[[H\" beginning-of-line # Home\nbindkey \"^[OH\" beginning-of-line # Home\nbindkey \"^A\" beginning-of-line # Ctrl + A\nbindkey \"^[[F\" end-of-line # End\nbindkey \"^[OF\" end-of-line # End\nbindkey \"^E\" end-of-line # Ctrl + E\nbindkey \"^[[3~\" delete-char # Del\n\n# Colour support of ls and friends\nif [ -x /usr/bin/dircolors ]; then\n test -r $HOME/.dircolors && eval \"$(dircolors -b $HOME/.dircolors)\" || eval \"$(dircolors -b)\"\n alias ls='ls --color=auto'\n alias dir='dir --color=auto'\n alias vdir='vdir --color=auto'\n alias grep='grep --color=auto'\n alias fgrep='fgrep --color=auto'\n alias egrep='egrep --color=auto'\nfi\nalias ll='ls -alhF'\nalias la='ls -Ah'\nalias l='ls -CFh'\n\n# A throwaway container, and what takes the space under a directory\nalias drun='docker run -it --rm'\ndisksize() {\n du -h --max-depth=1 \"${1:-.}\" | sort -h\n}\nsudo-disksize() {\n sudo du -h --max-depth=1 \"${1:-.}\" | sort -h\n}\n\nSTAND-INSTAND-IN\n",
"id": "rc",
"mode": "0644",
"path": "STAND-IN/.zshrc",
"type": "file"
},
{
"id": "state",
"mode": "0755",
"place": ".",
"type": "directory"
},
{
"content": "# Written by the mesh (module zsh, hq ADR 0268) from this machine's setting execute: serve or withhold.\n# Read by the zsh tools bundle when the runtime starts it; change the setting, not this.\nexecute=${config:execute}\n",
"id": "tools-conf",
"mode": "0644",
"path": "${dir:state}/tools.conf",
"type": "file"
}
],
"version": "1"
},
{
"claims": [
{
"name": "node-mounts",
"scope": "node",
"serves": [
"list",
"test",
"mount",
"unmount",
"adopt"
]
}
],
"config": {
"shares": {
"default": "none",
"description": "The shares of another machine this machine mounts: space-separated name=mountpoint, each optionally :ro, or none (novox/hq ADR 0263 rule 5).",
"trusted": true,
"type": "string"
},
"sources": {
"default": "none",
"description": "The occasional sources this machine mounts: space-separated name=smb://[<user>@]<host>/<share>@<mountpoint> or name=disk:<uuid>@<mountpoint>, each optionally :ro, or none (novox/hq ADR 0263 rule 6); an SMB source's username is in its entry, its password the secret smb-password-<name> (novox/hq ADR 0283).",
"trusted": true,
"type": "string"
}
},
"module": "mounts",
"resources": [
{
"id": "state",
"mode": "0700",
"place": ".",
"type": "directory"
},
{
"id": "config-dir",
"mode": "0755",
"path": "/etc/mesh-mounts",
"type": "directory"
},
{
"content": "# Written by the mesh (module mounts, novox/hq ADR 0263) from this machine's assignment: the settings\n# shares and sources, and the binding of nfs-share. Replaced on every push; change the settings, not this.\n# The module's process reads it as root every 30 seconds and writes one .mount and .automount per entry.\nshares=${config:shares}\nsources=${config:sources}\nserver=STAND-IN\nat=STAND-IN\naccount=STAND-IN\nnode=STAND-IN\npasswords=${dir:state}\nstate=${dir:state}\n",
"id": "config",
"mode": "0644",
"path": "/etc/mesh-mounts/mounts.conf",
"type": "file"
},
{
"id": "bus-dir-parent",
"mode": "0755",
"path": "/var/lib/mesh-mounts",
"type": "directory"
},
{
"id": "bus-dir",
"mode": "0755",
"path": "/var/lib/mesh-mounts/from-bus",
"type": "directory"
}
],
"version": "1"
},
{
"module": "bazarr",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/bazarr/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "bookshelf",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/bookshelf/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "jackett",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/jackett/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "kernel",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/kernel/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "kometa",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/kometa/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "lidarr",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/lidarr/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "nzbget",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/nzbget/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "ombi",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/ombi/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "plex",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/plex/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "qbittorrent",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/qbittorrent/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "radarr",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/radarr/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "sonarr",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/sonarr/conf.json",
"type": "file"
}
],
"version": "1"
},
{
"module": "tautulli",
"resources": [
{
"content": "{}",
"id": "conf",
"merge": "json",
"mode": "0640",
"path": "/var/lib/tautulli/conf.json",
"type": "file"
}
],
"version": "1"
}
],
"on": {
"artifact-store-tools": [
"laptop"
],
"baserow": [
"anchor"
],
"bazarr": [
"anchor"
],
"betterbird": [
"workstation",
"desktop"
],
"bookshelf": [
"anchor"
],
"claude-code": [
"anchor",
"laptop",
"workstation",
"desktop"
],
"desk-channel": [
"laptop"
],
"dunst": [
"workstation",
"desktop"
],
"gitea": [
"laptop"
],
"grafana": [
"anchor"
],
"home-assistant": [
"anchor"
],
"hostname": [
"anchor",
"laptop",
"workstation",
"desktop"
],
"icecast": [
"anchor"
],
"influxdb": [
"anchor"
],
"jackett": [
"anchor"
],
"kernel": [
"laptop"
],
"keycloak": [
"laptop"
],
"kometa": [
"anchor"
],
"lab": [
"desktop"
],
"letta": [
"anchor"
],
"lidarr": [
"anchor"
],
"mailu": [
"laptop"
],
"matrix": [
"anchor"
],
"messenger": [
"laptop"
],
"mounts": [
"desktop"
],
"nfs-server": [
"anchor"
],
"nodered": [
"anchor"
],
"nzbget": [
"anchor"
],
"ombi": [
"anchor"
],
"plex": [
"anchor"
],
"power": [
"anchor",
"laptop",
"workstation",
"desktop"
],
"qbittorrent": [
"anchor"
],
"radarr": [
"anchor"
],
"records": [
"laptop"
],
"restic": [
"anchor",
"laptop",
"workstation",
"desktop"
],
"searxng": [
"anchor"
],
"sonarr": [
"anchor"
],
"supabase": [
"anchor"
],
"tautulli": [
"anchor"
],
"unifi": [
"anchor"
],
"xdg": [
"workstation",
"desktop"
],
"zsh": [
"anchor",
"laptop",
"workstation",
"desktop"
]
},
"predefined": {
"agent-managed-settings": {
"description": "The managed settings every agent session on a node is held to (Claude Code's managed-settings.json), as a JSON object.",
"trusted": true,
"type": "object"
},
"font-size": {
"description": "The size a desktop program shows its text in, in points.",
"maximum": 72,
"minimum": 6,
"type": "number"
}
}
}