Files
operation-bin/migrate_to_network_drive.md
lemke_h 224c68e90b bin: consistent naming, pixi_pgroup_install, git cleanup
- Commands lose their .sh extension (create_personal_dir,
  gopass_bernina_setup, gopass_bernina_service_setup,
  kerberos_ticket_renew, migrate_to_network_drive, pixi_local_sync,
  pixi_rebuild_local, sync_personal_config); .sh now only marks files
  that are sourced. kerberos_ticket_renew.sh and pixi_local_sync.sh stay
  as links, since they are started at login.
- Sourced files gain .sh (bpy-env, anaconda_env, anaconda_bernina_env,
  mm_bernina); the old names stay as links.
- All references in scripts, the .desktop file and the docs updated;
  cheatsheet.md regenerated via list_bin_commands.
- New pixi_pgroup_install: installs the shared bernina pixi env into a
  pgroup work folder (group-shared, with its own package cache) and
  makes it visible in JupyterHub as bernina-<exp>-<env>.
- Fix '#/bin/bash' first lines (missing '!') in 11 scripts.
- Stop tracking clutter (.nfs placeholders, __pycache__, vim swap file,
  .bak/_bak/~ copies, delme_*, empty 'me'); add .gitignore.
2026-09-25 15:48:11 +02:00

8.4 KiB

Syncing dotfiles/config across hosts with PSI's Partially Shared Home

How ~/network_drives, linux_home_links.yaml, and two helper scripts (sync_personal_config, migrate_to_network_drive) keep personal config (.vimrc, .condarc, SSH keys, ...) the same across every PSI Linux host you log into. This is PSI's official Partially Shared Home feature.

See also: the gopass + gitea guide for why the gopass secrets store deliberately does not use this mechanism (§3 below).

The pieces

 ~/network_drives/home/           same network location on EVERY host
 ├── linux_config/                 (SMB share, auto-mounted at login)
 │    ├── ssh/, vimrc, condarc, ...
 └── linux_home_links.yaml        <- manifest: what goes where

 ~/  (LOCAL home, different per host, NOT shared)
 ├── .vimrc -> network_drives/home/linux_config/vimrc   (symlink)
 ├── .bashrc                       (real local file, refreshed by copy)
 └── ...

 create-automatic-links-in-home.service (systemd --user) reads the
 manifest and makes the local side match it, at login/ssh/`become`.
  • ~/network_drives: your personal PSI network drive (SMB), same path on every Linux client — nothing to sync for what lives here.
  • Local $HOME: per-host. Nothing follows you between machines unless something explicitly puts it there.
  • linux_home_links.yaml: the manifest bridging the two.
  • The service (/opt/pli/bin/create-automatic-links-in-home) applies it. Force it manually: systemctl --user restart create-automatic-links-in-home.service. Caveat: it runs via PAM session setup, which happens after sshd has already decided on pubkey auth — so it's structurally too late for anything sshd itself needs during authentication, notably authorized_keys (see the .ssh gotcha below).
link:
  '.vimrc':
     to: 'network_drives/home/linux_config/vimrc'
copy:
  '.bashrc':
     from: 'network_drives/home/linux_config/bashrc'
download:
  '.config/tool.config':
     from: 'https://token@gitea.psi.ch/you/linux_config/tool.config'
  • link: — local path becomes a symlink into network_drives. Edits go straight to the shared copy.
  • copy: — local path is overwritten from network_drives every time the service runs (one-directional). Edit the network-drive source, not the local file, or your edit gets clobbered on next apply. Used for anything needed before the mount is ready (.bashrc at shell startup, authorized_keys at SSH auth time).
  • download: — like copy:, from an HTTP(S) URL instead.

Nesting-depth bug

The tool creates each symlink using the literal to: string, with no adjustment for how deep the local path is — a relative target resolves relative to the symlink's own directory, not $HOME. So it only works out of the box for paths directly under $HOME (one segment). A nested path (e.g. .local/share/gnome-shell/extensions, 3 segments) needs that many ../ prepended to to::

'.local/share/gnome-shell/extensions':
   to: '../../../network_drives/home/gnome-ext'

migrate_to_network_drive computes this automatically — prefer it over hand-editing the yaml for anything not directly under $HOME.

No git repos on network_drives (SMB) — use /sf/bernina (NFS) instead

network_drives is an SMB/gvfs mount that doesn't support the atomic rename/mkstemp writes git needs, or plain permission-preservation:

rsync: [receiver] mkstemp "...git/objects/.../abc123.XXXXXX" failed: Operation not supported (95)

Plain files are fine there (that's what link:/copy: are for) — just never git init/clone/config (or gpg --gen-key) directly against a path under network_drives; those all fail the same way, trying to chmod an internal lock file. /sf/bernina (NFS4) doesn't have this problem — git init/add/commit work cleanly on it. Rule of thumb: check the mount type (df -T <path>) — nfs/nfs4 is generally fine, cifs/smb/gvfs is not.

That said, an already-initialized repo (or GPG keyring), copied on afterward with rsync -rlt (no -p/-a), keeps working normally from network_drives — add/commit/push/pull and gpg encrypt/decrypt all confirmed. gopass.md ("Store location") uses exactly this init-locally-then-copy workaround as the preferred location for a human's gopass setup, precisely because network_drives is already the same mount on every host. Do this by hand only if you understand the gotcha above; otherwise use the gopass scripts, which handle it for you.

The scripts

~/sync_personal_config (pre-existing) — seeds two hardcoded items (.ipython, .condarc). Never edits an existing linux_home_links.yaml, only prints the snippet to add by hand.

/sf/bernina/bin/migrate_to_network_drive (general-purpose) — handles any file/directory, link: or copy::

migrate_to_network_drive <path-relative-to-HOME> [link|copy] [dest-name] [from-host]
migrate_to_network_drive .npmrc
migrate_to_network_drive .config/htop copy htop-config
migrate_to_network_drive .npmrc link "" saresb-cons-07   # pull from another host first

It refuses anything containing a .git directory, diffs before overwriting anything already on the network drive, computes the correct ../ depth, and inserts the yaml entry itself (with a backup and a confirmation prompt) rather than just printing a snippet. [from-host] lets it scp the source from another host first — explicitly, or offered automatically when the local path doesn't exist yet (defaults to saresb-vcons-01).

For a file that belongs inside an existing shared subdirectory (e.g. another key under linux_config/ssh/), pass the real relative path as dest-name explicitly — the script's automatic flattening (.ssh/foo → ssh-foo) won't match a path that should nest under ssh/:

migrate_to_network_drive .ssh/some_new_key link ssh/some_new_key

Symptom: your key is genuinely in the shared authorized_keys, but a fresh ssh to some host still falls back to a password — inconsistently.

Cause: if .ssh is linked as a single whole-directory entry, it resolves through the same GVFS/SMB mount as network_drives generally — which only exists while a graphical session for your uid is already active on that host. sshd reads authorized_keys during authentication itself, before the linking service has run (see the caveat above), so a fresh connection only succeeds if some earlier session happened to leave the mount warm.

Fix: split .ssh into per-file entries, with authorized_keys specifically as copy: (a real local file, never a symlink):

link:
  '.ssh/config':
     to: '../network_drives/home/linux_config/ssh/config'
  '.ssh/known_hosts':
     to: '../network_drives/home/linux_config/ssh/known_hosts'
  '.ssh/id_ed25519':
     to: '../network_drives/home/linux_config/ssh/id_ed25519'
  '.ssh/id_ed25519.pub':
     to: '../network_drives/home/linux_config/ssh/id_ed25519.pub'

copy:
  '.ssh/authorized_keys':
     from: 'network_drives/home/linux_config/ssh/authorized_keys'

Each link: entry needs exactly one ../ (one segment below $HOME now that the symlink is ~/.ssh/<file>, not ~/.ssh itself). Verified fix on a real host: pubkey auth failed outright before the split, succeeded immediately after, and survived a service restart with authorized_keys re-copied identically. Apply the same split on any other host that hits this, or use migrate_to_network_drive per file (remember the explicit dest-name, e.g. ssh/config).

Troubleshooting

  • A link: entry resolves to "No such file or directory": the nesting-depth bug — check the ../ count against the path's depth.
  • Changes to a copy: file disappear: you edited the local copy instead of ~/network_drives/home/linux_config/<name>.
  • Nothing updates after editing the yaml: force systemctl --user restart create-automatic-links-in-home.service.
  • ~/network_drives/home missing: run setup-network-drives.
  • Trying to sync a git repo this way: don't — see above.
  • SSH key login silently falls back to a password on some hosts: the .ssh gotcha above.