From 707c912606294be08f289ddf686a7226aaf3388e Mon Sep 17 00:00:00 2001 From: archipelago Date: Thu, 9 Jul 2026 14:26:20 -0400 Subject: [PATCH] fix(apps): strfry container port 7777 + declarative strfry.conf via files: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dockurr/strfry image listens on 7777 (not 8080) and its default config demands a 1M NOFILES rlimit — above the rootless user-manager hard cap (524288), so the relay exited at startup even with the mount fixed. Ship the config declaratively (manifest files: + ro bind at /etc/strfry.conf) with nofiles = 0; this also skips the entrypoint's copy into /etc, which a readonly_root container cannot perform. Verified end-to-end on .228 in a quadlet-equivalent systemd-run environment: active, GET / and /health 200. Co-Authored-By: Claude Fable 5 --- apps/strfry/manifest.yml | 184 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 170 insertions(+), 14 deletions(-) diff --git a/apps/strfry/manifest.yml b/apps/strfry/manifest.yml index 80e4f827..301d2492 100644 --- a/apps/strfry/manifest.yml +++ b/apps/strfry/manifest.yml @@ -3,43 +3,199 @@ app: name: Strfry Nostr Relay version: 0.9.0 description: Lightweight Nostr relay written in C++. Alternative to nostr-rs-relay with lower resource usage. - + container: image: dockurr/strfry:1.0.4 image_signature: cosign://... pull_policy: verify-signature - + dependencies: - storage: 5Gi - + resources: cpu_limit: 1 memory_limit: 512Mi disk_limit: 5Gi - + security: capabilities: [] readonly_root: true no_new_privileges: true - user: 1000 seccomp_profile: default network_policy: isolated apparmor_profile: nostr-relay - + ports: - host: 8090 - container: 8080 - protocol: tcp # HTTP/WebSocket - + container: 7777 + protocol: tcp # HTTP/WebSocket (strfry listens on 7777) + volumes: - type: bind source: /var/lib/archipelago/strfry target: /app/strfry-db options: [rw] - - environment: - - RELAY_NAME=Archipelago Strfry Relay - + # Image default config demands a 1M NOFILES rlimit, above the rootless + # user-manager hard cap (524288) — ship the config with nofiles = 0. + # Mounting it also skips the entrypoint's copy into /etc, which a + # readonly_root container cannot do. + - type: bind + source: /var/lib/archipelago/strfry-config/strfry.conf + target: /etc/strfry.conf + options: [ro] + + files: + - path: /var/lib/archipelago/strfry-config/strfry.conf + overwrite: true + content: | + ## + ## Default strfry config + ## + + # Directory that contains the strfry LMDB database (restart required) + db = "./strfry-db/" + + dbParams { + # Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required) + maxreaders = 256 + + # Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required) + mapsize = 10995116277760 + + # Disables read-ahead when accessing the LMDB mapping. Reduces IO activity when DB size is larger than RAM. (restart required) + noReadAhead = false + } + + events { + # Maximum size of normalised JSON, in bytes + maxEventSize = 65536 + + # Events newer than this will be rejected + rejectEventsNewerThanSeconds = 900 + + # Events older than this will be rejected + rejectEventsOlderThanSeconds = 94608000 + + # Ephemeral events older than this will be rejected + rejectEphemeralEventsOlderThanSeconds = 60 + + # Ephemeral events will be deleted from the DB when older than this + ephemeralEventsLifetimeSeconds = 300 + + # Maximum number of tags allowed + maxNumTags = 2000 + + # Maximum size for tag values, in bytes + maxTagValSize = 1024 + } + + relay { + # Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required) + bind = "0.0.0.0" + + # Port to open for the nostr websocket protocol (restart required) + port = 7777 + + # Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required) + nofiles = 0 + + # HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case) + realIpHeader = "" + + info { + # NIP-11: Name of this server. Short/descriptive (< 30 characters) + name = "Archipelago Strfry Relay" + + # NIP-11: Detailed information about relay, free-form + description = "Self-hosted strfry Nostr relay on Archipelago." + + # NIP-11: Administrative nostr pubkey, for contact purposes + pubkey = "" + + # NIP-11: Alternative administrative contact (email, website, etc) + contact = "" + + # NIP-11: URL pointing to an image to be used as an icon for the relay + icon = "" + + # List of supported lists as JSON array, or empty string to use default. Example: "[1,2]" + nips = "" + } + + # Maximum accepted incoming websocket frame size (should be larger than max event) (restart required) + maxWebsocketPayloadSize = 131072 + + # Maximum number of filters allowed in a REQ + maxReqFilterSize = 200 + + # Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required) + autoPingSeconds = 55 + + # If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy) + enableTcpKeepalive = false + + # How much uninterrupted CPU time a REQ query should get during its DB scan + queryTimesliceBudgetMicroseconds = 10000 + + # Maximum records that can be returned per filter + maxFilterLimit = 500 + + # Maximum number of subscriptions (concurrent REQs) a connection can have open at any time + maxSubsPerConnection = 20 + + writePolicy { + # If non-empty, path to an executable script that implements the writePolicy plugin logic + plugin = "/app/write-policy.py" + } + + compression { + # Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required) + enabled = true + + # Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required) + slidingWindow = true + } + + logging { + # Dump all incoming messages + dumpInAll = false + + # Dump all incoming EVENT messages + dumpInEvents = false + + # Dump all incoming REQ/CLOSE messages + dumpInReqs = false + + # Log performance metrics for initial REQ database scans + dbScanPerf = false + + # Log reason for invalid event rejection? Can be disabled to silence excessive logging + invalidEvents = true + } + + numThreads { + # Ingester threads: route incoming requests, validate events/sigs (restart required) + ingester = 3 + + # reqWorker threads: Handle initial DB scan for events (restart required) + reqWorker = 3 + + # reqMonitor threads: Handle filtering of new events (restart required) + reqMonitor = 3 + + # negentropy threads: Handle negentropy protocol messages (restart required) + negentropy = 2 + } + + negentropy { + # Support negentropy protocol messages + enabled = true + + # Maximum records that sync will process before returning an error + maxSyncEvents = 1000000 + } + } + health_check: type: http endpoint: http://localhost:8090 @@ -47,7 +203,7 @@ app: interval: 30s timeout: 5s retries: 3 - + nostr_integration: relay_type: public monetization_enabled: true