Skip to content

Etap 1: Time Machine przez SMB do maszyny Lima (obok starego układu) - #32

Merged
mbeczynski merged 7 commits into
mainfrom
feat/vm-samba-time-machine
Oct 27, 2026
Merged

mbeczynski merged 7 commits into
mainfrom
feat/vm-samba-time-machine

Conversation

@mbeczynski

Copy link
Copy Markdown
Contributor

Po co

macOS 27 nie przyjmuje obrazu dysku jako nowego celu Time Machine (tmutil setdestination → error 45, także dla lokalnego sparsebundle), a NetFS blokuje SMB do własnego adresu Maca. Działający układ: maszyna Lima (vz, vzNAT) z Sambą; metadane obrazu na ext4, pasma przez punchfs na rclone → Google Drive. Ten PR przenosi go do repo jako nową ścieżkę obok starej.

Co jest

  • vm/ — szablon Lima cmsmb.yaml z idempotentnym provisioningiem (pakiety; rclone v1.75.1 z downloads.rclone.org sprawdzany sumą SHA256 z opublikowanego SHA256SUMS przed instalacją; użytkownik tm; katalogi; jednostki systemd; Samba; FUSE; kompilacja punchfs tylko po zmianie źródła). Jednostki i smb.conf jako osobne pliki (mode: data, wczytywane przez Limę przy tworzeniu maszyny, przepisywane przy każdym starcie).
    • Bez nazw w repo: folder na Drive (gdrive:CloudMachine/<folder>-tm) i nazwa obrazu (z scutil --get ComputerName/nazwy obrazu) trafiają do /etc/cloudmachine/vm.env i bundle.env, które zapisuje agent; jednostki czytają je przez EnvironmentFile. tm-bands-bind jest pomijany (ConditionPathExists), dopóki obraz nie zostanie przygotowany — dzięki temu Time Machine może utworzyć pierwszy, pusty obraz.
    • Limity deskryptorów po zatrzymaniu pierwszej kopii na EMFILE: LimitNOFILE=524288 dla punchfs i smbd (drop-in), max open files = 500000 w smb.conf.
  • vm/punchfs/ — punchfs.c = passthrough_ll.c z libfuse 3.14 z dwiema zmianami (fallocate PUNCH_HOLE/ZERO_RANGE → zapis zer; xattr → pliki w /srv/tm-xattr/<ino>/<nazwa>), nagłówek GPLv2 zachowany; README z opisem i licencją (GPLv2 tylko tego pliku, reszta MIT — zdanie też w głównym README); test-punchfs.sh uruchamiany w maszynie (tymczasowy rclone mount katalogu lokalnego + punchfs: punch hole, zero range, xattr set/get/list/remove, bez dotykania Drive).
  • cloudmachine-agent:
    • vm-setup — tworzy/uruchamia maszynę z szablonu, zapisuje folder Drive, kopiuje rclone.conf (istniejący w maszynie zostawia, --replace-rclone-config podmienia), generuje hasło tm do pliku 600 i ustawia je przez smbpasswd z pliku (nie z linii poleceń, nie w logach), wypisuje sudo tmutil setdestination 'smb://tm:<hasło>@<IP>/CloudMachine'. --install-agents instaluje agentów maszyny.
    • vm-prepare-bundle — konwersja pasm na 32 MB: TM nie biegnie, obraz < 2 GB, warstwy Drive działają, bands na Drive puste; stop smbd → kopia na Maca → hdiutil convert -format UDSB -imagekey sparse-band-size=65536 → przeniesienie com.apple.TimeMachine.* i pustego mapped/ → weryfikacja band-size i fsck_apfs -n (podpięcie tylko do odczytu) → instalacja (oryginał odkładany do /var/lib/cloudmachine/prepare-backup/, pasma do /srv/tm/bands, restart punchfs, bind, smbd na końcu), COPYFILE_DISABLE=1 i usuwanie ._*. Błąd przed instalacją: smbd wraca, obraz nietknięty; błąd w trakcie: smbd zostaje zatrzymany.
    • vm-guard — port tm-guard.sh (jedno sprawdzenie na uruchomienie): pauza > 110 GiB bufora lub < 40 GiB wolnego tylko przy działającej kopii; wznowienie < 40 GiB tylko gdy sam wstrzymał; brak odpowiedzi = nic. Jedna świadoma różnica: znacznik usuwany dopiero po udanym startbackup (skrypt usuwał przed, więc jedna porażka zostawiała kopię wstrzymaną na zawsze).
    • vm-status — adres, stan rclone-tm/punchfs/tm-bands-bind/smbd, bufor i kolejka z rc, wolne miejsce, otwarte deskryptory punchfs vs limit (ostrzeżenie > 80%), cel Time Machine.
  • launchd/vm/ — szablony vm-start (limactl start --tty=false cmsmb, PATH z /opt/homebrew/bin) i vm-guard (co 60 s). Osobny podkatalog celowo: install-launchd instaluje wszystko z górnego poziomu launchd/, a Mac na starym układzie nie może dostać agenta startującego maszynę, której nie ma. Instaluje je tylko LaunchdInstaller.installVMAgents() (vm-setup --install-agents).
  • build-app pakuje vm/ do Resources; release.yml reaguje też na vm/**; .gitignore przepuszcza vm/**/*.conf (poza rclone.conf).

Czego celowo NIE ma

  • Wdrożenia — nic nie zostało zainstalowane ani uruchomione na żadnym Macu ani w działającej maszynie.
  • Usunięcia starego kodu (obraz podpinany przez FUSE-T, agenci gdrive-*, buffer-guard, ...) — to etap 2, po udanej pierwszej pełnej kopii nowym układem.
  • Przejęcia działającej maszyny przez nowy kod: dzisiejsza maszyna ma inne nazwy plików (drop-in smbd, stała ścieżka obrazu w tm-bands-bind, brak vm.env/bundle.env), więc przejście na szablon to osobny krok.
  • Integracji z aplikacją w pasku menu.

Jak przetestowane

  • swift-format lint --strict --recursive Sources Tests, swift build, swift test — 474 testy, 0 błędów (nowe: VMGuardDecisionTests, VMLayoutTests, VMBundlePrepTests — decyzje strażnika i całe sekwencje kroków ze znacznikiem, parsowanie limactl list/ip addr/rc/hdiutil attach -plist/Info.plist/destinationinfo, kolejność skryptu instalacji, plik hasła 600, obecność plików szablonu i limitów deskryptorów, brak agentów maszyny w górnym launchd/).
  • limactl validate vm/cmsmb.yaml → OK; limactl template copy --embed poprawnie wczytuje wszystkie pliki data.
  • plutil -lint szablonów launchd; bash -n test-punchfs.sh.
  • Suma SHA256 rclone sprawdzona dodatkowo na pobranym archiwum.
  • punchfs.c porównany z example/passthrough_ll.c z tagu fuse-3.14.0: różnice tylko w fallocate i xattr.
  • Nie testowane na żywo: tworzenie maszyny z szablonu, vm-setup, vm-prepare-bundle, test-punchfs.sh w maszynie — na tym Macu trwa pierwsza pełna kopia przez działającą maszynę i nie wolno jej przerwać. Należy je przejść na czystej maszynie przed etapem 2.

Uwaga przed scaleniem

Scalenie zmienia mac-app/Sources/** i launchd/**, więc release.yml wyda nową wersję i otworzy PR do tapu. Nowi agenci nie instalują się sami (ani przez install-launchd, ani przez cask), ale aktualizacja przeładuje istniejących.

🤖 Generated with Claude Code

mbeczynski and others added 5 commits October 27, 2026 12:08
…he old layout)

macOS 27 refuses a disk image as a new Time Machine destination
(tmutil setdestination -> error 45), so the attached-image layout cannot be
set up from scratch there. This adds the replacement next to it; the old
code stays until the first full backup through the new layout has finished.

- vm/: Lima template cmsmb.yaml with idempotent provisioning (packages,
  rclone v1.75.1 checked against its published SHA256, user tm, directories,
  systemd units, Samba, FUSE, punchfs build); the units and smb.conf as
  files. Machine-specific values (Drive folder, image name) come from
  /etc/cloudmachine/*.env written by the agent - no names in the repo.
  Descriptor limits from the EMFILE stop of 27.10 (LimitNOFILE=524288 for
  punchfs and smbd, max open files = 500000).
- vm/punchfs: libfuse passthrough_ll.c with hole punch emulated by zeros and
  xattrs kept in side files (GPLv2, that file only), plus an in-machine test.
- cloudmachine-agent vm-setup, vm-prepare-bundle, vm-guard, vm-status.
- launchd/vm/: vm-start and vm-guard agents, installed only by
  vm-setup --install-agents (install-launchd does not pick them up).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er machine

Tried on a throwaway machine created from the template (cmtest), next to the
live one:
- provisioning stopped at the punchfs build: punchfs.c includes libfuse's
  passthrough_helpers.h (BSD-2), which was neither in the repo nor in the
  template. Both now; the machine provisions and test-punchfs.sh passes.
- CLOUDMACHINE_VM_INSTANCE and CLOUDMACHINE_VM_REMOTE point the commands at
  another machine and Drive folder; defaults unchanged (cmsmb, <folder>-tm).
- vm-prepare-bundle refused whenever Time Machine was backing up anywhere;
  now only when a destination is this machine.

vm-setup, vm-status and vm-prepare-bundle ran end to end on cmtest: an
875 MB-band image became 32 MB bands on Drive, fsck_apfs -n passed, the bind
and smbd came up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e size

27.10.2026 12:24 the guard paused the first full backup at 116 GiB and never
resumed it: it read diskCache.bytesUsed, the whole cache on disk, which keeps
uploaded bands for reading until --vfs-cache-max-size and does not fall when
the upload catches up. At 12:37 the real backlog was 30 GiB (966 bands in
vfs/queue) with the cache still at 116.

The reading now also carries the sum of vfs/queue sizes; pause and resume use
it, and a missing queue answer counts as unknown (do nothing), never as zero.
vm-status shows both numbers. Regression test with the 27.10 values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Machine

Both from the first full backup on 27.10.2026, which finished at 15:40
(214 GiB on Drive in 32 MB bands):
- Time Machine wrote faster than rclone uploaded, so the guard paused it every
  ~15 minutes and Time Machine reported each pause as an interruption.
  smb-throttle shapes incoming SMB (port 445 via ifb0 + HTB 320 Mbit,
  fq_codel) to roughly the upload rate; the backup then ran without a pause.
  Policing was tried first and held TCP at ~2.7 MB/s (12% drops) - a test
  keeps the shaper a queue.
- ~/.lima, with the machine's disk and rclone's buffer, was in the backup;
  the backup sat for half an hour on that one ever-changing file.
  vm-setup excludes it and vm-guard renews the exclusion on the hour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…isk-image layout

macOS 27 refuses a disk image as a new Time Machine destination, so the
rclone mount on the Mac and the image in /Volumes/CloudMachine are gone:
DriveBufferService, BackupImageService, ImageProbe, AttachStall,
MountRestart, OrphanMount, InstallationTraces, BufferGuardService,
ShutdownPrep, FuseInstaller, BufferReadiness, UploadDrain, OpenPortAlert,
CMLock, the POC harnesses, the gdrive-buffer/gdrive-attach/buffer-guard
agents and the commands that drove them.

- install-launchd installs app, backup-health, vm-start and vm-guard;
  the retired agents are booted out and their plists deleted on launch,
  by install-launchd and by backup-health; leftovers are only logged.
- backup-health judges the machine (Lima state, services, upload queue,
  free space, punchfs descriptors, the SMB destination) and the date of the
  last completed backup.
- prepare-shutdown stops the backup, drains the upload for a while and
  stops the machine cleanly.
- The app shows the machine and walks through install-rclone,
  configure-remote, brew install lima, vm-setup, tmutil setdestination
  (password masked on screen) and vm-prepare-bundle.
- vm/cmsmb.yaml forwards no ports: rclone's password-less remote control
  answered on the Mac's localhost; vm-status and the watchdog report it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AU1MhxJW1e9oTB2XvjX8WT
mbeczynski and others added 2 commits October 27, 2026 16:35
vm-status, backup-health and the app now show and check the SMB shaper; it
was installed by the template but missing from VMLayout.services. Checked
against the live machine after its restart (all five active).

The live machine also got the template's portForwards ignore rule: before
it, limactl published rclone's rc (127.0.0.1:5572, no auth) on the Mac's
localhost; after a restart the port is closed on the Mac and still answers
inside the machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AU1MhxJW1e9oTB2XvjX8WT
Etap 2: jeden układ (maszyna Lima + SMB), usunięty układ z obrazem dysku
@mbeczynski
mbeczynski merged commit 4f81f4b into main Oct 27, 2026
3 checks passed
@mbeczynski
mbeczynski deleted the feat/vm-samba-time-machine branch October 27, 2026 18:51
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