Thomas Klaehn f496eebe2a Initial commit
Signed-off-by: Thomas Klaehn <thomas.klaehn@perinet.io>
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00
2026-02-10 13:27:42 +01:00

Zwift Activity Monitor

Automatic monitoring service that continuously watches for new Zwift activities and downloads their FIT files.

Features

  • Continuous Monitoring: Automatically checks for new activities every 5 minutes (configurable)
  • Automatic Downloads: Downloads FIT files for new activities as soon as they're available
  • Docker Support: Easy deployment with Docker and Docker Compose
  • Persistent State: Tracks downloaded activities to prevent duplicates
  • Rate Limiting: Respects API rate limits with built-in throttling
  • Graceful Shutdown: Handles SIGTERM/SIGINT for clean container restarts
  • Structured Logging: JSON logging with configurable levels
  • Retry Logic: Automatic retry with exponential backoff for transient failures

Quick Start

  1. Clone the repository

    git clone <your-repo-url>
    cd zwift
    
  2. Create environment file

    cat > .env <<EOF
    ZWIFT_USERNAME=your@email.com
    ZWIFT_PASSWORD=yourpassword
    TZ=Europe/Berlin
    EOF
    
  3. Create data directory

    mkdir -p data/activities
    
  4. Start the service

    docker-compose up -d
    
  5. View logs

    docker-compose logs -f
    
  6. Check downloaded files

    ls -lh data/activities/
    

Using Docker

docker build -t zwift-monitor .

docker run -d \
  --name zwift-monitor \
  --restart unless-stopped \
  -e ZWIFT_USERNAME=your@email.com \
  -e ZWIFT_PASSWORD=yourpassword \
  -e TZ=Europe/Berlin \
  -v $(pwd)/data:/data \
  zwift-monitor

Local Development

# Install dependencies
go mod download

# Set credentials
export ZWIFT_USERNAME=your@email.com
export ZWIFT_PASSWORD=yourpassword
export ZWIFT_OUTPUT_DIR=./data/activities
export ZWIFT_STATE_FILE=./data/state.json

# Run the service
go run .

Configuration

Environment Variables

Variable Default Description
ZWIFT_USERNAME required Your Zwift email address
ZWIFT_PASSWORD required Your Zwift password
ZWIFT_OUTPUT_DIR /data/activities Directory for FIT files
ZWIFT_STATE_FILE /data/zwift-state.json State persistence file
ZWIFT_TOKEN_CACHE /data/.zwift-token.json OAuth token cache
ZWIFT_POLL_INTERVAL 5m How often to check for new activities
ZWIFT_RATE_LIMIT 5 Maximum requests per second
ZWIFT_MAX_RETRIES 3 Number of retries for failed requests
ZWIFT_LOG_LEVEL info Logging level (debug, info, warn, error)
ZWIFT_LOG_FILE `` Optional log file path
TZ UTC Timezone for timestamps

Configuration File (Optional)

You can also use a config.yaml file:

username: your@email.com
password: yourpassword
output_dir: /data/activities
poll_interval: 5m
rate_limit: 5
max_retries: 3
log_level: info

Place the config file:

  • Local: config.yaml in the project directory
  • Docker: Mount as /app/config.yaml

Note: Environment variables take precedence over config file values.

File Naming

Downloaded FIT files are named using the format:

{activityID}_{date}_{activity-name}.fit

Example:

12345678_2025-02-10_Morning-Ride.fit

Logging

The service uses structured logging with the following levels:

  • DEBUG: Detailed API calls and responses
  • INFO: Normal operation, downloads, statistics
  • WARN: Retries, non-fatal errors
  • ERROR: Failed downloads, authentication issues

Example Log Output

2025-02-10T10:00:00Z  INFO  Starting Zwift Activity Monitor  poll_interval=5m0s output_dir=/data/activities
2025-02-10T10:00:01Z  INFO  Loaded state  total_downloaded=42 last_check=2025-02-10T09:55:00Z
2025-02-10T10:00:02Z  INFO  Running initial check...
2025-02-10T10:00:03Z  INFO  Checking for new activities...
2025-02-10T10:00:05Z  INFO  Found new activities  count=1
2025-02-10T10:00:06Z  INFO  Downloading activity  activity_id=12345678 name="Morning Ride"
2025-02-10T10:00:08Z  INFO  Successfully downloaded activity  activity_id=12345678 filepath=/data/activities/12345678_2025-02-10_Morning-Ride.fit
2025-02-10T10:00:08Z  INFO  Download complete  downloaded=1 total_new=1

State Management

The service maintains a JSON state file (zwift-state.json) that tracks:

  • All downloaded activity IDs
  • Activity metadata (name, date, file path)
  • Last check timestamp
  • Total downloads

Example State File

{
  "downloaded_activities": {
    "12345678": {
      "id": 12345678,
      "name": "Morning Ride",
      "date": "2025-02-10T08:30:00Z",
      "file_path": "/data/activities/12345678_2025-02-10_Morning-Ride.fit",
      "downloaded_at": "2025-02-10T10:00:08Z",
      "sport_type": "cycling"
    }
  },
  "last_check": "2025-02-10T10:00:08Z",
  "total_downloaded": 1
}

This state file ensures:

  • No duplicate downloads after restarts
  • Resume capability after crashes
  • Historical tracking of all downloads

Troubleshooting

Authentication Fails

ERROR  Failed to create service  error="failed to create Zwift client: ..."

Solution: Verify your Zwift credentials are correct.

No New Activities Detected

INFO  No new activities found

Possible causes:

  • No new activities since last check
  • Activities already downloaded
  • Activity privacy settings

Check: View your state file to see what's already downloaded:

cat data/zwift-state.json | jq .

Rate Limiting Issues

WARN  Rate limited by Zwift API

Solution: Increase the poll interval:

ZWIFT_POLL_INTERVAL=10m  # Check every 10 minutes instead of 5

Disk Space Issues

ERROR  Failed to save FIT file  error="no space left on device"

Solution: Free up disk space or mount a larger volume.

Docker Commands

# View logs
docker-compose logs -f

# Restart service
docker-compose restart

# Stop service
docker-compose down

# Rebuild after code changes
docker-compose up -d --build

# View container stats
docker stats zwift-monitor

# Access container shell
docker-compose exec zwift-monitor sh

# Check state file
docker-compose exec zwift-monitor cat /data/zwift-state.json

Building from Source

# Build binary
go build -o zwift-monitor .

# Run tests (when available)
go test ./...

# Build Docker image
docker build -t zwift-monitor .

# Cross-compile for different platforms
GOOS=linux GOARCH=amd64 go build -o zwift-monitor-linux-amd64
GOOS=darwin GOARCH=arm64 go build -o zwift-monitor-darwin-arm64

Architecture

zwift/
├── main.go                      # Entry point with signal handling
├── internal/
│   ├── service/                 # Main service loop
│   │   └── service.go          # Ticker-based monitoring
│   ├── monitor/                 # Activity monitoring logic
│   │   └── monitor.go          # Check and download orchestration
│   ├── client/                  # Zwift API client
│   │   ├── client.go           # API wrapper with rate limiting
│   │   └── auth.go             # OAuth token management
│   ├── state/                   # State persistence
│   │   └── state.go            # Thread-safe JSON state
│   ├── storage/                 # File system operations
│   │   └── storage.go          # Atomic FIT file writes
│   └── config/                  # Configuration management
│       └── config.go           # Env vars + YAML loading
└── Dockerfile                   # Multi-stage Docker build

Security Considerations

  • Credentials: Never commit credentials to git. Use environment variables or Docker secrets.
  • Token Cache: Stored with 0600 permissions (owner read/write only).
  • State File: Contains activity IDs but no sensitive data.
  • Container: Runs as non-root user for security.

API Usage

This service uses the unofficial Zwift API via the gravl library. The API:

  • Is not officially supported by Zwift
  • May change without notice
  • Requires regular Zwift credentials (no special API key needed)
  • Respects rate limiting (5 requests/second by default)

License

[Your License Here]

Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

Support

For issues or questions:

  • Create an issue in the repository
  • Check existing issues for similar problems
  • Include logs and configuration (redact credentials!)

Acknowledgments

  • gravl - Go library for Zwift API
  • Zwift - Virtual cycling platform
Description
No description provided
Readme 50 KiB
Languages
Go 92.5%
Dockerfile 3.8%
Shell 3.7%