Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 43 additions & 37 deletions bundles/lemmy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
# postgres + pict-rs (image hosting). lemmy + lemmy-ui on crow-federation
# (Caddy reverse-proxies to lemmy-ui:1234); DB + pict-rs on default.
#
# Lemmy-UI serves the SPA AND proxies /api/v3/* to lemmy:8536 internally,
# so a single Caddy federation site is enough.
# lemmy-ui serves the web app only; it does NOT proxy the API (GET /api/v3/site on
# lemmy-ui answers 404, checked live on 0.19.17). The public site must send /api,
# /pictrs, /feeds, /nodeinfo, /.well-known and ActivityPub requests to lemmy:8536 and
# everything else to lemmy-ui:1234.
#
# Data:
# ~/.crow/lemmy/postgres/ Postgres data dir
# ~/.crow/lemmy/pictrs/ pict-rs sled db + media files
# ~/.crow/lemmy/lemmy.hjson Generated once at install time; not volume-mounted
# because lemmy reads env-var overrides too.
# volume "postgres" Postgres data (posts, users, the instance's private keys)
# volume "pictrs" pict-rs sled db + media files
# Both are Docker-managed volumes of this compose project: the images run as their
# own uids (postgres 70, pict-rs 991), which a host folder created by the Docker
# daemon as root does not suit, and uninstall with delete data removes both.
# Lemmy's config is written inside the container at every start (see the entrypoint).
#
# Images: dessalines/lemmy:0.19.17 and dessalines/lemmy-ui:0.19.17 (floats
# within 0.19.x — verify latest + CVE feed at implementation time per the
Expand All @@ -22,6 +26,10 @@ networks:
external: true
default:

volumes:
postgres:
pictrs:

services:
postgres:
image: postgres:16-alpine
Expand All @@ -33,7 +41,7 @@ services:
POSTGRES_PASSWORD: ${LEMMY_DB_PASSWORD}
POSTGRES_DB: lemmy
volumes:
- ${LEMMY_DATA_DIR:-~/.crow/lemmy}/postgres:/var/lib/postgresql/data
- postgres:/var/lib/postgresql/data
init: true
mem_limit: 512m
restart: unless-stopped
Expand All @@ -52,15 +60,16 @@ services:
- default
environment:
PICTRS__SERVER__API_KEY: ${LEMMY_PICTRS_API_KEY}
PICTRS__MEDIA__VIDEO_CODEC: vp9
PICTRS__MEDIA__GIF__MAX_WIDTH: "256"
PICTRS__MEDIA__GIF__MAX_HEIGHT: "256"
PICTRS__MEDIA__GIF__MAX_AREA: "65536"
PICTRS__MEDIA__GIF__MAX_FRAME_COUNT: "400"
# pict-rs 0.5 names (0.4's MEDIA__GIF__* and MEDIA__VIDEO_CODEC are ignored by 0.5)
PICTRS__MEDIA__VIDEO__VIDEO_CODEC: vp9
PICTRS__MEDIA__ANIMATION__MAX_WIDTH: "256"
PICTRS__MEDIA__ANIMATION__MAX_HEIGHT: "256"
PICTRS__MEDIA__ANIMATION__MAX_AREA: "65536"
PICTRS__MEDIA__ANIMATION__MAX_FRAME_COUNT: "400"
RUST_LOG: warn,tracing=warn
RUST_BACKTRACE: full
volumes:
- ${LEMMY_DATA_DIR:-~/.crow/lemmy}/pictrs:/mnt
- pictrs:/mnt
init: true
mem_limit: 512m
restart: unless-stopped
Expand All @@ -79,60 +88,56 @@ services:
RUST_LOG: "warn"
RUST_BACKTRACE: "full"
LEMMY_DATABASE_URL: "postgres://lemmy:${LEMMY_DB_PASSWORD}@postgres:5432/lemmy"
LEMMY_CONFIG_LOCATION: /config/config.hjson
# The image runs as uid 1000 (lemmy) with no writable config folder, so the
# entrypoint writes the config into the container's own /tmp at every start.
LEMMY_CONFIG_LOCATION: /tmp/crow-lemmy/config.hjson
# Secrets reach the entrypoint through the environment and are expanded by its
# shell ($${...}), so the script text that stays in PID 1's command line (init: true)
# holds variable names, never values.
CROW_LEMMY_DB_PASSWORD: "${LEMMY_DB_PASSWORD}"
CROW_LEMMY_HOSTNAME: "${LEMMY_HOSTNAME:?LEMMY_HOSTNAME is required}"
CROW_LEMMY_PICTRS_API_KEY: "${LEMMY_PICTRS_API_KEY}"
CROW_LEMMY_ADMIN_PASSWORD: "${LEMMY_ADMIN_PASSWORD}"
volumes:
- ./config:/config:ro
# Lemmy 0.19 reads only the keys below (federation on/off, captcha and registration
# are site settings in Admin -> Settings). tls_enabled: true because Caddy serves
# https; Lemmy itself still listens on plain http. `setup` is used once, at the
# first start against an empty database, to create the admin.
entrypoint:
- sh
- -c
- |
mkdir -p /config
cat > /config/config.hjson <<HJSON
umask 077
case "$${CROW_LEMMY_HOSTNAME}" in
""|*[!a-z0-9.:-]*|[.:-]*|*[.:-]|*..*) echo "LEMMY_HOSTNAME must be a bare lowercase hostname such as lemmy.example.com (no https://, no path)" >&2; exit 64 ;;
esac
mkdir -p /tmp/crow-lemmy
cat > /tmp/crow-lemmy/config.hjson <<HJSON
{
hostname: "${LEMMY_HOSTNAME}"
hostname: "$${CROW_LEMMY_HOSTNAME}"
bind: "0.0.0.0"
port: 8536
tls_enabled: false
database: {
host: postgres
port: 5432
database: lemmy
user: lemmy
password: "$${CROW_LEMMY_DB_PASSWORD}"
}
tls_enabled: true
pictrs: {
url: "http://pictrs:8080/"
api_key: "$${CROW_LEMMY_PICTRS_API_KEY}"
}
federation: {
enabled: ${LEMMY_FEDERATION_ENABLED:-true}
debug: false
}
setup: {
admin_username: "admin_pending"
admin_password: "$${CROW_LEMMY_ADMIN_PASSWORD}"
site_name: "Crow Lemmy"
}
captcha: { enabled: true, difficulty: medium }
opentelemetry_url: null
}
HJSON
exec /app/lemmy
exec lemmy_server
init: true
mem_limit: 768m
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8536/api/v3/site >/dev/null 2>&1 || exit 1"]
test: ["CMD-SHELL", "curl -fsS -o /dev/null http://127.0.0.1:8536/api/v3/site || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 60s
start_interval: 5s

lemmy-ui:
image: dessalines/lemmy-ui:0.19.17
Expand All @@ -151,8 +156,9 @@ services:
mem_limit: 256m
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:1234/ >/dev/null 2>&1 || exit 1"]
test: ["CMD-SHELL", "curl -fsS -o /dev/null http://127.0.0.1:1234/ || exit 1"]
interval: 30s
timeout: 10s
retries: 10
start_period: 60s
start_interval: 5s
14 changes: 6 additions & 8 deletions bundles/lemmy/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "lemmy",
"name": "Lemmy",
"version": "1.0.3",
"version": "1.0.4",
"description": "Federated link aggregator and discussion platform over ActivityPub — Reddit-alternative on the fediverse. Posts, comments, communities, votes; remote Lemmy/Mastodon/Kbin users can follow your communities and comment cross-server.",
"type": "bundle",
"author": "Crow",
Expand Down Expand Up @@ -31,16 +31,14 @@
"recommended_disk_mb": 50000
},
"env_vars": [
{ "name": "LEMMY_HOSTNAME", "description": "Public domain (subdomain; path-mounts break ActivityPub).", "required": true },
{ "name": "LEMMY_HOSTNAME", "description": "Public hostname, bare (no https://, no path), e.g. lemmy.example.com. Lemmy stores it in every federated identity at its first start, so choose it before installing.", "required": true },
{ "name": "LEMMY_DB_PASSWORD", "description": "Password for the bundled Postgres role. Generated by Crow at install; never shown.", "required": true, "secret": true, "generate": "secret" },
{ "name": "LEMMY_PICTRS_API_KEY", "description": "Shared secret Lemmy uses to talk to pict-rs. Generated by Crow at install; never shown.", "required": true, "secret": true, "generate": "secret" },
{ "name": "LEMMY_ADMIN_PASSWORD", "description": "Password of the first Lemmy admin (username admin_pending). Crow generates it at install and saves it in Settings → Passwords (this machine only); Lemmy creates the admin with it on its first successful start. (This bundle's container does not start yet — see the notes.)", "required": true, "secret": true, "generate": "secret", "keychain": true, "keychain_label": "admin password", "keychain_username": "admin_pending" },
{ "name": "LEMMY_JWT", "description": "Admin login JWT (obtain via POST /api/v3/user/login after registering the admin via the web UI).", "required": false, "secret": true },
{ "name": "LEMMY_URL", "description": "Internal URL the MCP server uses to reach Lemmy's API (default http://lemmy:8536 over crow-federation).", "default": "http://lemmy:8536", "required": false },
{ "name": "LEMMY_OPEN_REGISTRATION", "description": "Allow new user signups (true/false). Default false — opens registration only after moderation tooling is configured.", "default": "false", "required": false },
{ "name": "LEMMY_FEDERATION_ENABLED", "description": "Whether to federate with other ActivityPub servers (true/false).", "default": "true", "required": false }
{ "name": "LEMMY_ADMIN_PASSWORD", "description": "Password of the first Lemmy admin (username admin_pending). Crow generates it at install and saves it in Settings → Passwords (this machine only); Lemmy creates the admin with it on its first start against an empty database; change it after your first login.", "required": true, "secret": true, "generate": "secret", "keychain": true, "keychain_label": "admin password", "keychain_username": "admin_pending" },
{ "name": "LEMMY_JWT", "description": "Admin login JWT: POST /api/v3/user/login with username admin_pending and the admin password from Settings → Passwords, then paste the returned jwt here.", "required": false, "secret": true },
{ "name": "LEMMY_URL", "description": "Internal URL the MCP server uses to reach Lemmy's API (default http://lemmy:8536 over crow-federation).", "default": "http://lemmy:8536", "required": false }
],
"ports": [],
"webUI": null,
"notes": "Four containers (lemmy + lemmy-ui + postgres + pict-rs). Expose via caddy_add_federation_site { domain: LEMMY_HOSTNAME, upstream: 'lemmy-ui:1234', profile: 'activitypub' }. The UI proxies /api/v3/ to lemmy:8536 internally. Initial admin registers via the /signup page during the first-boot setup wizard (no CLI bootstrap needed). Known issue: the entrypoint runs a path the pinned Lemmy image does not have, so the container does not start yet; a fix needs a live test."
"notes": "Four containers (lemmy + lemmy-ui + postgres + pict-rs); data lives in two Docker volumes (database, images), removed by uninstall with delete data. On its first start Lemmy creates the admin admin_pending with the generated password saved in Settings → Passwords; log in with it, then change the password and rename the account or add your own admin. lemmy-ui does not proxy the API: the public site must send /api, /pictrs, /feeds, /nodeinfo, /.well-known and ActivityPub requests (Accept: application/activity+json) to lemmy:8536 and the rest to lemmy-ui:1234. caddy_add_federation_site takes one upstream, so that split is a hand-written Caddy site for now. Federation, registration and captcha are site settings in Lemmy's Admin → Settings (Lemmy 0.19 does not read them from its config file). Licensing: Lemmy and lemmy-ui are AGPL-3.0 (image labels: AGPL-3.0-or-later), pict-rs is AGPL-3.0-only, Postgres uses the PostgreSQL License. Crow pulls the unmodified upstream images and runs them as separate containers, so Crow's own code stays MIT; a hosted Crow offering that runs Lemmy for other people must offer them the source of the Lemmy version it runs, including any changes (AGPL section 13)."
}
8 changes: 5 additions & 3 deletions bundles/lemmy/scripts/backup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ set -euo pipefail

STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_ROOT="${CROW_HOME:-$HOME/.crow}/backups/lemmy"
DATA_DIR="${LEMMY_DATA_DIR:-$HOME/.crow/lemmy}"

mkdir -p "$BACKUP_ROOT"
WORK="$(mktemp -d)"
Expand All @@ -18,8 +17,11 @@ if docker ps --format '{{.Names}}' | grep -qw crow-lemmy-postgres; then
docker exec crow-lemmy-postgres rm "/tmp/lemmy-${STAMP}.pgcustom"
fi

# pict-rs sled + files
tar -C "$DATA_DIR/pictrs" -cf "$WORK/pictrs.tar" . 2>/dev/null || true
# pict-rs sled + files (a Docker-managed volume, read through the running container)
# A hot copy: pict-rs keeps running, so its sled db may need pict-rs's own repair on restore.
docker ps --format '{{.Names}}' | grep -qw crow-lemmy-pictrs || { echo "crow-lemmy-pictrs is not running; no backup written" >&2; exit 1; }
docker exec crow-lemmy-pictrs tar -C /mnt -cf - . > "$WORK/pictrs.tar"
[ -s "$WORK/pictrs.tar" ] || { echo "pict-rs archive is empty; no backup written" >&2; exit 1; }

OUT="${BACKUP_ROOT}/lemmy-${STAMP}.tar.zst"
if command -v zstd >/dev/null 2>&1; then
Expand Down
21 changes: 8 additions & 13 deletions bundles/lemmy/scripts/post-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,19 @@ cat <<EOF

Lemmy stack is up. Next steps:

1. Expose via Caddy (one-time):
caddy_add_federation_site {
domain: "${LEMMY_HOSTNAME:-lemmy.example.com}",
upstream: "lemmy-ui:1234",
profile: "activitypub"
}
1. Lemmy created the admin admin_pending with the password Crow generated
at install (Settings -> Passwords). There is no setup wizard.

2. Open https://${LEMMY_HOSTNAME:-<your-domain>}/ and complete the setup
wizard (admin username + password + site name). The compose
entrypoint creates a first admin, admin_pending, with the password
Crow generated at install (Settings -> Passwords).
2. Expose ${LEMMY_HOSTNAME:-<your-domain>} with a Caddy site that sends /api,
/pictrs, /feeds, /nodeinfo, /.well-known and ActivityPub requests to
lemmy:8536 and everything else to lemmy-ui:1234 (lemmy-ui does not proxy
the API).

3. Obtain a JWT:
curl -X POST https://${LEMMY_HOSTNAME:-<your-domain>}/api/v3/user/login \\
-H 'Content-Type: application/json' \\
-d '{"username_or_email":"admin","password":"<your-pw>"}'
Paste the returned jwt into .env as LEMMY_JWT, then:
crow bundle restart lemmy
-d '{"username_or_email":"admin_pending","password":"<from Settings -> Passwords>"}'
Put the returned jwt in LEMMY_JWT, then restart the extension.

4. Verify:
lemmy_status {}
Expand Down
18 changes: 6 additions & 12 deletions bundles/lemmy/skills/lemmy.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,16 @@ Gated by F.0's hardware check. Refused below **1 GB effective RAM after committe

## First-run bootstrap

1. After install, expose via Caddy:
```
caddy_add_federation_site {
domain: "lemmy.example.com",
upstream: "lemmy-ui:1234",
profile: "activitypub"
}
```
2. Open https://lemmy.example.com/ and complete the setup wizard (admin username + password + site name). The compose entrypoint creates a first admin, `admin_pending`, with a password Crow generated at install (Settings → Passwords); log in with it if the wizard does not appear, and rename or replace the account.
1. On its first start Lemmy creates the admin `admin_pending` with the password Crow generated at install (Settings → Passwords). There is no setup wizard.
2. Expose it on its own subdomain. lemmy-ui serves the web app only and does **not** proxy the API, so the site must split traffic: `/api`, `/pictrs`, `/feeds`, `/nodeinfo`, `/.well-known` and ActivityPub requests (`Accept: application/activity+json`) go to `lemmy:8536`, everything else to `lemmy-ui:1234`. `caddy_add_federation_site` takes a single upstream, so write this Caddy site by hand for now.
3. Obtain an auth JWT:
```bash
curl -X POST https://lemmy.example.com/api/v3/user/login \
-H 'Content-Type: application/json' \
-d '{"username_or_email":"admin","password":"<pw>"}'
-d '{"username_or_email":"admin_pending","password":"<password from Settings → Passwords>"}'
```
Copy `jwt` from the response into `.env` as `LEMMY_JWT`, then `crow bundle restart lemmy`.
Copy `jwt` from the response into `LEMMY_JWT` (the extension's settings), then restart the extension.
4. Federation, registration and captcha are site settings: change them in Lemmy's Admin → Settings. Rename `admin_pending` or add your own admin there too.

## Common workflows

Expand Down Expand Up @@ -114,7 +108,7 @@ Lemmy's moderation is layered: **community mods** handle their community; **inst
## Troubleshooting

- **"Cannot reach Lemmy"** — `docker ps | grep crow-lemmy`. First-boot migrations take 60+ seconds; the healthcheck has a 60s start period.
- **Federation not working** — check `LEMMY_FEDERATION_ENABLED` is true (default). Also verify `federation.enabled` is true in `/config/config.hjson` inside the container (`docker exec crow-lemmy cat /config/config.hjson`).
- **Federation not working** — check that federation is on in Lemmy's Admin → Settings, and that the public site sends `/.well-known`, `/nodeinfo` and ActivityPub requests to `lemmy:8536` (lemmy-ui answers them with 404).
- **Images not loading** — pict-rs container health check. `docker logs crow-lemmy-pictrs` and verify `LEMMY_PICTRS_API_KEY` matches between the two containers.
- **Disk filling fast** — federated community caching. Run `lemmy_media_prune` and reduce follow count on heavy communities.
- **Admin endpoints returning 404** — your `LEMMY_JWT` may belong to a regular user rather than admin. Verify in the web UI's **Admin** dropdown — if absent, log out, promote the account via DB, and re-login.
3 changes: 2 additions & 1 deletion bundles/skill-history.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@
],
"lemmy/skills/lemmy.md": [
"8159d8d03f02023153a8b78a885c92ed45d76bd163fee5f547487b400fb3529f",
"4da3ec3fd92fb5b03ddb54823c4d7147a6da63f76e30b6ab8c5334494c1f83aa"
"4da3ec3fd92fb5b03ddb54823c4d7147a6da63f76e30b6ab8c5334494c1f83aa",
"3a46fb8ae525356cd3c8f911ec23661c648963ea7bfd55c302a456553d0d6a7a"
],
"linkding/skills/linkding.md": [
"72ab294aa4ba9d905e8ccf81212e14290c7c3a864c5ab3f6119b2ca6e65526c6"
Expand Down
Loading
Loading