Add portable Claude Code config

Saves ~/.claude/settings.json (user-level prefs + Bash/Read permissions
allowlist) so the same Claude Code config can be replicated on other
hosts via install.sh.

What gets deployed:
- .claude/settings.json — theme, verbose, defaultMode, skipAutoPermissionPrompt
  and the cumulative "approve once" allowlist for common ops (systemctl,
  iptables, journalctl, git, curl, etc.). Server-specific allow entries
  (e.g. /home/mastodon/* paths) are dead weight elsewhere but harmless.
- .claude/.gitignore — explicit deny-list so a future `git add .claude`
  doesn't accidentally pull in credentials, session logs, project
  memories, file-history, telemetry, caches, or settings.local.json.

What is NOT tracked (by design):
- .credentials.json (auth)
- history.jsonl, sessions/, projects/ (chat data, project memories)
- settings.local.json (per-machine overrides — by Claude Code convention)
- file-history/, plans/, paste-cache/, shell-snapshots/, session-env/,
  telemetry/, downloads/, cache/, backups/, mcp-needs-auth-cache.json
- plugins/ (marketplace install paths are absolute and host-specific)

deploy_dir's merge semantics mean: on a host that already has
~/.claude/.credentials.json or ~/.claude/projects/, those stay untouched
because the repo doesn't track them. Only the files we explicitly
include get installed.

Existing settings.json on the target host is moved aside to
~/.drunkendotfiles.bak.<timestamp>/ before being replaced.
This commit is contained in:
2026-05-06 10:06:08 +00:00
parent 5208e9a5bb
commit 1b60a9364d
3 changed files with 144 additions and 1 deletions

33
.claude/.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Never track Claude Code per-machine state, secrets, or chat data.
#
# settings.json IS tracked (portable user prefs + permissions allowlist).
# Everything else under ~/.claude/ stays per-host: credentials, session
# logs, project memories, file-edit history, telemetry, caches.
# Auth — must NEVER be tracked
.credentials.json
# Per-machine settings overrides (by Claude Code convention)
settings.local.json
# Chat and session data
history.jsonl
sessions/
projects/
file-history/
plans/
# Caches and ephemeral state
cache/
downloads/
paste-cache/
shell-snapshots/
session-env/
backups/
telemetry/
mcp-needs-auth-cache.json
# Plugin marketplace state — known_marketplaces.json IS portable, but the
# resolved-plugin caches and the local blocklist are per-machine.
plugins/blocklist.json
plugins/marketplaces/

110
.claude/settings.json Normal file
View File

@@ -0,0 +1,110 @@
{
"permissions": {
"allow": [
"Bash(tmux source-file:*)",
"Bash(git config:*)",
"Read(//home/mastodon/live/**)",
"Bash(sed -i '/^$/N;/^\\\\n# Facebook Webhook/,/^MASTODON_BOT_ACCESS_TOKEN=.*/{ /# Facebook Webhook/d; /FACEBOOK_VERIFY_TOKEN/d; /FACEBOOK_APP_SECRET/d; /FACEBOOK_PAGE_ACCESS_TOKEN/d; /MASTODON_BOT_ACCESS_TOKEN/d }' .env.production)",
"Bash(head -10 grep -rn \"StatusReactions\\\\|status_reactions\" /home/mastodon/live/app/javascript/mastodon/features/)",
"Bash(ls -lt /home/mastodon/live/public/packs/status_quoted-*.js)",
"Bash(python3:*)",
"Bash(iptables:*)",
"Bash(ip6tables:*)",
"Bash(ufw status:*)",
"Bash(firewall-cmd:*)",
"Bash(systemctl status:*)",
"Bash(sysctl -a)",
"Bash(sysctl:*)",
"Bash(apt list:*)",
"Bash(dpkg:*)",
"Bash(systemctl list-units:*)",
"Bash(fail2ban-client status:*)",
"Bash(aa-status)",
"Bash(getenforce)",
"Bash(crontab:*)",
"Bash(mount)",
"Bash(netstat -tuln)",
"Bash(systemctl is-enabled:*)",
"Bash(usermod -s /usr/sbin/nologin mastodon)",
"Bash(usermod -s /usr/sbin/nologin postgres)",
"Bash(systemctl daemon-reload:*)",
"Bash(systemctl restart:*)",
"Bash(apt-get upgrade:*)",
"Bash(curl -sI https://auto.signers.online/webhook/ -o /dev/null -w \"%{http_code}\")",
"Bash(curl -sI https://auto.signers.online/ -o /dev/null -w \"%{http_code}\")",
"Bash(openssl x509:*)",
"Bash(systemctl list-timers:*)",
"Bash(find:*)",
"Bash(grep -v \"^$\")",
"Bash(du -sh /var/log/*)",
"Bash(lsmod)",
"Bash(xargs ls:*)",
"Bash(last:*)",
"Bash(netstat -tlnp)",
"Bash(systemctl cat:*)",
"Bash(ls:*)",
"Bash(chmod 600 /home/mastodon/live/.env.development /home/mastodon/live/.env.test /home/mastodon/live/.env.vagrant /home/mastodon/live/.env.production)",
"Bash(openssl rand:*)",
"Bash(nginx:*)",
"Bash(redis-cli:*)",
"Bash(curl -sI https://signers.online/ -o /dev/null -w \"%{http_code}\")",
"Bash(systemctl is-active:*)",
"Bash(curl -sI https://auto.signers.online/)",
"Bash(apt-get install:*)",
"Bash(rkhunter --update)",
"Bash(rkhunter --propupd)",
"Bash(rkhunter:*)",
"Bash(journalctl -u mastodon-web --since \"5 min ago\" --no-pager)",
"Bash(journalctl -u mastodon-web --since \"1 min ago\" --no-pager -l)",
"Bash(chown mastodon:mastodon /home/mastodon/live/.env.production /home/mastodon/live/.env.development /home/mastodon/live/.env.test /home/mastodon/live/.env.vagrant)",
"Bash(aideinit)",
"Bash(curl -kI https://signers.online/nonexistent-path)",
"Bash(dig:*)",
"Bash(openssl s_client -connect signers.online:443 -servername signers.online)",
"Bash(ulimit:*)",
"Bash(systemctl:*)",
"Bash(cat:*)",
"Bash(curl -sI https://signers.online/assets/test-nonexistent.js)",
"Bash(npm --version)",
"Bash(ruby --version)",
"Bash(getent passwd:*)",
"Bash(sqlite3:*)",
"Bash(curl -sI https://signers.online/ --connect-timeout 5 -o /dev/null -w \"http_code:%{http_code} time:%{time_total}\")",
"Bash(fail2ban-client set:*)",
"Bash(chmod 755 /usr/local/bin/fail2ban-ignoreip)",
"Bash(/usr/local/bin/fail2ban-ignoreip 76.95.82.63)",
"Bash(git -C /home/mastodon/live status -s)",
"Bash(git -C /home/mastodon/live diff --stat)",
"Bash(git:*)",
"Bash(curl:*)",
"Bash(nslookup signers.live)",
"Bash(journalctl -u n8n --since \"1 min ago\" --no-pager)",
"Bash(journalctl -u n8n --since \"10 sec ago\" --no-pager)",
"Bash(journalctl -u n8n --since \"30 sec ago\" --no-pager)",
"Bash(sudo:*)",
"Bash(journalctl -u mastodon-web --since \"1 hour ago\" --no-pager)",
"Bash(journalctl -u mastodon-streaming --since \"1 hour ago\" --no-pager)",
"Bash(chown mastodon:mastodon *)",
"Bash(journalctl -u mastodon-web -u mastodon-sidekiq --since \"10 minutes ago\")",
"Bash(journalctl -u mastodon-web --since \"10 minutes ago\")",
"Bash(journalctl -u nginx --since \"1 hour ago\")",
"Bash(zcat -f /var/log/nginx/access.log.1 /var/log/nginx/access.log)",
"Bash(zcat -f /var/log/nginx/access.log*)",
"Read(//home/**)",
"Read(//opt/**)",
"Bash(journalctl -u windmill --since \"2 hours ago\")",
"Read(//etc/nginx/sites-enabled/**)",
"Read(//etc/nginx/sites-available/**)",
"Bash(journalctl -u windmill --since \"1 day ago\")",
"Bash(journalctl -u windmill --since \"2026-04-10\" --until \"2026-04-14\")",
"Bash(journalctl -u windmill -n 100000)",
"Bash(/usr/local/bin/windmill --version)",
"Bash(journalctl -u windmill -n 500)",
"Bash(grep -l 'root ' /etc/nginx/sites-available/*)"
],
"defaultMode": "auto"
},
"theme": "dark",
"verbose": true,
"skipAutoPermissionPrompt": true
}

View File

@@ -36,7 +36,7 @@ PERSONAL_FILES=(
# untouched. This deliberately avoids wholesale-replacing $HOME/.local etc., # untouched. This deliberately avoids wholesale-replacing $HOME/.local etc.,
# which would sweep away unrelated user data (fonts, app state, ...). # which would sweep away unrelated user data (fonts, app state, ...).
PERSONAL_DIRS=( PERSONAL_DIRS=(
.fonts .irssi .nano .themes .local .mplayer .fonts .irssi .nano .themes .local .mplayer .claude
) )
have() { command -v "$1" >/dev/null 2>&1; } have() { command -v "$1" >/dev/null 2>&1; }