{"id":48911746,"url":"https://github.com/softcreatrmedia/rpushd","last_synced_at":"2026-04-18T01:00:25.396Z","repository":{"id":351889780,"uuid":"1212964444","full_name":"SoftCreatRMedia/rpushd","owner":"SoftCreatRMedia","description":"A reusable realtime push backend for application integrations, written in Rust.","archived":false,"fork":false,"pushed_at":"2026-04-17T00:12:32.000Z","size":23,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-17T00:27:58.987Z","etag":null,"topics":["apache","axum","haproxy","http-streaming","linux","long-polling","nginx","pubsub","push-daemon","realtime","reverse-proxy","rust","self-hosted","server-sent-events","sse","systemd","tokio"],"latest_commit_sha":null,"homepage":"https://softcreatr.dev","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SoftCreatRMedia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-16T23:01:29.000Z","updated_at":"2026-04-17T00:12:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SoftCreatRMedia/rpushd","commit_stats":null,"previous_names":["softcreatrmedia/rpushd"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/SoftCreatRMedia/rpushd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SoftCreatRMedia%2Frpushd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SoftCreatRMedia%2Frpushd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SoftCreatRMedia%2Frpushd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SoftCreatRMedia%2Frpushd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SoftCreatRMedia","download_url":"https://codeload.github.com/SoftCreatRMedia/rpushd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SoftCreatRMedia%2Frpushd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31952206,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"ssl_error","status_checked_at":"2026-04-18T00:39:20.671Z","response_time":62,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apache","axum","haproxy","http-streaming","linux","long-polling","nginx","pubsub","push-daemon","realtime","reverse-proxy","rust","self-hosted","server-sent-events","sse","systemd","tokio"],"created_at":"2026-04-17T00:00:58.105Z","updated_at":"2026-04-18T01:00:25.342Z","avatar_url":"https://github.com/SoftCreatRMedia.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rpushd\n\nThis service is a reusable realtime push backend for application integrations.\n\nIt keeps long-lived HTTP stream connections outside PHP-FPM and accepts lightweight\npublish events from application code.\n\n## Prerequisites\n\nThe daemon is intended for Linux servers where you can run an additional service\nnext to PHP-FPM. It is not meant for shared hosting.\n\nRequired:\n\n- A modern Linux distribution\n- An application that publishes events to the daemon and mints signed subscribe tokens\n- nginx, Apache 2.4, HAProxy, or another reverse proxy in front of the daemon\n- A public URL that browsers can reach for the push daemon\n\nRecommended:\n\n- systemd for service management\n\nThe included service file targets `systemd`, but the daemon itself is not tied to a\nspecific distribution. It should work on other Linux distributions as long as you can\nrun the binary as a long-lived service and expose it through a reverse proxy.\n\n## Linux Installation\n\nPrecompiled release binaries are available through GitHub Releases as `.tar.gz`\narchives. Current target variants:\n\n- `rpushd-linux-x86_64-gnu.tar.gz`\n  - use for `x86_64` Ubuntu, Debian, Arch, and other glibc-based Linux distributions\n- `rpushd-linux-x86_64-musl.tar.gz`\n  - use for `x86_64` Alpine Linux\n- `rpushd-linux-aarch64-gnu.tar.gz`\n  - use for `aarch64` / `arm64` Ubuntu, Debian, and other glibc-based Linux distributions\n- `rpushd-linux-aarch64-musl.tar.gz`\n  - use for `aarch64` / `arm64` Alpine Linux\n\nIf you use a release archive, unpack it and continue with the deployment and reverse\nproxy steps below. If no suitable precompiled binary exists for your platform, build\nfrom source as described here.\n\nTypical installation from a release archive:\n\n```bash\ncurl -LO https://github.com/SoftCreatRMedia/rpushd/releases/latest/download/rpushd-linux-x86_64-gnu.tar.gz\ncurl -LO https://github.com/SoftCreatRMedia/rpushd/releases/latest/download/rpushd-linux-x86_64-gnu.tar.gz.sha256\nsha256sum -c rpushd-linux-x86_64-gnu.tar.gz.sha256\ntar -xzf rpushd-linux-x86_64-gnu.tar.gz\nmkdir -p /opt/rpushd\ncp rpushd-linux-x86_64-gnu/rpushd /opt/rpushd/rpushd\ncp rpushd-linux-x86_64-gnu/rpushd.service /opt/rpushd/\ncp rpushd-linux-x86_64-gnu/nginx-location.conf /opt/rpushd/\ncp rpushd-linux-x86_64-gnu/README.md /opt/rpushd/\n```\n\nReplace `rpushd-linux-x86_64-gnu.tar.gz` with the archive that matches\nyour platform.\n\nClone the repository and enter the working directory:\n\n```bash\ngit clone https://github.com/SoftCreatRMedia/rpushd.git\ncd rpushd\n```\n\nInstall the Rust toolchain and basic build dependencies.\n\nUbuntu / Debian:\n\n```bash\napt update\napt install -y build-essential pkg-config curl ca-certificates\ncurl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal\n. \"$HOME/.cargo/env\"\nrustup default stable\nrustup component add rustfmt\n```\n\nAlpine Linux:\n\n```bash\napk add --no-cache alpine-sdk pkgconf curl ca-certificates rustup\nrustup-init -y --profile minimal\n. \"$HOME/.cargo/env\"\nrustup default stable\nrustup component add rustfmt\n```\n\nArch Linux:\n\n```bash\npacman -Sy --needed base-devel pkgconf curl ca-certificates rustup\nrustup default stable\nrustup component add rustfmt\n```\n\nIf your distribution already provides a sufficiently recent Rust toolchain, you can\nuse that instead. `rustup` is recommended because it keeps the build process\nconsistent across distributions.\n\nBuild the daemon:\n\n```bash\n. \"$HOME/.cargo/env\"\ncargo build --release\n```\n\nDeploy the binary and supporting files:\n\n```bash\nmkdir -p /opt/rpushd\ncp target/release/rpushd /opt/rpushd/rpushd\ncp rpushd.service /opt/rpushd/\ncp nginx-location.conf /opt/rpushd/\n```\n\nGenerate two long random secrets:\n\n- one for signed browser subscribe tokens\n- one for privileged server-side publish requests\n\nThose values must match:\n\n- `RPUSHD_SECRET`\n- `RPUSHD_PUBLISH_SECRET`\n\nRecommended:\n\n- store them in a root-readable only environment file instead of hardcoding them\n  into the unit itself\n- rotate them occasionally\n- treat the publish secret as especially sensitive because it authorizes event injection\n\nInstall and adjust the unit:\n\n```bash\ncp rpushd.service /etc/systemd/system/rpushd.service\neditor /etc/systemd/system/rpushd.service\nsystemctl daemon-reload\nsystemctl enable --now rpushd\nsystemctl status rpushd\n```\n\nIf your distribution does not use `systemd`, use the same binary and environment\nvariables with the native service manager for that platform instead.\n\nThe shipped `systemd` unit already includes a hardened baseline. If you prefer\nseparate secret storage, replace the inline `Environment=` lines with something like:\n\n```ini\nEnvironmentFile=/etc/rpushd.env\n```\n\nand store the secrets there with restrictive permissions, for example:\n\n```bash\ninstall -m 600 -o root -g root /dev/null /etc/rpushd.env\neditor /etc/rpushd.env\n```\n\nExpose the daemon through nginx:\n\n```bash\nmkdir -p /etc/nginx/snippets\ncp nginx-location.conf /etc/nginx/snippets/rpushd.conf\neditor /etc/nginx/sites-enabled/your-site.conf\nnginx -t\nsystemctl reload nginx\n```\n\nInside the relevant nginx `server { ... }` block, add:\n\n```nginx\ninclude snippets/rpushd.conf;\n```\n\nThis keeps the daemon routing in a dedicated snippet, so future updates only need\nto replace `/etc/nginx/snippets/rpushd.conf` instead of manually\ncopying directives into every virtual host configuration.\n\nApache 2.4 works as well. Enable the required modules first:\n\n```bash\na2enmod proxy proxy_http headers ssl\nsystemctl reload apache2\n```\n\nThen add something like this to the relevant `VirtualHost`:\n\n```apache\nProxyPreserveHost On\nProxyTimeout 75\n\nProxyPass        /push-daemon/healthz http://127.0.0.1:45831/healthz timeout=15 keepalive=On\nProxyPassReverse /push-daemon/healthz http://127.0.0.1:45831/healthz\n\nProxyPass        /push-daemon/api/stream/ http://127.0.0.1:45831/api/stream/ timeout=75 keepalive=On\nProxyPassReverse /push-daemon/api/stream/ http://127.0.0.1:45831/api/stream/\n\n\u003cLocation \"/push-daemon/api/stream/\"\u003e\n    Header always set Cache-Control \"no-cache, no-store, must-revalidate, no-transform\"\n    Header always set Pragma \"no-cache\"\n    Header always set Expires \"0\"\n\u003c/Location\u003e\n```\n\nKeep `/api/publish` and `/api/stats` internal-only there as well. Trusted\napplication or admin tooling should call those endpoints directly via the internal\ndaemon URL instead of exposing them through Apache.\n\nHAProxy works as well. A typical frontend/backend split looks like this:\n\n```haproxy\nfrontend https_in\n    bind *:443 ssl crt /etc/haproxy/certs alpn h2,http/1.1\n    mode http\n\n    acl path_push_daemon path_beg /push-daemon/\n    use_backend push_daemon if path_push_daemon\n\nbackend push_daemon\n    mode http\n    option forwardfor\n    http-reuse safe\n    timeout server 75s\n    timeout tunnel 75s\n    server local_push 127.0.0.1:45831 check\n```\n\nExpose only the public stream and health paths through that public HAProxy route.\nDo not proxy `/api/publish` or `/api/stats` publicly. Let trusted application or\nadmin tooling call those endpoints directly through the internal daemon URL instead.\n\nThen configure your application so that:\n\n- browsers use the public stream base URL, for example `https://your-domain.tld/push-daemon`\n- server-side publish requests target the internal daemon URL, for example `http://127.0.0.1:45831`\n- subscribe tokens are signed with `RPUSHD_SECRET`\n- privileged publish requests use `RPUSHD_PUBLISH_SECRET`\n\nIf you want runtime statistics, query the daemon directly on the internal address.\nDo not expose the stats endpoint publicly.\n\n## Operational Security\n\nFor a strong production setup, keep these points in mind:\n\n- bind the daemon only to `127.0.0.1` or another private interface\n- never expose the raw daemon port directly to the internet\n- proxy only `/healthz` and `/api/stream/` publicly\n- keep `/api/publish` and `/api/stats` internal-only\n- call `/api/publish` only from trusted application code\n- call `/api/stats` only from trusted internal admin tooling\n- store secrets outside the service unit if possible\n- rotate secrets with a planned deployment window\n\nSuggested rotation order:\n\n1. rotate the publish secret\n2. update the application publish side\n3. verify publishing still works\n4. rotate the subscription secret\n5. allow old subscribe tokens to expire\n\nIf publish traffic ever has to cross hosts, prefer a private network, VPN, IP\nallowlisting, or mTLS in front of the daemon rather than exposing publish traffic\nopenly on the public internet.\n\n## Build\n\n```bash\ncargo build --release\n```\n\n## Run\n\n```bash\nexport RPUSHD_SECRET='replace-with-a-long-random-secret'\nexport RPUSHD_PUBLISH_SECRET='replace-with-a-different-long-random-secret'\nexport RPUSHD_LISTEN='127.0.0.1:45831'\ncargo run --release\n```\n\nOptional environment variables:\n\n- `RPUSHD_HEARTBEAT_SECS`\n  Default: `15`\n- `RPUSHD_CHANNEL_IDLE_TTL_SECS`\n  Default: `3600`\n\n## HTTP API\n\n- `GET /healthz`\n- `POST /api/publish`\n- `GET /api/stats`\n- `POST /api/stream/{channel}`\n\n`/api/publish` expects:\n\n```json\n{\n  \"channel\": \"example-channel\",\n  \"message\": {\n    \"foo\": \"bar\"\n  }\n}\n```\n\nwith header:\n\n```text\nAuthorization: Bearer \u003cpublish-secret\u003e\n```\n\n`/api/stream/{channel}` expects:\n\n```json\n{\n  \"token\": \"\u003csigned-subscribe-token\u003e\"\n}\n```\n\nThe response is an `application/octet-stream` body using the same two-byte\nbig-endian length prefix that the browser-side `PushClient` already understands.\nZero-length frames are heartbeats.\n\n`/api/stats` expects:\n\n```text\nAuthorization: Bearer \u003cpublish-secret\u003e\n```\n\nWithout a `mode` parameter, it returns human-readable plain text.\n\nSupported output modes:\n\n- default / no `mode`: plain text\n- `?mode=json`\n- `?mode=xml`\n\nIt includes metrics such as:\n\n- uptime\n- active stream connections\n- total stream connections opened\n- publish request count\n- published byte count\n- current RSS memory usage\n- channel count and per-channel subscriber counts\n\nExample:\n\n```bash\ncurl -sS \\\n  -H 'Authorization: Bearer replace-with-the-publish-secret' \\\n  http://127.0.0.1:45831/api/stats\n```\n\nExample plain-text response:\n\n```text\nstarted_at: 1776181200\nuptime_seconds: 842\nactive_channels: 3\nactive_subscribers: 7\nactive_stream_connections: 7\nstream_connections_total: 24\npublish_requests_total: 18\npublished_bytes_total: 2914\nauth_failures_total: 0\nmemory_rss_bytes: 7348224\nchannels:\n  - name: notifications:96501\n    subscribers: 1\n    idle_seconds: 3\n  - name: thread-posts:459\n    subscribers: 3\n    idle_seconds: 1\n  - name: thread-writers:459\n    subscribers: 3\n    idle_seconds: 0\n```\n\nJSON:\n\n```bash\ncurl -sS \\\n  -H 'Authorization: Bearer replace-with-the-publish-secret' \\\n  'http://127.0.0.1:45831/api/stats?mode=json' | jq\n```\n\nExample JSON response:\n\n```json\n{\n  \"active_channels\": 3,\n  \"active_stream_connections\": 7,\n  \"active_subscribers\": 7,\n  \"auth_failures_total\": 0,\n  \"channels\": [\n    {\n      \"idle_seconds\": 3,\n      \"name\": \"notifications:96501\",\n      \"subscribers\": 1\n    },\n    {\n      \"idle_seconds\": 1,\n      \"name\": \"thread-posts:459\",\n      \"subscribers\": 3\n    },\n    {\n      \"idle_seconds\": 0,\n      \"name\": \"thread-writers:459\",\n      \"subscribers\": 3\n    }\n  ],\n  \"memory_rss_bytes\": 7348224,\n  \"publish_requests_total\": 18,\n  \"published_bytes_total\": 2914,\n  \"started_at\": 1776181200,\n  \"stream_connections_total\": 24,\n  \"uptime_seconds\": 842\n}\n```\n\nXML:\n\n```bash\ncurl -sS \\\n  -H 'Authorization: Bearer replace-with-the-publish-secret' \\\n  'http://127.0.0.1:45831/api/stats?mode=xml'\n```\n\nExample XML response:\n\n```xml\n\u003cstats\u003e\n  \u003cstarted_at\u003e1776181200\u003c/started_at\u003e\n  \u003cuptime_seconds\u003e842\u003c/uptime_seconds\u003e\n  \u003cactive_channels\u003e3\u003c/active_channels\u003e\n  \u003cactive_subscribers\u003e7\u003c/active_subscribers\u003e\n  \u003cactive_stream_connections\u003e7\u003c/active_stream_connections\u003e\n  \u003cstream_connections_total\u003e24\u003c/stream_connections_total\u003e\n  \u003cpublish_requests_total\u003e18\u003c/publish_requests_total\u003e\n  \u003cpublished_bytes_total\u003e2914\u003c/published_bytes_total\u003e\n  \u003cauth_failures_total\u003e0\u003c/auth_failures_total\u003e\n  \u003cmemory_rss_bytes\u003e7348224\u003c/memory_rss_bytes\u003e\n  \u003cchannels\u003e\n    \u003cchannel\u003e\n      \u003cname\u003enotifications:96501\u003c/name\u003e\n      \u003csubscribers\u003e1\u003c/subscribers\u003e\n      \u003cidle_seconds\u003e3\u003c/idle_seconds\u003e\n    \u003c/channel\u003e\n    \u003cchannel\u003e\n      \u003cname\u003ethread-posts:459\u003c/name\u003e\n      \u003csubscribers\u003e3\u003c/subscribers\u003e\n      \u003cidle_seconds\u003e1\u003c/idle_seconds\u003e\n    \u003c/channel\u003e\n    \u003cchannel\u003e\n      \u003cname\u003ethread-writers:459\u003c/name\u003e\n      \u003csubscribers\u003e3\u003c/subscribers\u003e\n      \u003cidle_seconds\u003e0\u003c/idle_seconds\u003e\n    \u003c/channel\u003e\n  \u003c/channels\u003e\n\u003c/stats\u003e\n```\n\n## Reverse Proxy\n\nThe browser-facing daemon URL should usually be exposed through nginx, Apache 2.4,\nHAProxy,\nor another reverse proxy. A minimal nginx location is included in\n[nginx-location.conf](./nginx-location.conf).\nIt intentionally exposes only the public stream and health endpoints. Keep\n`/api/publish` and `/api/stats` internal-only and let trusted application or admin\ntooling call the daemon directly via the internal daemon URL. The recommended\nsetup is to install that file as an nginx snippet and reference it from your\n`server` block via `include snippets/rpushd.conf;`.\n\nTypical setup:\n\n- the public stream base URL points to the browser-facing URL, usually `https://your-domain.tld/push-daemon`\n- privileged publish requests target the local daemon directly, for example `http://127.0.0.1:45831`\n\n## Monitoring\n\nAt minimum, watch these signals:\n\n- active stream count\n- reconnect rate\n- publish request rate\n- `401`, `403`, and `429` responses at the proxy layer\n- daemon restarts or crashes\n\nUseful operational checks:\n\n- `systemctl status rpushd`\n- `journalctl -u rpushd -f`\n- reverse proxy access/error logs for `/push-daemon/`\n\nIf you expect large traffic, set alerts for sudden reconnect spikes or sustained\nauth failures. Those often indicate proxy buffering/timeouts, abusive clients, or\nmisconfigured secrets.\n\n## systemd\n\nA sample unit file is included in [rpushd.service](./rpushd.service).\n\n## Uninstall\n\nIf the daemon is currently used by an application, disable that integration first.\n\nThen remove the service and reverse proxy configuration:\n\n```bash\nsystemctl disable --now rpushd\nrm -f /etc/systemd/system/rpushd.service\nsystemctl daemon-reload\n\neditor /etc/nginx/sites-enabled/your-site.conf\nrm -f /etc/nginx/snippets/rpushd.conf\nnginx -t\nsystemctl reload nginx\n```\n\nFinally remove the deployed daemon files if you no longer need them:\n\n```bash\nrm -rf /opt/rpushd\n```\n\n## License\n\nCopyright by SoftCreatR.dev.\n\nLicense terms:\n\n- https://softcreatr.dev/license-terms\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftcreatrmedia%2Frpushd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoftcreatrmedia%2Frpushd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftcreatrmedia%2Frpushd/lists"}