1024x1024 master, favicon
- kopi-docka-800x200.png (72 KB): wide README header banner — used by
the redesigned title in the preceding commit
- kopi-docka-1280x640.png (177 KB): GitHub Open Graph aspect — upload
via Repo Settings → Social preview to control the link card on
X/HN/Reddit/Discord (manual UI step, not scriptable via gh CLI)
- kopi-docka-logo.png (1.2 MB): 1024x1024 master for downstream
derivations (presentations, DR-bundle templates)
- kopi-docka-favicon.png (168 KB): 420x420 for any future doc site
Total +1.58 MB to the repo. All four sit alongside the existing
demo-*.svg files under docs/media/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swap the plain "# Kopi-Docka" H1 plus tagline blockquote for the 800x200
project banner from docs/media/, centred via HTML <p>, with badges and
tagline arranged horizontally below it. Visual identity instead of
straight markdown headers — same pattern modern Python projects
(FastAPI, Pydantic, Ruff) use.
The banner image already carries the project name visually; alt text
provides accessibility / screen-reader fallback for when the image
fails to load.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Docs / help-text only. Flags PowerShell's `>` binary corruption pitfall
in the --stream help text and in DISASTER_RECOVERY.md, plus a 7-Zip
extraction note for AES-256 archives. Combines the 7 demo SVGs from
v7.6.2 into one 113-second sequential animation to keep the README
scrollable.
No code path, config, or repository format changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 individual SVG embeds back-to-back made the README quite scroll-heavy.
This combines all 7 casts into a single 113-second animation that plays
the scenes sequentially with brief banner cards between them
("1 / 7 · doctor · system health check", etc.), then loops.
The individual SVG files are preserved (other docs may reference them
directly) and exposed as a collapsible "Individual scenes" table below
the combined animation, so the user-flow narrative is still visible at
a glance without expanding anything.
Combined SVG is 743 KB — larger than any single scene but acceptable
inline; GitHub renders animated SVGs without lazy-loading anyway.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Real user hit this on Windows: PowerShell 5.1's `>` operator re-encodes
stdout as UTF-16, doubling the byte count and corrupting the AES ZIP
beyond recognition. Even 7-Zip rejects the file ("not an archive").
Confirmed: locally a clean stream is 7 KB; the PS-corrupted version
arrives at 14 KB. cmd.exe's `>` works correctly.
Three touch-points so this is hard to miss:
1. `--stream` help text in disaster_recovery_commands.py now mentions
the PowerShell pitfall and points at docs/DISASTER_RECOVERY.md.
2. README.md gains an inline Windows note next to the stream example
plus a stronger 7-Zip hint at extract time.
3. docs/DISASTER_RECOVERY.md "SSH Stream Mode" section grows two
subsections: "Windows clients" (scp + cmd.exe patterns, verification
command) and "Extracting on Windows" (Explorer's lack of AES-256
support, recommended tools).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI runners don't have kopia binaries; the command's dependency-check
exited before reaching the streaming branch, so both stream regression
tests failed there even though the code path under test was correct.
Mock DependencyHelper.exists() (and the DR manager) so the test
focuses on the streaming-Console-print branch the v7.6.3 fix targets.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rich's Console.print() does not accept an `err=` kwarg; every
`disaster-recovery export --stream` invocation blew up with TypeError
before any ZIP byte hit stdout. Fix: bind a separate
`Console(stderr=True)` for the 3 streaming-path messages.
Reproduced over SSH against the testlab. Regression covered by 2 new
tests in test_disaster_recovery_commands.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The bare 'disaster-recovery export ... --yes' demo was too thin — one
panel, just file path and decrypt commands. The legacy
'disaster-recovery' (no subcommand) path triggers a more substantive
flow that's actually nicer for HN/r/selfhosted visitors:
1. Deprecation-warning panel → demonstrates the tool actively
guides users toward the new ZIP format (a trust signal — it
doesn't just silently break)
2. "Disaster Recovery Bundle Creation" intro panel
3. Progress log (rclone.conf added, password sidecar warning)
4. "Bundle Created" panel with all three file paths and the
openssl-decrypt command inline
Source: real transcript provided by the user against the testlab,
faithfully reconstructed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the narrative arc on the README from "setup the tool" to
"recover from total loss":
Doctor → Dry-run → Backup → History → Snapshot list → Restore → DR export
- history.svg (44 KB, ~6s): Backup History table with timestamps,
duration, status, scope, volumes, snapshot IDs. Operations / "did the
backups actually run?" sight.
- snapshot-list.svg (58 KB, ~8s): kopi-docka advanced snapshot list
--snapshots — shows the tag-based organisation (every snapshot tagged
with backup_id/type/unit so the wizard can group recipe + networks +
volumes from one backup_id as a unit). Direct illustration of the
Kopia-tag differentiator vs. archive-name-based tools.
- dr-export.svg (39 KB, ~8s): disaster-recovery export — shows the
encrypted-ZIP bundle creation panel, including the "passphrase NOT
stored in file" reminder.
All three captured from real testlab runs against the rclone+GDrive
backend (32 existing snapshots in the repo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The --yes (CI/CD) flow worked but missed the actual story: the wizard
is where the Kopia-tag-based session grouping becomes visible (each
"backup session" in the list is one backup_id tying recipe + networks
+ volumes), and where the safety-backup-before-overwrite step earns
user trust.
Recording reconstructed from a real interactive session captured by
the user on the testlab. Compressed from ~2min real time to ~34s while
keeping every decision point visible: session selection, "Recreate
network?" prompt, "Restore volume NOW?" confirmation, target-directory
prompt, completion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to the doctor/dry-run demos — adds the two missing pieces of
the natural user journey:
- demo-backup.svg (23 KB, ~7s): real cold backup via SFTP. Shows
per-stack container-stop → snapshot → restart cycle for both
test-stack-redis and test-stack-nginx. Real exit 0 capture from the
E2E SFTP test environment.
- demo-restore.svg (93 KB, ~10s): non-interactive 'restore --yes' run
showing the restore-wizard panel, snapshot listing, file restore,
network recreation, and container restart.
Order on the README is now: doctor → dry-run → backup → restore, matching
the actual user workflow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HN/r/selfhosted-prep: visitors land on the README and want to see
"what does this actually do" in ten seconds, before reading any prose.
Two short animated SVGs (~7-9s each) embedded inline:
- demo-dry-run.svg: discovers the test-lab stacks (redis + nginx),
prints the dry-run plan with estimates.
- demo-doctor.svg: dependency check, repository status, backend sanity,
DR-readiness — the "this tool checks itself" angle.
SVGs are 56 KB / 88 KB, text-selectable when zoomed, rendered by GitHub
inline as raw.githubusercontent.com URLs (works on both GitHub and PyPI
README rendering — the URLs are absolute).
Generated with termtosvg from manually-constructed asciicast files
(the testlab session was too long to script with expect; output captured
once and timed-out for typing animation, ~8s each).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
img.shields.io/pypi/dm has been serving "rate limited by upstream
service" for some time — PyPIStats throttles shields.io's polling.
pepy.tech is the standard alternative: free, no rate-limit, ~5min
cache, accurate download counts (currently ~10k/month).
Link target also updated to pepy.tech/project/kopi-docka so clicking
the badge lands on the actual stats page, not just the PyPI project.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PyPI link fix, plan-slug cleanup, CHANGELOG smoothing,
ARCHITECTURE.md refresh. No code changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the 8 helper modules previously absent from ARCHITECTURE.md
(backend_helper, sudo_helper, metadata_reader, docker_run_builder,
file_operations, process_lock, system_utils, logging, ui_utils,
constants) — each with a one-paragraph purpose + signature table where
the API is non-trivial.
Adds a new "Repair pattern (rebuild_kopia_params)" section documenting
how advanced config repair-kopia-params dispatches generically through
BackendBase + MissingCredentialsError, with the extension point for
future per-backend repair logic.
Also: final Plan-XXX sweep — README's tested-backends table now uses
version anchors (v7.4.0 + v7.6.1) instead of slug refs.
Plan 0039 — Block D.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Recent entries had drifted into PR-description voice — verbose narrative
with multiple subsections per release. Tightened to a uniform shape:
one-line headline, "Why" (1-2 sentences), bulleted "Changes", optional
"Upgrade notes". CHANGELOG dropped from 2255 → 1993 lines, no
information lost that wasn't already covered by commit history / PRs.
Plan-XXX slugs in entry bodies replaced with version anchors; slugs in
the [7.3.0 – 7.3.8] cluster header are kept (legitimate release-notes
shorthand). Entries pre-v7.4.0 are untouched.
Plan 0039 — Block C.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Internal planning artifacts ("Plan 0022", "Plan 0028") have no meaning to
end users reading the docs — rewrite to point at the release that
delivered the change (e.g. "since v7.3.0") instead. 8 occurrences across
FEATURES, CONFIGURATION, ARCHITECTURE, TROUBLESHOOTING.
Plan slugs are kept in CHANGELOG section headers where they serve as
release-notes shorthand (e.g. "v7.3.0 — Plan 0028 + post-release"), and
in the plan/ directory itself.
Plan 0039 — Block B.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PyPI ships only the source tree listed in pyproject.toml; relative URLs
like docs/CONFIGURATION.md or LICENSE 404 on the PyPI project page. 31
occurrences rewritten to https://github.com/TZERO78/kopi-docka/blob/main/
(or /tree/main/ for directories). twine check PASSED.
Plan 0039 — Block A.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the "what's tested" claim (rclone the Kopia backend, exercised
against a live test lab) from the "what's known broken" caveat (Google
Drive specifically has high small-file write overhead — upstream / rclone
limitation, not kopi-docka's). Links to the pinned tracking issue.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SFTP added after the v7.6.1 (Plan 0038) E2E verification: wizard →
`kopia repository create` → full `kopi-docka backup` against the
test-stack-nginx + test-stack-redis stacks via localhost SFTP, snapshot
persistence verified on the SFTP-backed Kopia repo. Rclone and Tailscale
already had real-world coverage from prior plans / live test lab.
Other backends (filesystem, S3, B2, Azure, GCS) are wired up and
unit-tested but I haven't driven an end-to-end backup cycle against
them — explicit about that so users know where community reports
would be most useful.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 0038 — finishes the Plan 0029 story for the direct SFTP wizard,
which still shipped the broken `--path=user@host:path` shape and an
invalid `--sftp-port` flag until now. Verified locally: Kopia rejects
`--sftp-port` with `unknown long flag`; the bug never triggered because
port 22 (default) skipped the branch.
Single source of truth: new `helpers/backend_helper.py::build_sftp_kopia_params()`
+ `ensure_known_hosts()`. SFTP wizard, Tailscale wizard, and
`rebuild_kopia_params()` repair hook all feed through it.
`advanced config repair-kopia-params` is now backend-dispatched —
`BackendBase.rebuild_kopia_params(credentials)` is the entrypoint, each
backend overrides if it has a credentials-based rebuild path. New
`MissingCredentialsError` lets each backend declare its required keys
without the command hardcoding them. No `if backend == "sftp"` branches
left in the command.
SFTP wizard now also persists a `[credentials]` block so future configs
are repairable through the same flow as Tailscale.
Doctor sanity regex extended from `--path=…` to `--path[=\s]…` — the
v7.0.0–v7.6.0 direct-SFTP wizard wrote the space form, which the
Plan 0029 regex did not catch.
1244 tests passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI surfaced two issues my local lint missed:
1. After the sudo_helper migration, `os` is no longer used anywhere in
`disaster_recovery_manager.py` (was only there for `os.environ.get`
and `os.chown` — both now go through the helper). ruff F401 caught
it. Dropped.
2. `test_dr_export.py::test_export_sets_ownership` patched
`pwd.getpwnam` and `os.chown` directly — testing the OS-level
implementation, not the new helper-mediated contract. Re-anchored
the patches at the helper boundary
(`kopi_docka.helpers.sudo_helper.os.chown`) and set SUDO_UID/SUDO_GID
env-vars instead of mocking pwd. Same intent, new seam.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Minor bump (not patch) because the SUDO_USER validation regex now
applies at four sites that previously skipped it. Real-world impact:
zero — legitimate usernames pass through unchanged. But the contract
at those sites shifted, so SemVer says minor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 0037. The SUDO_USER / SUDO_UID / SUDO_GID handling was duplicated
at 11 sites across 4 files with three different validation niveaus —
some checked SUDO_USER against a shell-injection regex, others didn't.
New module kopi_docka/helpers/sudo_helper.py exposes one typed API:
@dataclass SudoUserInfo: name, uid, gid, home, invoked_with_sudo
def get_sudo_user_info() -> SudoUserInfo
def chown_to_sudo_user(path) -> None
def find_in_sudo_user_home(relative) -> Optional[Path]
def sudo_user_home_path(relative) -> Optional[Path]
All 11 sites migrated:
- cores/disaster_recovery_manager.py (2 sites)
- cores/restore_manager.py (2 sites)
- helpers/file_operations.py (1 site)
- backends/rclone.py (4 sites)
Side benefit: the four previously-unvalidated SUDO_USER reads now go
through the same shell-injection validation as the other sites, at
zero cost for legitimate usernames. backends/rclone.py no longer needs
`import os` either.
+18 unit tests in tests/unit/test_helpers/test_sudo_helper.py covering
under-sudo / no-sudo / shell-injection / path-traversal / garbage-UID /
chown success+failure / find/path-build variants. All 1234 prior tests
unchanged (behavioral parity).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five naive datetime.now() ISO-string emit sites brought in line with
the v7.5.2 tz-aware-snapshot-tag convention. No user-visible change,
houskeeping after the read-only codebase scan.
Plan 0032 (proposed, nice-to-have) collects the non-trivial hygiene
items the scan also surfaced: subprocess timeout strategy for TAR-mode
and stdin-piping, Union[X,Y] modernization to X|Y, match-statement
adoption in the DR backend-dispatch chains, bare-except narrowing,
f-string-vs-%-style logging unification, and a pydantic<3 upper bound.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five remaining naive datetime.now().isoformat() call sites turned up
in a read-only codebase scan after v7.5.2 fixed the snapshot-tag
timestamps. All emit ISO strings that round-trip through
datetime.fromisoformat(); without a timezone they parse as naive and
break comparisons with tz-aware values downstream (same class of bug
that crashed the restore wizard in v7.5.2).
* cores/backup_manager.py:101 — BackupMetadata.timestamp goes into
/backup/kopi-docka/metadata/*.json
* cores/backup_volume_handler.py:125, 207 — TAR-mode snapshot tags
(legacy backup path)
* cores/disaster_recovery_manager.py:697 — created_at in DR-bundle
recovery-info.json
* cores/disaster_recovery_manager.py:1069 — timestamp in DR-bundle
backup-status.json
All five now emit datetime.now(timezone.utc).isoformat().
Pre-existing user-facing local-time strftime() calls used in filenames
(config-backup-*, restore-rollback tarballs, DR-bundle output
filenames) stay naive on purpose — they represent local wall-clock
time, not machine-parseable timestamps.
Two new regression tests cover the DR-bundle path; the existing v7.5.2
snapshot-timestamp tests cover the backup_manager paths. No user-
visible behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Patch release for two field bugs surfaced after v7.5.1:
- restore wizard crashed on mixed naive/aware snapshot timestamps
- `advanced repo init` ignored kopia_params backend swaps and left
Kopia connected to the previous backend, silently sending all
subsequent backups to the wrong destination
Both fixes are non-disruptive: existing repos remain usable, no
config-format changes, no repo re-init required for the
timestamp-fix path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Editing kopia_params in /etc/kopi-docka.json from one backend to
another (e.g. rclone -> sftp) followed by `advanced repo init`
silently kept Kopia talking to the old backend: initialize() only
checked is_connected() and treated any active connection as
"we're done", regardless of which backend it pointed to. The user
saw "Repository initialized successfully" while their new SFTP
target stayed empty.
Add _current_storage_type() that reads storage.type from Kopia's
connect-config and _expected_storage_type() that takes the first
token of kopia_params. In initialize(), compare both before the
is_connected() shortcut; on mismatch, log a warning and call
disconnect() so the subsequent create/connect actually retargets
to the new backend.
The check is intentionally limited to storage type — a path/host
swap inside the same backend still hits the shortcut and needs a
manual disconnect. That keeps the change minimal and focused on
the observed regression.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The restore wizard crashed with "can't compare offset-naive and
offset-aware datetimes" whenever the snapshot list contained one
tag-timestamp from pre-v7.5.2 backup_manager (naive, written via
datetime.now().isoformat()) and one fallback timestamp from the
restore_manager exception/default branch (aware, datetime.now(
timezone.utc)). sort() then crashed before any restore points
were shown.
Introduce _parse_snapshot_timestamp() in restore_manager that
always returns a tz-aware UTC datetime — naive legacy tags are
treated as UTC, parse errors fall back to now(utc). Use it from
both _find_restore_points() and _find_restore_points_for_machine().
In backup_manager, write new snapshot-tag timestamps and the
networks metadata backup_timestamp as datetime.now(timezone.utc).
isoformat() so future tags round-trip cleanly without ever needing
the legacy-as-UTC assumption.
Existing snapshots remain restorable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plan 0030. v7.5.0's E2E DR test surfaced two real issues:
1. recover.sh exited 1 for every SFTP/Tailscale bundle (legacy
"Unsupported auto-connect" fall-through).
2. The SSH key was never in the bundle (correct by design — defense in
depth, NIST SP 800-57) but nothing told the user that, so the gap
was invisible until the day they actually needed it.
This release fixes (1), and addresses (2) with explicit visibility on
three surfaces: the export end-of-run panel, RECOVERY-INSTRUCTIONS.txt,
and a new doctor Section 9 "Disaster Recovery Readiness". Plus an
opt-in `--include-ssh-key` for users whose threat model is "bundle is
held at higher trust than the SSH key itself".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New "What's NOT in the bundle (and why)" section in
docs/DISASTER_RECOVERY.md spells out:
* Which credentials live OUTSIDE the bundle per backend type (SSH key,
AWS keys, B2 keys, Azure key, GCP service-account JSON).
* The defense-in-depth / NIST SP 800-57 key-separation rationale and a
brief note that restic, Borg, Duplicity, rclone crypt all default to
the same posture.
* Suggested storage for the SSH key (password-manager attachment,
separate USB stick, GPG-symmetric, paper printout).
* The --include-ssh-key opt-in flag with its trade-off explicit.
Plan 0030 documentation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New doctor section that surfaces the SSH key status for SFTP /
Tailscale backends on every health check:
9. Disaster Recovery Readiness
----------------------------------------
Backend Type sftp
SSH Key ✓ Found /root/.ssh/kopi-docka_ed25519
SSH Key SHA256 71b25fd2b5bd…
Record this for DR verification
Known Hosts ✓ Found /root/.ssh/known_hosts
The point is to make the externally-held DR credential visible during
normal operation, not just at the moment of bundle export. If the key
disappears (renamed, perms broken, accidentally deleted) the user
finds out from the next routine doctor run rather than from a failed
recovery a year later. The SHA256 line is there so the user can record
the fingerprint and verify their externally-stored copy matches.
Silent for non-SFTP backends.
Plan 0030 / Phase 4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two additions on top of the recover.sh fix, both improving the trust-
boundary visibility of the DR bundle:
* After "Bundle Created", `disaster-recovery export` now prints a
second panel — "⚠ Additional Secrets Required" — listing what the
bundle does NOT contain and that the user must keep separately. For
SFTP that's the SSH key path + SHA256 (so the user has a fingerprint
to verify the externally-held copy on restore day). For cloud
backends it's the credential variable names. Filesystem repos are
silent.
* New `--include-ssh-key` flag for users who explicitly want the
all-in-one bundle (e.g. when the bundle lives at a higher trust
level than the SSH key itself — air-gapped storage, hardware vault).
Default OFF. When passed, the flag triggers a red Security Trade-off
warning panel and an explicit confirm prompt (`--yes` to skip in
automation). When enabled, the key is embedded under ssh-key/* in
the ZIP and recover.sh installs it at the expected target path with
mode 600 before connecting. Panel + instructions + script all switch
consistently to the embedded-key story.
Plan 0030 / Phases 3 + 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related issues in the disaster-recovery script generator:
* The legacy generic `else` branch printed "Unsupported auto-connect
for this repository scheme" and `exit 1` — even though the
file-restore steps that ran before it had succeeded. Every SFTP /
Tailscale recovery looked like a failure to the user.
* recover.sh had no real SFTP branch at all.
Now there's an explicit `elif repo_type == "sftp":` block that builds
a non-interactive `kopia repository connect sftp --path=… --host=…
--username=… --keyfile=… --known-hosts=…` from the connection info in
recovery-info.json. The block also guards on $KEYFILE being readable
before connecting — missing key prints a "install with mode 600 and
re-run" warning and exits 0 (since the file-restore portion already
succeeded). Unknown backends now also exit 0 with a manual-connect
hint instead of failing hard.
Also extends `_extract_repo_from_status` for SFTP to capture
port/username/keyfile/knownHostsFile (not just host/path) so the
generated connect call is complete. Adds module-level `sha256_file()`
helper used by the SHA256 fingerprints in instructions / doctor.
Plan 0030 / Phases 1 + 2. +11 unit tests covering the new branches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up release on Plan 0029. v7.4.0 detected the broken
v7.0–v7.3.13 Tailscale-wizard kopia_params and handed out a sed
snippet to fix it. v7.5.0 turns that into a real command:
sudo kopi-docka advanced config repair-kopia-params
The command rebuilds kopia_params from [credentials] atomically with
preview + confirmation. Idempotent, --dry-run, --yes flags. Doctor
Section 5.1 now points at this command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CONFIGURATION.md and TROUBLESHOOTING.md previously instructed
v7.0–v7.3.13 Tailscale users to copy/paste a sed line emitted by
doctor. v7.5.0 ships a real command for this; the docs now show that
instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Section 5.1 used to print a generated `sudo sed -i 's#…#…#' /etc/...`
line. Replaced with a one-line pointer to the new
`advanced config repair-kopia-params` subcommand — easier to read, no
shell-escaping foot-guns, no copy/paste mistakes.
The `_build_sftp_migration_command` helper is removed entirely; the
two unit tests that exercised it are replaced by a single
TestBackendSanityHint case asserting doctor surfaces the new command
(and no longer emits `sudo sed -i`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A one-command path to the v7.4.0 migration that v7.4.0 itself only
handed out as a sed snippet. Reads the still-correct [credentials]
section, rebuilds kopia_params in Kopia-SFTP's canonical
--path / --host / --username / --keyfile / --known-hosts shape, and
writes it back through the existing atomic Config.save().
Behavior:
- Shows an old-vs-new diff and prompts for confirmation.
- `--dry-run` to preview, `--yes` to skip the prompt.
- Idempotent: a second run says "already in the canonical shape".
- Refuses non-SFTP backends and configs missing remote_path / peer
FQDN / ssh_key — those need the full wizard, not a parameter
rebuild.
Follow-up to Plan 0029. The Kopia repository itself is untouched —
only the local config string changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>