feat(socialhome): implement the role so it actually deploys

The role was a scaffold: tasks/main.yml was a two-line comment, there was no
compose.yml.j2, no domains.yml, no volumes.yml, no env.j2. It deployed nothing
while carrying a Playwright spec and two matrix variants.

It also described the wrong project. meta/main.yml and meta/info.yml pointed at
jaywink/socialhome -- a Django ActivityPub hub. The target here is
social-home-io/socialhome: created 2026-04-21, MPL-2.0, aiohttp + SQLite +
Preact, federating peer-to-peer over Ed25519/WebRTC rather than ActivityPub.
Different codebase, no fork relationship.

It turns out to be the simplest web-app in the repo. One published multi-arch
image (ghcr.io/social-home-io/socialhome:2026.6.16, pinned), no Postgres, no
Redis, no queue, no worker, no collectstatic -- SQLite in one volume, one port,
migrations applied in-process at startup.

Two things the source forced that reading the README would not have:

- [standalone].external_url is reachable only through the TOML file, never
  through SH_* env (config.py:238-261). Without it routes/pairing.py answers
  422 NOT_CONFIGURED and federation pairing is dead, so the role renders a
  config and mounts it as type: config.
- provision_admin() runs from SH_ADMIN_PASSWORD but never calls
  mark_complete(), so / keeps serving the setup wizard instead of the login
  form -- upstream's own docstring claims otherwise. 01_setup.yml closes it
  with an idempotent POST to /api/setup/standalone.

No SSO sidecar, deliberately, against the pgadmin model this otherwise copies:
oauth2-proxy would redirect POST /federation/inbox and /api/pairing/accept to
Keycloak and break the one thing the software exists for.

Verified against the real image rather than reasoned about. The http_status
probe's exact argv ran inside the container and returned 0. /healthz answers
with the byte-for-byte shape the spec asserts. The setup POST returns 201 then
409 and flips setup_required true -> false, which is the whole justification
for 01_setup.yml existing. [standalone].external_url lands in the inbox_url of
POST /api/pairing/initiate. The container runs as uid 10001, matching
meta/users.yml.

Both persona blocks are proven, not assumed: biber gets 401 from the token
endpoint, and the 97 static files contain zero occurrences of logout / sign out
/ abmelden, so utils/logout.js has nothing to click. Injecting a control the
way web-app-prometheus does was rejected -- that model points its button at
OIDC.CLIENT.LOGOUT_URL, which does not exist for a role with no OIDC.

swarm stays disabled for the first round. The orchestrator chains
swarm -> compose -> host under mode_fail_fast, and sequencing measures serial
at 198 jobs, so a red swarm job here would skip 92 unrelated jobs and leave 85
roles untested. Compose-only drops that to 10. Flip it once compose is green.

Adversarial review caught five defects before this landed, three of them
certain rather than likely:

- SOCIALHOME_INTERNAL_URL hardcoded 127.0.0.1, which sys-svc-container rewrites
  to the bridge gateway whenever DOCKER_IN_CONTAINER is set -- from inside the
  Ansible container that address is the Ansible container, so both setup tasks
  would have burned five minutes of retries and failed the play.
- SH_WEBRTC_STUN_URL sat outside the coturn guard while the TURN pair sat
  inside it. Upstream reads by env PRESENCE, so the empty value would have
  beaten the in-app default and left WebRTC with no STUN at all.
- meta/csp.yml whitelisted blob: on worker-src. The bundle registers no service
  worker and constructs no Worker; the only blob of JavaScript is an audio
  worklet, and worklet modules load under script-src. Voice recording would
  have died with the app's own "Audio processing unavailable".
- tasks/main.yml hand-rolled meta: flush_handlers instead of including
  sys-svc-compose's finalize.yml, which silently skipped utils/post_deploy.yml.
- 01_setup.yml was gated on IS_STACK_HOST but not run_once, so a three-manager
  swarm would fire three concurrent setup POSTs at one ingress port.

TODO.md carried a claim that steered the CSP design and was simply false --
that img-src is not a directive csp_filters.py emits. It appends
"img-src * data: blob:" unconditionally at line 383.

lifecycle: alpha puts the role in the CI deploy matrix before it has ever run
anywhere, deliberately: the first CI round is its first execution. README and
TODO say so rather than claiming a maturity it does not have.

Still unverified: no browser has enforced the CSP, the swarm path was only
rendered, TURN against a real coturn is untested, and arm64 has not been
exercised. TODO.md lists them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-08-06 17:57:24 +02:00
1 parent 92faba5d2d
commit 2dbd6eb108
20 files changed
+318 -45

No files matched your search

+46 -28
View File
@@ -1,35 +1,45 @@
# SocialHome
# Social Home
## Description
Deploy **SocialHome**, a federated social network focused on content hubs and federation. This role provides a Docker-based scaffold and domain wiring so you can bring SocialHome into your Infinito.Nexus stack.
Deploy **[Social Home](https://github.com/social-home-io/socialhome)**, a privacy-first federated social platform built for a single household. One container, an embedded SQLite database, and peer-to-peer federation between households over Ed25519-signed WebRTC data channels — no ActivityPub, no message broker, no object store.
This is **not** [jaywink/socialhome](https://github.com/jaywink/socialhome), the Django content-hub project of the same name. Different codebase, different protocol, different maintainers.
## Overview
This role sets up a SocialHome application using Docker Compose with basic domain and port wiring. It follows your standard role layout and prepares the service to run behind your existing reverse proxy. The current version is a scaffold intended to be expanded with database/cache services and app-specific settings.
The role runs the upstream `ghcr.io/social-home-io/socialhome` image behind the central reverse proxy on its own canonical domain. Everything the application keeps lives in one volume at `/data`: the SQLite database, uploaded media, and installed apps. The administrator account is provisioned from the role's generated credential, and the role finishes the upstream first-boot wizard over the API so the front page renders a login form instead of a setup screen.
A rendered `socialhome.toml` is mounted alongside the environment file. It carries exactly one setting — `[standalone].external_url` — because that value has no `SH_*` environment equivalent upstream and federation pairing returns `422 NOT_CONFIGURED` without it.
## Cosmos
The diagram places SocialHome in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
The diagram places Social Home in the Infinito.Nexus cosmos: the components it deploys (capabilities), the central services it consumes (dependencies), and its outward reach (federation and bridged external networks).
```mermaid
flowchart LR
subgraph deps [Dependencies]
dep_svc_bkp_volume_2_local["svc-bkp-volume-2-local 💻"]
dep_web_app_dashboard["web-app-dashboard 🐳🐝"]
dep_web_app_keycloak["web-app-keycloak 🐳🐝"]
dep_web_app_prometheus["web-app-prometheus 🐳🐝"]
dep_web_svc_coturn["web-svc-coturn 🐳🐝"]
dep_web_svc_logout["web-svc-logout 🐳🐝"]
end
subgraph role [web-app-socialhome]
subgraph role [web-app-socialhome 🐳🐝]
svc_socialhome["socialhome"]
svc_coturn["coturn"]
svc_prometheus["prometheus"]
svc_logout["logout"]
svc_dashboard["dashboard"]
svc_sso["sso ❌"]
svc_container_backup["container_backup"]
end
dep_svc_bkp_volume_2_local -. "0..1" .-> svc_container_backup
dep_web_app_dashboard -. "0..1" .-> svc_dashboard
dep_web_app_keycloak -- "1:1" --> svc_sso
dep_web_app_prometheus -. "0..1" .-> svc_prometheus
dep_web_svc_coturn -. "0..1" .-> svc_coturn
dep_web_svc_logout -. "0..1" .-> svc_logout
```
@@ -37,17 +47,24 @@ Solid `1:1` edges are fixed relationships; dashed `0..1` edges are conditional (
## Features
- **Dockerized Scaffold:** Baseline Docker Compose integration and role structure to get you started quickly.
- **Domain & Port Wiring:** Integrates cleanly with your central domain/ports configuration.
- **Ready for Federation:** Intended to support ActivityPub-based federation once the application is fully wired.
- **Extensible Configuration:** Room for adding database, cache, worker processes, and environment tuning.
- **Single Container:** No database server, no cache, no worker, no object store. SQLite in one volume.
- **Headless Provisioning:** The administrator is seeded from the role's credential and the first-boot wizard is completed over the API, so no manual click-through is required.
- **Federation Ready:** The rendered `socialhome.toml` publishes the instance's external URL, which is what QR pairing hands to peer households.
- **Own STUN/TURN:** When `web-svc-coturn` is part of the deployment, WebRTC uses it with time-limited HMAC credentials instead of a third-party STUN server.
- **No Outbound Surprises:** The upstream app catalog fetch to github.com is disabled. Without `web-svc-coturn` the app still falls back to its built-in Google STUN server, since suppressing the variable entirely would leave WebRTC with none at all.
- **Desktop Integration Hooks:** This README ensures inclusion in the Web App Desktop overview.
## Limitations
- The role has never been deployed. `lifecycle` is `alpha`, so CI deploys it from the next run on; that run is its first execution. See [TODO.md](TODO.md).
- No SSO. The application authenticates against its own user table and has no OIDC client, so the reverse proxy is not SSO-gated — federation endpoints must stay publicly reachable.
- The application ships no logout control at `2026.6.16`, so the Playwright administrator persona is declared blocked.
## Quick Setup
### Development
Clone, set up the workstation, and deploy SocialHome onto the local stack:
Clone, set up the workstation, and deploy Social Home onto the local stack:
```bash
git clone https://github.com/infinito-nexus/core.git
@@ -58,33 +75,34 @@ make compose-deploy mode=reinstall apps=web-app-socialhome full_cycle=false
### Production
Install SocialHome directly onto the target machine — clone the repository, install the OS prerequisites and the repository toolchain, then deploy against localhost over a local connection (no SSH, no container):
Run the published image to provision the inventory and deploy Social Home to a managed server (the mounted volume persists the inventory):
```bash
git clone https://github.com/infinito-nexus/core.git
cd core
bash scripts/install/package.sh
make install
source scripts/meta/env/load.sh
APP=web-app-socialhome
HOST=<your-server>
TLS_MODE=self_signed
SSH_PUBLIC_KEY="<your-ssh-public-key>"
INVENTORY=inventories/production
infinito administration inventory provision "$INVENTORY" \
--inventory-file "$INVENTORY/devices.yml" \
--host localhost \
--include "$APP" \
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}"
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
--password-file "$INVENTORY/.password" \
--diff -vv
docker run --rm -it \
-v "$PWD/inventories:/etc/infinito.nexus/inventories" \
-e APP="$APP" -e HOST="$HOST" -e TLS_MODE="$TLS_MODE" -e SSH_PUBLIC_KEY="$SSH_PUBLIC_KEY" \
ghcr.io/infinito-nexus/core/debian bash -c '
INVENTORY=/etc/infinito.nexus/inventories/production
infinito administration inventory provision "$INVENTORY" \
--inventory-file "$INVENTORY/devices.yml" \
--host "$HOST" \
--include "$APP" \
--vars "{\"TLS_MODE\": \"$TLS_MODE\", \"users\": {\"administrator\": {\"authorized_keys\": [\"$SSH_PUBLIC_KEY\"]}}}" &&
infinito administration deploy dedicated "$INVENTORY/devices.yml" \
--password-file "$INVENTORY/.password" \
--diff -vv'
```
## Further Resources
- [SocialHome Project](https://socialhome.network/)
- [ActivityPub (W3C)](https://www.w3.org/TR/activitypub/)
- [Social Home source](https://github.com/social-home-io/socialhome)
- [Published container images](https://github.com/social-home-io/socialhome/pkgs/container/socialhome)
- [TURN REST API credential scheme](https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00)
## Credits
+49 -1
View File
@@ -1,3 +1,51 @@
# Todo
- Implement <https://socialhome.network/>
## Never deployed
`lifecycle: alpha` puts this role into the CI deploy matrix before it has ever run against a real
host, deliberately: the first CI round is its first execution. Everything below the first live
`make compose-deploy apps=web-app-socialhome` is design-by-source-reading.
Open items that only a live run can settle:
- `index.html` ships an inline pre-paint theme bootstrap, and the rendered header is
`script-src-elem 'self' https://cdn.…` with no hash, so it is blocked and the first paint
flashes the wrong theme. Pinning `csp.hashes.script-src-elem` fixes it but has to be re-pinned
on every image bump; the flash is the cheaper trade until the theme matters.
- `meta/csp.yml` whitelists `blob:` on `script-src` for the audio worklet the DM composer builds
with `URL.createObjectURL`; worklet modules load under `script-src`, not `worker-src`. The
bundle registers no service worker and constructs no `Worker`. Whether the Preact bundle also
needs `unsafe-inline` for style attributes is unknown until a browser reports violations.
- The published image is multi-arch, but only `linux/amd64` has been exercised.
- `Dockerfile.sh` exposes 8124 for aiolibdatachannel signalling beside 8099. Nothing listens on
it at rest, so only 8099 is published; whether it binds lazily during a call is untested.
## Blocked personas
`PERSONA_ADMINISTRATOR_BLOCKED=true`
: Social Home 2026.6.16 imports `logout` from `client/src/store/auth.ts` in exactly one place —
`client/src/main.tsx` wires it as the 401 handler via `setUnauthorizedHandler(logout)`. No
component renders a logout control, so `personas/utils/logout.js` has nothing to click and the
admin journey cannot complete. The alternative — injecting a `[data-injected-logout]` button via
`templates/javascript.js.j2` — was rejected: the model
(`roles/web-app-prometheus/templates/javascript.js.j2`) points the button at
`OIDC.CLIENT.LOGOUT_URL`, which only exists for OIDC-gated roles, and this role deliberately runs
without SSO. Path back: upstream ships a logout control (or an in-app logout endpoint we can
target), then drop the flag and keep the `adminInteraction` already wired in the spec.
`PERSONA_BIBER_BLOCKED=true`
: The app authenticates against its own `platform_users` table. There is no OIDC client and no
auto-provisioning, so the biber account never exists inside Social Home. Path back: upstream
grows an OIDC/trusted-header auth path.
## Deferred wiring
- `SH_MAX_STORAGE_BYTES` is pinned to 4 GiB in `vars/main.yml` so the app's media cap stays inside
`services.socialhome.min_storage` (5 GB). Raising one without the other lets the volume outgrow
its declared budget.
- `SH_APPS_CATALOG_URL` is emptied so the container never fetches the app catalog from github.com.
Set it to a self-hosted catalog when one exists.
- `SH_CORS_ALLOWED_ORIGINS` is unset — the SPA is same-origin. Needed only if a third-party client
should talk to the API.
- The GFS / global-server side of upstream (`Dockerfile.gfs`) is not deployed. Only the household
instance (`Dockerfile.sh`) is.
@@ -12,21 +12,21 @@ test.beforeEach(async ({ page }) => {
await page.context().clearCookies();
});
test("Socialhome front page is served under canonical domain with TLS", async ({ page }) => {
test("Social Home front page is served under canonical domain with TLS", async ({ page }) => {
const response = await page.goto(`${appBaseUrl}/`);
expect(response, "Expected Socialhome response").toBeTruthy();
expect(response.status(), "Expected Socialhome front page status < 400").toBeLessThan(400);
expect(response, "Expected Social Home response").toBeTruthy();
expect(response.status(), "Expected Social Home front page status < 400").toBeLessThan(400);
expect(
response.url().includes(canonicalDomain),
`Expected canonical domain "${canonicalDomain}" to back the Socialhome URL`
`Expected canonical domain "${canonicalDomain}" to back the Social Home URL`
).toBe(true);
const headers = response.headers();
expect(headers["strict-transport-security"], "Socialhome must emit HSTS").toBeTruthy();
expect(headers["strict-transport-security"], "Social Home must emit HSTS").toBeTruthy();
});
test("Socialhome returns HTML content under canonical domain", async ({ request }) => {
test("Social Home returns HTML content under canonical domain", async ({ request }) => {
const response = await request.get(`${appBaseUrl}/`);
expect(response.status(), "Expected Socialhome front page status < 400").toBeLessThan(400);
expect(response.status(), "Expected Social Home front page status < 400").toBeLessThan(400);
const contentType = response.headers()["content-type"] || "";
expect(
contentType.includes("text/html"),
@@ -34,6 +34,14 @@ test("Socialhome returns HTML content under canonical domain", async ({ request
).toBe(true);
});
test("Social Home reports subsystem health on /healthz", async ({ request }) => {
const response = await request.get(`${appBaseUrl}/healthz`);
expect(response.status(), "Expected /healthz to report healthy").toBe(200);
const body = await response.json();
expect(body.status, "Expected /healthz status ok").toBe("ok");
expect(body.subsystems.db, "Expected the SQLite subsystem to answer SELECT 1").toBe("ok");
});
// Persona scenarios.
// Bodies live in the shared helper roles/test-e2e-playwright/files/personas
// so every role's persona flow stays consistent.
@@ -49,15 +57,14 @@ test("biber: app → universal logout", async ({ page }) => {
test("administrator: app → universal logout", async ({ page }) => {
await runAdminFlow(page, {
adminInteraction: async (interactivePage) => {
// web-app-socialhome admin-only interaction: open a management surface.
const link = interactivePage
.getByRole("link", { name: /^(stream|profile|admin|federation)$/i })
.getByRole("link", { name: /^(feed|spaces|calendar|federation|admin)$/i })
.first();
if (await link.isVisible({ timeout: 10_000 }).catch(() => false)) {
await link.click().catch(() => {});
await interactivePage.waitForLoadState("domcontentloaded", { timeout: 30_000 }).catch(() => {});
await expect(interactivePage.locator("body")).toContainText(
/stream|profile|federation|content|socialhome/i,
/feed|spaces|calendar|federation|admin/i,
{ timeout: 30_000 },
);
}
+6
View File
@@ -0,0 +1,6 @@
---
whitelist:
media-src:
- "blob:"
script-src:
- "blob:"
@@ -0,0 +1,4 @@
---
canonical:
- socialhome.{{ DOMAIN_PRIMARY }}
aliases: []
+3 -1
View File
@@ -1,2 +1,4 @@
---
homepage: https://socialhome.network/
logo:
class: fa-solid fa-house-signal
homepage: https://github.com/social-home-io/socialhome
+3 -1
View File
@@ -2,7 +2,7 @@
# nocheck: info-media no official demo video
galaxy_info:
author: Kevin Veen-Birkenbach
description: Federated social network for content hubs and ActivityPub-based federation across the fediverse
description: Privacy-first social platform for households, federating peer-to-peer over WebRTC on an embedded SQLite store
license: Infinito.Nexus Community License (Non-Commercial)
company: |
Kevin Veen-Birkenbach
@@ -11,6 +11,8 @@ galaxy_info:
- socialhome
- social
- federated
- webrtc
- sqlite
issue_tracker_url: https://s.infinito.nexus/issues
min_ansible_version: "2.9"
platforms:
@@ -0,0 +1,3 @@
---
local:
subnet: 192.168.74.0/24
+6
View File
@@ -0,0 +1,6 @@
---
credentials:
administrator_password:
description: Password of the Social Home standalone administrator account
algorithm: alphanumeric
validation: ^[A-Za-z0-9]{32,}$
+30 -1
View File
@@ -1,14 +1,38 @@
---
socialhome:
healthcheck:
flavor: http_status
path: healthz
modes:
host:
enabled: false
lifecycle: pre-alpha
compose:
enabled: true
# Exception: the orchestrator chains swarm -> compose -> host under
# mode_fail_fast, so a red swarm job here skips 92 unrelated jobs. Flip to
# true once compose has been green once.
swarm:
enabled: false
# nocheck: default-placement-manager embedded sqlite in a node-local volume; no clustered storage
placement: manager
image: ghcr.io/social-home-io/socialhome
version: "2026.6.16"
ports:
local:
http: 8089
internal:
http: 8099
lifecycle: alpha
min_storage: "5GB"
cpus: "0.5"
mem_reservation: "256m"
mem_limit: "1g"
pids_limit: 512
# nocheck: playwright-service-flag — TURN/STUN server, no Playwright surface (provider: web-svc-coturn)
coturn:
bond: 1
enabled: "{{ 'web-svc-coturn' in group_names }}"
shared: "{{ 'web-svc-coturn' in group_names }}"
prometheus:
bond: 1
enabled: "{{ 'web-app-prometheus' in group_names }}"
@@ -27,3 +51,8 @@ dashboard:
sso:
enabled: false
shared: false
# nocheck: playwright-service-flag, dynamic-flag infrastructure backup consumer
container_backup: # nocheck: bond-baseline backup host joins outside the variant topology
bond: 1
enabled: "{{ 'svc-bkp-volume-2-local' in group_names }}"
shared: true
+2
View File
@@ -0,0 +1,2 @@
---
administrator: {}
@@ -5,6 +5,11 @@
# variant pinning every dynamic services.yml flag to true so the
# matrix exercises the in-group_names branch of web-app-socialhome.
- services:
container_backup:
enabled: true
coturn:
enabled: true
shared: true
dashboard:
enabled: true
shared: true
@@ -15,6 +20,12 @@
enabled: true
shared: true
- services:
container_backup:
enabled: false
shared: false # nocheck: variants-static-override backup shared stays literally true
coturn:
enabled: false
shared: false
dashboard:
enabled: false
shared: false
+15
View File
@@ -0,0 +1,15 @@
---
data:
type: volume
name: socialhome_data
mounts:
- service: socialhome
target: /data
config:
type: config
source: "{{ [lookup('container', 'web-app-socialhome', 'directories.volumes'), 'socialhome.toml'] | path_join }}"
mode: "0444"
mounts:
- service: socialhome
target: /etc/socialhome/socialhome.toml
@@ -0,0 +1,28 @@
---
# Exception: upstream provisions the admin from SH_ADMIN_PASSWORD in
# platform/standalone/adapter.py on_startup but never calls mark_complete(), so the SPA keeps
# rendering the setup wizard. Re-posting the same credentials is a no-op for provision_admin()
# and flips the setup flag; 409 means a previous run already did it.
- name: "⏳ Wait for Social Home at {{ SOCIALHOME_INTERNAL_URL }}/healthz"
uri_retry:
url: "{{ SOCIALHOME_INTERNAL_URL }}/healthz"
method: GET
status_code: 200
register: socialhome_health
retries: 30
delay: 5
until: socialhome_health.status == 200
- name: "🔐 Mark the standalone first-boot setup complete"
uri_retry:
url: "{{ SOCIALHOME_INTERNAL_URL }}/api/setup/standalone"
method: POST
body_format: json
body:
username: "{{ SOCIALHOME_ADMIN_USERNAME }}"
password: "{{ SOCIALHOME_ADMIN_PASSWORD }}"
status_code: [201, 409]
register: socialhome_setup
changed_when: socialhome_setup.status == 201
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
+20 -2
View File
@@ -1,3 +1,21 @@
---
# Pre-alpha scaffold: see TODO.md. The role does not deploy SocialHome yet.
# A full implementation will follow the standard webapp pattern via sys-stk-full.
- name: "🧩 load docker, proxy for '{{ application_id }}'"
include_role:
name: sys-stk-full
vars:
compose_handlers_flush: false
- name: "📄 Render socialhome.toml for '{{ application_id }}'"
stack_host_template:
src: socialhome.toml.j2
dest: "{{ SOCIALHOME_CONFIG_HOST_FILE }}"
mode: "0444"
notify: compose up
- name: "🐳 Finalize compose deploy for '{{ application_id }}'"
include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/finalize.yml') }}"
- name: "🔐 Complete the standalone first-boot setup for '{{ application_id }}'"
include_tasks: 01_setup.yml
run_once: true
when: IS_STACK_HOST | bool
@@ -0,0 +1,15 @@
{% include 'roles/sys-svc-compose/templates/base.yml.j2' %}
{% set service_name = entity_name %}
{{ service_name }}:
{{ lookup('container_image', application_id, service_name) }}
{{ lookup('compose_only', 'container_name', entity_name) }}
{% include 'roles/sys-svc-container/templates/base.yml.j2' %}
{{ lookup('container_ports', [service_name, 'http', DOCKER_BIND_HOST]) | indent(4) }}
{{ lookup('container_depends_on', application_id) | indent(4) }}
{{ lookup('container_networks') | indent(4) }}
{{ lookup('container_healthcheck', service_name) | indent(4) }}
{{ lookup('container_volumes', application_id, service_name) | indent(4) }}
{{ lookup('compose_volumes', application_id) }}
{{ lookup('compose_networks') }}
+27
View File
@@ -0,0 +1,27 @@
{% include 'roles/sys-svc-container/templates/env.j2' %}
SH_CONFIG={{ SOCIALHOME_CONFIG_FILE | dotenv_quote }}
SH_LISTEN_PORT={{ SOCIALHOME_LISTEN_PORT }}
SH_INSTANCE_NAME={{ SOCIALHOME_INSTANCE_NAME | dotenv_quote }}
SH_SECURE_COOKIES=true
SH_MAX_STORAGE_BYTES={{ SOCIALHOME_MAX_STORAGE_BYTES }}
SH_APPS_CATALOG_URL=
{# Exception: upstream reads these by env PRESENCE, so an empty value beats the
in-app default and would leave WebRTC with no STUN server at all. #}
{% if SOCIALHOME_COTURN_ENABLED | bool %}
SH_WEBRTC_STUN_URL={{ SOCIALHOME_STUN_URL | dotenv_quote }}
SH_WEBRTC_TURN_URL={{ SOCIALHOME_TURN_URL | dotenv_quote }}
SH_WEBRTC_TURN_SECRET={{ SOCIALHOME_COTURN_SECRET | dotenv_quote }}
{% endif %}
SH_ADMIN_USERNAME={{ SOCIALHOME_ADMIN_USERNAME | dotenv_quote }}
SH_ADMIN_PASSWORD={{ SOCIALHOME_ADMIN_PASSWORD | dotenv_quote }}
@@ -4,4 +4,10 @@ CANONICAL_DOMAIN={{ lookup('domain', application_id) | dotenv_quote }}
LOGOUT_SERVICE_ENABLED={{ lookup('config', application_id, 'services.logout.enabled') | string | lower }}
# nocheck: persona-block-rationale — Social Home 2026.6.16 wires `logout` only as the 401 handler and renders no logout control, so personas/utils/logout.js has nothing to click and the admin journey cannot close (see TODO.md "Blocked personas").
PERSONA_ADMINISTRATOR_BLOCKED=true
# nocheck: persona-block-rationale — the app authenticates against its own platform_users table with no OIDC client and no auto-provisioning, so the biber account never exists inside Social Home (see TODO.md "Blocked personas").
PERSONA_BIBER_BLOCKED=true
SSO_SERVICE_ENABLED={{ lookup('config', application_id, 'services.sso.enabled') | string | lower }}
@@ -0,0 +1,5 @@
# Exception: external_url has no SH_* env equivalent (socialhome/config.py `_split_toml` keeps
# [standalone] in platform_options), and without it POST /api/pairing/initiate answers 422
# NOT_CONFIGURED and federation pairing is dead. That is the only reason this file exists.
[standalone]
external_url = "{{ SOCIALHOME_EXTERNAL_URL }}"
+22 -1
View File
@@ -1,2 +1,23 @@
---
application_id: web-app-socialhome
application_id: "web-app-socialhome"
entity_name: "{{ application_id | get_entity_name }}"
SOCIALHOME_EXTERNAL_URL: "{{ lookup('tls', application_id, 'url.base') }}"
SOCIALHOME_INTERNAL_URL: "http://{{ DOCKER_REACH_HOST }}:{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.ports.local.http') }}"
SOCIALHOME_LISTEN_PORT: "{{ lookup('config', application_id, 'services.' ~ entity_name ~ '.ports.internal.http') }}"
SOCIALHOME_INSTANCE_NAME: "{{ defaults_service_provider.platform.titel }}"
SOCIALHOME_ADMIN_USERNAME: "{{ lookup('users', 'administrator').username }}"
SOCIALHOME_ADMIN_PASSWORD: "{{ lookup('config', application_id, 'credentials.administrator_password') }}"
SOCIALHOME_CONFIG_FILE: "/etc/socialhome/socialhome.toml"
SOCIALHOME_CONFIG_HOST_FILE: "{{ [lookup('container', application_id, 'directories.volumes'), 'socialhome.toml'] | path_join }}"
SOCIALHOME_MAX_STORAGE_BYTES: 4294967296
SOCIALHOME_COTURN_ENABLED: "{{ lookup('config', application_id, 'services.coturn.enabled') | bool }}"
SOCIALHOME_COTURN_HOST: "{{ lookup('domain', 'web-svc-coturn') if SOCIALHOME_COTURN_ENABLED | bool else '' }}"
SOCIALHOME_COTURN_PORT: "{{ lookup('config', 'web-svc-coturn', 'services.coturn.ports.public.stun_turn') if SOCIALHOME_COTURN_ENABLED | bool else '' }}"
SOCIALHOME_COTURN_SECRET: "{{ lookup('config', 'web-svc-coturn', 'credentials.auth_secret') if SOCIALHOME_COTURN_ENABLED | bool else '' }}"
SOCIALHOME_STUN_URL: "{{ 'stun:' ~ SOCIALHOME_COTURN_HOST ~ ':' ~ SOCIALHOME_COTURN_PORT if SOCIALHOME_COTURN_ENABLED | bool else '' }}"
SOCIALHOME_TURN_URL: "{{ 'turn:' ~ SOCIALHOME_COTURN_HOST ~ ':' ~ SOCIALHOME_COTURN_PORT if SOCIALHOME_COTURN_ENABLED | bool else '' }}"