A Reproducible Dev Machine
Debian Development Machine Setup
Chapter 10 ยท Capstone: A Reproducible Dev Machine
You now have a working devserver: Debian 13, a graphics driver, Python, Node.js, PHP, MariaDB,
editors, and your own Git server. The last question is the one that decides whether all that work is safe:
if the disk died tomorrow, how long would it take to get back here? This chapter turns
what you built into something you can repeat and recover: a list of what is installed, a script that
rebuilds it, your settings stored in Git, and backups you have actually tested.
Step 1: Audit What Is There
Start from facts, using the checking habits from Chapter 3. Keep everything in one folder so it can go into a Git repository:
Node's version is not in that list because it comes from nvm, so record it separately:
. ~/.nvm/nvm.sh && nvm current. Read each file. packages-manual.txt is longer
than you might expect, because the Debian installer and the desktop environment marked many packages as
manual too; that is normal, and it is why the script below installs a deliberate list of your own instead of
replaying the whole file.
packages-manual.txt to apt install on a different install can fail, because
packages get renamed or removed between releases, and it drags in installer choices you did not make on
purpose. Use it as a reference to build your own short list, and to spot things you forgot you had.
Fill in the server side
Chapter 7 promised a comparison with debserver. Run the same version commands there and keep
both files:
When debserver is reinstalled with Debian 13, run it again and the two files should agree.
Step 2: A Provisioning Script
A provisioning script is a shell script that does the setup for you. The important property is that it is
safe to run twice: each step checks whether the work is already done, exactly the
check-before-change habit from Chapter 3, using the same have and pkg_installed
helpers. Run it as your normal user, not as root; it calls sudo where it needs to.
Read it as a table of contents for the whole course: every section is a chapter, and every chapter's choices appear as a line. Things it deliberately does not do are worth noticing. It never stores a password. It does not restore Gitea, because Gitea's data comes from a backup, not from a script. And it leaves a few steps manual and says so, because a script that silently guesses (a Secure Boot key, an SSH key) is worse than one that tells you what is left.
bash -n bootstrap.sh, and run it a second time on devserver
itself: it should print “already installed” for everything and change nothing. That proves the
checks work. The real proof is a fresh Debian 13 virtual machine, if you have one to spare. Read the
sed line's effect on debian.sources afterwards, as it is the one step that edits a
system file.
Step 3: Your Settings, in Git
Installed software is half of a machine. The other half is your configuration: the files in your home directory whose names start with a dot. Keep the ones you care about in a repository on your own Gitea (the payoff of Chapter 9):
A symbolic link means the real file lives in the repository, so editing ~/.bashrc edits the
tracked copy, and git diff shows what you changed.
.env files do not
belong in it, even on your own server. Store the list of which secrets you need, not the secrets.
Also remember that the repository lives on devserver itself: a copy that shares a disk with the
original is not a backup, which brings us to the next step.
Step 4: Backups You Have Tested
Decide what must survive, and where a second copy lives.
| What | Where it lives | How to protect it |
|---|---|---|
| Your files and projects | /home/you | Copy to another disk, leaving out what can be rebuilt (node_modules, virtual environments, vendor) |
| Gitea data | /var/lib/gitea, /etc/gitea, and the database if you chose MariaDB | Stop or dump consistently, then copy |
| Databases you use for development | MariaDB | mariadb-dump for the ones that matter |
| Software and settings | Package lists, bootstrap.sh, dotfiles | In Git, and Git copied elsewhere |
| Secrets and keys | ~/.ssh | Encrypted copy in a safe place, never in Git |
Files
--delete makes the destination an exact mirror, including removing files you deleted. That is
what you want for a mirror and dangerous when you get the two paths the wrong way round or the disk is not
mounted, so always do the preview first and check that /mnt/backup is really your backup disk
(findmnt /mnt/backup). If rsync is missing, sudo apt install rsync.
Gitea
Gitea's own dump command bundles its repositories, database and configuration into one archive.
Run /usr/local/bin/gitea dump --help first to see the options for your version, and then run it
as the git user so that file ownership matches the running service:
If the command complains about a working directory or a permission, read its message rather than working
around it; the --help output tells you where the archive is written. Then copy the archive to
the backup disk. Keep a few dated dumps, not just the latest.
Upgrading Gitea, the manual routine
Because apt does not manage Gitea, updating it is your job. Do it in this order: dump first,
then download the new binary and verify its signature (Chapter 9), then
sudo systemctl stop gitea, copy the new file over /usr/local/bin/gitea,
sudo systemctl start gitea, and check journalctl -u gitea -n 30. Read the release
notes for the versions you are skipping, since a jump across several versions can include changes you need
to know about.
The Regular Routine
| How often | Do this |
|---|---|
| Weekly | sudo apt update && sudo apt full-upgrade; reboot if the kernel changed (NVIDIA driver check, Chapter 4); run the file backup |
| Monthly | Gitea dump, MariaDB dump, refresh packages-manual.txt and vscode-extensions.txt, commit and push the setup repository, look at the Gitea release page |
| When you install something new | Add it to bootstrap.sh in the same sitting |
| Twice a year | Restore something from backup on purpose; re-check Node's and Debian's support dates |
Closing Checklist
Each line points back to the chapter that built it.
| Chapter | You can now… |
|---|---|
| 1 Planning & Installing | Install Debian 13 with sensible choices, and know what you chose |
| 2 First Boot | Keep the system updated, use sudo, log in over SSH with keys, and tell devserver from debserver at a glance |
| 3 What's Installed | Check whether something is installed, which version, and where it came from |
| 4 Graphics Drivers | Install and verify the NVIDIA driver, handle Secure Boot, and survive kernel updates |
| 5 Python | Use venvs, pipx and pyenv instead of fighting the system Python |
| 6 Node.js | Run several Node versions with nvm and keep project and global installs apart |
| 7 PHP & Databases | Install PHP without a web server, use Composer, run MariaDB with a limited user, and compare with the server |
| 8 Editors | Install VS Code safely, set a default editor, and record extensions |
| 9 Gitea | Run a Git server as a proper service and push to it |
| 10 This chapter | Rebuild the machine from a script, keep your settings in Git, and restore from a backup you have tested |
bootstrap.sh as an eleventh section. When debserver is reinstalled with Debian 13,
much of this course, and this very script, applies to it too, with the web server courses (Setting Up a Web
Server on Debian, and Nginx In Depth) covering the parts that differ.
Hands-On Exercises
Run the audit: create ~/machine-setup with the package list, versions, extension list and non-apt tool list, plus the debserver versions file. Then find three packages in packages-manual.txt that you did not install on purpose, and say what each is.
Save the provisioning script as bootstrap.sh, syntax-check it, and run it twice on devserver. Show from the output that the second run changed nothing. Then add one new tool of your choice to it, correctly, using the helper functions.
Put your dotfiles and setup folder into a Gitea repository. Take a file backup with an rsync preview first and then for real, and a Gitea dump. Prove both work by restoring one file from the rsync copy and listing the contents of the Gitea archive.
๐ View solutionChapter 10 Quick Reference
- Audit:
apt-mark showmanual, version commands,pipx list,code --list-extensions,nvm current - The manual-package list is a reference, not something to replay blindly
- A provisioning script is re-runnable: every step checks first (
have,pkg_installed) - Install
php-cliplus extensions, never thephpmetapackage; addcontrib non-freebefore the NVIDIA packages - The script never holds passwords and leaves risky steps (Secure Boot, SSH keys, Gitea restore) manual
- Dotfiles: real files in a Git repository, symbolic links from the home directory, no secrets
- Backups:
rsync -avnpreview then run;gitea dump(check--help); a copy on the same disk is not a backup - Upgrade Gitea by hand: dump, verify, stop, copy, start, check the journal
- A backup is only real once you have restored from it