diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index 4d77961..c4fa74b 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -90,3 +90,12 @@ Same branch. Prompt at start of Darwin block (MACOS_SHELL=bash|zsh env overrides - [x] install.sh: use_brew_bash_login_shell → set_login_shell , called at end with chosen shell - [x] README + CLAUDE.md - [x] shellcheck/bash -n/zsh -n; runtime: theme in zsh (prompt render, timer, git bits), dtach-router sourced in zsh; harness both choices + +## Feature — user-scope ~/.gitconfig from repo template, VIUSER/VIMAIL → USER/EMAIL (2026-10-06) +- [x] rc files (bashrc-linux, bashrc-osx, zshrc-osx): `VIUSER`/`VIMAIL` → `USER`/`EMAIL` +- [x] `gitconfig` template: git never expands `$VAR` → `@USER@`/`@EMAIL@` placeholders, `excludesfile = ~/.gitignore` +- [x] install.sh `deploy_gitconfig`: values read from deployed bashrc, rendered → ~/.gitconfig, differing old one → ~/.gitconfig.backup- +- [x] install.sh: `$USER` → `$(id -un)` (dscl, code-server unit): rc now overrides USER with identity +- [x] README + CLAUDE.md layout +- [x] Verify: shellcheck, bash -n, render to temp HOME, `git config --file` reads values +- [x] `git-delta` added to apt + brew lists (gitconfig pager = delta) diff --git a/CLAUDE.md b/CLAUDE.md index 28aa7ea..01e233b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,8 @@ install.sh one-shot installer (OS auto-detected) vim/vimrc vim config (pathogen, molokai, syntastic, NERDTree) vim/autoload/ pathogen loader (committed) vim/colors/ molokai colorscheme (committed) -bash/bashrc-{linux,osx} OS-detected bashrc +bash/bashrc-{linux,osx} OS-detected bashrc (exports USER/EMAIL identity) +gitconfig user-scope ~/.gitconfig template, @USER@/@EMAIL@ filled at install zsh/{zshrc-osx,bchanot.zsh-theme} macOS zsh option: oh-my-zsh zshrc + theme porting the bash prompt bin/{dt,dtach-router,claude-provider} CLI scripts deployed to ~/.local/bin etc/profile.d/disk-usage-warning.sh login-time low-disk warning → /etc/profile.d (Linux only) diff --git a/README.md b/README.md index d03270d..6e64bbe 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ curl -fsSL https://git.bchanot.fr/bchanot/config/raw/branch/master/remote-instal | `vim/vimrc` | Vim config: pathogen, molokai, syntastic (C with `-Wall -Werror -Wextra`), NERDTree, 42-style canonical class generators (`:ClassH`, `:ClassC`). | | `vim/autoload/` | `pathogen.vim` plugin loader (committed). | | `vim/colors/` | `molokai.vim` colorscheme (committed). | +| `gitconfig` | Template of the user-scope `~/.gitconfig`. `@USER@` and `@EMAIL@` are filled at install with the `USER` and `EMAIL` exported by the bashrc (git never expands `$VARS` itself). | | `bash/bashrc-linux` | bashrc for desktop Linux (git-aware prompt + command timer). | | `bash/bashrc-osx` | bashrc for macOS: `bashrc-linux` adapted (Homebrew on `PATH`, BSD `ls -G`, bash 5 clock for the timer, `cc` without `systemd-run`). | | `zsh/zshrc-osx` | zshrc for macOS when zsh is chosen: oh-my-zsh + the same env, aliases and dtach menu as `bashrc-osx`. Loads `~/.zshrc.local` for machine-specific lines. | @@ -64,11 +65,11 @@ What it does: 3. Moves any existing `~/.vim`, `~/.vimrc`, `~/.bashrc`, `~/.Sublivim` to `~/Oldconfig`. 4. Clones the `syntastic` and `nerdtree` vim plugins into `~/.vim/bundle/`. 5. Copies the tracked vim files into `~/.vim` and symlinks `~/.vimrc`. -6. Picks the bashrc by OS: macOS → `bashrc-osx` (falls back to `bashrc-linux` if missing), everything else → `bashrc-linux`. Copies it to `~/.bashrc`. +6. Picks the bashrc by OS: macOS → `bashrc-osx` (falls back to `bashrc-linux` if missing), everything else → `bashrc-linux`. Copies it to `~/.bashrc`. Then renders `gitconfig` into `~/.gitconfig` with that bashrc's `USER` / `EMAIL`. A different existing `~/.gitconfig` is saved as `~/.gitconfig.backup-`; an identical one is left alone. It is the global level only: a repo's own `.git/config` still overrides it. `core.excludesfile` points at `~/.gitignore`, ignored by git when the file does not exist. 7. Installs Python CLIs via `pipx` (`PyMuPDF` → `pymupdf`, `Markdown` → `markdown_py`) — skipped if `pipx` is absent. 8. Copies the `bin/` scripts (`dt`, `dtach-router`, `claude-provider`) into `~/.local/bin`. The dtach session-resume menu ships in the deployed bashrc (both OSes), so every interactive shell offers it — including VS Code Remote-SSH terminals, which are non-login and never read `~/.profile`. The installer also strips any older dtach block left in `~/.profile` so a plain SSH login doesn't prompt twice. 9. On Linux, installs `etc/profile.d/disk-usage-warning.sh` to `/etc/profile.d/` (needs `sudo`) so each login warns when `/` or `/home` cross 85% usage. -10. On Linux, installs **code-server** (VS Code in the browser) via its vendor script — skipped if already present — and enables the `code-server@$USER` systemd service. +10. On Linux, installs **code-server** (VS Code in the browser) via its vendor script — skipped if already present — and enables the `code-server@` systemd service (login from `id -un`: the bashrc overrides `$USER`). 11. On Linux, installs **`ubuntu-desktop-minimal`** (GDM + GNOME Shell, ~1.5 GB): the RDP remote login below hands out a GNOME session, which a bare server install does not have. Then sets up **RDP remote login** via `gnome-remote-desktop` (Wayland-native): installs the daemon + `openssl`, generates a self-signed TLS cert once, and prompts interactively for shared "gate" credentials (skipped when no terminal is attached, or already set). Disables `xrdp` if present; opens UFW port `3389` only when UFW is already active. Finally, when `lspci` sees an NVIDIA GPU, runs `ubuntu-drivers install` to put on the driver the distro recommends for the card (no version pinned; loads at the next reboot). Skipped on machines without an NVIDIA GPU. 12. On Linux, installs the **`cloudpex`** NAS mount helper to `/usr/local/bin` via `cloudpex/install.sh`, which prompts for the NAS host, share name, SMB user, mount point and SMB version and writes them to `/etc/cloudpex.conf` (root, `0600`; an existing config is shown and kept unless you say `n`; skipped when no terminal is attached). Nothing is mounted, no password stored, see [`cloudpex/README.md`](cloudpex/README.md). 13. On Linux, installs the **security baseline**, always, no prompt: **fail2ban** (+ `nftables`) with `etc/fail2ban/jail.d/local.conf` (sshd jail reading the journal, bans the offending IP on every port so the SSH port does not matter, 5 failures in 10 min → 1 h ban, loopback and private LAN ranges never banned); **unattended-upgrades** enabled through `etc/apt/apt.conf.d/20auto-upgrades`; and the **sshd drop-in** `etc/ssh/sshd_config.d/20-hardening.conf` (`PermitRootLogin no`, `MaxAuthTries 3`, `LoginGraceTime 20`), checked with `sshd -t` and removed again if sshd rejects it, then `reload ssh`. Authentication methods, port and user lists are left as they are. @@ -77,7 +78,7 @@ What it does: ### Packages installed (apt) -- **Build / VCS / C dev**: `vim git git-lfs git-filter-repo gitleaks gcc make pkg-config dkms valgrind shellcheck gh` +- **Build / VCS / C dev**: `vim git git-lfs git-filter-repo gitleaks gcc make pkg-config dkms valgrind shellcheck gh git-delta` (`git-delta` = `delta`, the pager set in `gitconfig`) - **Net / security / transport**: `curl gnupg ca-certificates apt-transport-https net-tools openssh-server cifs-utils lftp ftp` - **Shell tooling**: `unzip tree tmux fzf dtach` - **Runtimes**: `nodejs python3-pip pipx php-cli` @@ -99,7 +100,7 @@ The script is re-runnable: each run re-backs up to `~/Oldconfig` (overwriting th The same `./install.sh` detects macOS and replaces `apt-get` with Homebrew. It first asks which login shell you want, **bash** or **zsh** (`[bash]` by default; answer in advance with `MACOS_SHELL=zsh ./install.sh`, and with no terminal attached it picks bash): 1. Installs Homebrew with its official script when `brew` is missing (this also pulls the Xcode Command Line Tools: clang, make, git), then `brew update` + `brew upgrade`. -2. Installs the apt list mapped to formulae: `vim git git-lfs git-filter-repo gitleaks pkgconf shellcheck gh curl gnupg lftp inetutils unzip tree tmux fzf dtach node python pipx php mariadb imagemagick ffmpeg weasyprint poppler qpdf webp libavif bash`. Brew's `php` already ships gd, mbstring, xml, intl, curl and mysql. +2. Installs the apt list mapped to formulae: `vim git git-lfs git-filter-repo gitleaks pkgconf shellcheck gh git-delta curl gnupg lftp inetutils unzip tree tmux fzf dtach node python pipx php mariadb imagemagick ffmpeg weasyprint poppler qpdf webp libavif bash`. Brew's `php` already ships gd, mbstring, xml, intl, curl and mysql. 3. Docker: `colima` (the Linux VM) + `docker docker-compose docker-buildx`. Writes `~/.docker/config.json` with `cliPluginsExtraDirs` so `docker compose` works, only when that file does not exist yet (otherwise prints the line to add). 4. Starts `colima`, `code-server` and `mariadb` as `brew services` (the `systemctl enable --now` equivalent), skipping any already started. 5. Deploys `bashrc-osx`, then appends one line to `~/.bash_profile` that sources `~/.bashrc`: macOS terminals open login shells, which never read `~/.bashrc` on their own. Done for both choices, so `bash` stays usable. diff --git a/bash/bashrc-linux b/bash/bashrc-linux index eb20b2e..8e916f6 100644 --- a/bash/bashrc-linux +++ b/bash/bashrc-linux @@ -25,9 +25,9 @@ fi #export LANG=en_US.UTF-8 -# Used for vim header -export VIUSER=bchanot -export VIMAIL=bchanot@gmail.fr +# Identity for the vim header; install.sh also writes it into ~/.gitconfig +export USER=bchanot +export EMAIL=bchanot@gmail.fr ## Activate and custom bash completion #bind 'TAB:menu-complete' diff --git a/bash/bashrc-osx b/bash/bashrc-osx index 3695316..a5d98aa 100644 --- a/bash/bashrc-osx +++ b/bash/bashrc-osx @@ -35,9 +35,9 @@ fi #export LANG=en_US.UTF-8 -# Used for vim header -export VIUSER=bchanot -export VIMAIL=bchanot@gmail.fr +# Identity for the vim header; install.sh also writes it into ~/.gitconfig +export USER=bchanot +export EMAIL=bchanot@gmail.fr ## Activate and custom bash completion #bind 'TAB:menu-complete' diff --git a/gitconfig b/gitconfig new file mode 100644 index 0000000..e2ec94b --- /dev/null +++ b/gitconfig @@ -0,0 +1,31 @@ +# Template for the user-scope ~/.gitconfig, rendered by install.sh. +# Git never expands $VARS: @USER@ and @EMAIL@ are replaced at install +# time with the USER and EMAIL exported by the deployed bashrc. +# A repo .git/config still overrides these values for that repo. +[user] + name = @USER@ + email = @EMAIL@ +[push] + default = current +[color] + ui = auto +[pull] + rebase = true +[core] + editor = vim + pager = delta + excludesfile = ~/.gitignore +[advice] + detachedHead = false +[merge] + tool = vimdiff + conflictStyle = zdiff3 +[pager] + branch = false +[url "git@salsa.debian.org:installer-team/"] + pushInsteadOf = https://salsa.debian.org/installer-team/ +[interactive] + diffFilter = delta --color-only +[delta] + navigate = true # use n and N to move between diff sections + dark = true # or light = true, or omit for auto-detection diff --git a/install.sh b/install.sh index b1e486c..408058e 100755 --- a/install.sh +++ b/install.sh @@ -303,7 +303,7 @@ install_brew_packages() { brew update brew upgrade brew install \ - vim git git-lfs git-filter-repo gitleaks pkgconf shellcheck gh \ + vim git git-lfs git-filter-repo gitleaks pkgconf shellcheck gh git-delta \ curl gnupg lftp inetutils \ unzip tree tmux fzf dtach \ node python pipx php \ @@ -364,7 +364,8 @@ set_login_shell() { if ! grep -qxF "$target" /etc/shells; then echo "$target" | sudo tee -a /etc/shells >/dev/null fi - current="$(dscl . -read "/Users/$USER" UserShell | awk '{ print $2 }')" + # id -un, not $USER: the deployed rc sets USER to the git/vim identity. + current="$(dscl . -read "/Users/$(id -un)" UserShell | awk '{ print $2 }')" if [ "$current" = "$target" ]; then echo "Login shell already $target — skipping" return 0 @@ -429,6 +430,50 @@ wire_bash_profile() { "$line" >> "$profile" } +# Value of `export NAME=value` ($1) in the rc file $2, quotes stripped. The last +# match wins, as when the shell sources it. Empty when absent. +rc_export_value() { + sed -n "s/^export $1=//p" "$2" | tail -n 1 | tr -d "\"'" +} + +# Print the repo gitconfig template with @USER@ and @EMAIL@ replaced by $1 and +# $2. Bash substitution, so the values need no sed escaping. +render_gitconfig() { + local name="$1" email="$2" line + while IFS= read -r line || [ -n "$line" ]; do + line="${line//@USER@/$name}" + line="${line//@EMAIL@/$email}" + printf '%s\n' "$line" + done < "$SCRIPT_DIR/gitconfig" +} + +# Install the user-scope ~/.gitconfig (a repo's own .git/config still wins). +# The identity is read from the USER/EMAIL exports of the deployed rc ($1), so +# git and the shell agree. A ~/.gitconfig that differs is kept as +# ~/.gitconfig.backup-, outside ~/Oldconfig which every run wipes. +# Idempotent: an identical ~/.gitconfig is left alone. +deploy_gitconfig() { + local rc="$1" name email rendered backup + name="$(rc_export_value USER "$rc")" + email="$(rc_export_value EMAIL "$rc")" + if [ -z "$name" ] || [ -z "$email" ]; then + echo "USER/EMAIL not exported by $rc — skipping ~/.gitconfig" >&2 + return 0 + fi + rendered="$(render_gitconfig "$name" "$email")" + if printf '%s\n' "$rendered" | cmp -s - "$HOME/.gitconfig"; then + echo "$HOME/.gitconfig already up to date — skipping" + return 0 + fi + if [ -e "$HOME/.gitconfig" ]; then + backup="$HOME/.gitconfig.backup-$(date +%Y%m%d-%H%M%S)" + echo "Saving the current ~/.gitconfig to $backup" + mv "$HOME/.gitconfig" "$backup" + fi + echo "Deploying gitconfig to ~/.gitconfig ($name <$email>)" + printf '%s\n' "$rendered" > "$HOME/.gitconfig" +} + # What the Linux install sets up that this macOS run did not, and why. print_macos_gaps() { cat <<'EOF' @@ -463,11 +508,12 @@ if command -v apt-get >/dev/null 2>&1; then sudo apt-get update sudo apt-get upgrade -y - # Build + version control + C dev tooling (gitleaks backs the pre-commit hook). + # Build + version control + C dev tooling (gitleaks backs the pre-commit hook, + # git-delta provides `delta`, the pager set in gitconfig). # Web stack: MariaDB + PHP modules for local WordPress/LAMP work; the php-* metapackages # follow the distro's PHP version instead of pinning php8.x-*. sudo apt-get install -y \ - vim git git-lfs git-filter-repo gitleaks gcc make pkg-config dkms valgrind shellcheck \ + vim git git-lfs git-filter-repo gitleaks gcc make pkg-config dkms valgrind shellcheck git-delta \ curl gnupg ca-certificates apt-transport-https \ unzip tree tmux fzf dtach net-tools \ openssh-server cifs-utils lftp ftp \ @@ -483,7 +529,8 @@ if command -v apt-get >/dev/null 2>&1; then if ! command -v code-server >/dev/null 2>&1; then curl -fsSL https://code-server.dev/install.sh | sh fi - sudo systemctl enable --now "code-server@$USER" + # id -un, not $USER: the deployed bashrc sets USER to the git/vim identity. + sudo systemctl enable --now "code-server@$(id -un)" # GNOME desktop (GDM + Shell): the RDP remote login below needs a GNOME session # to hand out; a bare server install has none. Ubuntu-only metapackage. @@ -558,6 +605,9 @@ fi echo "Deploying $bashrc" cp "$SCRIPT_DIR/$bashrc" "$HOME/.bashrc" +# User-scope git config, identity taken from the bashrc just deployed. +deploy_gitconfig "$SCRIPT_DIR/$bashrc" + # Python CLIs via pipx (run as the user, never sudo). Skipped if pipx is absent. if command -v pipx >/dev/null 2>&1; then echo "Installing pipx CLIs (PyMuPDF -> pymupdf, Markdown -> markdown_py)" diff --git a/zsh/zshrc-osx b/zsh/zshrc-osx index 10d2cc9..08a1fd1 100644 --- a/zsh/zshrc-osx +++ b/zsh/zshrc-osx @@ -41,9 +41,9 @@ else SAVEHIST=10000000 fi -# Used for vim header -export VIUSER=bchanot -export VIMAIL=bchanot@gmail.fr +# Identity for the vim header; install.sh also writes it into ~/.gitconfig +export USER=bchanot +export EMAIL=bchanot@gmail.fr # claude-dans-dtach : creer une session (claude tournant dans dtach, detache via Ctrl-\). # Usage : cd ~/projets/seo && cc seo -> session nommee "seo".