Skip to content

Fix backup and restore tools, add DNS tools, pin dependencies - #1

Merged
junxit merged 9 commits into
mainfrom
review-fixes
Oct 18, 2026
Merged

junxit merged 9 commits into
mainfrom
review-fixes

Conversation

@junxit

@junxit junxit commented Oct 18, 2026

Copy link
Copy Markdown
Owner

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 adds uv.lock and the workflow.

Commits

Commit What and why
chore: tidy .gitignore and ignore script output Removes dead JetBrains entries. Ignores caches and the backup files the scripts write, which hold personal data.
chore: manage Python dependencies with uv Exact pins in pyproject.toml and uv.lock, Python 3.15.0, and ruff rules requiring timeouts and banning catch-all excepts.
fix: restore every Forward Email alias setting On update, import cleared descriptions and labels; on create, it dropped the quota, disabled state, IMAP, and vacation settings. Also adds pagination (limit and X-Page-Count), timeouts, and exit codes.
fix: make zonetool import, restore and delete safe Multipart BIND import. restore detects duplicates by Cloudflare's 81057/81058 codes and matches records on type, name, and value. Adds pagination, and delete_all now backs up before deleting.
feat: add a Cloudflare dynamic DNS updater ddns.sh changes only the A record's address, with a PATCH.
feat: add provider-agnostic DNS tools scandns.py, flatten-spf.py, check_email_dns.sh, rdap-lookup.zsh
chore: lint Python and shell scripts in CI Ruff and ShellCheck on ubuntu-24.04, with SHA-pinned actions and uv 0.13.1.
fix: label the status line timer as API time The timer shows time spent waiting on the API, not session time.
docs: fix README links and document side effects Fixes the dead forwardemail.com link, links every tool, adds setup sections, and documents the telemetry opt-out's side effects.

Verification

  • 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 fake curl. The PATCH body contains only content, nothing is sent when the IP is unchanged, and every failure exits 1.
  • DNS tools against live public DNS and RDAP (read-only queries):
    • scandns.py output parses as a valid zone, with a long DKIM key intact.
    • flatten-spf.py joins split records, and --check exits 0 or 1 correctly.
    • check_email_dns.sh and rdap-lookup.zsh work on public domains.
  • Ruff, the ruff format check, and ShellCheck pass. Every added line was scanned for personal data and secrets.

Not yet verified

The restore paths have not been run against live accounts. Before relying on them, try fe-full.py import on a throwaway alias and zonetool.py import/restore on a test zone.

Deliberately unchanged

  • Secret handling in the wrapper scripts (the API_KEY placeholder stays)
  • flush_cache.sh, reset_pop.sh, and the usage report's output format

🤖 Generated with Claude Code

- 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
junxit merged commit 526145b into main Oct 18, 2026
1 check passed
@junxit
junxit deleted the review-fixes branch October 18, 2026 23:56
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant