Self-hosted sync server for Tickit task manager
Keep your tasks synced across all your devices β on your own infrastructure.
- β¨ Features
- π Quick Start
- π³ Docker / Podman Deployment
- βοΈ Configuration
- π Authentication
- π‘ API Reference
- ποΈ Architecture
- π§ Building from Source
- π€ Contributing
- π License
|
Your data stays on your infrastructure. No third-party services, no data mining, complete privacy. Single binary, ~5MB. SQLite storage. Runs on anything from a Raspberry Pi to a cloud VM. Simple API token authentication. Generate tokens per-device for easy management. |
One-command deployment with Docker or Podman. Includes docker-compose for production use. Last-write-wins with timestamp-based conflict detection. Your most recent changes always win. Sync unlimited devices. Each device gets its own token for tracking and security. |
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Laptop β β Desktop β β Phone β
β Tickit β β Tickit β β (future) β
ββββββββ¬βββββββ ββββββββ¬βββββββ ββββββββ¬βββββββ
β β β
β HTTPS + Token β β
βββββββββββββ¬ββββββββ΄ββββββββββββββββββββ
β
βΌ
βββββββββββββββββ
β tickit-sync β
β Server β
β (SQLite) β
βββββββββββββββββ
From Binary (Recommended)
# Download latest release
curl -LO https://github.com/ricardodantas/tickit-sync/releases/latest/download/tickit-sync-linux-x86_64.tar.gz
tar xzf tickit-sync-linux-x86_64.tar.gz
sudo mv tickit-sync /usr/local/bin/From crates.io
cargo install tickit-sync# Create config file
tickit-sync init
# This creates ~/.config/tickit-sync/config.toml# Create a token for your first device
tickit-sync token --name "my-laptop"
# Token is auto-saved to config (hashed) and displays setup instructions
# for both mobile app and desktop CLI
β οΈ Important: If the server is already running, you must restart it after generating a new token:# Docker docker restart tickit-sync # Podman podman restart tickit-sync
# Start on default port 3030
tickit-sync serve
# Or specify port
tickit-sync serve --port 8080The token generation command shows setup instructions for all clients:
Mobile App (tickit-mobile):
- Open Settings
- Set Sync Server URL
- Paste the generated token
- Enable Sync
Desktop CLI (tickit):
- Press
sto open Settings - Navigate to Sync Server, press Enter, type URL
- Navigate to Sync Token, press Enter, paste token
- Enable Sync
Or manually add to ~/.config/tickit/config.toml:
[sync]
enabled = true
server = "http://your-server:3030"
token = "tks_a1b2c3d4e5f6..."
interval_secs = 300 # Sync every 5 minutesNote: All commands below work with both Docker and Podman. Simply replace
dockerwithpodmanif you prefer Podman.
# Docker
docker run -d \
--name tickit-sync \
-p 3030:3030 \
-v tickit-data:/data \
ricardodantas/tickit-sync
# Podman
podman run -d \
--name tickit-sync \
-p 3030:3030 \
-v tickit-data:/data \
docker.io/ricardodantas/tickit-sync# docker-compose.yml / podman-compose.yml
services:
tickit-sync:
image: ricardodantas/tickit-sync:latest
container_name: tickit-sync
restart: unless-stopped
ports:
- "3030:3030"
volumes:
- ./data:/data
environment:
- TICKIT_SYNC_PORT=3030# Docker
docker compose up -d
# Podman
podman-compose up -d# Docker
docker exec tickit-sync tickit-sync token --name "my-device"
# Podman
podman exec tickit-sync tickit-sync token --name "my-device"
# The token is automatically saved to the server config.
# The output shows setup instructions for mobile app and desktop CLI.
β οΈ Important: After generating a token, restart the container for it to take effect:docker restart tickit-sync # or: podman restart tickit-sync
# Caddyfile
sync.yourdomain.com {
reverse_proxy tickit-sync:3030
}
services:
tickit-sync:
image: ricardodantas/tickit-sync:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.tickit-sync.rule=Host(`sync.yourdomain.com`)"
- "traefik.http.routers.tickit-sync.tls.certresolver=letsencrypt"
volumes:
- ./data:/dataConfiguration file: ~/.config/tickit-sync/config.toml (or /data/config.toml in Docker)
# Server settings
[server]
port = 3030
bind = "0.0.0.0"
# Database settings
[database]
path = "/data/tickit-sync.sqlite"
# API tokens (managed via CLI, hashed with argon2)
[[tokens]]
name = "my-laptop"
token_hash = "$argon2id$v=19$m=19456,t=2,p=1$..."
[[tokens]]
name = "my-desktop"
token_hash = "$argon2id$v=19$m=19456,t=2,p=1$..."| Variable | Default | Description |
|---|---|---|
TICKIT_SYNC_PORT |
3030 |
Server port |
TICKIT_SYNC_HOST |
0.0.0.0 |
Bind address |
TICKIT_SYNC_DB |
./tickit-sync.sqlite |
Database path |
TICKIT_SYNC_CONFIG |
~/.config/tickit-sync/config.toml |
Config file path |
All API endpoints (except /health) require a Bearer token.
# Generate new token (automatically saved to config, hashed with argon2)
tickit-sync token --name "device-name"
# List all tokens
tickit-sync token --list
# Revoke a token
tickit-sync token --revoke "device-name"
β οΈ Important: Tokens are hashed with Argon2 before storage. The plaintext token is only shown once when generated. Save it immediately!
Include the token in the Authorization header:
curl -H "Authorization: Bearer tks_your_token_here" \
https://sync.example.com/api/v1/syncTokens are prefixed with tks_ followed by 32 random alphanumeric characters:
tks_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Tokens are stored hashed in the config file using Argon2id:
[[tokens]]
name = "my-laptop"
token_hash = "$argon2id$v=19$m=19456,t=2,p=1$..."GET /healthResponse:
{
"status": "ok",
"version": "0.1.0"
}POST /api/v1/sync
Authorization: Bearer <token>
Content-Type: application/jsonRequest:
{
"device_id": "uuid-of-device",
"last_sync": "2026-02-06T22:00:00Z",
"changes": [
{
"type": "list",
"id": "uuid",
"name": "Work",
"icon": "πΌ",
"is_inbox": false,
"sort_order": 0,
"created_at": "2026-02-06T20:00:00Z",
"updated_at": "2026-02-06T22:30:00Z"
},
{
"type": "task",
"id": "uuid",
"title": "Buy groceries",
"completed": false,
"priority": "medium",
"list_id": "uuid",
"tag_ids": ["tag-uuid-1", "tag-uuid-2"],
"created_at": "2026-02-06T20:00:00Z",
"updated_at": "2026-02-06T22:30:00Z"
},
{
"type": "deleted",
"id": "uuid",
"record_type": "task",
"deleted_at": "2026-02-06T22:15:00Z"
}
]
}Response:
{
"server_time": "2026-02-06T22:35:00Z",
"changes": [
// Changes from other devices since last_sync
],
"conflicts": [] // Reserved for future conflict reporting
}| Type | Description |
|---|---|
task |
Task record with title, description, priority, etc. |
list |
List/folder for organizing tasks |
tag |
Tag for categorizing tasks |
task_tag |
Association between task and tag |
deleted |
Tombstone for deleted records |
.
βββ src/
β βββ main.rs # CLI entry point (clap)
β βββ api.rs # Axum HTTP handlers
β βββ config.rs # TOML config loading
β βββ db.rs # SQLite operations
β βββ models.rs # Shared data types
βββ Dockerfile # Multi-stage build
βββ docker-compose.yml # Production deployment
βββ Cargo.toml
| Component | Technology |
|---|---|
| Runtime | Rust (Edition 2024) |
| HTTP Framework | Axum |
| Database | SQLite via rusqlite |
| Async Runtime | Tokio |
| CLI Parser | Clap |
| Serialization | Serde + JSON |
| Config | TOML |
-- Tasks synced from all devices
CREATE TABLE tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
url TEXT,
priority TEXT DEFAULT 'medium',
completed INTEGER DEFAULT 0,
list_id TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
completed_at TEXT,
due_date TEXT,
FOREIGN KEY (list_id) REFERENCES lists(id)
);
-- Lists/folders for organizing tasks
CREATE TABLE lists (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
icon TEXT DEFAULT 'π',
color TEXT,
is_inbox INTEGER DEFAULT 0,
sort_order INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
-- Tags for categorizing tasks
CREATE TABLE tags (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
color TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT
);
-- Task-Tag junction table
CREATE TABLE task_tags (
task_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (task_id, tag_id),
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
-- Tombstones for deleted records
CREATE TABLE tombstones (
id TEXT PRIMARY KEY,
record_type TEXT NOT NULL,
deleted_at TEXT NOT NULL
);
-- Device sync state tracking
CREATE TABLE device_sync (
device_id TEXT PRIMARY KEY,
last_sync TEXT NOT NULL
);- Rust 1.93+ (uses Edition 2024 features)
- SQLite development libraries
# Clone
git clone https://github.com/ricardodantas/tickit-sync
cd tickit-sync
# Build release binary
cargo build --release
# Binary at: target/release/tickit-syncdocker build -t tickit-sync .# For Linux (musl - static binary)
cargo build --release --target x86_64-unknown-linux-musl
# For macOS
cargo build --release --target x86_64-apple-darwin
# For Windows
cargo build --release --target x86_64-pc-windows-msvc- Always use HTTPS in production (via reverse proxy)
- Tokens are hashed - stored using Argon2id, never in plaintext
- Keep tokens secret - treat them like passwords, only shown once at generation
- Firewall - only expose the server to trusted networks or use a VPN
- Backups - regularly backup the SQLite database
- Updates - keep tickit-sync updated for security patches
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see LICENSE for details.
Built with β€οΈ for Tickit