Repository navigation
Fix backup and restore tools, add DNS tools, pin dependencies - #1
Merged
Merged
Conversation
- Drop /shelf/ and /workspace.xml: JetBrains' own .idea/.gitignore entries, dead here because .idea/ is already ignored - Ignore .DS_Store and the Python/uv caches (__pycache__/, .venv/, .ruff_cache/) - Ignore the files the backup scripts write when run inside the repo; they hold personal data such as alias recipients and DNS records
- Pin the runtime (requests 2.34.2, dnspython 2.9.0) and dev tools (ruff 0.17.1, shellcheck-py 0.11.0.1) exactly in pyproject.toml and uv.lock; Python 3.15.0 is pinned in .python-version - dnspython is for the DNS tools added later in this branch - Ruff: add S113 (HTTP calls need a timeout) and BLE001 (no catch-all except) to the defaults, and turn off LOG015, since these standalone scripts log through the root logger on purpose
- import sent only name and recipients, so updating an existing alias cleared its description and labels, and recreating one lost its quota, disabled state, IMAP, and vacation settings. It now sends every field the API accepts, with the quota as a "10 GB"-style string and the vacation responder flattened - Match existing aliases by name and update them by ID, so catch-all and regex names never land in a URL path - Clear recipients again after creating an IMAP-only alias; the API fills empty recipients with the account owner's address - Paginate with limit=1000 and X-Page-Count in both scripts - Time out every request after 30 seconds, exit non-zero on failures and a missing API_KEY, and stop the wrappers at the first failing domain (set -euo pipefail); run them through uv from any directory - README: drop the CSV backup/restore claim (no such tool exists), fix typos, and document the restore behavior
- import sent the BIND file as a raw JSON-typed body; Cloudflare only accepts a multipart upload with a "file" field - restore waited for HTTP 409, but Cloudflare reports duplicates as 400 with error 81057/81058, so existing records were logged as failures. It now detects those codes, strips read-only fields, and updates the record matching on type, name, and value, so several MX or TXT records on one name can't overwrite each other - list, restore, and delete_all read only the first 100 records; all listing now follows pagination - delete_all writes a timestamped BIND backup before deleting - Time out every request, exit non-zero on failures, a missing token, or an unknown zone, and require a filename where one is needed - README: fix the section order and document every command
- ddns.sh points an existing A record at this machine's public IPv4 address, and does nothing when the address hasn't changed - It PATCHes only the address, so the record's TTL, proxy status, comment, and tags survive (a PUT would reset them) - The zone and record come from arguments and the token from CF_TOKEN; nothing machine-specific lives in the script
- scandns.py: rebuild a BIND zone file from DNS answers for hosts that offer no export. Records use BIND presentation format (long TXT strings intact), a CNAME is written alone, SOA/NS are real, and TTLs come from the zone's authoritative nameserver - flatten-spf.py: flatten include:, a, mx, and redirect= into IP ranges, join split TXT strings, guard against include loops, and report lookups and length; --check exits 1 when a published record drifts from its source - check_email_dns.sh: show MX, SPF, DMARC, BIMI, MTA-STS, TLS-RPT, DKIM selectors, and MAIL FROM SPF for any list of domains - rdap-lookup.zsh: look up registration data over RDAP, finding each TLD's server in IANA's bootstrap file
- Run ruff check, ruff format --check, and ShellCheck on every pull request and push to main, plus ShellCheck on the status line script embedded in its prompt - Pinned per repo practice: ubuntu-24.04, actions by commit SHA, uv 0.13.1, and uv sync --locked - Replaces the pylint workflow removed in 2024
- The timer shows cost.total_api_duration_ms, the time spent waiting on the API, not wall-clock session time; the "Time:" label implied the latter
- The Forward Email link pointed at forwardemail.com, which doesn't resolve and isn't the service's domain; use forwardemail.net - List and link every tool, add the DNS section, and add the prerequisites, install, run, test, uninstall, and assumptions sections - Document that the telemetry opt-out stops auto-updates and makes Remote Control unavailable, that some variables still opt out when set to 0, and that DISABLE_BUG_COMMAND is the older name of DISABLE_FEEDBACK_COMMAND
junxit
added a commit
that referenced
this pull request
Oct 19, 2026
Merges pull request #1 from junxit/review-fixes: the fixes and new tools from the October 2026 review of this repo, as nine commits, one per concern. Recorded as version 0.1.0 in changelog.txt, which was added right after this merge. Forward Email (forwardemail/) - fe-full.py import used to send only name and recipients, so updating an alias cleared its description and labels, and recreating one lost its quota, disabled state, IMAP, and vacation responder. It now restores every field the API accepts: the quota goes back as a "10 GB"-style string and the vacation responder as flat vacation_responder_* fields - Existing aliases are matched by name and updated by ID, so catch-all and regex names never land in a URL path. On IMAP-only aliases, the account address the API adds is removed again - Both scripts paginate with limit=1000 and X-Page-Count, time out every request after 30 seconds, and exit non-zero on any failure or a missing API_KEY - The wrappers stop at the first failing domain (set -euo pipefail) and run the scripts through uv from any directory Cloudflare (cloudflare/) - zonetool.py import now uploads the BIND file as multipart form data, the only format Cloudflare accepts - restore detects duplicates by error code 81057/81058 instead of HTTP 409, strips read-only fields, and updates only the record with the same type, name, and value, so records sharing a name (MX or TXT sets) can't overwrite each other - Every listing follows pagination instead of stopping at 100 records, and delete_all writes a timestamped BIND backup first - New ddns.sh points an A record at the machine's public IPv4 address with a PATCH, so TTL, proxy status, comment, and tags stay DNS tools (dns/, new) - scandns.py rebuilds a BIND zone file from DNS answers for hosts with no export: real SOA/NS, TTLs from the zone's authoritative nameserver, CNAMEs on their own, long TXT records (DKIM) intact - flatten-spf.py flattens include:, a, mx, and redirect= into IP ranges; --check exits 1 when a published record has drifted - check_email_dns.sh shows MX, SPF, DMARC, BIMI, MTA-STS, TLS-RPT, DKIM selectors, and MAIL FROM SPF for any list of domains - rdap-lookup.zsh looks up registration data over RDAP, finding each TLD's server in IANA's bootstrap file Tooling - Exact pins in pyproject.toml and uv.lock: requests 2.34.2 and dnspython 2.9.0, plus ruff 0.17.1 and shellcheck-py 0.11.0.1 for development, on Python 3.15.0 - CI runs ruff and ShellCheck on every pull request and push to main (ubuntu-24.04, SHA-pinned actions, uv 0.13.1) - .gitignore keeps the scripts' output files, which hold personal data, out of git Docs and Claude Code - READMEs: fixed the dead forwardemail.com link and the CSV claim, linked every tool, and added setup sections - Documented the telemetry opt-out's side effects (no auto-updates, no Remote Control) and relabeled the status line timer "API time" Not yet run against live accounts: fe-full.py import and zonetool.py import/restore. Try them on a throwaway alias or a test zone first. Refs #1
junxit
added a commit
that referenced
this pull request
Oct 19, 2026
- Record version 0.1.0 (2026-10-18): everything from pull request #1 plus the Dependabot configuration, as Added, Fixed, Changed, and Removed entries - Summarize the history before versioning, with dates, so the file covers the whole project; the git history has the details - Entries use the "[YYYY-MM-DD] - Version X.Y.Z" format, newest first, so each future release adds its section at the top - Exempt changelog.txt from the .gitignore rule that keeps the scripts' root-level .txt output out of git
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the backup and restore paths found in the review, adds sanitized versions of four DNS tools and a Cloudflare dynamic DNS updater, and pins every dependency with uv, linted in CI. There's one commit per concern, so it reads best commit by commit.
The Dependabot config landed separately on
main(b33e7d8). Dependabot has nothing to scan until this PR addsuv.lockand the workflow.Commits
chore: tidy .gitignore and ignore script outputchore: manage Python dependencies with uvpyproject.tomlanduv.lock, Python 3.15.0, and ruff rules requiring timeouts and banning catch-all excepts.fix: restore every Forward Email alias settingimportcleared descriptions and labels; on create, it dropped the quota, disabled state, IMAP, and vacation settings. Also adds pagination (limitandX-Page-Count), timeouts, and exit codes.fix: make zonetool import, restore and delete saferestoredetects duplicates by Cloudflare's 81057/81058 codes and matches records on type, name, and value. Adds pagination, anddelete_allnow backs up before deleting.feat: add a Cloudflare dynamic DNS updaterddns.shchanges only the A record's address, with aPATCH.feat: add provider-agnostic DNS toolsscandns.py,flatten-spf.py,check_email_dns.sh,rdap-lookup.zshchore: lint Python and shell scripts in CIfix: label the status line timer as API timedocs: fix README links and document side effectsVerification
fe-full.py,check-usage.py,zonetool.py: 42 checks against a mocked HTTP API, all passing. They cover pagination, restore payloads, record matching, backups before deletes, timeouts, and exit codes. The harness is not committed.ddns.sh: 8 scenarios against a fakecurl. ThePATCHbody contains onlycontent, nothing is sent when the IP is unchanged, and every failure exits 1.scandns.pyoutput parses as a valid zone, with a long DKIM key intact.flatten-spf.pyjoins split records, and--checkexits 0 or 1 correctly.check_email_dns.shandrdap-lookup.zshwork on public domains.Not yet verified
The restore paths have not been run against live accounts. Before relying on them, try
fe-full.py importon a throwaway alias andzonetool.py import/restoreon a test zone.Deliberately unchanged
API_KEYplaceholder stays)flush_cache.sh,reset_pop.sh, and the usage report's output format🤖 Generated with Claude Code