* xftp: add PostgreSQL backend design spec
* update doc
* adjust styling
* add implementation plan
* refactor: move usedStorage from FileStore to XFTPEnv
* refactor: add getUsedStorage, getFileCount, expiredFiles store functions
* refactor: change file store operations from STM to IO
* refactor: extract FileStoreClass typeclass, move STM impl to Store.STM
* refactor: make XFTPEnv and server polymorphic over FileStoreClass
* feat: add PostgreSQL store skeleton with schema migration
* feat: implement PostgresFileStore operations
* feat: add PostgreSQL INI config, store dispatch, startup validation
* feat: add database import/export CLI commands
* test: add PostgreSQL backend tests
* fix: map ForeignKeyViolation to AUTH in addRecipient
When a file is concurrently deleted while addRecipient runs, the FK
constraint on recipients.sender_id raises ForeignKeyViolation. Previously
this propagated as INTERNAL; now it returns AUTH (file not found).
* fix: only decrement usedStorage for uploaded files on expiration
expireServerFiles unconditionally subtracted file_size from usedStorage
for every expired file, including files that were never uploaded (no
file_path). Since reserve only increments usedStorage during upload,
expiring never-uploaded files caused usedStorage to drift negative.
* fix: handle setFilePath error in receiveServerFile
setFilePath result was discarded with void. If it failed (file deleted
concurrently, or double-upload where file_path IS NULL guard rejected
the second write), the server still reported FROk, incremented stats,
and left usedStorage permanently inflated. Now the error is checked:
on failure, reserved storage is released and AUTH is returned.
* fix: escape double quotes in COPY CSV status field
The status field (e.g. "blocked,reason=spam,notice={...}") is quoted in
CSV for COPY protocol, but embedded double quotes from BlockingInfo
notice (JSON) were not escaped. This could break CSV parsing during
import. Now double quotes are escaped as "" per CSV spec.
* fix: reject upload to blocked file in Postgres setFilePath
In Postgres mode, getFile returns a snapshot TVar for fileStatus. If a
file is blocked between getFile and setFilePath, the stale status check
passes but the upload should be rejected. Added status = 'active' to
the UPDATE WHERE clause so blocked files cannot receive uploads.
* fix: add CHECK constraint on file_size > 0
Prevents negative or zero file_size values at the database level.
Without this, corrupted data from import or direct DB access could
cause incorrect storage accounting (getUsedStorage sums file_size,
and expiredFiles casts to Word32 which wraps negative values).
* fix: check for existing data before database import
importFileStore now checks if the target database already contains
files and aborts with an error. Previously, importing into a non-empty
database would fail mid-COPY on duplicate primary keys, leaving the
database in a partially imported state.
* fix: clean up disk file when setFilePath fails in receiveServerFile
When setFilePath fails (file deleted or blocked concurrently, or
duplicate upload), the uploaded file was left orphaned on disk with
no DB record pointing to it. Now the file is removed on failure,
matching the cleanup in the receiveChunk error path.
* fix: check storeAction result in deleteOrBlockServerFile_
The store action result (deleteFile/blockFile) was discarded with void.
If the DB row was already deleted by a concurrent operation, the
function still decremented usedStorage, causing drift. Now the error
propagates via ExceptT, skipping the usedStorage adjustment.
* fix: check deleteFile result in expireServerFiles
deleteFile result was discarded with void. If a concurrent delete
already removed the file, deleteFile returned AUTH but usedStorage
was still decremented — causing double-decrement drift. Now the
usedStorage adjustment and filesExpired stat only run on success.
* refactor: merge STM store into Store.hs, parameterize server tests
- Move STMFileStore and its FileStoreClass instance from Store/STM.hs
back into Store.hs — the separate file was unnecessary indirection
for the always-present default implementation.
- Parameterize xftpFileTests over store backend using HSpec SpecWith
pattern (following SMP's serverTests approach). The same 11 tests
now run against both memory and PostgreSQL backends via a bracket
parameter, eliminating all *Pg test duplicates.
- Extract shared run* functions (runTestFileChunkDeliveryAddRecipients,
runTestWrongChunkSize, runTestFileChunkExpiration, runTestFileStorageQuota)
from inlined test bodies.
* refactor: clean up per good-code review
- Remove internal helpers from Postgres.hs export list (withDB, withDB',
handleDuplicate, assertUpdated, withLog are not imported externally)
- Replace local isNothing_ with Data.Maybe.isNothing in Env.hs
- Consolidate duplicate/unused imports in XFTPStoreTests.hs
- Add file_path IS NULL and status guards to STM setFilePath, matching
the Postgres implementation semantics
* test: parameterize XFTP server, agent and CLI tests over store backend
- xftpTest/xftpTest2/xftpTest4/xftpTestN now take XFTPTestBracket as
first argument, enabling the same test to run against both memory
and PostgreSQL backends.
- xftpFileTests (server tests), xftpAgentFileTests (agent tests), and
xftpCLIFileTests (CLI tests) are SpecWith-parameterized suites that
receive the bracket from HSpec's before combinator.
- Test.hs runs each parameterized suite twice: once with
xftpMemoryBracket, once with xftpPostgresBracket (CPP-guarded).
- STM-specific tests (store log restore/replay) stay in memory-only
xftpAgentTests. SNI/CORS tests stay in memory-only xftpServerTests.
* refactor: remove dead test wrappers after parameterization
Remove old non-parameterized test wrapper functions that were
superseded by the store-backend-parameterized test suites.
All test bodies (run* and _ functions) are preserved and called
from the parameterized specs. Clean up unused imports.
* feat: add manual tests and guide
* refactor: merge file_size CHECK into initial migration
* refactor: extract rowToFileRec shared by getFile sender/recipient paths
* refactor: parameterize XFTPServerConfig over store type
Embed XFTPStoreConfig s as serverStoreCfg field, matching SMP's
ServerConfig. runXFTPServer and newXFTPServerEnv now take a single
XFTPServerConfig s. Restore verifyCmd local helper structure.
* refactor: minimize diff in tests
Restore xftpServerTests and xftpAgentTests bodies to match master
byte-for-byte (only type signatures change for XFTPTestBracket
parameterization); inline the runTestXXX helpers that were split
on this branch.
* refactor: restore getFile position to match master
* refactor: rename withSTMFile back to withFile
* refactor: close store log inside closeFileStore for STM backend
Move STM store log close responsibility into closeFileStore to
match PostgresFileStore, removing the asymmetry where only PG's
close was self-contained.
STMFileStore holds the log in a TVar populated by newXFTPServerEnv
after readWriteFileStore; stopServer no longer needs the explicit
withFileLog closeStoreLog call. Writes still go through XFTPEnv.storeLog
via withFileLog (unchanged).
* refactor: rename XFTPTestBracket to XFTPTestServer
* fix: move file_size check from PG schema to store log import
* refactor: use SQL-standard type names in XFTP schema
* perf: batch expired file deletions with deleteFiles
* refactor: stream export instead of loading recipients into memory
* refactor: parameterize XFTP store with FSType singleton dispatch
* refactor: minimize diff per review feedback
* refactor: use types over strings, deduplicate parser
* refactor: always parse database store type, fail at startup
* fix compilation without postgresql
* refactor: always parse database store type, fail at startup
SimpleXMQ
📢 SimpleXMQ v1 is released - with many security, privacy and efficiency improvements, new functionality - see release notes.
Please note: v1 is not backwards compatible, but it has the version negotiation built into all protocol layers for forwards compatibility of this version and backwards compatibility of the future versions, that will be backwards compatible for at least two versions back.
If you have a server deployed please deploy a new server to a new host and retire the previous version once it is no longer used.
Message broker for unidirectional (simplex) queues
SimpleXMQ is a message broker for managing message queues and sending messages over public network. It consists of SMP server, SMP client library and SMP agent that implement SMP protocol for client-server communication and SMP agent protocol to manage duplex connections via simplex queues on multiple SMP servers.
SMP protocol is inspired by Redis serialization protocol, but it is much simpler - it currently has only 10 client commands and 8 server responses.
SimpleXMQ is implemented in Haskell - it benefits from robust software transactional memory (STM) and concurrency primitives that Haskell provides.
SimpleXMQ roadmap
- SimpleX service protocol and application template - to enable users building services and chat bots that work over SimpleX protocol stack. The first such service will be a notification service for a mobile app.
- SMP queue redundancy and rotation in SMP agent connections.
- SMP agents synchronization to share connections and messages between multiple agents (it would allow using multiple devices for simplex-chat).
Components
SMP server
SMP server can be run on any Linux distribution, including low power/low memory devices. OpenSSL library is required for initialization.
To initialize the server use smp-server init -n <fqdn> (or smp-server init --ip <ip> for IP based address) command - it will generate keys and certificates for TLS transport. The fingerprint of offline certificate is used as part of the server address to protect client/server connection against man-in-the-middle attacks: smp://<fingerprint>@<hostname>[:5223].
SMP server uses in-memory persistence with an optional append-only log of created queues that allows to re-start the server without losing the connections. This log is compacted on every server restart, permanently removing suspended and removed queues.
To enable store log, initialize server using smp-server -l command, or modify smp-server.ini created during initialization (uncomment enable: on option in the store log section). Use smp-server --help for other usage tips.
Starting from version 2.3.0, when store log is enabled, the server would also enable saving undelivered messages on exit and restoring them on start. This can be disabled via a separate setting restore_messages in smp-server.ini file. Saving messages would only work if the server is stopped with SIGINT signal (keyboard interrupt), if it is stopped with SIGTERM signal the messages would not be saved.
Please note: On initialization SMP server creates a chain of two certificates: a self-signed CA certificate ("offline") and a server certificate used for TLS handshake ("online"). You should store CA certificate private key securely and delete it from the server. If server TLS credential is compromised this key can be used to sign a new one, keeping the same server identity and established connections. CA private key location by default is
/etc/opt/simplex/ca.key.
SMP server implements SMP protocol.
Running SMP server on MacOS
SMP server requires OpenSSL library for initialization. On MacOS OpenSSL library may be replaced with LibreSSL, which doesn't support required algorithms. Before initializing SMP server verify you have OpenSSL installed:
openssl version
If it says "LibreSSL", please install original OpenSSL:
brew update
brew install openssl
echo 'PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"' >> ~/.zprofile # or follow whatever instructions brew suggests
. ~/.zprofile # or restart your terminal to start a new session
Now openssl version should be saying "OpenSSL". You can now run smp-server init to initialize your SMP server.
SMP client library
SMP client is a Haskell library to connect to SMP servers that allows to:
- execute commands with a functional API.
- receive messages and other notifications via STM queue.
- automatically send keep-alive commands.
SMP agent
SMP agent library can be used to run SMP agent as part of another application and to communicate with the agent via STM queues, without serializing and parsing commands and responses.
Haskell type ACommand represents SMP agent protocol to communicate via STM queues.
See simplex-chat terminal UI for the example of integrating SMP agent into another application.
SMP agent executable can be used to run a standalone SMP agent process that implements plaintext SMP agent protocol via TCP port 5224, so it can be used via telnet. It can be deployed in private networks to share access to the connections between multiple applications and services.
Using SMP server and SMP agent
You can either run your own SMP server locally or deploy using Linode StackScript, or try local SMP agent with the deployed servers:
smp://[email protected]
smp://[email protected]
smp://[email protected]
It's the easiest to try SMP agent via a prototype simplex-chat terminal UI.
Deploy SMP/XFTP servers on Linux
You can run your SMP/XFTP server as a Linux process, optionally using a service manager for booting and restarts.
Notice that smp-server and xftp-server requires openssl as run-time dependency (it is used to generate server certificates during initialization). Install it with your packet manager:
# For Ubuntu
apt update && apt install openssl
Install binaries
Using Docker
On Linux, you can deploy smp and xftp server using Docker. This will download image from Docker Hub.
-
Create directories for persistent Docker configuration:
mkdir -p $HOME/simplex/{xftp,smp}/{config,logs} && mkdir -p $HOME/simplex/xftp/files -
Run your Docker container.
-
smp-serverYou must change your_ip_or_domain.
-e "pass=password"is optional variable to password-protect yoursmpserver:docker run -d \ -e "ADDR=your_ip_or_domain" \ -e "PASS=password" \ -p 5223:5223 \ -v $HOME/simplex/smp/config:/etc/opt/simplex:z \ -v $HOME/simplex/smp/logs:/var/opt/simplex:z \ simplexchat/smp-server:latest -
xftp-serverYou must change your_ip_or_domain and maximum_storage.
docker run -d \ -e "ADDR=your_ip_or_domain" \ -e "QUOTA=maximum_storage" \ -p 443:443 \ -v $HOME/simplex/xftp/config:/etc/opt/simplex-xftp:z \ -v $HOME/simplex/xftp/logs:/var/opt/simplex-xftp:z \ -v $HOME/simplex/xftp/files:/srv/xftp:z \ simplexchat/xftp-server:latest
-
Using installation script
Please note that currently, only Ubuntu distribution is supported.
You can install and setup servers automatically using our script:
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/simplex-chat/simplexmq/stable/install.sh -o simplex-server-install.sh &&\
if echo '53fcdb4ceab324316e2c4cda7e84dbbb344f32550a65975a7895425e5a1be757 simplex-server-install.sh' | sha256sum -c; then
chmod +x ./simplex-server-install.sh
./simplex-server-install.sh
rm ./simplex-server-install.sh
else
echo "SHA-256 checksum is incorrect!"
rm ./simplex-server-install.sh
fi
Build from source
Using Docker
Please note: to build the app use source code from stable branch.
On Linux, you can build smp server using Docker.
-
Build your images:
git clone https://github.com/simplex-chat/simplexmq cd simplexmq git checkout stable DOCKER_BUILDKIT=1 docker build -t local/smp-server --build-arg APP="smp-server" --build-arg APP_PORT="5223" . # For xmp-server DOCKER_BUILDKIT=1 docker build -t local/xftp-server --build-arg APP="xftp-server" --build-arg APP_PORT="443" . # For xftp-server -
Create directories for persistent Docker configuration:
mkdir -p $HOME/simplex/{xftp,smp}/{config,logs} && mkdir -p $HOME/simplex/xftp/files -
Run your Docker container.
-
smp-serverYou must change your_ip_or_domain.
-e "pass=password"is optional variable to password-protect yoursmpserver:docker run -d \ -e "ADDR=your_ip_or_domain" \ -e "PASS=password" \ -p 5223:5223 \ -v $HOME/simplex/smp/config:/etc/opt/simplex:z \ -v $HOME/simplex/smp/logs:/var/opt/simplex:z \ simplexchat/smp-server:latest -
xftp-serverYou must change your_ip_or_domain and maximum_storage.
docker run -d \ -e "ADDR=your_ip_or_domain" \ -e "QUOTA=maximum_storage" \ -p 443:443 \ -v $HOME/simplex/xftp/config:/etc/opt/simplex-xftp:z \ -v $HOME/simplex/xftp/logs:/var/opt/simplex-xftp:z \ -v $HOME/simplex/xftp/files:/srv/xftp:z \ simplexchat/xftp-server:latest
-
Using your distribution
-
Install dependencies and build tools (
GHC,cabaland dev libs):# On Ubuntu. Depending on your distribution, use your package manager to determine package names. sudo apt-get update && apt-get install -y build-essential curl libffi-dev libffi7 libgmp3-dev libgmp10 libncurses-dev libncurses5 libtinfo5 pkg-config zlib1g-dev libnuma-dev libssl-dev export BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3 export BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.3.0 curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh ghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}" ghcup set cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}" source ~/.ghcup/env -
Build the project:
git clone https://github.com/simplex-chat/simplexmq cd simplexmq git checkout stable cabal update cabal build exe:smp-server exe:xftp-server -
List compiled binaries:
smp-servercabal list-bin exe:smp-serverxftp-servercabal list-bin exe:xftp-server
-
Initialize SMP server with
smp-server init [-l] -n <fqdn>orsmp-server init [-l] --ip <ip>- depending on how you initialize it, either FQDN or IP will be used for server's address. -
Run
smp-server startto start SMP server, or you can configure a service manager to run it as a service. -
Optionally,
smp-servercan be setup for having an onion address intornetwork. See:scripts/tor. In this case, the server address can have both public and onion hostname pointing to the same server, to allow two people connect when only one of them is using Tor. The server address would be:smp://<fingerprint>@<public_hostname>,<onion_hostname>
See this section for more information. Run smp-server -h and smp-server init -h for explanation of commands and options.
Deploy SMP server on Linode
* You can use free credit Linode offers when creating a new account to deploy an SMP server.
Deployment on Linode is performed via StackScripts, which serve as recipes for Linode instances, also called Linodes. To deploy SMP server on Linode:
- Create a Linode account or login with an already existing one.
- Open SMP server StackScript and click "Deploy New Linode".
- You can optionally configure the following parameters:
- SMP Server store log flag for queue persistence on server restart, recommended.
- Linode API token to attach server address etc. as tags to Linode and to add A record to your 2nd level domain (e.g.
example.comdomain should be created in your account prior to deployment). The API token access scopes:- read/write for "linodes"
- read/write for "domains"
- Domain name to use instead of Linode IP address, e.g.
smp1.example.com.
- Choose the region and plan, Shared CPU Nanode with 1Gb is sufficient.
- Provide ssh key to be able to connect to your Linode via ssh. If you haven't provided a Linode API token this step is required to login to your Linode and get the server's fingerprint either from the welcome message or from the file
/etc/opt/simplex/fingerprintafter server starts. See Linode's guide on ssh . - Deploy your Linode. After it starts wait for SMP server to start and for tags to appear (if a Linode API token was provided). It may take up to 5 minutes depending on the connection speed on the Linode. Connecting Linode IP address to provided domain name may take some additional time.
- Get
addressandfingerprinteither from Linode tags (click on a tag and copy it's value from the browser search panel) or via ssh. - Great, your own SMP server is ready! If you provided FQDN use
smp://<fingerprint>@<fqdn>as SMP server address in the client, otherwise usesmp://<fingerprint>@<ip_address>.
Please submit an issue if any problems occur.
Deploy SMP server on DigitalOcean
🚧 DigitalOcean snapshot is currently not up to date, it will soon be updated 🏗️
* When creating a DigitalOcean account you can use this link to get free credit. (You would still be required either to provide your credit card details or make a confirmation pre-payment with PayPal)
To deploy SMP server use SimpleX Server 1-click app from DigitalOcean marketplace:
- Create a DigitalOcean account or login with an already existing one.
- Click 'Create SimpleX server Droplet' button.
- Choose the region and plan according to your requirements (Basic plan should be sufficient).
- Finalize Droplet creation.
- Open "Console" on your Droplet management page to get SMP server fingerprint - either from the welcome message or from
/etc/opt/simplex/fingerprint. Alternatively you can manually SSH to created Droplet, see DigitalOcean instruction. - Great, your own SMP server is ready! Use
smp://<fingerprint>@<ip_address>as SMP server address in the client.
Please submit an issue if any problems occur.
Please note: SMP server uses server address as a Common Name for server certificate generated during initialization. If you would like your server address to be FQDN instead of IP address, you can log in to your Droplet and run the commands below to re-initialize the server. Alternatively you can use Linode StackScript which allows this parameterization.
smp-server delete
smp-server init [-l] -n <fqdn>
