Sign inSign up

vm75/message-sync

By vm75

•Updated 3 days ago

Image
0

2.9K

vm75/message-sync repository overview

⁠message-sync

GitHub Repository Docker Pulls Docker Image Size Platforms Go Version Privacy Security

message-sync is a self-hosted, privacy-first service that synchronizes configured WhatsApp groups, Discord channels, and Telegram groups or supergroups. Multiple accounts and bots participate in alias-based sync sets while a transport-neutral canonical router owns message identity, fan-out, and lifecycle behavior without storing PII/PHI.

The release workflow publishes the same multi-architecture message-sync image to:

⁠Key features

  • Multi-Transport Synchronization: Seamless bidirectional sync across WhatsApp (groups and community subgroups), Discord (channels and threads/forums), and Telegram (groups, supergroups, and topics).
  • Full Message Lifecycle: Synchronizes text with provenance attribution, transient in-memory media (images, video, audio/voice notes, documents, stickers), native replies with fallback attribution, emoji reactions, edits, and deletions/revocations.
  • Polls & Live Aggregation: Native polls with source labels included in cross-endpoint Discord/Telegram poll presentation, aggregate-only live-result companions, and live on-demand aggregate summaries that update with poll changes via configurable reply triggers (e.g. aggregate-response, /poll-results). Encrypted poll questions/options survive server restarts while remaining outside the PII-free routing database.
  • Source-Local Message Suppression: Configurable message prefix filtering (e.g. !local, #local, //, [local]) to suppress private or internal messages before transmission or media loading.
  • Zero-PII Privacy & Route-Aware Anonymization: Core routing database (sync.db) and application logs store zero message content, credentials, phone numbers, or user IDs; actor identities are HMAC-derived from IDENTITY_SECRET. Optional per-sync-set one-way Discord anonymization (anonymizeToDiscord) transforms sender names, phone numbers, and structured mentions into deterministic pseudonyms (e.g. Silent Falcon Q7M) exclusively for Discord destinations without storing PII or mapping tables.
  • Reliable Ordered Delivery: Single-worker deterministic ingress loop, independent per-destination FIFO lanes with exponential backoff retry, ambiguity-safe create handling (awaiting_replay), and real-time delivery health monitoring.
  • Embedded Web Management Console: Zero-dependency embedded Web UI and REST API for dynamic connection setup, serialized WhatsApp QR pairing, atomic endpoint alias renaming, sync set mesh configuration, and live runtime configuration reloads.
  • Sensitive Control Plane & Multi-User RBAC: Isolated mode-0600 control.db supporting Admin and Operator roles, bcrypt passwords, HMAC session tokens, session revocation, one-time invite tokens, audit logging, and AES-256-GCM encrypted bot tokens.
  • Membership Intake Pipeline (Experimental): Sync-set bound public intake forms, automated email verification challenges, private evidence handling (mode 0600 with automatic purge), optional advisory AI image analysis (zero-training), and human review with automated fulfillment.
  • Security & Hardening: Static non-root container (UID 1000), read-only root filesystem, dropped capabilities, no extra privileges required, and fully compatible with Docker, rootful Podman, and rootless Podman.

See the README⁠ for detailed product setup and provider requirements. This page is limited to image behavior, runtime configuration, and container deployment.

⁠Tags and platforms

  • A release tag vX.Y.Z must exactly match VERSION=X.Y.Z in the tagged commit.
  • The validated version is published as the immutable exact image tag in both registries.
  • Stable releases also move latest; pre-releases such as v0.3.0-rc.1 never move latest.
  • Release images target linux/amd64 and linux/arm64.

Development builds use the development version string and are not published by the release workflow. Exact release tags/images are never rebuilt from another commit; fix a bad release with a new version.

⁠Quick start

Create a private environment file and data directory:

mkdir -p data
printf 'IDENTITY_SECRET=%s\nDATA_DIR=/data\nPORT=8080\nLOG_LEVEL=info\n' "$(openssl rand -hex 32)" > .env
chmod 600 .env

Run the current published release:

docker pull docker.io/vm75/message-sync:latest
docker run -d \
  --name message-sync \
  --read-only \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  --tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m \
  --env-file .env \
  -p 8080:8080 \
  -v "$PWD/data:/data" \
  docker.io/vm75/message-sync:latest

On SELinux systems, add :Z to the /data bind mount. The image already runs as the non-root message-sync user (UID/GID 1000); ensure the mounted directory is writable by that user in your container runtime's user namespace.

Open http://localhost:8080, create the first administrator, then add connections, endpoints, and sync sets in the Web UI.

⁠Compose

The repository's compose.yml⁠ builds message-sync:dev locally. To use the published image, remove its build block and set:

services:
  message-sync:
    image: docker.io/vm75/message-sync:latest

Keep the repository's read-only root filesystem, dropped capabilities, /tmp tmpfs, environment, port, and /data volume settings. Then run:

docker compose up -d

Podman users can use podman compose up -d with the same file.

⁠Runtime interface

The image entry point is /usr/local/bin/message-sync; its default command is run.

VariableRequiredDefaultPurpose
IDENTITY_SECRETyesnoneIdentity-HMAC and credential-encryption root secret; minimum 32 bytes.
DATA_DIRno/dataPersistent state and private evidence directory.
PORTno8080HTTP listen port when API_ADDR is unset.
API_ADDRnoderived from PORTComplete HTTP listen address.
LOG_LEVELnoinfodebug, info, warn, or error.
WHATSAPP_DEVICE_NAMEnomessage-syncCompanion device name shown in WhatsApp Linked Devices.
VERIFICATION_MAIL_API_KEYnononeEnables Resend-compatible email delivery for experimental membership verification with VERIFICATION_MAIL_FROM.
VERIFICATION_MAIL_FROMnononeSender used by optional experimental membership email delivery.
VERIFICATION_PUBLIC_BASE_URLnorequest originExternal base URL used in experimental membership links behind a proxy.
OPENROUTER_API_KEYnononeEnables advisory image evidence analysis only when training is explicitly disabled.
OPENROUTER_ALLOW_TRAININGnounsetMust equal false for OpenRouter analysis to run.
OPENROUTER_MODELnoopenrouter/freeModel used by optional advisory analysis.

Discord and Telegram bot tokens are pasted into the authenticated Web UI and encrypted in control.db; do not place them in container environment variables.

The service exposes HTTP on the configured port and requires one persistent writable mount at /data. That mount contains:

  • sync.db — PII-free routing/configuration state;
  • control.db — sensitive accounts, sessions, encrypted credentials, audit, and experimental membership state;
  • whatsapp/<connection-id>.db — isolated sensitive whatsmeow protocol state;
  • membership-evidence/ — short-lived private membership evidence when the experimental verification feature is used.

Do not publish, inspect as application data, or expose these files through another service. Back up /data before upgrades; the project does not have a general versioned migration framework, though schema changes may use narrowly scoped idempotent initialization migrations with explicit upgrade tests.

⁠Hardening

The final image contains the compiled binary on Alpine and runs as UID/GID 1000. It needs no privileged mode, host networking, host PID namespace, or added Linux capabilities. /data is the only persistent writable location; use a read-only root filesystem and the bounded /tmp tmpfs shown above.

⁠Version and configuration checks

docker run --rm docker.io/vm75/message-sync:latest version
docker run --rm \
  --env-file .env \
  -v "$PWD/data:/data" \
  docker.io/vm75/message-sync:latest validate-config

validate-config requires an existing sync.db with a complete valid configuration (at least two endpoints in one or more sync sets).

⁠Publishing

.github/workflows/release-images.yml runs only for pushed release tags matching v*.*.*. A normal push to main, including a commit that changes VERSION, does not publish anything. The workflow validates the tag against VERSION, runs go test ./... and go vet ./..., verifies the release-mode binary version, and only then publishes the multi-architecture images.

Stable versions publish both the exact version and latest to Docker Hub and GHCR. Pre-release versions publish only their exact version. The workflow refuses to overwrite an existing exact-version image, verifies the published GHCR image reports the same version, and creates the matching GitHub Release only after publishing succeeds.

The GitHub repository must provide DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets. GHCR authentication uses the workflow GITHUB_TOKEN; the publish job receives only contents: read and packages: write, while the final GitHub Release job receives contents: write. No registry credentials belong in the repository.

Maintainers should follow RELEASING.md⁠; do not create release tags by editing this document or by rebuilding an old exact version.

Tag summary

Content type

Image

Digest

sha256:45349e433…

Size

14.3 MB

Last updated

3 days ago

docker pull vm75/message-sync