100 Commits
Author SHA1 Message Date
branderson cd06db0274 Merge pull request 'Scroll copy mode 4 lines per mouse wheel tick instead of half a page' (#8) from tmux-wheel-scroll-4 into master
Reviewed-on: #8
Reviewed-by: branderson <brad@branderson.io>
2026-09-02 18:58:20 -07:00
branderson eacaebfdef Scroll copy mode 4 lines per mouse wheel tick instead of half a page 2026-09-02 18:32:22 -07:00
branderson b0a2f0b132 Merge pull request 'Fix basedpyright install failure on Debian' (#7) from nvim-basedpyright-venv into master
Reviewed-on: #7
Reviewed-by: branderson <brad@branderson.io>
2026-08-17 14:42:55 -07:00
branderson 237a56fb6c Fix basedpyright install failure on Debian
Debian ships venv's ensurepip in a separate python3-venv package. Without
it, `python3 -m venv` exits 1, so mason cannot build the venv it installs
basedpyright into, and mason-lspconfig reports a failed install on every
startup.

Add python3-venv to the apt package list, and guard the basedpyright entry
in ensure_installed the same way the npm-sourced servers are already
guarded, so a machine missing it gets one warning instead of a failed
install every time.
2026-08-17 14:36:39 -07:00
branderson 7849b91f06 Merge pull request 'Fix multi-line paste corruption in nested tmux on 3.5/3.5a' (#6) from tmux-extended-keys-paste into master
Reviewed-on: #6
Reviewed-by: branderson <brad@branderson.io>
2026-08-17 14:27:55 -07:00
branderson 123c68dbae Fix multi-line paste corruption in nested tmux on 3.5/3.5a
Pasting multi-line text into a tmux session reached over ssh from inside
another tmux replaced every newline with a literal CSI 27;5;106~, i.e.
C-j -- the pasted newline -- rendered in modifyOtherKeys form rather than
sent as a newline.

tmux 3.5 rewrote extended-key handling and encodes keys inside a
bracketed paste; the layer further in passes those bytes through verbatim
instead of decoding them, so the application receives the escape as text.
A hop only corrupts when whatever sits downstream of it has asked for
modifyOtherKeys mode 2. A nested tmux always asks, because of the
"*:extkeys" terminal-feature above it; an application running directly
under screen-256color generally does not. That is why one tmux in the
chain was fine and two were not.

Confirmed by building 3.2a, 3.3a, 3.4, 3.5a, 3.6b and 3.7c and driving a
real bracketed paste through a pty into every outer/inner combination:
only 3.5 and 3.5a corrupt, and only on the requesting hop. Fixed upstream
in 3.6, so upgrading is the real answer where it is available.

For hosts pinned to a distro tmux, disable extended keys on exactly those
two versions. Nothing narrower works: extended-keys-format csi-u only
changes which escape lands in the text, and withholding the extkeys
terminal-feature stops a tmux asking its own parent for extended keys
without stopping it encoding them for its pane. With the guard in place
all sixteen version combinations paste correctly, and extended keys stay
on everywhere except 3.5/3.5a.

The guard tests the version with an awk END block rather than a bare
exit, so that empty input -- a tmux -V that failed for any reason --
leaves extended keys on instead of silently disabling them.

Also corrects the comment that attributed this to a Claude Code paste
tokenizer bug. It reproduces just as readily pasting into neovim.
2026-08-17 14:20:42 -07:00
branderson 86d19db1e9 Merge pull request 'Open Claude Code in its own tab on dejima auto-start' (#5) from nvim-claude-tab into master
Reviewed-on: #5
Reviewed-by: branderson <brad@branderson.io>
2026-08-10 14:14:56 -07:00
branderson 068dd8fe25 Open Claude Code in its own tab on dejima auto-start
The auto-start left the Claude terminal only as a narrow right split. Follow it with a tab split so the same terminal buffer also gets a full-screen tab, while the original tab keeps the side-by-side layout with the editor.
2026-08-10 14:11:06 -07:00
branderson 1b9b98c424 Update gitea utils 2026-08-03 20:24:34 -07:00
branderson fe27e8178e Merge pull request 'Add tmux-fzf for session/window/pane navigation' (#4) from tmux-fzf-navigation into master
Reviewed-on: #4
2026-08-02 23:30:30 -07:00
branderson 3cbe6b31b2 Add tmux-fzf for session/window/pane navigation
Many concurrent sessions with many panes each make it hard to track what is
running where, and moving a pane between windows means join-pane with a
hand-typed target, so it rarely gets done.

tmux-fzf gives a fuzzy-searchable list of every session, window and pane with
a live preview of each, and implements break/join/swap as pane actions --
which is the pane-movement gap. fzf is already in apt.global, arch.global and
brew.global, so no new packages are needed.

Checked out as a submodule rather than left for TPM to clone on first
prefix+I: ~/.tmux symlinks to config/tmux, so a TPM-installed plugin lands
inside this repo as untracked files. The submodule pins a version and
installs via the existing "git submodule update --init". TPM still sources it
from the @plugin line and can still update it.

prefix+F is the plugin's own top-level menu. prefix+P/W/S skip straight to
the pane, window and session panels. TMUX_FZF_CLIENT pins each action to the
client that opened it, as the plugin's own binding does -- run-shell has no
client context, so tmux otherwise targets the most recently active client,
which is wrong when a second client is attached to the same session.

No env-var configuration was needed: preview is already on by default, and
the TMUX_FZF_* variables are read from the environment only, so the README's
suggestion to set them in .tmux.conf is not valid tmux syntax.

Tested against tmux 3.5a and fzf 0.38.0: config parses with no errors, all
three bindings register, existing bindings are unaffected, TPM sources the
plugin and binds prefix+F, the three scripts pass bash -n, and the plugin's
fzf-version detection resolves to fzf-tmux with preview enabled. The
interactive picker itself needs a real terminal and is left to manual check.
2026-08-02 23:07:57 -07:00
branderson bd8cb97548 Don't leave insert mode after scroll in Claude Code nvim plugin 2026-07-28 18:19:15 -07:00
branderson a53f854095 Various fixes for claude-sandbox -> dejima 2026-07-27 16:45:17 -07:00
branderson 889e51f10d Add claude-code statusline 2026-07-24 15:23:40 -07:00
branderson 55a5d7ab01 Add CLAUDE.md and installer logic for if it's absent 2026-07-24 15:12:03 -07:00
branderson 99634aadc1 Fix terminal mode inconsistent behavior in nvim 2026-07-24 14:47:15 -07:00
branderson 792e2a1940 nvim: guard clipboard-copy integration on executable(), not assumed present
vim.g.clipboard pointed unconditionally at ~/dotfiles/bin/clipboard-copy,
which isn't there on every machine this config runs on now - claude-sandbox
mounts in only the nvim config directory itself, not the rest of dotfiles.
Without the guard, every yank errored inside the sandbox. Same
gracefully-degrade approach the colorscheme fallback already uses elsewhere
in this config.
2026-07-23 18:54:30 -07:00
branderson dc03a79f70 Migrate claude-sandbox out of dotfiles 2026-07-23 13:00:47 -07:00
branderson b360904421 Merge pull request 'claude-sandbox: host-matching container user + dedicated headless SSH agent' (#3) from worktree-claude-sandbox-ssh-and-plugin-mounts into master
Reviewed-on: #3
2026-07-22 13:05:37 -07:00
branderson 621bc28f7a claude-sandbox: document SANDBOX_SSH_KEY in Security notes; warn on default-looking personal keys
Second-round review follow-up. Security notes was written as the
canonical list of residual risks but never mentioned the new
SANDBOX_SSH_KEY mechanism, which has the same "signs for any repo it can
reach" shape as the forwarded agent (already documented) - add a bullet
covering it and the /root-only mount.

Also nothing enforced that SANDBOX_SSH_KEY actually be a dedicated
low-privilege key rather than a real personal identity, which is the
whole basis for accepting it as passphrase-less and left loaded for an
entire unattended session. bin/claude-sandbox now warns (non-fatal) if
it looks like a default personal keyfile (id_rsa, id_ed25519, etc).
2026-07-22 13:03:30 -07:00
branderson 5fef8dcf90 claude-sandbox: mount the dedicated SSH key source under /root, not $HOME
The headless-SSH-key feature claimed the plaintext key was "deleted
immediately after being loaded into the agent, so nothing readable is
left on disk" - but entrypoint.sh only deleted the copy it made; the
original bind-mounted source at $SANDBOX_HOME/.ssh-sandbox-key-src stayed
mounted, readable, for the container's whole life. Combined with the
sandbox user's uid now matching the host user's, that file was directly
readable by the very process the sandbox exists to constrain, letting a
compromised session exfiltrate the raw key instead of being limited to
signing through the local agent socket.

Move the mount to /root/.ssh-sandbox-key-src (root:root, 700 by default)
so only entrypoint.sh's root-run startup code can ever reach it; the
sandbox user has no path to that directory at all. Update the matching
static/runtime tests (the runtime one now asserts the sandbox user
actually gets permission denied reading the source) and correct the
README's description of the mechanism.
2026-07-22 12:36:40 -07:00
branderson 6b833bd222 claude-sandbox: support a dedicated in-container SSH agent for headless git push 2026-07-21 23:59:52 -07:00
branderson 075f4b2ba4 claude-sandbox: build the container image tagged and configured for the real host user, fixing plugin/skill resolution 2026-07-21 21:42:57 -07:00
branderson 46f57624e6 claude-sandbox: parametrize the container user, defaulting to today's node/1000/1000//home/node 2026-07-21 21:17:41 -07:00
branderson df3399b0d3 Add Playwright/Chromium for testing frontend changes
Installs playwright + a matching Chromium build at image build time
(system deps as root via install-deps, npm package + browser binary as
node so they land in node's own npm prefix/cache), so a session never
needs network access for either. NODE_PATH makes require("playwright")
work from any script, not just one with its own local node_modules -
global npm install alone only puts the CLI on PATH.

Testing against a project's own local dev server needs no firewall
changes: localhost traffic never goes through the egress firewall.
Verified the firewall does apply to the browser's own traffic for
anything else, the same as curl/git - a non-allowlisted domain gets
blocked from inside Playwright too, not silently bypassed, while an
allowlisted one still works.

Chromium's own internal sandbox is documented honestly rather than
assumed either way: it launches without --no-sandbox and without error,
but its setuid helper isn't actually setuid here, and this host has the
same unprivileged-userns restriction that blocks bubblewrap - so it's
likely not providing real isolation, just not hard-failing about it the
way bubblewrap does. Not treated as a security boundary regardless: this
container is already the boundary, and a compromised session already has
full node-level bash access that a Chromium exploit couldn't exceed.

Test suite grew to 29: Playwright is installed and require()-able, it
actually launches and renders (not just installs), and the firewall
applies to its traffic.
2026-07-20 17:32:07 -07:00
branderson 7267e1ca77 Carry git identity into the sandbox; warn on an empty ssh-agent
Git commits inside the sandbox need a user.name/user.email. Carries over
a throwaway copy of the host's ~/.gitconfig, generated by bin/claude-sandbox
and mounted as its own directory (not the real file, and not a single-file
bind mount at ~/.gitconfig) with GIT_CONFIG_GLOBAL pointing git at the file
inside it.

That took two iterations to get right, both caught by actually testing gh
auth setup-git (the thing that makes GitHub read-only from in here) rather
than just checking the identity resolved:
- Mounting the real file read-only broke gh auth setup-git outright, which
  needs to *write* a credential-helper entry into the global gitconfig
  ("could not write config file: Device or resource busy").
- Mounting a read-write *copy* of the file directly at ~/.gitconfig hit the
  exact same error - a single-file bind mount can't have its backing inode
  replaced, and git config writes via the usual write-temp-then-rename
  pattern. Only a directory mount, with git pointed at a file inside it,
  actually supports that.
Verified end to end this time: gh auth setup-git completes cleanly, the
credential-helper entries land in the sandbox's copy, user.name/email
resolve correctly, and the real host ~/.gitconfig is untouched throughout.

Also has bin/claude-sandbox warn (non-fatally) at launch if ssh-agent has
zero identities loaded - the forwarded socket still connects fine either
way, but git-over-ssh silently fails until a key's been ssh-add'ed, which
otherwise only surfaces confusingly deep into a session as a failed push.
Confirmed the forwarding mechanism itself works correctly by testing with
a throwaway key - the reported failure was the host agent having no
identities loaded, not a bug in the forwarding.

Test suite grew to 26 with a check for the gitconfig mount shape.
2026-07-20 01:03:20 -07:00
branderson 7249da8fc3 Add a Go toolchain to the sandbox
Installs the latest stable Go release, fetched and verified the same way
as gosu: go.dev's own release JSON gives both the download URL and a
SHA256 per file, checked before extracting since this runs as root.

Also allowlists proxy.golang.org and sum.golang.org (Go's module proxy and
checksum database) - go build/go mod download need them reachable at
runtime, same as registry.npmjs.org/pypi.org for npm/pip.

Verified end to end: go version, and a real `go mod tidy` + `go run`
against an actual module (rsc.io/quote, pulling in transitive
dependencies) through the real firewall - not just that the binary
installs. All 25 existing tests still pass.
2026-07-20 00:19:27 -07:00
branderson 0c3c3aacad Disable Claude Code's internal sandbox in claude-sandbox; fix worktree location
Claude Code's own per-command sandboxing (~/.claude/settings.json's
"sandbox" block) is redundant on top of this container - which is already
a real, working sandbox on its own - and it couldn't even function here
anyway (bwrap can't create a namespace on this host; even CAP_SYS_ADMIN
only got it to a mount-propagation error, not further). Rather than
leaving it half-broken, disable it properly: settings.json is mounted
read-only specifically so a session can't tamper with it, so it can't be
patched from inside - bin/claude-sandbox now generates a sandbox-specific
copy (real settings.json with sandbox.enabled forced to false) on the
host and mounts that instead. bubblewrap/socat (added for this last
commit, never actually working) are removed - nothing needs them now.

Also fixes a real bug found in the same session: sandbox-context.sh told
agents to worktree via `git worktree add ../<branch>`, a sibling of the
mounted project directory. That path isn't backed by real host storage -
only the exact bind-mounted directory is - so a worktree created there
(observed landing under /home/node/<branch> in practice) silently
disappears, along with whatever work went into it, when the container
exits. Now instructs `.worktrees/<branch>` under the project root instead,
plus a .git/info/exclude entry so it doesn't clutter the user's real
checkout's git status.

Test suite extended to 25: sandbox.enabled=false generation, no
bubblewrap/socat, and no worktree instruction pointing outside the mounted
project. All pass.
2026-07-20 00:06:15 -07:00
branderson 0b7857eb3f Mount ~/specs read-write; add bubblewrap/socat (partial fix)
~/.claude/CLAUDE.md (already carried into the container) tells agents to
write specs to ~/specs/ instead of a project-local path - that instruction
was already in effect inside the sandbox, but nothing mounted the
directory it points at, so an agent following it would write into a path
that vanishes with the container. Bind-mounted read-write at the fixed
host path ~/specs (auto-created if missing), same pattern as the shared
scratch space but not configurable - it's tied to the CLAUDE.md
convention, not a per-run choice.

Also adds bubblewrap/socat, which Claude Code checks for before offering
its own internal per-command sandboxing and /remote-control. This clears
the "Sandbox disabled: dependencies are missing" warning, but doesn't
actually fix /remote-control: bwrap still can't create a namespace on this
host ("kernel does not allow non-privileged user namespaces"), and even
adding CAP_SYS_ADMIN to the container only got further to a mount-
propagation permission error, not further progress. Making it actually
work would need privilege escalation approaching full --privileged mode,
which would undo most of this session's hardening - not pursued. Kept the
packages anyway since they at least make the startup warning accurate
rather than noise, in case a future decision revisits this tradeoff.

All 22 existing tests still pass.
2026-07-19 23:45:01 -07:00
branderson 832aaef3d8 Add a bats test suite for the sandbox's security invariants
Addresses the "no regression protection" gap called out in Security notes:
everything there was hand-verified at a point in time, with nothing to
catch a future change silently breaking one of those properties again -
which had already happened once (the ipset -exist bug hit twice, on two
different ipsets, before being caught).

entrypoint.sh gets a SANDBOX_TEST_MODE hook: run the real DNS-proxy/
firewall/logging setup exactly as normal, then exec `sleep infinity` as
node instead of nvim (which needs a real tty). Lets the suite attach to a
live, fully-initialized container and assert against it, rather than
reimplementing the setup sequence in the test file where it could drift
out of sync with the real thing.

test/static.bats: fast, no Docker required - sudo/sudoers absence, every
ipset add is duplicate-safe, no docker.sock/host-credential mounts, the
~/.claude read-only overlay lines are present, claude-sandbox-allow's
input validation.

test/runtime.bats: builds the image, boots a container in test mode,
asserts IPv6 is blocked, DNS/IP allowlist enforcement in both directions,
GitHub read-only, no working sudo/iptables/init-firewall.sh as node,
capabilities are actually stripped (not just absent because docker exec
doesn't have them to begin with), ulogd2 runs unprivileged, and the
dynamic-ipset CDN-rotation fix actually re-syncs on a fresh DNS answer.

Container/network names are derived from bats' own BATS_FILE_TMPDIR rather
than $$ (caught via a real failure: bats reruns each file's top-level code
in a fresh subshell per test, so $$ differs across setup_file/tests/
teardown_file, silently pointing them at different, nonexistent
containers - several tests "passed" only because asserting failure against
a missing container also happens to return nonzero). BATS_FILE_TMPDIR is
guaranteed stable across the whole file's run, verified directly before
relying on it.

All 22 tests pass cleanly against the current sandbox.
2026-07-19 22:08:15 -07:00
branderson d7858d9579 Document sandbox threat model and tradeoffs honestly in README
Adds an explicit threat-model framing to Security notes: this reduces
blast radius for a compromised/prompt-injected session, it isn't a hard
adversarial boundary. Names the gaps that weren't written down anywhere -
the mounted project has no write protection (hooks are fast-fail regex
guards, not a security boundary), allowlisting a domain doesn't stop
exfiltration via that service's own request surface, this is container-
level isolation sharing the host kernel rather than VM-level, and none of
this is regression-tested going forward, only hand-verified at a point in
time.

Also fixes a real internal LAN IP (the Loki example) that had ended up in
this public repo as a "for instance" - swapped for a placeholder hostname.
2026-07-19 20:46:52 -07:00
branderson 0f3e2c8362 Add cowsay/fortune, auto-open Claude Code, and a shared scratch volume to sandbox
cowsay/cowthink/fortune install to /usr/games on Debian, not on this image's
PATH by default - added the packages and extended PATH via ENV so nvim's
start-screen header (plugins/ui.lua) can actually find them as non-login
subprocesses. That surfaced a real, pre-existing bug in ui.lua itself
(unrelated to the sandbox): `cowsay -l` wraps cow names across multiple
space-separated lines after a header line, but the code treated each whole
line as a single cow name, so a wrapped line got passed to cowthink -f as
one bogus multi-word argument. Fixed to parse every name from every line.
Verified directly against the real cowsay/fortune installed in the image.

The sandbox now opens claudecode.nvim's terminal on startup (CLAUDE_SANDBOX-
gated, so this doesn't affect normal host nvim use) instead of requiring
<leader>ac - starting a Claude session is the point of being in there.
:ClaudeCode is only defined once the plugin loads (it's lazy-loaded on that
keymap, no `cmd` in its spec), so the autocmd force-loads it first.

Also adds an optional read-write scratch volume (~/shared in the container,
$SANDBOX_SHARE_DIR on the host, auto-created) separate from the project
mount, for passing files back and forth with a sandboxed session without
touching the actual project checkout. Mentioned in sandbox-context.sh so a
sandboxed agent knows it's there.
2026-07-19 19:11:12 -07:00
branderson a86fefc663 Fix firewall init crash on overlapping GitHub CIDR ranges
Same class of bug as 718f6b7, on the newer github-domains ipset that
didn't exist yet at the time of that fix: aggregate -q dedupes overlapping
ranges within each of GitHub meta's web/api/git lists individually, but not
necessarily across all three combined once merged, so ipset add without
-exist could hit a duplicate and error - which set -e turns into the whole
firewall, and thus the whole container, failing to start with no clear
error surfaced to the user. Reproduced against live GitHub meta data
(exactly what was seen), fixed with -exist, and reran the same firewall
init end to end to confirm it now completes.
2026-07-19 18:16:40 -07:00
branderson 5294e72a32 Keep sandbox firewall ipsets fresh and batch Loki log shipping
init-firewall.sh's ipset population was a one-time snapshot at container
start: an already-allowed domain could still start failing to connect
later if its CDN edge rotated to an IP that was never added
(pypi.org/files.pythonhosted.org/GitHub's release-assets CDN all do this),
and allowing a domain like crates.io didn't help traffic that actually
goes to a different hostname under it (index.crates.io/static.crates.io).

dns-proxy.sh's dnsmasq now also uses --ipset= so the resolved IP gets added
on every fresh answer it serves for the life of the session, not just once
at boot - verified this actually repopulates after a real (non-cached)
re-resolution, and that dnsmasq tolerates the target ipset not existing yet
when its config is generated (it runs before init-firewall.sh creates it).

Also batches log-shipper.sh's Loki pushes (up to 20 lines or 2s, whichever
first) instead of one HTTP request per line - a burst of activity (e.g.
npm install resolving/connecting to many hosts quickly) would otherwise
spam Loki with one request per line. Verified via the sandbox's own
connection logging: 15 rapid lines produced 2 requests instead of 15.
2026-07-19 18:09:16 -07:00
branderson 07926638d4 Add ulogd user 2026-07-19 17:58:24 -07:00
branderson 04c1b1e1f4 Add DNS allowlisting, IPv6 block, and network logging to sandbox
DNS resolution was previously unrestricted - only the resulting IP
connections were firewalled - so a compromised session could still
exfiltrate data by encoding it into query names to an attacker-controlled
domain. dns-proxy.sh now runs a local dnsmasq with no fallback resolver;
only allowlisted names (shared with init-firewall.sh via fixed-domains.sh)
get forwarded, everything else is refused outright.

IPv6 is blocked outright via ip6tables DROP-all, since there was no IPv6
equivalent of the IPv4 allowlist.

If LOKI_URL is set, every DNS query and every new outbound connection
attempt (allowed or rejected, via NFLOG + ulogd2) ships to Loki as JSON,
for visibility in the existing Grafana/Prometheus setup.

Also fixes two bugs found along the way:
- init-firewall.sh's script-wide IFS (set for stricter splitting elsewhere)
  silently broke EXTRA_ALLOWED_DOMAINS whenever it held more than one
  space-separated entry, collapsing it into a single bogus token.
- bin/claude-sandbox-allow interpolated the domain argument directly into a
  bash -c string executed via docker exec - a command injection path,
  since sandbox-context.sh tells a sandboxed agent to hand this exact
  command to the user to copy-paste. Fixed with upfront domain validation
  and positional-argument passing instead of string interpolation.
2026-07-19 17:16:55 -07:00
branderson b4d22b97c0 Add quick allowlist for domains in sandbox 2026-07-19 16:26:58 -07:00
branderson 4551dc5a59 Limit push privileges 2026-07-19 16:10:47 -07:00
branderson ca76b46750 Lock down .claude in sandbox 2026-07-19 13:45:35 -07:00
branderson 373655963a Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-19 13:07:03 -07:00
branderson fae504043e Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-19 13:03:55 -07:00
branderson bdb8c8b149 Fix profile scope issue 2026-07-19 13:03:32 -07:00
branderson d76d2f4b3a Fix clipboard OSC 52 fallback: walk past nvim's tty-less embed process
Nvim isn't a single process attached to the terminal: the process that
actually spawns jobs (clipboard-copy included) is a separate `nvim --embed`
core process with no tty of its own (ps shows `?`); the real pty belongs to
a distinct parent UI process. Checking only the immediate parent's tty (as
the previous fix did) found the embed process's `?` and fell through to the
broken /dev/tty path every time in real interactive use, even though it
looked fine in headless testing. Walk up the process tree past any tty-less
ancestors instead.

Verified against the real docker-compose path in actual (non-headless) TUI
mode, with real keystrokes fed through a pty: the OSC 52 sequence now
reaches the terminal with no error, where it previously still failed.
2026-07-19 11:46:31 -07:00
branderson 08a2ae52f1 Fix clipboard OSC 52 write and mason registry access in the sandbox
- clipboard-copy: nvim's job-spawned clipboard provider process doesn't get
  its own controlling terminal, so /dev/tty (ENXIO) fails. Resolve the real
  tty from the parent (nvim)'s recorded tty via ps instead.
- entrypoint.sh: chown the allocated pty to node before dropping privileges.
  A normal login/ssh session always does this; this entrypoint skipped it,
  leaving the tty root-owned so node couldn't write to it directly even
  after resolving the right path.
- init-firewall.sh/README: allow release-assets.githubusercontent.com.
  GitHub release assets (e.g. mason-registry's own index) redirect here,
  a separate CDN from github.com's published IP ranges - without it mason
  can't even look up a package's install spec, regardless of PyPI access.

All three verified against a rebuilt image: OSC 52 write reaches the real
pty with no error, and mason's registry refresh succeeds through the
firewall.
2026-07-19 04:08:14 -07:00
branderson 718f6b7600 Fix firewall init crash on overlapping IPs across allowlisted domains
pypi.org and files.pythonhosted.org share Fastly edge IPs, so the second
domain's ipset add hit a duplicate-element error. Without -exist, that
error plus set -e killed the whole firewall init (and thus the container)
before nvim ever started. Reported by the user as a silent hang after a
successful build.
2026-07-19 03:31:50 -07:00
branderson 18e111b00c Fix sandbox dotfiles path, terminal type, and PyPI access
- Bind-mount dotfiles at node's own $HOME/dotfiles too, not just the
  host-path mount needed for the ~/.claude/hooks symlink: node's $HOME is
  /home/node, which broke clipboard-copy and anything else in this repo
  assuming ~/dotfiles.
- Forward the host's real TERM into the container instead of letting
  Docker's tty allocation default to a generic xterm, which was breaking
  the Ctrl-H/Backspace disambiguation the tmux config sets up.
- Add pypi.org/files.pythonhosted.org to the firewall's default allowlist
  so mason's pip-installed Python tools (ruff, basedpyright, debugpy) can
  actually install; verified with a real venv pip install.
2026-07-19 03:23:11 -07:00
branderson a2be0e5678 Sandbox hardening 2026-07-19 02:43:17 -07:00
branderson b5e18a1b29 Fix mount path 2026-07-19 01:50:38 -07:00
branderson e0ce0e79fc Fix allowlist of domains 2026-07-19 01:33:31 -07:00
branderson bcc0d4ab14 Fix permissions issue 2026-07-19 01:01:41 -07:00
branderson 37e77a4c30 Add claude code container sandbox 2026-07-19 00:30:05 -07:00
branderson 93b93b30b8 Fix Ctrl+J in claude code 2026-07-18 17:22:48 -07:00
branderson 49c638cda8 Re-enable extended keys 2026-07-18 17:15:46 -07:00
branderson efd1ddebd9 Fix corruption in copy-paste over nested tmux 2026-07-18 16:03:19 -07:00
branderson ea5a2b77d4 Fix some stale comments 2026-07-17 16:34:37 -07:00
branderson bd4d579a90 Fix remote tmux copy 2026-07-17 16:02:43 -07:00
branderson 0ba29f8d89 Fix clipboard copy across terminal emulator -> tmux -> SSH -> tmux -> nvim 2026-07-17 15:13:40 -07:00
branderson 25dcfc6802 Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-17 14:05:13 -07:00
branderson a56e04a0aa Make alacritty's highlight copy to system clipboard 2026-07-17 14:05:00 -07:00
branderson 1caa1be1d2 Support <Shift><Enter> through multiple nested tmux's and alacritty 2026-07-17 12:51:53 -07:00
branderson aa71eebf0b Fix nvim errors when editing html 2026-07-16 14:39:28 -07:00
branderson 9987dcacd8 Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-14 21:12:17 -07:00
branderson 58a3512c28 Disable claude-code systemd limits 2026-07-14 21:11:35 -07:00
branderson 7b61fde6e9 Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-14 19:45:30 -07:00
branderson db267226a8 Enable sixel for iTerm2 2026-07-14 18:39:48 -07:00
branderson 521c1b1c4d Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-14 18:08:06 -07:00
branderson a94e283e98 Add sandboxing tools to global install 2026-07-14 18:07:59 -07:00
branderson b26e564672 Use cargo build instead of prebuilt binary for tree-sitter-cli manual install 2026-07-14 14:23:06 -07:00
branderson 1deb25008f Make nvim installation helpers platform-aware and add local install helper for tree-sitter-cli 2026-07-14 14:11:42 -07:00
branderson 9ffe754410 Add claude gruvbox theme, fix nvim errors with custom npm 2026-07-14 13:48:36 -07:00
branderson d0a4cb0db7 Add authorship disclaimer to LLM generated PR summaries 2026-07-13 18:28:08 -07:00
branderson 46ca53e0c0 Prevent runaway claude-code 2026-07-13 18:24:22 -07:00
branderson ddd7f6bf2c Merge pull request 'gitea-pr: token-file auth + skip duplicate PR creation' (#2) from worktree-gitea-pr-review-workflow into master
Reviewed-on: #2
2026-07-12 22:15:01 -07:00
branderson 06e60bda8c Match existing-PR check on base branch too, not just head
The lookup was matching on head.ref alone, so running create with a
different --base for the same branch would silently return an
unrelated PR's URL instead of opening a new one. Resolve base before
the check and filter on both head.ref and base.ref.
2026-07-12 22:14:07 -07:00
branderson 567d0a1718 Skip creating a duplicate PR when one is already open for the branch
gitea-pr create now checks for an existing open PR on the current
branch first and returns its URL instead of creating a new one. Only
matches open PRs by design -- once merged, new commits on the same
branch correctly get a new PR.
2026-07-12 22:14:07 -07:00
branderson eec71b5f82 Add gitea-pr script and PR review workflow (#1)
Adds bin/gitea-pr script for gitea PR workflow.
Reviewed-on: #1
Co-authored-by: Brad Anderson <brad@branderson.io>
Co-committed-by: Brad Anderson <brad@branderson.io>
2026-07-12 18:49:36 -07:00
branderson 4e8c3c6269 Add keybind to fix missing cursor after SSH timeout in tmux 2026-07-11 13:11:14 -07:00
branderson 8badfdb9b8 Fix copy-paste in tmux 2026-07-10 19:10:41 -07:00
branderson 1eb4ee50d8 Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-10 18:36:42 -07:00
branderson 5bf98d5b1d Make ,, work in terminal mode nvim 2026-07-10 18:36:30 -07:00
branderson 90d67c5a4a Fix file modification issue in non-modifiable nvim buffers 2026-07-10 17:16:28 -07:00
branderson ad9a5072d3 Handle shellcheck cleanly in vim 2026-07-10 16:26:09 -07:00
branderson c6c51c81ef Improve <leader>? keybind search 2026-07-10 16:21:34 -07:00
branderson f318cd953f Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-10 16:04:27 -07:00
branderson f75adf42ae Add nvim debugging for python 2026-07-10 16:04:12 -07:00
branderson 192b457c61 Add new nvim deps to apt.global 2026-07-10 15:43:45 -07:00
branderson 0d3260dab1 Fix issues when nvim plugin deps aren't available and make system clipboard more consistent 2026-07-10 15:38:33 -07:00
branderson e8f6eeb8fb Fix a few nvim issues 2026-07-09 22:27:07 -07:00
branderson 3f420bf740 Merge branch 'master' of gitbox.bradina.localdomain:branderson/dotfiles 2026-07-09 21:17:17 -07:00
branderson 30effbb474 Port neovim configs from vimscript to lua 2026-07-09 21:15:47 -07:00
branderson cbda5efd3c Add tmux selection highlight for nested sessions 2026-07-03 17:34:22 -07:00
branderson baf24a5198 Merge branch 'master' of https://gitbox.branderson.io/branderson/dotfiles 2026-07-03 15:26:07 -07:00
branderson 69a3dfed30 Modernize and clean up tmux configs 2026-07-03 15:25:56 -07:00
branderson b650051b81 Add Claude Code plugin to nvim 2026-07-02 13:51:49 -07:00
branderson a6a9b44784 Fix picom.conf deprecated settings 2026-06-01 21:51:59 -07:00
branderson 535b6dc8f9 Add VSCodium config 2026-06-01 21:50:17 -07:00
branderson 99b90050ae Remove picom configuration which produces warnings 2026-03-19 15:05:14 -07:00
branderson 3e40b07e76 Fix Arch linux corrupted keys issues 2026-03-19 13:24:38 -07:00
branderson f849865294 Fix wallpaper updater monitor order to apply wallpapers to correct monitors 2026-01-16 17:49:47 -08:00
branderson b4ee1e28ef Add ideavimrc 2026-01-12 17:10:18 -08:00