# LinkSyncServer A self-hosted bookmark server with advanced collection and query capabilities, designed to work with browser extensions for bookmark synchronization. ## Overview LinkSyncServer replaces the need for workarounds in existing bookmark sync solutions. It provides: - **True Collections** - First-class collection objects with saved queries - **Advanced Query Engine** - Supports AND, OR, XOR set operations - **Firefox-Compatible Fields** - All bookmark attributes natively supported - **Multi-User Support** - Authentication with admin and regular user roles - **RESTful API** - Full CRUD operations for links and collections - **Web Interface** - Modern UI for browsing, searching, and managing collections - **Docker-Ready** - Easy deployment with Docker Compose ## Features ### Collections Two types of collections: | Type | Description | |------|-------------| | **Static** | Explicit set of link IDs | | **Dynamic** | Query expression evaluated on each access | #### Dynamic Collection Query Syntax ``` ('term1', 'term2', 'term3') OR tagA AND tagB XOR url:example.com ``` - Parentheses evaluated first (innermost to outermost) - Left-to-right evaluation otherwise - Precedence: `()` > XOR > AND > OR ### Set Operations Query builder supports visual set operations: ``` Set1 AND Set2 XOR Set3 OR Set4 ``` This evaluates as: `(((Set1 AND Set2) XOR Set3) OR Set4)` ### Synchronization Modes | Mode | Browser → Server | Server → Browser | |------|------------------|------------------| | **Bi-directional** | Add/update | Add/update | | **Browser Authoritative** | Add/update | Overwrite | | **Server Authoritative** | Download only | Overwrite | Optional: Enable deletions for all modes. ### Bookmarks (Links) All Firefox bookmark attributes supported: - `id` - Unique identifier - `url` - Bookmark URL (duplicates allowed) - `title` - Display title - `description` - Optional description - `notes` - User notes - `tags` - Array of tag names - `favicon_url` - Icon URL - `path` - Folder structure - `created_at`, `updated_at` - Timestamps - `visit_count` - Number of visits - `is_bookmarked` - Bookmarked status - `source_set_id` - Collection that added this link ## Architecture ``` ┌─────────────────────────────────────┐ │ LinkSyncServer │ │ │ │ ┌──────────────┐ ┌─────────────┐ │ │ │ API Layer │ │ Auth │ │ │ └──────────────┘ └─────────────┘ │ │ ┌──────────────┐ ┌─────────────┐ │ │ │ Query │ │ Models │ │ │ │ Engine │ │ (SQLAlchemy)│ │ │ └──────────────┘ └─────────────┘ │ │ ┌──────────────┐ ┌─────────────┐ │ │ │ Templates │ │ Static │ │ │ └──────────────┘ │ Files │ │ │ ┌──────────────┐ └─────────────┘ │ │ │ PostgreSQL │ │ │ │ │ │ │ │ │ └──────────────┘ │ │ └─────────────────────────────────────┘ ``` ## Quick Start ### Prerequisites - Docker and Docker Compose - Port 5000 available (or configurable) ### Docker Compose ```yaml version: '3.8' services: web: build: . ports: - "5000:5000" environment: - DATABASE_URL=postgresql://user:password@db:5432/linksync - SECRET_KEY=your-secret-key-here - ADMIN_USERNAME=admin - ADMIN_PASSWORD=admin123 depends_on: - db db: image: postgres:15-alpine environment: - POSTGRES_DB=linksync - POSTGRES_USER=user - POSTGRES_PASSWORD=password volumes: - linkdata:/var/lib/postgresql/data volumes: linkdata: ``` ### Build and Run ```bash docker-compose up -d --build ``` ### How `build: .` Works In `docker-compose.yml`, the `web` service uses `build: .` instead of `image:`. This is a key distinction: | Key | Behavior | |-----|----------| | `image: postgres:15-alpine` | Pulls a pre-built image from Docker Hub | | `build: .` | Builds a custom image from a `Dockerfile` in the current directory (`.`) | **The build process works like this:** ``` docker-compose up --build │ ▼ Reads docker-compose.yml │ ▼ Finds build: . → looks for Dockerfile in current directory │ ▼ Executes each instruction in the Dockerfile: 1. FROM python:3.12-slim ← Base image 2. RUN apt-get install curl ← Install system deps 3. COPY requirements.txt . ← Copy dependency list 4. RUN pip install -r ... ← Install Python packages 5. COPY . . ← Copy all project files 6. EXPOSE 5000 ← Declare port 7. CMD ["uvicorn", ...] ← Set startup command │ ▼ Tags the built image as linksyncserver-web (auto-generated name) │ ▼ Starts the container from the built image ``` **Why build instead of pull?** - You're running your own application code, not a third-party image - Every code change requires a rebuild to take effect - The `Dockerfile` defines exactly how your app is packaged **Rebuilding after code changes:** ```bash # Rebuild and restart (picks up all code changes) docker-compose up -d --build # Rebuild without cache (forces fresh pip install) docker-compose build --no-cache && docker-compose up -d # Just restart without rebuilding (uses existing image) docker-compose restart ``` **The `--build` flag:** Forces Docker Compose to rebuild images before starting containers. Without it, Compose reuses any previously built image, meaning your code changes won't be reflected. ### Initial Login - URL: `http://localhost:5000` - Admin credentials from environment variables - Create first admin account - Admin can create regular users and admin users ## API Documentation See `/api/docs` or `/api/openapi.json` for complete API specification. ## Configuration Environment variables: | Variable | Description | Default | |----------|-------------|---------| | `DATABASE_URL` | PostgreSQL connection string | Required | | `SECRET_KEY` | JWT secret key | Required | | `ADMIN_USERNAME` | Initial admin username | - | | `ADMIN_PASSWORD` | Initial admin password | - | | `DEBUG` | Debug mode | False | | `HOST` | Bind address | 0.0.0.0 | | `PORT` | Port | 5000 | ## Project Structure ``` LinkSyncServer/ ├── README.md ├── TODOs.txt ├── design.md ├── tasks.md ├── AGENTS.md ├── docker-compose.yml ├── Dockerfile ├── requirements.txt ├── app.py ├── config/ ├── api/ ├── models/ ├── queries/ ├── templates/ └── static/ ``` ## Deployment ### Deploy Script The project includes `deploy.ps1` (Windows) and `deploy.sh` (Linux/macOS) to prepare a clean deployment package. These scripts copy only production files, exclude development artifacts (`tests/`, `__pycache__/`, `.git/`, etc.), and create a starter `.env` file. #### Usage ```powershell # Windows .\deploy.ps1 C:\deploy\linksync ``` ```bash # Linux/macOS chmod +x deploy.sh ./deploy.sh /opt/deploy/linksync ``` #### What Gets Deployed ``` linksync-deploy/ ├── .env ← starter file (edit with production secrets) ├── .env.example ├── docker-compose.yml ├── Dockerfile ├── requirements.txt ├── app.py ├── api/ ├── models/ ├── queries/ ├── config/ ├── templates/ ├── static/ ├── alembic/ ├── pyproject.toml ├── README.md ├── AGENTS.md ├── design.md ├── tasks.md └── TODOs.txt ``` #### What Is Excluded `tests/`, `__pycache__/`, `.pytest_cache/`, `.git/`, `.vscode/`, `*.pyc`, `*.db`, `*.sqlite3`, `node_modules/`, `dist/`, `build/`, and the deploy scripts themselves. #### Full Deployment Workflow ```bash # 1. Clone the repository to a temporary location git clone /tmp/linksync-src cd /tmp/linksync-src # 2. Run the deploy script to prepare the package ./deploy.sh /opt/linksync # 3. Configure production secrets cd /opt/linksync nano .env # Set these values: # DATABASE_URL=postgresql://user:pass@db:5432/linksync # SECRET_KEY= # ADMIN_PASSWORD= # 4. Build and start docker-compose up -d --build # 5. Verify curl http://localhost:5000/health # 6. Clean up the source clone rm -rf /tmp/linksync-src ``` #### Updating an Existing Deployment ```bash # On the server, pull latest code and redeploy cd /tmp/linksync-src && git pull ./deploy.sh /opt/linksync cd /opt/linksync docker-compose up -d --build rm -rf /tmp/linksync-src ``` ## License MIT License ## Support For issues and feature requests, see the GitHub repository.