Skip to content

Debian Setup

Personal Debian VPS dotfiles and setup scripts powered by apt, proto, and Oh My Zsh.

Prerequisites

The script requires either root or a user with sudo access. It will exit early with an error if neither is available.

PrerequisiteNotes
sudo or rootRequired to install packages and write to system directories
curlUsed to bootstrap Oh My Zsh, proto, Helm, and k9s
gitInstalled automatically via apt in the common step

Installation

Remote (fresh server)

sh
# Full install — all profiles, dotfiles, completions
bash <(curl -fsSL https://raw.githubusercontent.com/KevinDeBenedetti/dotfiles/main/os/debian/init.sh) -a

The script clones the repository to ~/.dotfiles if not already present, then re-executes from there.

Local

sh
# Clone
git clone https://github.com/KevinDeBenedetti/dotfiles.git ~/.dotfiles
cd ~/.dotfiles

# Full install
./os/debian/init.sh -a

# Dotfiles only
./os/debian/init.sh -d

# Specific profiles + completions
./os/debian/init.sh -p "base,kubernetes" -c

Flags

FlagDescription
-aFull install: all profiles + dotfiles + completions + cleanup
-p <profiles>Comma-separated list of profiles (e.g. base,kubernetes)
-dLink dotfiles into $HOME
-cInstall zsh CLI completions
-lLite mode — skip optional/heavy packages
-rRemove bootstrap temp directory after install
-hPrint help

At least one flag is required. Running without flags prints help and exits.

Common Packages

Installed unconditionally before any profile (via apt):

ca-certificates, curl, gnupg, gzip, jq, unzip, wget, xz-utils, git, build-essential

Oh My Zsh is also installed at this stage if ~/.oh-my-zsh does not exist.

Profiles

base

Core CLI tools installed via apt. Split into lite (always) and additional (full mode only).

Lite:

ToolDescription
Docker CEDocker engine from the official upstream repository
docker-ce, docker-ce-cli, containerd.ioCore engine, CLI client, and container runtime
docker-buildx-plugin, docker-compose-pluginBuildKit and Compose v2 plugins
fzfFuzzy finder for shell history, files, and more
sshOpenSSH client
treeDirectory tree visualizer
watchRepeat a command at intervals
yqPortable YAML/JSON/TOML processor
rsyncIncremental file transfer

Docker CE is installed from https://download.docker.com/linux/debian using the official GPG key. The calling user is automatically added to the docker group.

Additional (full mode):

ToolDescription
ghGitHub CLI — PRs, issues, repos from the terminal
nmapNetwork exploration and port scanning
protoMulti-language toolchain version manager (installed via curl if not present)

security

Hardens the system using a layered approach. Activated with -p security or included in -a (full install).

Environment variables

Set these before invoking the script to customize security behaviour:

VariableDefaultDescription
SSH_PORT22TCP port sshd listens on
SSH_ALLOWED_USERS(all)Space-separated list of users permitted to log in via SSH
sh
# Example: custom port + restrict SSH to a single user
SSH_PORT=2222 SSH_ALLOWED_USERS="deploy" bash <(curl -fsSL ...) -a

SSH hardening (/etc/ssh/sshd_config.d/99-hardening.conf)

SettingValueEffect
Port$SSH_PORTConfigurable port (default: 22)
PermitRootLoginnoDisable direct root login
PasswordAuthenticationnoKey-only authentication
MaxAuthTries3Block brute-force attempts
X11ForwardingnoNo X11 tunnelling
AllowTcpForwardingnoNo arbitrary port forwarding
ClientAliveInterval300Disconnect after 5 min idle
AllowUsers$SSH_ALLOWED_USERSRestrict login to named users (optional)

UFW firewall

Default policy: deny all incoming, allow all outgoing. Rules added:

RuleProtocolComment
$SSH_PORT/tcpTCPSSH (respects custom port)
80/tcpTCPHTTP
443/tcpTCPHTTPS

Fail2Ban (/etc/fail2ban/jail.local)

SettingValue
bantime1 hour
findtime10 minutes
maxretry3 attempts
backendsystemd
banactionufw

Sysctl hardening (/etc/sysctl.d/99-security.conf)

Network:

ParameterValueEffect
net.ipv4.tcp_syncookies1SYN flood protection
net.ipv4.tcp_rfc13371TIME_WAIT assassination protection
net.ipv4.ip_forward0No IP forwarding
net.ipv4.conf.all.accept_redirects0Ignore ICMP redirects (MITM)
net.ipv4.conf.all.rp_filter1Reverse path filtering (anti-spoof)
net.ipv6.conf.all.accept_ra0Disable IPv6 router advertisements

Kernel:

ParameterValueEffect
kernel.kptr_restrict2Hide kernel pointer addresses in /proc
kernel.dmesg_restrict1Restrict kernel log access to root
kernel.randomize_va_space2Full ASLR enabled
kernel.yama.ptrace_scope1Restrict ptrace to parent processes
net.core.bpf_jit_harden2Harden BPF JIT compiler
vm.mmap_min_addr65536Prevent null pointer dereference exploits

Filesystem:

ParameterValueEffect
fs.suid_dumpable0No core dumps for setuid binaries
fs.protected_symlinks1Protect symlinks in world-writable sticky dirs
fs.protected_hardlinks1Only owner can follow hardlinks
fs.protected_fifos2Restrict FIFO creation in sticky directories
fs.protected_regular2Restrict regular file creation in sticky directories

Shared memory & /tmp (/etc/fstab)

Mount pointOptions
/run/shmnoexec,nosuid
/tmpnoexec,nosuid,nodev,size=1G

AppArmor

Installs apparmor and apparmor-utils, adds GRUB boot parameters (apparmor=1 security=apparmor), enables the service, and sets all loaded profiles to enforce mode.

CrowdSec (full mode only)

Collaborative threat-intelligence IPS. Installs from install.crowdsec.net and adds:

  • crowdsec-firewall-bouncer-iptables — automatic firewall banning
  • Collections: crowdsecurity/linux, crowdsecurity/sshd

Unattended upgrades

Configures automatic security patches only (${distro_id}:${distro_codename}-security). Auto-reboot is disabled.

Security audit tools (full mode only)

ToolDescription
lynisSystem and security auditing
rkhunterRootkit scanner
chkrootkitAnother rootkit checker
auditdKernel audit framework

kubernetes

Tools for Kubernetes cluster management.

Lite:

ToolDescription
kubectlKubernetes command-line client (installed from the official pkgs.k8s.io repo, currently v1.32)

Additional (full mode):

ToolDescription
helmKubernetes package manager (installed via the official get-helm-3 script)
k9sTerminal UI for Kubernetes clusters (latest release fetched from GitHub)

Dotfiles Linking

Running -d symlinks (or copies) config files from the repository into $HOME. Any existing file is backed up with a datestamp (e.g. ~/.zshrc.bak.20260311) before being replaced.

SourceTargetMethod
config/zsh/.zshrc~/.zshrccopy¹
config/git/.gitconfig~/.gitconfigsymlink
config/proto/.prototools~/.proto/.prototoolssymlink
config/oh-my-zsh/*.zsh-theme~/.oh-my-zsh/custom/themes/symlink
config/shell/*~/.config/dotfiles/symlink
config/vscode/settings.json~/.config/Code/User/settings.jsonsymlink (if code is available)
config/vscode/mcp.json~/.config/Code/User/mcp.jsonsymlink (if code is available)

¹ .zshrc is copied rather than symlinked so machine-specific patches can be applied without affecting the tracked file.

Local override stubs

The -d step also creates the following stub files if they do not already exist. They are never tracked by git and always take precedence:

FilePurpose
~/.zshrc.localMachine-specific zsh aliases, exports, and path additions
~/.gitconfig.localMachine-specific git overrides (e.g. user.email, signingkey)
~/.config/dotfiles/env.local.shMachine-specific environment variable overrides

SSH allowed signers

If ~/.ssh/id_rsa.pub exists, the script automatically creates ~/.ssh/allowed_signers for local SSH commit signature verification.

Completions

Running -c installs zsh completions for:

ToolSource
ghgh completion -s zsh
protoproto completions --shell zsh
uvuv generate-shell-completion zsh
zsh-completionsCloned into Oh My Zsh custom plugins and added to fpath

Completions are written to /usr/local/share/zsh/site-functions (with sudo when not root).

Zsh Configuration

The .zshrc at config/zsh/.zshrc is copied to ~/.zshrc so machine-specific patches can be applied without affecting the tracked file.

History

SettingValueEffect
HISTSIZE10000Number of commands kept in memory per session
SAVEHIST10000Number of commands persisted to ~/.zsh_history
HIST_STAMPSyyyy-mm-ddDate prefix on every history entry
HISTFILE~/.zsh_historyPersistent history file location

Theme

A custom Oh My Zsh theme kevin-de-benedetti is linked from config/oh-my-zsh/ into ~/.oh-my-zsh/custom/themes/ and set via ZSH_THEME.

Plugins

The following Oh My Zsh plugins are enabled in .zshrc:

PluginWhat it provides
aliasesals command — lists all active aliases with descriptions
colored-man-pagesANSI colour for man pages — much easier to read
dockerDocker CLI completion and aliases (dbl, dcin, dps, etc.)
docker-composedocker compose completion and shorthand aliases (dcup, dcdown, etc.)
ghGitHub CLI (gh) shell completion
gitExtensive Git aliases (gst, gco, glog, gcmsg, etc.) and branch in prompt
gitignoregi <lang> — fetches a .gitignore template from gitignore.io
rsyncAliases for common rsync patterns (rsync-copy, rsync-move, etc.)
sudoDouble-press ESC to prepend sudo to the current or previous command

fzf Integration

When installed (via the base profile), fzf provides the following shell key bindings:

BindingAction
CTRL-TFuzzy-search files/directories and paste the path
CTRL-RFuzzy-search command history and run selected entry
ALT-CFuzzy cd into a subdirectory

Local Overrides

The following files are sourced at the end of .zshrc if they exist. They are never tracked by git:

FilePurpose
~/.config/dotfiles/env.local.shMachine-specific secrets and environment variables
~/.zshrc.localMachine-specific aliases, path additions, and exports

Shell Functions

Custom functions live in config/shell/functions.sh and are sourced automatically by .zshrc. Run lsfn to list all functions with their help text.

FunctionUsageDescription
b64db64d <string>Decode a base64 string
b64eb64e <string>Encode a string to base64
browserbrowser [-- <url>]Start a Browsh terminal browser via Docker
cheat_glowcheat_glow <sheet>Render a cheat cheatsheet through glow at 150 columns for readability
check_certcheck_cert <url>Print TLS certificate details for a domain using curl
dksdks <secret> [namespace]Decode a Kubernetes secret — outputs all .data values base64-decoded via yq
kbpkbp <port>Kill the process currently listening on the given TCP port
randompassrandompass [length]Generate a secure random password (default 24 chars) with mixed complexity
timestampdtimestampd <unix_ts>Convert a Unix timestamp to a human-readable date (cross-platform)
timestampetimestampe <YYYY-mm-ddTHH:MM:ss>Convert a date string to its Unix timestamp (cross-platform)

Run any function with -h or --help to see its usage message, e.g. dks -h.

Git Configuration

config/git/.gitconfig is symlinked to ~/.gitconfig. Key settings:

SectionSettingValueEffect
pushdefaultsimplePush to the tracking branch only (safe default)
pullrebasetrueAlways rebase on pull instead of merge
branchautosetuprebasealwaysNew branches track their remote with rebase
rerereenabledtrueRecord and replay conflict resolutions automatically
commitgpgsigntrueSign every commit with your SSH key
taggpgSigntrueSign every tag with your SSH key
gpgformatsshUse SSH key (not GPG keyring) for signing
gpg.sshallowedSignersFile~/.ssh/allowed_signersFile that maps emails to trusted public keys
initdefaultBranchmainNew repositories default to main
credentialhelpercache --timeout=3600Cache credentials for 1 hour

Machine-specific overrides

Create ~/.gitconfig.local — it is [include]d at the bottom of .gitconfig and is never tracked by git:

ini
# ~/.gitconfig.local
[user]
  email = server@yourdomain.com
  signingkey = ~/.ssh/id_ed25519_server.pub

SSH commit signing

When ~/.ssh/id_rsa.pub exists the dotfiles install creates ~/.ssh/allowed_signers automatically. This allows Git to verify signed commits locally with git log --show-signature.

SSH Client Configuration

config/ssh/config is symlinked to ~/.ssh/config. The global Host * block applies to every connection:

DirectiveValueEffect
SendEnv -LC_* -LANG(off)Do not forward locale variables — avoids setlocale warnings on servers
ControlMasterautoReuse an existing connection if one is already open
ControlPath~/.ssh/cm-…Socket path for multiplexed connections
ControlPersist60sKeep the master connection open for 60 s after the last session closes
ServerAliveInterval60Send a keepalive packet every 60 s
ServerAliveCountMax3Drop the connection after 3 missed keepalives (~3 min)

proto Toolchain

config/proto/.prototools is symlinked to ~/.proto/.prototools. It pins global tool versions and configures proto's behaviour:

toml
bun   = "latest"
node  = "latest"
npm   = "bundled"   # ships with the Node.js version
pnpm  = "latest"

[tools.node]
bundled-npm = true  # keep npm in sync with node

[tools.npm]
shared-globals-dir = true  # global npm packages in a single shared dir

[settings]
auto-install  = true     # install missing tool versions automatically
auto-clean    = true     # remove unused tool versions after upgrades
pin-latest    = "global" # record the resolved "latest" version globally

Managing tool versions

sh
# Install / pin a specific version
proto install node 22

# Use a version in the current directory only
proto use python 3.12 --local

# List all tools managed by proto
proto list

# Upgrade proto itself
proto upgrade

Useful apt Commands

sh
# Refresh package index
sudo apt-get update

# Upgrade all installed packages
sudo apt-get upgrade

# Install a package without recommended extras
sudo apt-get install -y --no-install-recommends <package>

# Search for a package
apt-cache search <keyword>

# Remove a package and its config
sudo apt-get purge <package> && sudo apt-get autoremove

Common Issues

sudo not available on a fresh VPS:

sh
# As root, install sudo and add your user
apt-get install -y sudo
usermod -aG sudo <username>
# Then log out and back in

Docker CE daemon not started after install:

sh
sudo systemctl enable docker
sudo systemctl start docker
# Log out and back in for group membership to take effect
# (the script already runs: usermod -aG docker $USER)

Docker GPG key or repository error:

sh
# Re-add the GPG key manually
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg \
  | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

kubectl not found after install:

sh
# Verify the apt source was added correctly
cat /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update && sudo apt-get install -y kubectl