This guide is for people who want to run a Cosimo sync server. If you only want
to use an existing server from Cosimo, read the Deck Sync section of the user
guide instead.
The sync server stores and serves deck snapshots. It does not run scheduling logic and does not merge independently edited decks. Owner uploads can include review history; public and unlisted downloads are derived as content-only bundles so another user's review database is not served accidentally.
The sync HTTP API uses the /api/v1 route prefix. From the release candidate
onward, current /api/v1 endpoints should remain available on future sync
servers so RC-and-later Cosimo clients can keep using upgraded servers. Future
releases may add endpoints, fields, or headers, but existing endpoint behaviour
should not be removed or incompatibly changed unless security or comparable
safety requirements make that necessary.
This compatibility promise applies to the machine API used by Cosimo clients. The server storage directory is private implementation detail, not a public API; future releases may change how bundles are stored internally. The human-facing sync pages, including the root status page, privacy notice, registration, device-authorisation, and account-deletion pages, are also not a machine-readable compatibility surface. Cosimo may open those pages for the user, but clients should not scrape their text or HTML structure.
Version matching is still prudent during the RC transition, especially
for decks with newly supported sidecar formats such as imported MP3 audio.
Older beta 11-era servers reject bundles containing MP3 sidecars because they
only accepted the earlier Opus/WAV sidecar formats.
The public project sync server at https://cosimo.isonomia.net/sync.cgi
currently runs the RC line. Users who want to use that public server should use
Cosimo 0.1.11-rc.1 or a later RC/stable build. Operators of private servers
can choose their own upgrade cadence, but should avoid mixing beta-era servers
with RC-era clients for decks containing MP3 sidecars or browser-mediated sync
registration/login flows.
Basic Requirements
A deployed sync server should have:
- an HTTPS URL reachable by Cosimo clients
- a private storage directory that is not served as static web content
- regular server-side backups of that storage directory
- enough disk space for deck bundles, especially decks with audio
- a test pass covering login, upload, download, remote deck listing, and owner-only deletion before relying on it with real decks
The storage directory contains the SQLite sync database, bearer-token
verifiers, pending OAuth/login records, privacy-acceptance records, deck
metadata, deck UUID owner reservations, and stored bundle files. Treat it as
private application data.
The sync SQLite backend enables PRAGMA secure_delete = ON for its ordinary
tables so deleted or replaced sync metadata is overwritten. This does not cover
web-server logs, external backups, or bundle files; account deletion removes
owned bundle directories separately.
Release Packages
The normal Windows portable Cosimo package includes the sync executables under
server: cosimo-sync-server.exe for local or reverse-proxy style use, and
cosimo-sync-cgi.exe for CGI-compatible Windows hosting.
For Linux amd64 servers, the project release process can also produce
cosimo-sync-x.y.z-linux-x86_64.tar.gz. That archive is server-only and
contains cosimo-sync-cgi, cosimo-sync-server, this deployment guide, and
the matching source archive.
CGI Deployment
CGI behind an existing HTTPS web server is the preferred deployment model at present. It is simpler to host, does not need a long-running process, and has had the most direct deployment testing.
The intended layout is similar to Fossil's CGI mode. Put a small executable
config script where the web server runs CGI programs, but keep the real
cosimo-sync-cgi binary and the storage directory outside the web-readable
tree.
In that intended layout, neither the binary nor the stored deck data is served as static web content. The web server only exposes the small CGI config script, and that script is executed as CGI rather than offered as a download.
The CGI binary can generate that script interactively:
cosimo-sync-cgi config sync.cgi
The wizard asks for the private storage root, public or domestic service mode,
privacy/controller details, domestic allowlist if relevant, and optional quotas
such as maximum bundle size and owner storage limits. It writes a complete
executable config script in the format shown below. Existing output files are
not overwritten unless --force is passed:
cosimo-sync-cgi config sync.cgi --force
Review the generated file before deployment. It is intended to be executed as CGI, not served as static text. The storage root named by the script must stay outside the web-readable tree.
A minimal config script looks like this:
#!/home/user/bin/cosimo-sync-cgi
storage-root: /home/user/private/cosimo-sync-storage
service-mode: public
controller-name: Example Sync Operator
controller-contact: privacy@example.invalid
rights-contact: privacy@example.invalid
privacy-notice-version: 2026-06-03
data-categories: Fediverse account identity, bearer-session metadata, deck bundles, review-history bundles, and operational logs
processing-purposes: Cosimo deck synchronisation and account security
lawful-basis: Explicit consent
recipients: Server operator and hosting provider
international-transfers: No international transfers beyond the configured hosting location
retention-policy: Account data is retained until deletion; logs are retained for up to one week unless a security hold applies
complaint-contact: the operator's supervisory authority or privacy contact
Mark the script executable and configure the web server so it runs as CGI. The
URL of that script is the sync server URL users enter in Tools -> Options.
For example, if the script is reachable as:
https://example.invalid/cgi-bin/cosimo-sync.cgi
then Cosimo should use that exact URL. Cosimo appends /api/v1/... paths below
the same CGI endpoint.
Opening the CGI URL in a browser should show a small Cosimo Sync Server status page. This is only a sanity check; Cosimo itself uses the API paths.
Owner routes need this request header to reach the CGI process:
Authorization: Bearer ...
In CGI environments this is normally exposed as HTTP_AUTHORIZATION. If the
web server strips it, login may succeed but owner listing, upload, download, or
deletion will fail with an authentication error. On Apache-style setups this
may require CGIPassAuth On or the host's documented equivalent.
The CGI executable resolves storage in this order:
--storage-root PATHstorage-root:in the config scriptCOSIMO_SYNC_STORAGE_ROOT- a
cosimo-sync-storagedirectory beside the CGI executable
Prefer an explicit private storage root outside the web-readable tree. In the recommended layout above this keeps all sync data out of the document root. The fallback directory beside the executable is convenient for testing, but may be unsafe if you copy the real CGI binary into a web-readable CGI directory and also let the web server serve sibling files as static content.
Concurrent CGI requests are allowed. SQLite serializes sync storage writes, so
two simultaneous uploads or deletes do not update metadata at the same time,
even if they target different decks. Upload bundle bytes are staged before the
SQLite write transaction starts, then the server re-checks ownership, expected
revision, and quota inside a short BEGIN IMMEDIATE transaction before
atomically promoting the staged bundle files and updating metadata. This keeps
large audio-deck uploads from holding the SQLite write lock for the whole
transfer.
A second writer waits up to 15 seconds for the current writer before SQLite
reports the database as busy. With the staged-upload path this should normally
be a short wait, but it can still happen during bursts of uploads, deletes, or
maintenance. When the sync server sees SQLite busy/locked contention, it
reports 503 Service Unavailable with a Retry-After hint rather than a
generic server failure.
Configure the front-end web server to enforce request-size and slow-client limits as well. In CGI mode, Cosimo deliberately avoids rejecting ordinary configured sync quota limits before consuming stdin, because doing so can make some clients report a transport-level send failure instead of receiving a useful HTTP response. The CGI executable still has a hard global body-size safety cap, and the normal upload route rejects over-limit declared bundle sizes and quota failures after the request body is available. The web server controls connection-level timeouts, buffering, and how long a slow client can occupy a CGI worker before Cosimo starts.
Unlike the standalone reverse-proxy server, the current CGI entry point does not yet enforce a portable no-progress timeout while reading the request body from stdin. Deployments that can configure the front-end server should use request-body timeout or minimum-rate controls. Deployments without that control should treat slow-client protection as a known limitation of the CGI path.
Standalone Server Behind A Reverse Proxy
The standalone cosimo-sync-server is intended to run behind a reverse proxy
for any real deployment. Do not expose it directly to the public internet at
this stage. The proxy should provide TLS, mature HTTP parsing, request-size
limits, slow-client protection, logging, and any site-level rate limiting. The
Cosimo process should be treated as a private backend service.
This model is supported, but it is more operationally involved and currently less exercised than CGI. It is most suitable when you control a VPS, dedicated server, home server, or VPN host and can run a supervised process.
Run the server bound to a private address, for example:
cosimo-sync-server --bind 127.0.0.1:44791 --storage-root /home/user/private/cosimo-sync-storage
Do not omit --storage-root for a real deployment. Without a storage root, the
standalone server uses volatile in-memory storage intended only for development
or temporary tests. Uploaded decks, login sessions, and OAuth state are lost
when the process exits.
Run only one standalone server process for a given storage root. The server
uses an OS-backed cosimo-sync-server.lock file in that storage root and exits
if another standalone server already holds it. This lock is deliberately not
used by CGI deployments, where concurrent per-request processes are expected
and SQLite transactions serialize storage changes. On Unix, the lock file is
opened relative to a no-follow storage-root directory descriptor, and the
storage root must be a plain directory.
Put an HTTPS reverse proxy in front of it. The reverse proxy should provide the public URL and TLS; the sync server itself has no built-in TLS and uses a small HTTP implementation intended for this controlled backend role only.
The standalone server handles one request per TCP connection and expects
Content-Length request bodies. It applies a request read timeout, a response
write timeout, and a bounded concurrent-request gate. When too many requests
are already active, it returns 503 Service Unavailable with a Retry-After
hint. A normal reverse proxy should buffer and pass requests in a compatible
form, and should have its own stricter or equivalent request-size and timeout
rules.
For OAuth rate limiting, the standalone server normally uses the TCP peer
address. Behind a same-host reverse proxy this is usually 127.0.0.1, which
means all users share the same client-address limit. If the proxy supplies a
real client-IP header, start the server with:
cosimo-sync-server --bind 127.0.0.1:44791 --storage-root /home/user/private/cosimo-sync-storage --trusted-client-ip-header X-Forwarded-For
Only use this when the reverse proxy strips any incoming copy of that header and then supplies its own trusted value. Cosimo only honours the configured trusted header from loopback peers, so accidentally exposing the backend port does not let remote clients spoof their rate-limit identity through this option.
Limits
Both CGI and reverse-proxy deployments support the same limit environment variables:
COSIMO_SYNC_MAX_BUNDLE_BYTES
COSIMO_SYNC_MAX_BUNDLE_EXPANDED_BYTES
COSIMO_SYNC_MAX_BUNDLE_ENTRIES
COSIMO_SYNC_MAX_DECK_FILE_BYTES
COSIMO_SYNC_MAX_DECKS_PER_OWNER
COSIMO_SYNC_MAX_STORAGE_BYTES_PER_OWNER
COSIMO_SYNC_MAX_STORAGE_BYTES_TOTAL
COSIMO_SYNC_MAX_OAUTH_STARTS_PER_CLIENT_PER_HOUR
COSIMO_SYNC_MAX_OAUTH_STARTS_PER_INSTANCE_PER_HOUR
COSIMO_SYNC_MAX_PENDING_OAUTH_LOGINS_PER_INSTANCE
The standalone reverse-proxy server also supports:
COSIMO_SYNC_MAX_CONCURRENT_REQUESTS
If this limit is reached, new connections receive 503 Service Unavailable
with Retry-After. The default is deliberately modest; use the reverse proxy
for broader traffic shaping rather than increasing this value blindly.
CGI config scripts can also set the same limits with lower-case hyphenated keys, such as:
max-bundle-bytes: 52428800
max-bundle-expanded-bytes: 52428800
max-decks-per-owner: 20
max-storage-bytes-per-owner: 104857600
max-storage-bytes-total: 1073741824
controller-name: Example Sync Operator
controller-contact: privacy@example.invalid
rights-contact: privacy@example.invalid
privacy-notice-version: 2026-06-03
data-categories: Fediverse account identity, bearer-session metadata, deck bundles, review-history bundles, and operational logs
processing-purposes: Cosimo deck synchronisation and account security
lawful-basis: Explicit consent
recipients: Server operator and hosting provider
international-transfers: No international transfers beyond the configured hosting location
retention-policy: Account data is retained until deletion; logs are retained for up to one week unless a security hold applies
complaint-contact: the operator's supervisory authority or privacy contact
Uploads are checked before they become authoritative. The server verifies declared hashes, rejects unsupported bundle paths, rejects review databases in content-only bundles, limits deck file size, and enforces configured storage and deck-count quotas. Declared request and bundle lengths are rejected early when they exceed configured limits, but deployments should still set matching or stricter proxy/web-server body-size limits so obviously oversized uploads are stopped before they reach Cosimo.
Current operational limits to understand:
- SQLite remains a single-writer database. Uploads and deletes are staged to keep write transactions short, but metadata writes still serialize.
- A stale upload from another device is rejected by expected-revision conflict. The client must download or otherwise refresh before retrying.
- On startup/open, the SQLite backend reconciles known Cosimo bundle leftovers:
stale
.upload-*staged files and unreferenced server-generated revision bundle files are removed. Unknown files are preserved, and symlinked bundle paths are rejected rather than followed. - Sync does not yet have a manifest/preflight protocol, so a client may still transfer a large bundle before learning that another device already uploaded a newer revision.
OAuth Behaviour
Cosimo uses Mastodon-compatible OAuth as the account proof. Public-service mode is the default and refuses new account registration until the operator has configured controller and privacy notice information. Domestic mode is available for non-public personal deployments; if it is exposed beyond that, the operator is responsible for the legal consequences. Domestic mode can restrict registration to a comma-separated allowlist of specific Fediverse account identities.
Deck -> Sync -> Register New Account asks the server to create a pending
browser registration and opens the sync server's registration page. That page
shows the configured privacy notice, asks for the Fediverse instance or full
account, and requires an unchecked acceptance checkbox before redirecting to
the Fediverse authorisation URL. If the user enters a full account, the
callback rejects a successful OAuth return for a different account on the same
instance. After authorisation, the Fediverse instance redirects the browser
back to the sync server callback. The server asks only for the read:accounts
scope, verifies the account, revokes the temporary Fediverse token, checks the
privacy/domestic registration policy, records the notice version/hash,
timestamp, verified owner identity, and lawful basis, and then lets Cosimo poll
the pending login token to receive a Cosimo bearer token.
Deck -> Sync -> Log In is for authorising another Cosimo installation for an
account that already exists on the sync server. It uses the same server-hosted
browser OAuth callback, but it does not create a new account and does not show
the full registration/privacy-acceptance flow again.
The server registers OAuth applications for deployment-specific callback URLs as needed during browser login. Cosimo bearer sessions are stored as hashed verifiers rather than plaintext bearer tokens.
The browser-login OAuth redirect URI is deployment-specific, for example:
https://example.invalid/sync.cgi/auth/fedi/callback
Account deletion uses the same browser OAuth mechanism with a separate callback URL:
https://example.invalid/sync.cgi/auth/fedi/delete/callback
Users can reach the deletion form from the sync server root page. Deletion requires the full Fediverse account and deletes server-side Cosimo sync data only after OAuth returns that same verified account. It does not require a Cosimo bearer token, so users can delete their server-side sync account even if they have lost or invalidated local Cosimo tokens. Account deletion also retires the account's deck UUID reservations by clearing the owner identity from them. This prevents later takeover of deleted deck identities without retaining the deleted Fediverse account identity.
For CGI, Cosimo constructs this from the request host, request scheme, and CGI script name. If a reverse proxy is involved, configure it so the public host and scheme reaching the sync server match the URL users enter in Cosimo.
Open the sync server root page in a browser and follow the privacy notice link,
or request /api/v1/privacy below the sync server URL for JSON, to inspect the
configured notice and registration status. Public deployments should configure
at least:
service-mode: publiccontroller-namecontroller-contact, using email, a web form, or postal contact rather than Fediverse-only contact- optional
controller-fedias an additional contact route rights-contactorcomplaint-contactprivacy-notice-versiondata-categoriesprocessing-purposeslawful-basisrecipientsinternational-transfersretention-policy
For a private personal deployment, use:
service-mode: domestic
domestic-registration-allowlist: acct:you@example.social
If the allowlist is omitted in domestic mode, any account that can complete the Fediverse OAuth proof can register. Do not use that configuration for a server offered to general users.
Moving a Sync Server
To move a sync server to another host or domain, stop traffic and copy the whole storage directory consistently. The SQLite database and bundle files must match.
After copying:
- Configure the new CGI script or reverse proxy to use the copied storage directory.
- Open the new server URL in a browser and check that the status page appears.
- Configure a Cosimo client to use the new sync server URL.
- Log in again and test sync status, upload, download, remote deck listing, and deletion with a test deck.
Moving a sync server changes the browser-login callback URL. Test login after a move. Browser login may need the server to register a fresh OAuth application for the new callback URL.
Changing the sync URL in Cosimo clears the local sync login state. Existing server-side sessions may still be present in the copied database, but clients should log in again after the move.
Production Deployment And Upgrade Checklist
Use this checklist before trusting a new deployment with real decks, and again when upgrading a server that already stores user data.
Storage And Backup Checks
- Confirm the storage root is outside any web-readable document tree.
- Confirm the storage root is writable by the CGI or server process and not writable by unrelated local users.
- Confirm the storage root contains the SQLite database and bundle directories, and that both are covered by the same backup process.
- Before an upgrade, take a fresh backup of the whole storage root while no sync request is running, or use a filesystem/database backup method that gives a consistent snapshot.
- Preserve file ownership and permissions when copying or restoring the storage root.
- Confirm server backups are retained according to the operator's privacy and retention policy.
Binary And Configuration Checks
- Deploy matching release binaries for the intended platform.
- For CGI, keep the real
cosimo-sync-cgibinary outside the web-readable tree when possible, and expose only the executable config script. - For CGI, review the config script after generation or upgrade and confirm
storage-root:points to the intended private storage root. - For the standalone server, start it with an explicit
--storage-rootand bind it to loopback or another private backend interface. - Confirm configured quota values are intentional: bundle size, expanded bundle size, deck-file size, decks per owner, per-owner storage, and total storage.
- Confirm the deployed public URL is the URL users enter in Cosimo. Browser OAuth callback URLs are derived from that public URL.
Front-End Web Server Checks
- Confirm the public endpoint uses HTTPS.
- Confirm
Authorization: Bearer ...reaches the CGI process asHTTP_AUTHORIZATION, or reaches the reverse-proxy backend unchanged. - Confirm the front-end server enforces request body size limits compatible with the Cosimo quota settings.
- Confirm the front-end server enforces slow-client request-body protection. This is especially important for CGI because the current CGI entry point does not yet enforce a portable no-progress timeout while reading stdin.
- Confirm reverse-proxy deployments strip untrusted incoming client-IP
headers before setting any header named with
--trusted-client-ip-header. - Confirm logs do not record bearer tokens, OAuth secrets, deck contents, or review-history contents.
Privacy And Registration Checks
- Open the server root page in a browser and confirm it identifies the Cosimo Sync Server and links to the health endpoint, privacy notice, and account deletion page with meaningful link text.
- Request
/api/v1/healthbelow the sync server URL and confirm it returns a successful response. - Request
/api/v1/privacybelow the sync server URL and confirm the configured privacy notice and registration state are correct. - For a public server, confirm registration is refused unless controller, rights or complaint contact, notice version, data categories, processing purposes, lawful basis, recipients, international transfers, and retention policy are configured.
- For a domestic or personal server, confirm the operator understands that it should not be offered to general public users, and configure a registration allowlist if only named accounts should be able to register.
- Confirm optional privacy fields that are not configured are omitted from browser pages rather than shown as placeholder text.
Functional Smoke Test
- Configure a disposable Cosimo installation with the deployed sync server URL.
- Register a new test account through
Deck -> Sync -> Register New Account. - Confirm the server registration page explains the configured notice and requires explicit acceptance before continuing.
- If the registration page asks for a full Fediverse account, confirm OAuth rejects a callback for a different account on the same instance.
- Upload a small test deck with study history.
- Check
Deck -> Sync -> Sync Statusand confirm the remote owner, revision, visibility, storage quota, and last upload device are plausible. - Open
Deck -> Sync -> Remote Decksand inspect the uploaded deck details. - Download the deck to a separate local path or test installation.
- Confirm public or unlisted shared downloads are content-only and do not include the owner's review database.
- Authorise a second Cosimo installation with
Deck -> Sync -> Log Inand confirm this does not repeat the full registration/privacy-acceptance flow. - Log out locally from one installation and confirm another installation using its own token still works.
- Invalidate the current token or all tokens, then confirm affected installations lose owner access.
- Delete the remote test deck while logged in as its owner.
- Confirm another account cannot delete a deck it does not own.
- Delete the test sync account through the server account-deletion flow and confirm the browser OAuth proof must match the account being deleted.
- After account deletion, confirm the Cosimo client reports the missing remote account or deck coherently and does not offer impossible download actions.
Upgrade Checks
- Read the release notes for sync schema, auth-flow, sidecar-media, or API compatibility changes.
- Take a fresh storage-root backup before deploying new binaries.
- Prevent new sync requests during the binary replacement when possible. For CGI, this can be a short maintenance window or a temporary web-server rule that rejects the sync endpoint.
- Replace the binary or packaged server files.
- Review the CGI config script or service command line for newly required configuration.
- Open the root page, health endpoint, and privacy endpoint before allowing normal use again.
- Run the functional smoke test with a disposable deck.
- Watch the web-server error log and Cosimo sync logs during the first real upload and download after upgrade.
Standalone Server Restart Check
- If you use
cosimo-sync-server, restart it and confirm uploaded decks are still listed. - If uploaded decks disappear after restart, the server was using volatile
in-memory storage rather than durable
--storage-rootstorage. - Confirm a second standalone server process cannot start against the same storage root.
Keep the storage directory private and backed up. The server is designed to avoid serving review databases through public or unlisted downloads, but owner study-history bundles still contain personal learning data.