mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
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:
1 parent
92faba5d2d
commit
2dbd6eb108
20 files changed
+318
-45
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
whitelist:
|
||||
media-src:
|
||||
- "blob:"
|
||||
script-src:
|
||||
- "blob:"
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
canonical:
|
||||
- socialhome.{{ DOMAIN_PRIMARY }}
|
||||
aliases: []
|
||||
@@ -1,2 +1,4 @@
|
||||
---
|
||||
homepage: https://socialhome.network/
|
||||
logo:
|
||||
class: fa-solid fa-house-signal
|
||||
homepage: https://github.com/social-home-io/socialhome
|
||||
@@ -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
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
credentials:
|
||||
administrator_password:
|
||||
description: Password of the Social Home standalone administrator account
|
||||
algorithm: alphanumeric
|
||||
validation: ^[A-Za-z0-9]{32,}$
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}"
|
||||
@@ -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') }}
|
||||
@@ -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 }}"
|
||||
@@ -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 '' }}"
|
||||
Reference in new issue
Block a user