In this repo I store config files for my development environment. It helps me to
install quickly all software I need for work and fun.
Feel free to check & alter the configs as you like.
curl -fsSL https://deno.land/install.sh | sh- Linux: zypper (openSUSE), dnf (Fedora/RHEL), apt (Debian/Ubuntu)
This repository includes two main automation scripts:
Installs applications from a unified JSON configuration with cross-platform support.
Configures a complete shell environment with Zsh, Oh My Zsh, Powerlevel10k theme, and custom aliases.
deno task install-apps
### Setup Shell Environment
```bash
deno task install-shell
### Complete Setup (Both Scripts)
```bash
deno task install-allRepo holds dotfiles, home uses symlinks.
# tmux
mv ~/.tmux.conf ~/.tmux.conf.bak-$(date +%Y%m%d%H%M%S)
ln -s ~/dev/dotfiles/.tmux.conf ~/.tmux.conf
# opencode
mv ~/.config/opencode ~/.config/opencode.bak-$(date +%Y%m%d%H%M%S)
ln -s ~/dev/dotfiles/.config/opencode ~/.config/opencode
# nvim
mv ~/.config/nvim ~/.config/nvim.bak-$(date +%Y%m%d%H%M%S)
ln -s ~/dev/dotfiles/.config/nvim ~/.config/nvim
# zsh
mv ~/.zshrc ~/.zshrc.bak-$(date +%Y%m%d%H%M%S)
ln -s ~/dev/dotfiles/.zshrc ~/.zshrc
# p10k
mv ~/.p10k.zsh ~/.p10k.zsh.bak-$(date +%Y%m%d%H%M%S)
ln -s ~/dev/dotfiles/.p10k.zsh ~/.p10k.zshRunning a dozen or more agent sessions at once pushes two kernel and daemon limits that a
single-user desktop never reaches. The files under system/ are the tracked copies. Nothing
installs them automatically β copy them by hand on a new machine.
inotify instances. Every editor, file watcher, dev server and agent session opens its own
inotify instance, and each one counts against fs.inotify.max_user_instances. On Fedora with
Plasma, kde-inotify-survey notices the limit filling up, raises it a little, writes
/etc/sysctl.d/50-kde-inotify-survey-max_user_instances.conf, and pops a notification. It will
keep doing that. system/etc/sysctl.d/90-inotify.conf sets the limit to 1024 once; the 90-
prefix sorts after the KDE file, so this value wins.
sudo cp system/etc/sysctl.d/90-inotify.conf /etc/sysctl.d/90-inotify.conf
sudo sysctl --systemThe watch limit (fs.inotify.max_user_watches) is a separate number and is already generous on
Fedora β check it before assuming it is the one being hit.
/tmp on tmpfs. Fedora mounts /tmp as tmpfs and cleans it at an age of 10 days. Throwaway
Deno caches from CI-emulation runs accumulate far faster than that, and because tmpfs is RAM
they cost memory the whole time. system/etc/tmpfiles.d/tmp.conf shortens the age to 2 days;
the agent instructions tell every run to delete its own cache, and this is the backstop for the
ones that get killed. It keeps the upstream basename on purpose β a drop-in under a different
name would also win, by sort order, but systemd then logs a duplicate-line warning on every
cleanup run.
sudo cp system/etc/tmpfiles.d/tmp.conf /etc/tmpfiles.d/tmp.conf
sudo systemd-tmpfiles --cleanSyncthing and worktrees. Agent worktrees are created under ~/sync/code/worktrees/, inside a
Syncthing folder. Syncthing indexes and watches everything it does not ignore, so without an
ignore line it holds an inotify watch per worktree directory and replicates build output to
every other machine. A worktree is not worth syncing: it is rebuilt from origin on demand, and
its .git file names an absolute path in the main checkout, so the copy is broken anyway.
system/syncthing-code.stignore is the tracked copy of the ignore list for the code folder.
cp system/syncthing-code.stignore ~/sync/code/.stignoreSyncthing re-reads the file on the next scan. To confirm it took effect, open the web UI and rescan the folder, then check that the Syncthing process is holding far fewer inotify watches:
for p in $(pgrep -x syncthing); do echo "$p $(cat /proc/$p/fdinfo/* 2>/dev/null | grep -c '^inotify ')"; doneOne global instruction file, .config/opencode/AGENTS.md, feeds every harness. Same text
everywhere β three harnesses reading three dialects of the rules is how they drift.
| Harness | File read |
|---|---|
| OpenCode | ~/.config/opencode/AGENTS.md |
| DSH | $DSH_HOME/AGENTS.md (default ~/.local/share/dsh/...) |
| Claude Code | ~/.claude/CLAUDE.md |
Claude Code also gets the contents of .claude/ in this repo:
| Tracked here | Lands in | How |
|---|---|---|
.claude/agents, skills |
~/.claude/<same> |
mirrored; *.test.ts skipped |
.claude/settings.json |
~/.claude/settings.json |
merged β tracked keys win, the rest is kept |
Use the sync script β do not symlink manually:
deno task sync-agents-md # dry-run, prints planned actions
deno task sync-agents-md --apply # write to runtime locations
deno task sync-agents-md --check # exit 1 if any target driftedRun --apply from the main checkout, after merge. From a linked worktree --apply writes
only the tracked copies inside that worktree and prints the rest as [WITHHELD]: config from an
unmerged branch β hooks included β must not go live for every session on the machine before it
is reviewed. --apply --from-worktree overrides that, deliberately.
Why copies, not symlinks:
- DSH does not reliably follow a symlink at
$DSH_HOME; a chain breaks silently. - Claude Code skips a symlinked
~/.claude/CLAUDE.mdin some session types, and an app that savessettings.jsonatomically replaces a symlink with a real file. - This repo's path differs per machine, so an absolute
@importcannot be shared either.
Details worth knowing:
- Settings merge. The tracked file owns the keys it names, one level deep inside objects:
permissions.askandhooks.PreToolUseare replaced wholesale, whilepermissions.allow,themeand anything else the app saved are left alone. Machine-specific settings go in~/.claude/settings.jsondirectly β just not under a tracked key. - The merge adds and replaces; it never removes. Deleting a key here does not delete it
from the runtime β the merge only overlays the keys the tracked file still names, so the old
value survives in
~/.claude/settings.jsonand--applyreportsin sync. Droppingpermissions.askfrom the tracked file, for example, leaves the prompts firing on every machine that already had them. Remove such a key from~/.claude/settings.jsonby hand on each machine, in the same change. - Manifest.
~/.claude/.dotfiles-sync.jsonlists what the script wrote. A file deleted here is deleted there on the next--apply; a file the script never wrote is never touched. - Symlinked runtime dirs. Where
~/.config/opencodeor~/.local/share/dshis a symlink into a checkout of this repo, the "runtime" file is a tracked file. The script detects that and skips it β git updates it. deno fmtpicks the wrong config under.config/opencode/. That directory has its ownpackage.json, so Deno treats it as the project root and ignores this repo'sdeno.jsoncwhenever the first path argument is inside it β or, with no path argument, whenever the current directory is. Markdown is then rewrapped at 80 columns instead of left as written, and a synced skill file stops matching its.claude/twin. Pass--config deno.jsonc, or put a path outside.config/opencode/first..claude/does double duty. Claude Code also reads it as project config for sessions opened in this repo. Identical hook handlers from user and project settings run once.
Written, tested, not wired up: settings.json has no hooks key, so nothing runs and the
scripts are not copied to ~/.claude/. A PreToolUse hook on Bash runs before every shell
command, which is more ceremony than the rules it guards are worth right now.
| Hook | Event | Would do |
|---|---|---|
guard-git.ts |
PreToolUse (Bash) |
ask before a commit on main/master and before gh pr merge; deny git worktree add inside a checkout; run gitleaks on unpushed commits and on gh bodies |
worktree.ts |
WorktreeCreate/WorktreeRemove |
put Claude Code's built-in worktrees in the sibling worktrees/<repo>/<type>/<slug> layout instead of <repo>/.claude/worktrees/ |
To enable: copy the hooks block from .claude/hooks/settings.hooks.json into
.claude/settings.json, merge, deno task sync-agents-md --apply. The sync script ships hooks/
only while that key exists, and removes the scripts again when it goes. To run the guard less
often, add "if": "Bash(git *)" to its handler (and a second handler with "Bash(gh *)").
Without the worktree hook, Claude Code's built-in worktree features nest checkouts under
<repo>/.claude/worktrees/, which repo tooling then walks. AGENTS.md tells the agent to create
worktrees by hand instead; don't tick the worktree option when starting a desktop session.
deno task test runs the hook and sync tests against temp dirs.
OpenCode picks up AGENTS.md changes on the next session start; DSH reads the global file once
per session start. .dsh/skills and .dsh/.agent-presets are generated β re-run
python3 tools/gen_dsh_skills.py / tools/gen_dsh.py after editing opencode.json commands or
.config/opencode/agents/.
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpmIn tmux: prefix + I to install plugins.
- Cross-Platform: Automatically detects package manager (apt, dnf, zypper, winget, homebrew)
- Fallback Support: Tries Flatpak if native package installation fails (Linux)
- Repository Management: Handles adding repositories with GPG key support
- Architecture Filtering: Skips apps incompatible with current architecture
- Pre/Post Commands: Executes custom commands before/after installation
- Smart Recovery: Continues with Flatpak if native packages fail
- Installation Summary: Reports successful, failed, and skipped installations
Edit apps.jsonc to customize applications. Supports comments via //. Each app supports:
- Package Managers:
dnf,apt,zypper,winget,homebrewarrays - Flatpak:
flatpakID (Linux only, preferred for GUI apps) - Repositories:
repoUrlandrepoGpgKeyfor adding repositories - Commands:
preInstallCommandsandpostInstallCommandsarrays - System:
requiresRebootboolean flag - Platform:
architecturesarray to limit supported architectures
{
"name": "VS Code",
"repoUrl": "https://packages.microsoft.com/yumrepos/vscode",
"repoGpgKey": "https://packages.microsoft.com/keys/microsoft.asc",
"dnf": ["code"],
"apt": ["code"],
"zypper": ["code"],
"winget": ["Microsoft.VisualStudioCode"],
"homebrew": ["visual-studio-code"],
"flatpak": "com.visualstudio.code",
"architectures": ["x86_64", "aarch64"]
}| Field | Type | Description |
|---|---|---|
name |
string |
Display name of the application |
dnf |
string[] |
Package names for DNF (Fedora/RHEL) |
apt |
string[] |
Package names for APT (Debian/Ubuntu) |
zypper |
string[] |
Package names for Zypper (openSUSE) |
winget |
string[] |
Package IDs for Winget (Windows) |
homebrew |
string[] |
Formula names for Homebrew (macOS) |
flatpak |
string |
Flatpak application ID (Linux only) |
repoUrl |
string |
Repository URL to add before installation |
repoGpgKey |
string |
GPG key URL for repository verification |
preInstallCommands |
string[] |
Commands to run before installation |
postInstallCommands |
string[] |
Commands to run after installation |
requiresReboot |
boolean |
Whether installation requires system reboot |
architectures |
string[] |
Supported architectures (x86_64, aarch64, etc.) |
winget |
string[] |
Package IDs for Winget (Windows) |
homebrew |
string[] |
Formula names for Homebrew (macOS) |
flatpak |
string |
Flatpak application ID (Linux only) |
repoUrl |
string |
Repository URL to add before installation |
repoGpgKey |
string |
GPG key URL for repository verification |
preInstallCommands |
string[] |
Commands to run before installation |
postInstallCommands |
string[] |
Commands to run after installation |
requiresReboot |
boolean |
Whether installation requires system reboot |
architectures |
string[] |
Supported architectures (x86_64, aarch64, etc.) |
Commands support environment variable expansion:
$USER- Current username$HOME- User home directory
- Zsh Shell: Modern shell with advanced features
- Oh My Zsh: Popular Zsh framework with plugins and themes
- Powerlevel10k: Fast, customizable prompt theme
- Custom Aliases: Automatically integrates
aliases.shfrom this repository - Default Shell: Sets Zsh as your default shell automatically
- Cross-Platform: Automatically detects package manager (apt, dnf, zypper, etc.)
- Smart Installation: Skips already installed components
- Sequential Execution: Steps depend on previous steps - stops on first failure
- Aliases Integration: Sources
aliases.shdirectly (no manual setup needed) - Automatic Configuration: Sets up Powerlevel10k theme automatically
- Error Handling: Reports success/failure for each step
- Shell Integration: Automatically sets Zsh as default shell
The script executes these steps in order:
- Package Manager Update: Updates package lists
- Install Zsh: Installs Zsh shell via system package manager
- Install Oh My Zsh: Downloads and installs Oh My Zsh framework
- Install Powerlevel10k: Clones Powerlevel10k theme
- Configure Theme: Updates
.zshrcto use Powerlevel10k - Setup Aliases: Configures
.zshrcto source custom aliases - Set Default Shell: Changes default shell to Zsh
- Verification: Verifies shell change took effect
After installation completes:
- New Terminal Sessions: Open a new terminal to use Zsh with the new configuration
- Powerlevel10k Setup: On first run, you'll be prompted to configure the theme with
p10k configure - Custom Aliases: All aliases from
aliases.share automatically available - Manual Theme Config: Run
p10k configureanytime to reconfigure the theme
- Shell Change: The script automatically attempts to set Zsh as your default shell
- Session Reload: Changes take effect in new terminal sessions or by running
exec zsh - Theme Configuration: Powerlevel10k will prompt for configuration on first use
- Aliases Integration: No manual copying needed - aliases are sourced directly
dotfiles/
βββ .tmux.conf # tmux config (tpm, resurrect, continuum)
βββ .zshrc # zsh config
βββ .p10k.zsh # powerlevel10k theme config
βββ .config/
β βββ opencode/ # opencode config
β βββ nvim/ # neovim config
βββ install-apps.ts # Application installer
βββ install-shell.ts # Shell environment setup
βββ shared.ts # Common utilities
βββ apps.jsonc # Application configurations (with comments)
βββ aliases.sh # Custom shell aliases
βββ deno.jsonc # Deno configuration
βββ README.md # This file
- Edit
apps.jsonc - Add your application with appropriate package manager entries
- Test with
deno task install-apps
- Aliases: Edit
aliases.shto add custom aliases - Theme: Modify the Powerlevel10k configuration in the script
- Additional Steps: Add new setup steps to the
setupSteps()method