From 1b60a9364d20da11ad9a3a1bc6b4196a5192d77d Mon Sep 17 00:00:00 2001 From: dissimulo Date: Wed, 6 May 2026 10:06:08 +0000 Subject: [PATCH] Add portable Claude Code config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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./ before being replaced. --- .claude/.gitignore | 33 +++++++++++++ .claude/settings.json | 110 ++++++++++++++++++++++++++++++++++++++++++ install.sh | 2 +- 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 .claude/.gitignore create mode 100644 .claude/settings.json diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 00000000..f2a6ba9e --- /dev/null +++ b/.claude/.gitignore @@ -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/ diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..be9fb41e --- /dev/null +++ b/.claude/settings.json @@ -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 +} diff --git a/install.sh b/install.sh index b8c485ee..892a5856 100755 --- a/install.sh +++ b/install.sh @@ -36,7 +36,7 @@ PERSONAL_FILES=( # untouched. This deliberately avoids wholesale-replacing $HOME/.local etc., # which would sweep away unrelated user data (fonts, app state, ...). PERSONAL_DIRS=( - .fonts .irssi .nano .themes .local .mplayer + .fonts .irssi .nano .themes .local .mplayer .claude ) have() { command -v "$1" >/dev/null 2>&1; }