A tiny Python script that keeps your Control D Folders in sync with a set of remote block-lists.
- Downloads the current JSON block-lists.
- Deletes any existing folders with the same names.
- Re-creates the folders and pushes all rules in batches.
- Log in to your Control D account.
- Navigate to the "Preferences > API" section.
- Click the "+" button to create a new API token.
- Copy the token value.
- Log in to your Control D account.
- Open the Profile you want to sync.
- Copy the profile ID from the URL.
https://controld.com/dashboard/profiles/741861frakbm/filters
^^^^^^^^^^^^
-
Clone & install
git clone https://github.com/your-username/ctrld-sync.git cd ctrld-sync -
Install dependencies
uv sync --all-extras
All CI workflows (including the main sync job) use
uv sync --all-extras. This is the recommended method for both local development and CI/production. -
Configure secrets Create a
.envfile (or set GitHub secrets) with:TOKEN=your_control_d_api_token PROFILE=your_profile_id # or comma-separated list of profile ids (e.g. your_id_1,your_id_2)
For GitHub Actions, set
TOKENandPROFILEsecrets to the raw values (not the fullTOKEN=.../PROFILE=...lines). -
Configure Folders
You can configure which folders to sync using a YAML configuration file instead of editing
main.py:cp config.yaml.example config.yaml # Edit config.yaml to add, remove, or change folder URLsConfiguration file locations (checked in order):
--config FILECLI flagconfig.yamlorconfig.ymlin the current directory~/.ctrld-sync/config.yamlor~/.ctrld-sync/config.yml- Built-in defaults (the
DEFAULT_FOLDER_URLSlist inmain.py)
Example
config.yaml:folders: - name: "Native Tracker – Amazon" url: "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/controld/native-tracker-amazon-folder.json" action: "block" - name: "Apple Private Relay – Allow" url: "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/controld/apple-private-relay-allow-folder.json" action: "allow" Alternatively, you can still pass folder URLs directly on the command line (these override any config file): ```bash python main.py --folder-url https://example.com/my-blocklist.json
Or point to a specific config file:
python main.py --config /path/to/my-config.yaml
The script includes 23 default folder URLs from hagezi's dns-blocklists covering:
- Native tracker blocking (Amazon, Apple, Samsung, etc.)
- Badware and spam protection
- Allow lists for common services
Note
Currently only Folders with one action are supported. Either "Block" or "Allow" actions are supported.
-
Run locally
python main.py --dry-run # plan only, no API calls python main.py --profiles your_id # live run (requires TOKEN)
-
Run in CI The included GitHub Actions workflow (
.github/workflows/sync.yml) runs the sync script daily at 02:00 UTC and can also be triggered manually via workflow dispatch.
- Fork this repo.
- Go to the "Actions" Tab and enable actions.
- Go to the Repo Settings.
- Under "Secrets and variables > Actions" create the following secrets like above, under "Repository secrets":
TOKEN: your Control D API tokenPROFILE: your Control D profile ID(s)
- Python 3.13+
- Runtime dependencies (install with
uv sync --all-extras):httpx– HTTP clientpython-dotenv–.envfile supportpyyaml– YAML configuration file support
This project includes a comprehensive test suite to ensure code quality and correctness.
Basic test execution:
# Install dev dependencies first
uv sync --all-extras
# Run all tests
uv run pytest tests/Parallel test execution (recommended):
# Run tests in parallel using all available CPU cores
uv run pytest tests/ -n auto
# Run with specific number of workers
uv run pytest tests/ -n 4Note on parallel execution: The test suite is currently small (~78 tests, <1s execution time), so parallel execution overhead may result in longer wall-clock time compared to sequential execution. However, pytest-xdist is included for:
- Test isolation verification - Ensures tests don't share state
- Future scalability - As the test suite grows, parallel execution will provide significant speedups
- CI optimization - May benefit from parallelization in CI environments with different characteristics
For active development with frequent test runs:
# Run tests sequentially (faster for small test suites)
uv run pytest tests/ -v
# Run specific test file
uv run pytest tests/test_security.py -v
# Run tests matching pattern
uv run pytest tests/ -k "test_validation" -vThis project uses manual releases via GitHub Releases. To create a new release:
-
Ensure all changes are tested and merged to
main# Verify tests pass uv run pytest tests/ # Verify security scans pass bandit -r main.py -ll
-
Update version in
pyproject.toml[project] version = "0.2.0" # Increment appropriately
-
Create and push a version tag
git tag -a v0.2.0 -m "Release v0.2.0: Description of changes" git push origin v0.2.0 -
Create GitHub Release
- Go to Releases
- Click "Draft a new release"
- Select the tag you just created
- Add release notes highlighting:
- New features
- Bug fixes
- Breaking changes (if any)
- Security updates
- Publish the release
Release Checklist:
- All tests passing
- Security scans clean (Bandit, Codacy)
- Version updated in
pyproject.toml - Git tag created and pushed
- GitHub Release created with notes
- Release announcement (optional)
| Workflow | File | Trigger | Purpose |
|---|---|---|---|
| Sync | sync.yml |
Daily at 02:00 UTC, manual dispatch | Main synchronization workflow — runs main.py to keep Control D folders in sync |
| Bandit | bandit.yml |
Push/PR to main, weekly schedule |
Security vulnerability scanning for Python code |
| Codacy | codacy.yml |
Push/PR to main, weekly schedule |
Code quality analysis and SARIF upload to GitHub Security tab |
The GitHub Actions workflows use automatic dependency caching to speed up CI runs:
- Cache Key: Based on
uv.lock(managed byastral-sh/setup-uv@v4) - Cache Location: uv's shared cache directory (managed by
astral-sh/setup-uv@v4) - Invalidation: Automatic when
uv.lockchanges
- First run (cold cache): ~30-40 seconds for dependency installation
- Subsequent runs (warm cache): ~5-10 seconds for cache restoration
- Cache hit rate: Expected >80% for typical PR/commit workflows
Important: pyproject.toml is the single source of truth for dependencies. After updating dependencies, regenerate the lockfile with uv lock.
When updating dependencies:
-
Update
pyproject.toml[project] dependencies = [ "httpx>=0.28.1", "python-dotenv>=1.1.1", ]
-
Regenerate the lockfile
uv lock
-
Verify locally
uv sync --all-extras uv run python main.py --help # Smoke test
If you suspect cache issues:
-
Check cache hit/miss in workflow logs:
Run astral-sh/setup-uv@v4 Cache restored successfully: true -
Manually clear cache (if needed):
- Go to Actions → Caches
- Delete relevant uv cache entries
- Re-run workflow to rebuild cache
-
Verify dependencies are up to date:
uv sync --all-extras uv run python main.py --help # Smoke test