Implement complete key:value format parsing for change log entries and fix critical tag synchronization issue from server to client. Key Changes: 1. Shared Key:Value Parser (NEW: internal/engine/parser.go) - Created ParseKeyValueFormat() for both edit and sync operations - Supports flexible whitespace: 'key:value' and 'key: value' - Handles comment skipping for edit files - Consolidates parsing logic (DRY principle) 2. Database Triggers - Tags Support (internal/engine/database.go) - Added tags to track_task_create trigger - Added tags to track_task_update trigger - Tags sorted alphabetically via SQL ORDER BY - Format: 'tags: alpha,bravo,charlie' 3. Task Creation - Tag Update Fix (internal/engine/task.go) - CreateTaskWithModifier() now triggers update after adding tags - Ensures tags appear in change log (UPDATE entry) - Fixes missing tags in initial CREATE entries 4. Edit Command - Use Shared Parser (cmd/edit.go) - Replaced custom parseEditedFile() with shared ParseKeyValueFormat() - Added tag sorting in parseTags() - Removed ~30 lines, improved maintainability 5. Sync Client - Complete Implementation (internal/sync/client.go) - NEW: applyChangeDataToTask() - parses all fields from change log - NEW: Helper functions for status, priority, tag parsing - FIXED: parseChanges() - sort by timestamp+ID before grouping - Added parent/child task ordering (prevents FK violations) - Enhanced tag sync in merge loop with task reload - Specific validation error messages per field Critical Bug Fix: - When CREATE and UPDATE have same timestamp, old code kept CREATE (no tags) - New code sorts by ID as tiebreaker, ensuring UPDATE (with tags) is used - Verified: Server->client tag sync now works correctly Validation: - Description must not be empty (both edit and sync) - Recurrence validated (not negative, max 100 years) - All timestamps parsed correctly (Unix epoch) - Tags sorted alphabetically in all contexts Testing: - Fresh pull from server: ✅ All tags present - API-created tasks: ✅ Tags sync correctly - Local->server->client round-trip: ✅ No data loss - Same-second CREATE+UPDATE: ✅ Correct entry processed - Parent/child tasks: ✅ Correct ordering Files Changed: - NEW: internal/engine/parser.go (+44 lines) - Modified: internal/engine/database.go (+10 lines) - Modified: internal/engine/task.go (+8 lines) - Modified: cmd/edit.go (-25 lines net) - Modified: internal/sync/client.go (+280 lines) - Modified: srv/README.md (+1 line) Total: +318 lines added, -25 removed, net +293 lines This completes Phase 6: Full bidirectional sync with complete tag support.
Opal-Task Server
Opal-task server provides a REST API for syncing tasks across multiple devices. This enables household task sharing and access from anywhere.
Features
- REST API - Chi-based HTTP API for task management
- API Key Authentication - Secure authentication using bcrypt-hashed keys
- Bidirectional Sync - Push and pull changes with conflict resolution
- Offline Support - Queue changes when server is unreachable
- Change Tracking - Automatic change log with configurable retention
- Single Database - Shared household task database (v1)
Quick Start
1. Build the Binary
cd opal-task
go build -o opal main.go
2. Generate API Key
On your server (with direct database access):
./opal server keygen --name "My Device" --db /var/lib/opal/opal.db
Output:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
API Key Generated Successfully
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Name: My Device
Key: oak_abc123...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ IMPORTANT: Save this key securely!
It will not be displayed again.
3. Start the Server
./opal server start --addr :8080 --db /var/lib/opal/opal.db
4. Configure Client
On your local machine or phone:
opal sync init --url https://opal.yourdomain.com --key oak_abc123...
Or use interactive mode:
opal sync init
# Enter server URL: https://opal.yourdomain.com
# Enter API key: oak_abc123...
5. Sync Your Tasks
# Full bidirectional sync
opal sync now
# Push local changes only
opal sync up
# Pull server changes only
opal sync down
# Initial merge (for existing local database)
opal sync merge --prefer-local
Server Deployment
Option 1: SystemD Service (Recommended)
1. Install Binary
# Build
go build -o opal main.go
# Copy to system location
sudo cp opal /usr/local/bin/
# Create data directory
sudo mkdir -p /var/lib/opal
sudo chown $USER:$USER /var/lib/opal
2. Generate First API Key
opal server keygen --name "Admin" --db /var/lib/opal/opal.db
# Save the generated key!
3. Create SystemD Service
Create /etc/systemd/system/opal.service:
[Unit]
Description=Opal Task API Server
After=network.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/var/lib/opal
ExecStart=/usr/local/bin/opal server start --addr :8080 --db /var/lib/opal/opal.db
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
4. Enable and Start Service
sudo systemctl daemon-reload
sudo systemctl enable opal
sudo systemctl start opal
sudo systemctl status opal
Option 2: Direct Execution
# Run in background
nohup ./opal server start --addr :8080 --db /var/lib/opal/opal.db > opal.log 2>&1 &
Reverse Proxy Setup
Caddy (Recommended)
Add to your Caddyfile:
opal.yourdomain.com {
reverse_proxy localhost:8080
}
Reload Caddy:
sudo systemctl reload caddy
Nginx
server {
listen 443 ssl http2;
server_name opal.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
API Endpoints
Authentication
All endpoints (except /health) require authentication via API key:
Authorization: Bearer oak_abc123...
Health Check
GET /health
Returns server status.
Tasks
GET /tasks - List tasks (with filters)
GET /tasks/{uuid} - Get specific task
POST /tasks - Create task
PUT /tasks/{uuid} - Update task
DELETE /tasks/{uuid} - Delete task
POST /tasks/{uuid}/complete - Mark task complete
POST /tasks/{uuid}/start - Start task
POST /tasks/{uuid}/stop - Stop task
Query Parameters for GET /tasks:
status- pending, completed, deletedproject- Project namepriority- L, M, H, Dtag- Tag name (can specify multiple)
Tags
GET /tasks/{uuid}/tags - Get task tags
POST /tasks/{uuid}/tags - Add tag
DELETE /tasks/{uuid}/tags/{tag} - Remove tag
GET /tags - List all tags
Projects
GET /projects - List all projects
Sync
POST /sync/changes - Get changes since timestamp
POST /sync/push - Push local changes
API Keys
GET /auth/keys - List API keys
DELETE /auth/keys/{id} - Revoke API key
Client Configuration
Sync settings are stored in ~/.config/jade/opal.yml:
# Task settings
default_filter: status:pending
default_sort: due,priority
color_output: true
week_start_day: monday
default_due_time: ""
# Sync settings
sync_enabled: true
sync_url: https://opal.yourdomain.com
sync_api_key: oak_abc123...
sync_client_id: 550e8400-e29b-41d4-a716-446655440000
sync_strategy: last-write-wins
sync_queue_offline: true
Sync Strategies
- last-write-wins (default) - Most recently modified version wins
- server-wins - Server version always wins
- client-wins - Client version always wins
Change Log Retention
By default, the change log retains entries for 30 days. This can be configured in the database:
UPDATE sync_config SET value = '60' WHERE key = 'change_log_retention_days';
Or programmatically:
engine.SetChangeLogRetentionDays(60)
Cleanup runs manually or can be scheduled:
# In your cron or systemd timer
./opal server cleanup --db /var/lib/opal/opal.db
Troubleshooting
Server won't start
- Check if port 8080 is already in use:
sudo lsof -i :8080 - Verify database path exists and is writable
- Check logs:
journalctl -u opal -f
Client can't connect
- Test server health:
curl https://opal.yourdomain.com/health - Verify API key is correct
- Check firewall allows traffic on port 8080
- Ensure reverse proxy is properly configured
Sync conflicts
- View conflict log:
opal sync log - Conflicts are automatically resolved based on strategy
- By default, last-write-wins is used
Offline changes not syncing
- Check queue status:
opal sync status - Verify
sync_queue_offline: truein config - Clear queue if needed: Delete
~/.config/jade/sync_queue.json
Database Schema
Core Tables
tasks- Main task storagetags- Task tags (many-to-many)working_set- Display ID mapping (client-local)
Sync Tables
users- User accounts (currently single shared user)api_keys- Authentication keyssync_state- Per-client sync statechange_log- Change tracking (key:value format)sync_config- Server configuration
Security Considerations
- API keys are hashed with bcrypt before storage
- Never log full API keys
- Use HTTPS in production (via reverse proxy)
- Implement rate limiting for production use
- Backup database regularly
- Rotate API keys periodically
Future Enhancements
- OAuth2 with Authentik for web/mobile clients
- Per-user task separation with sharing
- Real-time sync via WebSockets
- Web/mobile frontend (SvelteKit PWA)
- Access control (share specific tags/projects)
- Audit logging
- Database backup automation
Support
For issues or questions:
- Check logs:
journalctl -u opal -f - View sync status:
opal sync status - View conflicts:
opal sync log - Test connectivity:
curl https://opal.yourdomain.com/health
License
Same as opal-task main project.