One command to build a complete Born2beRoot Debian VM with SSH, WordPress, Docker, and VS Code Remote SSH that actually works.
make
That's it. Go grab a coffee. Come back to a fully configured VM.
- What Is This
- Quick Start
- What
makeDoes (Step by Step) - Makefile Commands
- Connecting with VS Code Remote SSH
⚠️ Known Issue: VS Code SSH Drops After 15 Minutes⚠️ Known Issue: Docker Permission Denied- Credentials
- What's Inside the VM
- Project Structure
- Troubleshooting
I built this because I was tired of manually installing Debian, configuring SSH, setting up WordPress, and then having VS Code Remote SSH die on me every 15 minutes.
This project automates the entire Born2beRoot setup from zero to a working VM:
- Downloads the latest Debian netinst ISO
- Injects a preseed file for fully unattended installation
- Creates a VirtualBox VM with the right specs
- Installs Debian with LUKS encryption + LVM partitions (bonus part)
- Configures SSH on port 4242, UFW, sudo, password policy, AppArmor
- Installs Docker, WordPress, lighttpd, MariaDB, PHP-FPM
- Sets up the monitoring script with cron
- Configures your host's
~/.ssh/configsossh b2bjust works - Injects your SSH public key so you never type a password
- Configures VS Code Remote SSH settings to fix the timeout bug
Everything is scripted. make re destroys everything and rebuilds from scratch.
- VirtualBox installed (or run
make depsto install it) - xorriso and curl (also installed by
make deps) - ~4GB free disk space
git clone https://github.com/LESdylan/setup_arch_linux.git
cd setup_arch_linux
makeThe orchestrator will:
- Install dependencies if needed
- Download the Debian ISO
- Inject the preseed + setup scripts into the ISO
- Create the VirtualBox VM (2GB RAM, 3 CPUs, 32GB disk)
- Boot and install Debian automatically (~15-25 min depending on your machine)
- Power off when done
After installation finishes:
- Start the VM:
make start_vm(GUI) ormake bstart_vm(headless) - Enter the LUKS passphrase:
tempencrypt123 - Wait ~30 seconds for SSH to be ready
- Connect:
ssh b2b
Ctrl+Shift+P→ "Remote-SSH: Connect to Host..."- Select
b2b - No password needed (SSH key was injected during install)
make
│
├─ 1. Check/install dependencies (VirtualBox, xorriso, curl)
│
├─ 2. Download latest Debian netinst ISO
│ └─ Automatically detects the latest version from cdimage.debian.org
│
├─ 3. Build custom ISO
│ ├─ Inject preseed.cfg into initrd (fully automated install)
│ ├─ Copy b2b-setup.sh (SSH, UFW, sudo, password policy, AppArmor, etc.)
│ ├─ Copy monitoring.sh (Born2beRoot monitoring script)
│ ├─ Copy first-boot-setup.sh (Docker + WordPress on first real boot)
│ ├─ Copy host SSH public key (for passwordless auth)
│ └─ Rebuild ISO with modified boot menu (auto-selects automated install)
│
├─ 4. Create VirtualBox VM
│ ├─ 2048 MB RAM, 3 CPUs, 64 GB dynamic disk
│ ├─ NAT networking with port forwarding:
│ │ SSH:4242 HTTP:80 HTTPS:443 Frontend:5173
│ │ Backend:3000 Docker:5000 MariaDB:3306 Redis:6379
│ └─ Attach the custom ISO
│
├─ 5. Boot and install (unattended)
│ ├─ Debian installer runs preseed.cfg
│ ├─ LUKS + LVM partitions created automatically
│ ├─ b2b-setup.sh runs in chroot (16 configuration sections)
│ └─ VM powers off when done
│
└─ 6. Configure host
├─ Write ~/.ssh/config (b2b alias, keepalives)
├─ Configure VS Code settings.json (fix SSH timeout bug)
└─ Display summary with credentials and URLs
| Command | Description |
|---|---|
make |
Full pipeline — build everything from zero |
make re |
Destroy and rebuild — clean slate |
make status |
Show environment status dashboard |
make start_vm |
Start the VM (GUI mode) |
make bstart_vm |
Start headless + auto-unlock encryption |
make poweroff |
Shut down the VM |
make deps |
Install VirtualBox + tools |
make gen_iso |
Download Debian ISO + inject preseed |
make setup_vm |
Create the VirtualBox VM |
make clean |
Remove downloaded ISOs |
make fclean |
Remove ISOs + disk images |
make rm_disk_image |
Delete the VM completely |
make prune_vms |
Delete ALL VirtualBox VMs |
make list_vms |
List all VirtualBox VMs |
make help |
Show this help in the terminal |
After make completes, your host is already configured. Just:
Ctrl+Shift+P → Remote-SSH: Connect to Host → b2b
The orchestrator automatically:
- Wrote
~/.ssh/configwith ab2balias pointing to127.0.0.1:4242 - Injected your SSH public key into the VM (no password needed)
- Configured VS Code settings to prevent the SOCKS proxy timeout bug
You can connect from the terminal with any of these:
ssh b2b # shortest
ssh vm # also works
ssh born2beroot # full nameIf you use VS Code Remote SSH with the default settings to connect to a VirtualBox VM with NAT networking, the connection will die after ~15 minutes of idle time with:
Connection timed out during banner exchange
The VS Code log shows:
Running server is stale. Ignoring
SSH from the terminal works fine. Only VS Code breaks.
VS Code Remote SSH defaults to "Local Server Mode" (remote.SSH.useLocalServer: true). In this mode, it runs:
ssh -T -D 49963 -o ConnectTimeout=15 user@host
That -D flag creates a SOCKS5 proxy. All VS Code traffic goes through this single shared tunnel. VirtualBox NAT has a connection tracking table with an idle timeout (~5-15 min). When the SOCKS proxy data channels go idle, VirtualBox NAT silently drops them. The SSH keepalives keep the TCP connection alive, but the SOCKS data inside the tunnel is dead.
This is a VS Code + VirtualBox NAT issue, not an SSH issue. Documented in:
Add these to your VS Code settings.json (Ctrl+Shift+P → "Preferences: Open User Settings (JSON)"):
{
"remote.SSH.useLocalServer": false,
"remote.SSH.enableDynamicForwarding": false,
"remote.SSH.useExecServer": false,
"remote.SSH.connectTimeout": 60,
"remote.SSH.showLoginTerminal": true
}What this does:
useLocalServer: false→ Terminal Mode: each window gets its own direct SSH connection (no shared SOCKS proxy)enableDynamicForwarding: false→ removes the-Dflag entirely, uses direct TCP forwardinguseExecServer: false→ simpler connection, less cached state to go stale
Then clean stale server cache:
rm -rf ~/.config/Code/User/globalStorage/ms-vscode-remote.remote-ssh/vscode-ssh-host-*Or run this one-liner to fix everything automatically:
python3 -c "
import json, os, glob
# Fix VS Code settings
p = os.path.expanduser('~/.config/Code/User/settings.json')
try:
s = json.load(open(p))
except:
s = {}
s['remote.SSH.useLocalServer'] = False
s['remote.SSH.enableDynamicForwarding'] = False
s['remote.SSH.useExecServer'] = False
s['remote.SSH.connectTimeout'] = 60
s['remote.SSH.showLoginTerminal'] = True
json.dump(s, open(p, 'w'), indent=4)
# Clean stale cache
for d in glob.glob(os.path.expanduser('~/.config/Code/User/globalStorage/ms-vscode-remote.remote-ssh/vscode-ssh-host-*')):
import shutil; shutil.rmtree(d, ignore_errors=True)
print('Done! Reload VS Code (Ctrl+Shift+P → Developer: Reload Window)')
"Note:
makealready does this automatically. This section is for people who configured their VS Code manually or are hitting this issue on an existing setup.
For the full deep dive (12 hours of debugging distilled into one doc), see doc/SSH_VSCODE_FIX.md.
After make re and connecting to the VM, Docker commands fail:
permission denied while trying to connect to the Docker daemon socket
at unix:///var/run/docker.sock
Docker is installed by first-boot-setup.sh during the first real boot (it needs systemd + network, so it can't run during preseed). When Docker installs, it adds dlesieur to the docker group. But if VS Code's Remote SSH server was already running before Docker finished installing, the server process has a stale group list — it doesn't know about the docker group.
Linux group changes only take effect on new login sessions. The VS Code server is a persistent process (--enable-remote-auto-shutdown), so even reconnecting from VS Code reuses the same stale server.
This is now auto-fixed:
first-boot-setup.shkills any running VS Code server after adding the docker group, andb2b-setup.shpre-creates thedockergroup during preseed so it's present from the very first login. If you still hit this on an older build, use the manual fix below.
Kill the VS Code server on the VM and reconnect:
# From the host — kill stale VS Code server
ssh b2b 'pkill -f vscode-server'
# Then reconnect from VS Code:
# Ctrl+Shift+P → Remote-SSH: Connect to Host → b2bOr from VS Code:
Ctrl+Shift+P → Remote-SSH: Kill VS Code Server on Host → select b2b
Then reconnect.
Or from a terminal inside the VM:
# Start a new shell with the docker group
newgrp docker
# Verify it works
docker psThis is a one-time issue that only happens on the very first connection after make re. Every subsequent connection will have the docker group loaded.
# From the host
ssh b2b 'docker ps && echo "Docker OK"'
# From inside the VM
docker run --rm hello-world| What | Value |
|---|---|
| Root password | temproot123 |
| User (dlesieur) | tempuser123 |
| LUKS disk encryption | tempencrypt123 |
| SSH port | 4242 |
⚠️ Change these passwords after setup if you're doing the real Born2beRoot evaluation.
- ✅ Debian Trixie (latest stable)
- ✅ LUKS encrypted disk + LVM partitions (root, swap, home, var, srv, tmp, var-log)
- ✅ SSH on port 4242 (no root login)
- ✅ UFW firewall (only 4242, 80, 443 open)
- ✅ sudo with strict rules (3 tries, TTY required, full logging)
- ✅ Password policy (min 10 chars, uppercase, lowercase, digit, max 3 repeats)
- ✅ AppArmor enabled at boot
- ✅ Monitoring script via cron (every 10 minutes, wall broadcast)
- ✅ Hostname:
dlesieur42
- ✅ WordPress with lighttpd + MariaDB + PHP-FPM
- ✅ Docker + Docker Compose
- ✅ Custom LVM partition layout per subject requirements
- ✅ tmux with auto-attach (SSH sessions survive disconnects)
- ✅ Git configured for NAT (large clone fix)
- ✅ Developer tools (build-essential, python3, curl, vim, htop, etc.)
- ✅ SSH key auth (no passwords for VS Code)
- ✅ NAT keepalive service (prevents VirtualBox NAT timeout)
- ✅ SSHD watchdog service (auto-restarts if sshd dies)
- ✅ Aggressive keepalives (both client and server side)
sda
├── sda1 500 MB /boot (ext2, unencrypted)
└── sda5 ~31 GB LUKS encrypted
└── LVM
├── root 5.0 GB /
├── swap 1.0 GB [SWAP]
├── home 5.0 GB /home
├── var 12.0 GB /var
├── srv 1.0 GB /srv
├── tmp 1.5 GB /tmp
└── var-log ~5 GB /var/log (fills remaining space)
| Service | Host Port | VM Port |
|---|---|---|
| SSH | 4242 | 4242 |
| HTTP | 80 | 80 |
| HTTPS | 443 | 443 |
| Vite Frontend | 5173 | 5173 |
| Backend API | 3000 | 3000 |
| Docker Registry | 5000 | 5000 |
| MariaDB | 3306 | 3306 |
| Redis | 6379 | 6379 |
.
├── Makefile # Entry point — all commands start here
├── README.md # This file
│
├── preseeds/
│ ├── preseed.cfg # Debian preseed — fully automated install
│ ├── b2b-setup.sh # Main post-install script (SSH, UFW, sudo, etc.)
│ ├── first-boot-setup.sh # Docker + WordPress install (runs on first boot)
│ └── monitoring.sh # Born2beRoot monitoring script
│
├── generate/
│ ├── orchestrate.sh # TUI dashboard — orchestrates `make all`
│ ├── create_custom_iso.sh # Downloads Debian ISO + injects preseed
│ ├── status.sh # Environment status dashboard
│ └── help.sh # Makefile help display
│
├── setup/
│ └── install/vms/
│ └── install_vm_debian.sh # VirtualBox VM creation script
│
├── monitore/
│ ├── monitoring.sh # Main monitoring script
│ └── classes/ # Modular monitoring components
│ ├── cpu-load-module.sh
│ ├── memory-module.sh
│ ├── disk-module.sh
│ └── ...
│
├── diagnostic/ # Diagnostic scripts (run inside VM)
│ ├── b2b_verifier.sh
│ ├── check_internet.sh
│ ├── disk_details.sh
│ ├── LVM_CHECK.sh
│ └── ...
│
├── fixes/ # Fix scripts for common issues
│ ├── fix_ssh_stability.sh
│ ├── fix_lighttpd_php_mysql.sh
│ └── ...
│
├── doc/
│ ├── SSH_VSCODE_FIX.md # Deep dive: VS Code SSH timeout fix
│ └── en.subject.pdf # Born2beRoot subject PDF
│
├── wordpress/ # WordPress themes + plugins
├── management_tools/ # sudo/user management scripts
├── utils/ # Color schemes, welcome screen
└── tests/ # Security tests (AppArmor, WordPress)
The VM isn't running or SSH isn't ready yet.
# Check VM status
make status
# Start the VM
make start_vm
# Wait for SSH (check every 2 seconds)
while ! ssh -o ConnectTimeout=2 -o BatchMode=yes b2b exit 2>/dev/null; do
echo "Waiting..."; sleep 2
done && echo "Ready!"This is the VS Code SOCKS proxy bug. Run the fix:
python3 -c "
import json, os, glob, shutil
p = os.path.expanduser('~/.config/Code/User/settings.json')
try: s = json.load(open(p))
except: s = {}
s.update({'remote.SSH.useLocalServer':False,'remote.SSH.enableDynamicForwarding':False,'remote.SSH.useExecServer':False,'remote.SSH.connectTimeout':60,'remote.SSH.showLoginTerminal':True})
json.dump(s, open(p,'w'), indent=4)
[shutil.rmtree(d, True) for d in glob.glob(os.path.expanduser('~/.config/Code/User/globalStorage/ms-vscode-remote.remote-ssh/vscode-ssh-host-*'))]
print('Fixed! Reload VS Code.')
"See doc/SSH_VSCODE_FIX.md for the full explanation.
Close and reopen your VS Code window. The docker group wasn't loaded in your current session. See Known Issue: Docker Permission Denied.
Your SSH key wasn't injected during install (ISO build issue). Fix it manually:
# Copy your key to the VM (will ask for password ONE time)
ssh-copy-id -p 4242 dlesieur@127.0.0.1
# Password: tempuser123
# Verify — should NOT ask for password
ssh b2b echo "Key auth works"# Force power off from host
VBoxManage controlvm debian poweroffNormal — the VM was rebuilt with a new host key. The ~/.ssh/config already has StrictHostKeyChecking no and UserKnownHostsFile /dev/null for the b2b host, so this shouldn't happen. If it does:
ssh-keygen -R "[127.0.0.1]:4242"ssh -o BatchMode=yes -p 4242 dlesieur@127.0.0.1 '
echo "=== UPTIME ===" && uptime
echo "=== MEMORY ===" && free -m
echo "=== SSH ===" && systemctl is-active ssh && ss -tlnp | grep 4242
echo "=== DOCKER ===" && docker ps 2>&1 | head -3
echo "=== SERVICES ===" && systemctl is-active nat-keepalive sshd-watchdog docker
echo "=== GROUPS ===" && groups
echo "=== AUTH KEYS ===" && wc -l ~/.ssh/authorized_keys
'This is a 42 school project. Use it, learn from it, make it your own. Don't copy it blindly for your evaluation — understand what each script does.
Built with frustration, caffeine, and 12 hours of debugging VS Code SSH timeouts. 🫠