Mes dotfiles, sur deux machines :
| Machine | OS | Desktop | Profil |
|---|---|---|---|
| pro | Arch + Omarchy | Hyprland | omarchy |
| perso | Pop!_OS 24.04 | COSMIC | popos |
Le terminal, le thème et les outils CLI sont identiques sur les deux. Le desktop ne l'est pas, et n'essaie pas de l'être.
git clone https://github.com/brieucdlf/dotdotdots ~/.dotfiles
cd ~/.dotfiles && ./install.shEn HTTPS, et pas en SSH — l'ordre est contraint. Cloner en SSH exigerait la
clé sk, que ssh-keygen -K régénère depuis la YubiKey, ce qui exige
libfido2, qu'installe install.sh, qui est dans le dépôt qu'on cherche à
cloner. La boucle ne s'ouvre que par HTTPS : le dépôt est public, la lecture
anonyme ne demande rien. Une fois les clés régénérées (voir Sur une seconde
machine), on bascule le remote :
git remote set-url origin git@github.com:brieucdlf/dotdotdotsC'est le sens de la bascule qui compte : lire n'a jamais besoin d'identité, écrire en exige une. Rien d'autre dans ce dépôt ne doit dépendre d'un secret pour être installé — sans quoi il cesse d'être ce qui suffit à refaire une machine.
Le script détecte le profil, installe les paquets manquants, stow les paquets,
rend le thème et lance mise install. Il est idempotent — relançable à volonté.
Il déblaie aussi le terrain avant de stower, parce qu'un seul conflit fait avorter stow en entier et laisse la machine sans ancienne config ni nouvelle :
- tout ce qui existe déjà et bloquerait stow — vrai fichier, mais aussi lien
posé par un autre outil, sur un fichier comme sur un dossier — part dans
~/.dotfiles-backup/<horodatage>/, jamais écrasé ; - les liens de
~qui pointent dans le repo vers un chemin disparu (après un renommage, par exemple) sont retirés : ils ne contiennent rien.
Si stow refuse quand même, le script s'arrête en disant où sont les fichiers écartés — plutôt que de mourir sur l'erreur brute de stow.
./install.sh --profile popos # forcer un profil
./install.sh --no-packages # stow + thème seulementLa règle qui tranche : si ça tourne dans un terminal → common/. Si ça dépend
du compositeur ou du gestionnaire de paquets → un profil.
common/ # les 2 machines, identique au byte près
├── .bashrc # loader : Omarchy si présent, puis init.bash
├── .gitconfig
├── .config/
│ ├── bash/init.bash # socle shell indépendant de la distro
│ ├── bash/{exports,aliases,work}.bash
│ ├── ghostty/ tmux/ nvim/ starship.toml
│ ├── mise/config.toml # ← versions des outils, la clé de l'iso
│ └── zed/
├── .claude/ # config Claude Code (settings, skills, statusline)
└── .local/bin/ # dots-herdr-graft, dots-secrets, dots-shell-dump
theme/
├── nurburgreen/ # colors.toml = SOURCE UNIQUE des couleurs (+ ui.toml)
├── kreide/ # idem — tout dossier avec un colors.toml est un thème
├── cosmic/ # palette sémantique stock de COSMIC, reprise telle quelle
└── render.sh # → ~/.config/theme/current/{ghostty,tmux,colors.sh,*.ron}
omarchy/ # hypr/ (Lua), omarchy/ (shell.json + gabarits de thème)
popos/ # thème COSMIC + override ghostty (voir son README)
common/ et le profil sont stowés en une seule invocation
(stow --restow common $PROFILE) : traités séparément, stow refuse de déplier
un dossier posé par l'autre passage.
Deux, et le dépôt en accepte d'autres : install.sh traite tout dossier de
theme/ qui contient un colors.toml, le lie dans le sélecteur d'Omarchy, et
rend celui qui est ACTIF — pas un nom écrit en dur.
Construit autour d'une Porsche 911 GT3 en British Racing Green.
background #001a0f BRG dark
foreground #d4b88a cognac leather
accent #f0c000 GT yellow (used sparingly)
Une 911 GT3 RS craie : bitume mouillé, gris froids, un seul jaune d'étrier.
background #15181a bitume mouillé
foreground #c9ccca craie
accent #e8b33c étrier de frein
Le colors.toml du thème est la seule source de vérité. theme/render.sh
en dérive les fichiers consommés par ghostty, tmux, fzf, eza, btop et herdr dans
~/.config/theme/current/ — aucune couleur n'est codée en dur dans une config.
Sur Omarchy, install.sh rend le thème actif — le bureau fait foi. Ailleurs
il n'y a pas de thème actif à lire, d'où --theme :
./install.sh --no-packages --theme kreideLe choix est retenu : render.sh laisse le nom dans
~/.config/theme/current/.theme-name, et les passages suivants le reprennent.
Sans quoi --theme n'aurait valu que pour une fois.
herdr est thémé par install.sh sur les deux machines, pas seulement par le
crochet theme-set — ce crochet est dans le paquet omarchy et n'existe pas
sur Pop!_OS, où .bashrc préfère pourtant herdr dès qu'il est installé. Les
deux appellent common/.local/bin/dots-herdr-graft.
Le même exécutable greffe les raccourcis, mais seulement depuis
install.sh : herdr/keys.toml est la source unique des deux machines, et la
greffe remplace la section [keys] en place. Les deux blocs sont indépendants
— changer de thème ne touche pas aux touches, et ungraft keys rend les
raccourcis d'Omarchy sans défaire le thème.
Deux contraintes valent pour tout thème ajouté ici :
- son fond d'écran doit porter l'assombrissement du haut (la barre ne peint
aucun fond — voir
theme/<nom>/assets/README.md) ; - ses clés
herdr_*dansui.tomldoivent tenir 3:1 sur ses propres surfaces, les slots ANSI sombres n'y suffisent pas — fonds compris :herdr_row_bgdoit se voir surherdr_panel_bg, sinon la ligne active ne se distingue de rien ; - le thème d'icônes qu'il nomme doit exister sur la machine.
install.shprévient quand ce n'est pas le cas, il ne l'installe pas. Les tokens qui ne concernent querender.sh(bordure de pane tmux, fond de barre tmux, rampe neutre COSMIC) sont dansui.toml, à côté.
Changer une couleur = éditer colors.toml, relancer ./install.sh --no-packages.
Sur la machine Omarchy, install.sh lie aussi le thème dans le sélecteur
Omarchy, puis le réapplique — depuis la v4 les fichiers que lisent Hyprland, la
barre et le verrouillage ne sont plus dans le thème, ils en sont dérivés
au moment du omarchy theme set (voir « Omarchy 4 » plus bas). Le terminal,
lui, ne dépend pas d'Omarchy : c'est ce qui rend l'iso vraie.
Sur Pop!_OS, le desktop COSMIC a droit au même traitement : render.sh produit
un cosmic-<thème>-dark.ron importable via Réglages > Apparence >
Importer un thème. Détail du mapping dans popos/README.md.
Le shell interactif attache herdr au démarrage — un serveur en arrière-plan
dans lequel les terminaux vivent, à la place de tmux et non dedans. DOTS_MUX
en décide :
| Valeur | Effet |
|---|---|
| (vide) | herdr, et tmux s'il n'est pas installé |
tmux |
l'ancien comportement |
none |
shell nu — DOTS_NO_TMUX=1 fait pareil |
Le garde est HERDR_PANE_ID, ce que TMUX est à tmux : posé seulement dans un
pane. La conf tmux reste en place et thémée ; elle sert dès que DOTS_MUX=tmux.
La machine pro est passée d'Omarchy 3 à 4. Ce n'est pas une montée de version
ordinaire : trois des quatre choses que le profil omarchy/ posait ont changé
de format ou d'adresse. Ce que le dépôt contient a donc changé aussi.
| v3 | v4 | ce que ça change ici |
|---|---|---|
~/.config/hypr/*.conf |
~/.config/hypr/*.lua |
tout le profil réécrit en Lua |
waybar (config.jsonc + style.css) |
shell Quickshell (~/.config/omarchy/shell.json) |
les deux fichiers supprimés, un seul les remplace |
hypridle.conf |
shell.json, clé idle |
le verrouillage n'est plus un démon à part |
hyprlock.conf |
plugin omarchy.lock, thémé |
l'écran de verrouillage n'est plus dessinable à la main |
~/.config/omarchy/current/ |
~/.local/state/omarchy/current/ |
ce qui est généré quitte ~/.config |
~/.local/share/omarchy/ |
/usr/share/omarchy/ |
Omarchy est un paquet pacman ; l'ancien chemin n'est plus qu'un lien |
En v3, bindings.conf et input.conf étaient les fichiers stock d'Omarchy
recopiés, avec quelques lignes à soi noyées dedans. C'est ce qui a rendu cette
migration longue : il a fallu rejouer la comparaison ligne à ligne avec les
défauts de la v4 pour savoir ce qui était un choix et ce qui était un héritage.
Il en restait très peu. bindings.lua tient en une ligne (btop sur
Super+Shift+T ; Omarchy 4 le met sur Super+Ctrl+T, les deux coexistent) et
input.lua en quatre valeurs (us,fr, repeat_delay, deux réglages de
touchpad). Tout le reste était devenu, ou était déjà, le défaut.
La règle qui en découle : ne rien recopier d'Omarchy « pour la lisibilité ». Une valeur redite dans le profil fige le défaut du jour et ne bougera plus jamais avec les mises à jour, sans que rien ne le signale.
Le moteur de gabarits de la v4 expose toute clé de colors.toml aux
templates de default/themed/. Deux conséquences :
- les bordures de fenêtres sortent de
looknfeel.luaet deviennenthyprland_active_border/hyprland_inactive_borderdanscolors.toml. Elles appartiennent au thème, pas au compositeur — et surtout,looknfeel.luaest chargé après le thème : y laissercol.active_borderécrasait n'importe quel thème, y compris après en avoir changé ; - ce qui reste dans
ui.tomln'y est plus parce qu'Omarchy « ne saurait pas le lire », mais parce qu'Omarchy n'a aucune raison de le voir (tmux, COSMIC).
La barre et le verrouillage se thèment par des sections de shell.toml,
déposées en gabarits dans omarchy/.config/omarchy/themed/ :
shell.bar.toml.tpl (barre sans aucun fond — l'assombrissement est cuit dans
le fond d'écran du thème, voir theme/kreide/assets/README.md) et
shell.lock.toml.tpl (couleur d'échec lisible). Ils sont rendus depuis
colors.toml — donc valables pour tous les thèmes, et sans une couleur en
dur. Contrepartie : Omarchy remplace la section entière, pas clé par clé ; une
clé [bar] ajoutée en amont ne parviendra pas tant qu'elle n'est pas reprise.
- La carte de verre dépoli du verrouillage. L'ancien
hyprlock.confdessinait une carte arrondie, la photo de profil et une horloge en 64 px. Le pluginomarchy.lockne se règle que par des couleurs : le fond d'écran, un champ de saisie, rien d'autre. Seule la palette a été reportée. - Le module CPU de la barre. Il n'a pas d'équivalent en v4 ;
Super+Shift+Touvre toujours btop, ce que faisait le clic sur le module. hypridle, à la seconde près. La v4 n'a pas de « verrouiller sans économiseur » :idle.screensaveretidle.locksont deux délais, pas un interrupteur, et un délai nul déclenche l'économiseur immédiatement au lieu de l'éteindre. Les deux sont donc posés à 120 s : le délai de verrouillage de la v3 est conservé exact, rien ne se passe avant, et l'économiseur qui démarre au même instant est tué paromarchy-system-lockdans la foulée.
shell.json déclare le plugin brieuc.launcher, hypr/bindings.lua lui donne
SUPER + A, looknfeel.lua floute sa couche et
themed/shell.launcher.toml.tpl l'habille. Le plugin lui-même n'est pas
ici : il vit dans son propre dépôt, cloné vers
~/.config/omarchy/plugins/brieuc.launcher/.
install.sh ne l'installe pas, exprès — écrire dans
~/.config/omarchy/plugins/ recharge à chaud TOUS les plugins du shell, y
compris le verrouillage, et un script d'install n'a pas à déclencher ça. Il se
contente de dire ce qui manque : chaque id de shell.json sans dossier
correspondant sort en warn. Sur une machine neuve, cloner le dépôt du plugin
à cet emplacement, puis omarchy-shell shell rescanPlugins.
launcher_rail reste dans les colors.toml pour le plugin, mais pas dans
le gabarit partagé : ~/.config/omarchy/themed/ est rendu pour tous les
thèmes, y compris ceux d'Omarchy, qui n'ont pas cette clé — le moteur y
laisserait le gabarit non substitué.
~/.config/omarchy/shell.json est stowé comme le reste, mais le shell le
réécrit lui-même en temp + rename (atomicWrites: true) : chaque réglage de
barre — déplacer un widget, omarchy bar … — remplace le lien par un vrai
fichier et arrête la synchro sans rien dire. install.sh récupère le contenu
au passage suivant, comme il le fait pour ~/.claude/settings.json.
common/.config/mise/config.toml fixe les versions de tout le socle CLI
(eza, fzf, zoxide, starship, delta, lazygit, fd, rg, btop, bat, gh, jq, neovim)
en plus des runtimes. Un mise install et les deux machines ont les mêmes
binaires, indépendamment d'apt et de pacman.
Restent hors mise, installés par install.sh : ghostty (.deb
mkasberg/ghostty-ubuntu sur Pop,
pacman sur Arch), tmux, stow et la JetBrainsMono Nerd Font.
Le socle shell vient d'Omarchy sur la machine pro et de init.bash sur Pop :
rien ne garantit qu'ils soient identiques. Pour rendre l'écart visible :
dots-shell-dump > ~/shell-$(hostname).txt # sur chaque machine
diff ~/shell-pro.txt ~/shell-perso.txtCe qui manque côté Pop est à recopier dans common/.config/bash/init.bash.
Les clés API et l'identité pro sont chiffrées dans le repo, et ne s'ouvrent
qu'avec une YubiKey. La clé privée age est générée dans l'applet PIV du token
et n'en sort jamais : il n'y a rien à sauvegarder ailleurs, et un vol de machine
ne donne accès à rien.
C'est ce qui ferme la boucle du reset — avant, un poste neuf retrouvait un shell parfait mais aucune clé, et il fallait les recopier à la main depuis on ne sait où.
dots-secrets status # état de la chaîne
dots-secrets verify # prouver que la clé branchée ouvre tout
dots-secrets edit local.bash # éditer un secret (le clair reste en tmpfs)
dots-secrets unseal # (re)déposer les secrets sur la machine
dots-secrets enroll # enrôler une YubiKey de plus./install.sh déchiffre tout seul si la clé est branchée, et se termine quand
même sinon. Détail du modèle, ajout d'un secret, perte d'une clé :
secrets/README.md.
ykman est installé avec le reste : c'est le seul outil qui inspecte et
reconfigure les applets du token — lister les identifiants résidents, remettre
un PIN, lire le numéro de série. Rien ici n'en dépend au quotidien, mais sans
lui un token qui se comporte mal est une boîte noire.
Il sert notamment à retirer l'applet OTP si elle n'est pas utilisée
(ykman otp delete 1). Cette applet tape une longue chaîne à chaque touche
reçue hors d'une demande FIDO — donc dans un terminal, un champ de mot de
passe ou une messagerie, selon ce qui a le focus. Sur un token qui ne sert
qu'au FIDO et au PIV, elle ne fait que transformer une touche mal placée en
accident au lieu d'un non-événement.
La chaîne ne sert pas qu'aux fichiers de configuration. todo-secu y range les
faiblesses connues et non corrigées — une liste de trous ouverts est une
carte, pas une note de travail : elle dit par où entrer et ce qui n'est pas
surveillé. Le reste du dépôt peut rester public sans dommage ; publier qu'on
utilise Tailscale et une YubiKey n'affaiblit rien, la sécurité ne tenant pas au
secret du dispositif.
~/.ssh/config est versionné ; les clés ne le sont jamais. github.com
n'accepte plus que les YubiKeys — une par token, -usbc au quotidien,
-usba au coffre. Pousser exige une présence physique, la clé privée ne
sortant jamais de l'applet FIDO.
L'épinglage dans config ne suffisait pas : c'est une préférence locale, et
GitHub continuait d'accepter la clé logicielle. Quiconque volait le fichier
écrivait sa propre config et poussait. La clé logicielle a donc été retirée
du compte — c'est ce retrait, pas le fichier de config, qui rend la règle
réelle. Elle reste en service pour tous les autres hôtes.
Les clés sont résidentes : le token est sa propre sauvegarde. Sur une
machine neuve, ssh-keygen -K les régénère depuis la YubiKey — rien à mettre
dans secrets/, et pas d'amorce circulaire (pas de clé SSH enfermée dans le
dépôt qu'il faudrait SSH pour cloner). Leurs noms de fichier sont laids exprès :
ce sont ceux que -K recrée seul à partir de la chaîne application.
$ git push # sans le token
sign_and_send_pubkey: signing failed for ED25519-SK — agent refused operation
git@github.com: Permission denied (publickey).
C'est le comportement voulu, pas une panne. Le multiplexage garde la connexion 10 min : un seul touch pour une rafale de commandes.
Le même message veut dire deux choses. agent refused operation sort aussi
bien quand aucun token n'est branché que quand le bon token est branché mais
n'a pas été touché dans la fenêtre. Le seul moyen de les distinguer est la
durée : un refus immédiat, c'est l'absence de token ; un refus après ~20 s par
clé, c'est un touch manqué. Avec deux IdentityFile, ssh les essaie l'une
après l'autre — d'où ~40 s avant le Permission denied final.
time git push # ~0 s : pas de token. ~40 s : personne n'a touché.Corollaire pratique : ce n'est pas automatisable. Une commande lancée par un agent, un cron ou un script attend un doigt qui n'arrive jamais et échoue au bout de 40 s. Le push est un geste manuel, par construction.
Enfin, hors terminal, ssh ne demande pas la passphrase lui-même : il délègue à
un askpass externe, et sans askpass il abandonne sans rien afficher.
install.sh installe donc ssh-askpass-gnome (Pop!_OS) ou ksshaskpass
(Arch), et init.bash pose SSH_ASKPASS sur le premier des deux qui existe.
Tant que gnome-keyring détient les passphrases le manque ne se voit pas — il
se voit le jour où un agent propre doit charger une clé sans tty.
Le fichier a un piège documenté sur place — IdentityFile est la seule option
d'un ssh_config qui soit cumulative au lieu de premier-gagnant. Mise dans
Host *, la clé logicielle s'ajouterait à celle de github.com et ssh
retomberait dessus dès le token absent, ce qui annulerait l'épinglage. D'où le
bloc négatif Host * !github.com. Le contrôle tient en une commande :
ssh -G github.com | grep '^identityfile' | grep -c heartbeat # doit valoir 0Le contrôle porte sur la nature des clés, pas sur leur nombre : github.com en liste deux, une par YubiKey. Ce qui doit être absent, c'est la clé logicielle.
Les hôtes de travail ne sont pas dans le fichier versionné : une IP, un
compte ou même un alias désignent une infra interne, et le dépôt est public.
Ils vivent dans ~/.ssh/config.local, tiré par un Include en tête de
config — premier-gagnant oblige, placé en bas il n'aurait rien pu redresser.
Un Include absent n'est pas une erreur pour ssh : les machines qui n'ont pas ce
fichier ne voient rien.
Un hôte déclaré là doit nommer sa propre clé. Le bloc négatif ne propose que
id_ed25519_heartbeat, et IdentitiesOnly yes interdit tout le reste : sans
IdentityFile local, ssh se présente les mains vides et le serveur refuse
(Permission denied (publickey)), précédé du révélateur no such identity
quand la clé logicielle générique n'existe pas non plus sur cette machine.
install.sh crée ~/.ssh en vrai dossier avant de stower. Sans ça, stow le
plierait en un lien vers le dépôt et le premier ssh-keygen écrirait une clé
privée dans un dépôt public. .gitignore couvre le cas en second rideau :
common/.ssh/* est refusé par défaut, seul config est suivi.
Chaque commit est signé par SSH, avec la même YubiKey qui authentifie. Un commit à ton nom exige donc une présence physique : une machine compromise ne peut pas en fabriquer.
Par SSH et non par GPG, pour deux raisons. Aucune clé à faire expirer — la GPG de ce dépôt l'a fait, sans prévenir. Et la clé matérielle est déjà là : une seule chose à protéger plutôt que deux.
user.signingkey vit dans identity.gitconfig, chiffré, et pas dans le
.gitconfig committé : c'est un choix de poste, chaque machine nommant le
token qu'elle a sous la main.
allowed_signers est ce qui permet de vérifier et pas seulement de signer —
sans lui git n'a aucun moyen de relier une adresse à une clé publique :
git log --show-signature -1
git verify-commit HEADLe namespaces="git" y cantonne la confiance à la signature de commits. La
même clé sert à s'authentifier auprès de GitHub, et une clé valable partout est
une clé qu'on ne peut pas révoquer pour un seul usage.
Le coût est un touch par commit, y compris à chaque commit rejoué par un rebase. Pour une opération en lot :
git -c commit.gpgsign=false rebase ...Git ne prend qu'une clé de signature, sans repli — contrairement à
l'authentification SSH qui essaie une liste. La YubiKey de secours est déjà
enregistrée chez GitHub en type signing, donc la bascule tient en une ligne :
# dans ~/.config/git/identity.gitconfig
signingkey = ~/.ssh/id_ed25519_sk_rk_github-usba.pub
dots-secrets seal identity.gitconfig # aucun token requisRien à faire côté GitHub, aucun commit à re-signer. C'est tout l'intérêt d'avoir enregistré les deux clés pendant que tout allait bien : le jour de la panne, il ne reste qu'un fichier à éditer, pas un compte à manipuler.
Sur Pop!_OS, les correctifs de sécurité s'installent seuls. Les timers
apt-daily et apt-daily-upgrade tournaient déjà — le paquet apt les arme
d'office — mais sans unattended-upgrades ils n'avaient rien à exécuter, et
sans APT::Periodic dans 20auto-upgrades le paquet lui-même ne fait rien.
install.sh pose les deux.
Restent volontairement désactivés : le redémarrage automatique, qui ferait perdre du travail sur un poste de bureau (les correctifs de noyau attendent donc un reboot manuel), et tout ce qui dépasse les dépôts de sécurité — un poste ne doit pas changer de version majeure pendant la nuit.
Côté Omarchy, rien n'est automatisé, et c'est délibéré. Arch n'a pas de
dépôt de sécurité séparé : automatiser reviendrait à lancer un pacman -Syu
complet sans surveillance, avec son risque de mise à jour partielle et ses
interventions manuelles annoncées sur la page d'accueil. Sur Arch, mettre à
jour reste un geste conscient.
.gitignore, le filtre autoMode et le hook pre-commit ne couvrent que les
fuites anticipées : un chemin qu'on a pensé à interdire. gitleaks regarde
le contenu, pas le nom du fichier. C'est la différence entre une liste
d'interdits et une relecture.
Le scan tourne au pre-commit, sur l'index, et refuse le commit en cas de
trouvaille — comme il refuse de s'exécuter si gitleaks est absent. Laisser
passer dans ce cas donnerait précisément l'illusion d'être couvert.
install.sh prend le binaire amont sur Pop!_OS, pas le paquet apt :
Ubuntu livre 8.16 (mi-2023), dont le jeu de règles ignore par exemple les clés
Anthropic — vérifié. Pour un scanner, la fraîcheur des règles est la
fonction. L'archive est vérifiée par empreinte SHA256 avant installation : un
outil censé ne pas faire confiance au reste ne peut pas se fier au transfert.
Côté Arch le dépôt suit l'amont, le paquet suffit.
.gitleaks.toml n'écarte que des faux positifs structurels : les .age
(du chiffré, haute entropie par construction — les signaler reviendrait à
signaler que le coffre est bien fermé), les clés publiques age et SSH, les
.sample, et les plugins tmux vendorisés. Rien n'y nomme d'employeur ni de
point d'accès : le fichier est committé, une règle taillée sur un secret précis
le décrirait.
Un secret committé ne se supprime pas, il se révoque. Réécrire l'historique
ne rattrape ni les clones, ni les forks, ni les caches. C'est pour ça que le
filet est au pre-commit et pas ailleurs : après, il est trop tard, et le seul
recours est la rotation de la clé.
gitleaks git --redact -c .gitleaks.toml . # tout l'historique
gitleaks dir --redact -c .gitleaks.toml . # l'arbre courantTout le reste de ce dépôt organise des flux sortants. Ici on ouvre une porte, et la forme de la porte compte plus que ses réglages.
Il n'y a pas d'openssh-server sur ces machines, et rien n'écoute sur le
LAN ni sur Internet. L'accès passe par le SSH intégré à Tailscale, qui
authentifie par l'identité du pair dans le réseau maillé — pas par une clé
posée sur le client. C'est la seule raison de ce choix : depuis Android, une
YubiKey est inutilisable en SSH (l'OpenSSH de Termux est compilé sans FIDO, et
l'accès USB-HID ou NFC au token n'est pas ouvert aux applis non root). Un
authorized_keys classique aurait donc signifié déposer sur le téléphone
exactement la clé logicielle qu'on vient de retirer de GitHub. Avec Tailscale
SSH, il n'y a pas de clé sur le téléphone du tout : l'identité est celle de
l'appareil dans le réseau, adossée à ton fournisseur d'identité — que tu peux
protéger par la YubiKey en passkey. La chaîne matérielle est rétablie par un
autre chemin.
install.sh installe Tailscale et active le démon, mais ne lance jamais
tailscale up : rattacher une machine à un réseau est une décision, pas une
étape d'installation. À faire à la main, une fois par machine :
sudo tailscale up --ssh # rattache la machine ET active le SSH Tailscale
tailscale status # vérifier qui est joignableCôté téléphone : l'application Tailscale, plus n'importe quel client SSH (Termux). Aucune clé à générer.
Dans les ACL du réseau, restreindre l'usage à tes propres appareils et exiger une ré-authentification périodique plutôt qu'un accès permanent :
check est le point important : sans lui, un téléphone volé et déverrouillé
donne un shell permanent. Avec, il redemande l'identité — donc la passkey.
Pour piloter une session de travail depuis le téléphone, se rattacher à un tmux existant plutôt qu'ouvrir un shell neuf :
ssh <machine> -t 'tmux new -A -s phone'new -A rattache la session si elle existe, la crée sinon : la même session
survit aux coupures réseau, qui sont la règle en mobilité.
Une machine endormie n'a pas de tailscaled éveillé : elle est injoignable,
et aucun réglage sur la machine n'y change rien. Le Wake-on-LAN se fait par
un paquet envoyé sur le réseau LOCAL, ce qui suppose un appareil déjà allumé
pour l'émettre — un Raspberry Pi, un NAS, une box qui sache le faire. Il n'y en
a pas ici.
Le choix est donc assumé : install.sh désactive la suspension
automatique sur le poste fixe, en écrivant None dans le réglage COSMIC
CosmicIdle/v1/suspend_on_ac_time. L'écran continue de s'éteindre au bout de
30 min — c'est l'essentiel de l'économie, et ça ne coupe pas le réseau. Seule
la suspension disparaît.
C'est le seul endroit du dépôt où l'accès distant coûte quelque chose : la consommation d'un fixe qui ne s'endort plus. Le contraire aurait coûté un accès qui marche une fois sur deux, ce qui ne vaut rien.
Le réglage ne s'applique qu'à un châssis de bureau (types DMI 3/4/6/7) :
sur un portable la suspension est un service rendu, et le profil popos
pourrait un jour tourner ailleurs que sur cette tour.
Vérifier que ça tient — l'écriture du fichier ne prouve rien, seule l'absence de suspension le prouve :
cat ~/.config/cosmic/com.system76.CosmicIdle/v1/suspend_on_ac_time # -> None
journalctl --since "7 days ago" | grep -c "PM: suspend entry" # -> 0Avant ce changement, cette machine s'endormait 14 fois par mois.
Une StreamCam est branchée sur le poste fixe. dots-cam permet de l'allumer
depuis le téléphone — à travers le réseau maillé, jamais autrement.
dots-cam snap [fichier] # une image, la caméra se referme aussitôt
dots-cam start [secondes] # flux MJPEG, 30 min par défaut
dots-cam stop
dots-cam statusLe flux s'ouvre sur http://<adresse-tailscale>:8080/, lisible dans le
navigateur du téléphone sans rien installer.
Trois propriétés, et ce sont elles qui ont dicté le code :
Éteinte par défaut. Rien n'est enable, rien ne démarre au boot. Le flux
est une unité systemd transitoire : elle n'existe pas tant qu'on ne l'a pas
demandée et disparaît en s'arrêtant. Il n'y a pas d'état oublié quelque part.
Jamais hors du réseau maillé. Le flux se lie à l'adresse Tailscale, et le
script refuse de démarrer si elle n'existe pas. Se lier à 0.0.0.0 « en
attendant » offrirait la caméra au réseau local entier — exactement le repli
commode que ce dépôt passe son temps à retirer.
Extinction automatique. RuntimeMaxSec coupe au bout du délai, que la
connexion ait tenu ou non. Le risque n'est pas d'allumer une caméra, c'est de
la laisser allumée.
S'y ajoute un témoin que le logiciel ne contrôle pas : la LED blanche de la StreamCam est allumée tant que le capteur diffuse. Aucune commande de ce dépôt ne l'éteint. Chaque allumage laisse par ailleurs une trace dans le journal systemd, l'unité y étant nommée.
install.sh ajoute l'utilisateur au groupe video, et ça n'est pas du
confort : sans lui, l'accès à /dev/video0 ne tient qu'à l'ACL que logind
pose pour la session graphique active. La caméra marcherait tant qu'on est
assis devant, et échouerait précisément quand on est loin — le seul moment où
elle sert. Une reconnexion est nécessaire pour que le groupe prenne effet.
Le mode snap est le moins exposé des deux : la caméra s'ouvre le temps d'une
image et se referme, rien n'écoute sur le réseau. Si le flux tourne déjà, la
capture lui est demandée à lui plutôt que d'échouer sur un « device busy »
incompréhensible.
Côté Omarchy, rien : la caméra est sur le fixe, et ustreamer n'est pas dans
les dépôts officiels d'Arch.
Le clair de ces fichiers n'est jamais committé. Certains sont restaurables depuis leur version chiffrée, les autres sont propres à la machine et doivent être réécrits à la main :
| Fichier | Usage | Après un reset |
|---|---|---|
~/.config/bash/local.bash |
secrets, clés API, overrides — sourcé en dernier | déchiffré |
~/.config/bash/work.bash |
aliases pro : noms de projets internes | .sample à recopier |
~/.config/git/identity.gitconfig |
nom et email git | déchiffré |
~/.config/zed/mcp.json |
serveurs MCP de Zed (endpoints internes) | .sample à recopier |
~/.local/bin/dev-tmux |
lanceur de session tmux propre au boulot | à réécrire |
~/.config/ghostty/local.conf |
font-size selon le DPI ; posé par le profil popos pour l'opacité |
à refaire (dépend de l'écran) |
~/.config/git/work.gitconfig |
identité pro, appliquée d'office sous ~/Work/ |
déchiffré |
omarchy/.config/hypr/monitors.lua |
résolutions et scaling (committé, mais par profil) | committé |
common/.claude/settings.private.json |
bloc autoMode de Claude Code, extrait par le filtre git |
régénéré par le filtre |
Chacun a son .sample committé à côté, qui montre la structure sans nommer
l'employeur, les projets internes ni l'identité. work.bash n'est chargé que
s'il existe — une machine fraîche ne casse pas.
Aucune clé ne se copie d'une machine à l'autre — ni par clé USB, ni par le
dépôt. Les sk sont des handles : la clé privée ne quitte jamais l'applet
FIDO, et le token la redonne à la demande. Après le clone :
./install.sh # ~/.ssh en vrai dossier + sockets, pcscd, askpass
dots-secrets unseal # YubiKey PIV : identity.gitconfig, local.bash…
cd ~/.ssh && ssh-keygen -K # YubiKey FIDO : régénère la sk du token branché
chmod 600 ~/.ssh/id_ed25519_sk_rk_github-*Sur Arch, ssh-keygen -K échoue tant que libfido2 manque : Debian en
fait une dépendance dure d'openssh-client, Arch une simple optdepend. Le
message ne parle ni de FIDO ni de YubiKey (libfido2.so.1: cannot open shared object file, puis unexpected internal error) et arrive après le PIN, ce
qui laisse croire à un problème de token. install.sh l'installe.
ssh-keygen -K écrit dans le répertoire courant — d'où le cd, sans quoi
les fichiers atterrissent dans le dépôt. Il ne rend que les identifiants du
token branché : une seule YubiKey suffit, l'autre se régénère le jour où on la
branche. Les noms recréés sont ceux qu'attend déjà ~/.ssh/config, il n'y a
donc rien à renommer ni à éditer.
Ce qui reste propre à la machine : ~/.ssh/id_ed25519_heartbeat (clé
logicielle pour les autres hôtes), ~/.ssh/config.local (hôtes de travail,
voir « SSH ») et les fichiers marqués « à réécrire » ou « .sample à
recopier » ci-dessus.
~/.claude/ est stowé depuis common/.claude/ : settings.json, les skills
perso et la statusline suivent d'une machine à l'autre.
~/.claude/, pas ~/.config/claude/ — Claude
Code ne lit jamais ce dernier. Il n'existe pas non plus de
~/.claude/settings.local.json : le suffixe .local n'a de sens qu'au niveau
d'un projet.
~/.claude/settings.json est un lien vers le dépôt, et c'est volontaire :
Claude Code écrit lui-même dans ce fichier (/config, thème, effort, plugins
activés), donc ses réglages atterrissent directement dans le dépôt, prêts à
committer. S'il venait à remplacer le lien par un vrai fichier — écriture
atomique, comme cosmic-settings —, install.sh récupère le contenu et
re-soude le lien.
Ne sont pas suivis : les serveurs MCP (ils vivent dans ~/.claude.json,
mélangés à l'OAuth et aux caches, chaînes de connexion en clair), l'historique
(projects/, 169 Mo), les plugins (ils se réinstallent seuls depuis
enabledPlugins). Sur une machine neuve, les MCP sont à rejouer à la main :
claude mcp add anytype ...
claude mcp add mongodb ...Ce dépôt est public, et settings.json porte un bloc autoMode.environment
qui décrit l'environnement de travail (dépôt privé, bases Notion, politique de
traitement des données personnelles). Un filtre git le retire :
.gitattributes |
associe le fichier au filtre claude-settings |
.githooks/claude-settings-filter |
clean retire le bloc et l'archive ; smudge le réinjecte |
common/.claude/settings.private.json |
l'archive, gitignorée |
.githooks/pre-commit |
refuse le commit si le bloc atteint quand même l'index |
Les deux sens comptent. Sans smudge, le filtre serait destructeur : un
git stash, un git checkout ou un reset --hard fait transiter le fichier par
l'index et le bloc disparaîtrait de la config vivante, sans un mot.
install.sh configure le tout (filter.claude-settings.*, core.hooksPath),
avec required = true — un jq absent fait échouer l'indexation au lieu de
publier le fichier brut. Un clone sur lequel install.sh n'a pas tourné n'a
pas cette protection : le hook pre-commit est là pour ce cas.
common/.claude/statusline.sh affiche modèle, effort, contexte, quotas 5 h et
7 j, coût et branche. Les trois derniers ne s'inventent pas : rate_limits,
cost.total_cost_usd et context_window.used_percentage ne sont exposés qu'au
stdin de la statusline, ni par la CLI ni par les transcripts.
Le panneau tmux qui les relisait a été retiré : les agents en cours et la conso
se lisent dans le module omarchy.agents de la barre, et le multiplexeur est
herdr (voir « Multiplexeur »).
Contrepartie : avec une statusline active, Claude Code masque la plupart des
rappels de raccourcis du footer (dont esc to interrupt). Retirer la clé
statusLine de settings.json les fait revenir.
- Omarchy gère le système de base. Ces dotfiles sont des overrides —
/usr/share/omarchy/n'est jamais touché (v3 :~/.local/share/omarchy/, aujourd'hui un simple lien vers le premier). - Hyprland recharge à chaud.
shell.jsonet les plugins aussi. Les couleurs passent paromarchy theme set nurburgreen, pas par un redémarrage. - tpm vit dans
~/.config/tmux/plugins(XDG), pas~/.tmux.
