Building a Fast Daily Driver Terminal on Linux
This article sets up a lean daily working environment on Linux: a lightweight terminal, a modern compiler toolchain, secure logging for GnuPG, and a couple of Python modules you will actually use. It is for developers and sysadmins who already run a Linux desktop and want less overhead, not more features.
Requirements
You need a Linux distribution with X11 (Wayland support in `zutty` is still experimental as of version 0.14). The examples below assume Debian 12 or Ubuntu 24.04, where the package names are stable.You also need:
- `gcc` and `g++` version 12 or newer (Debian 12 ships 12.2, Ubuntu 24.04 ships 13.2)
- Python 3.11 or newer, with `tkinter` available
- GnuPG 2.2 or newer if you plan to use `watchgnupg`
- Root or `sudo` access for package installation
```bash gcc --version g++ --version python3 --version gpg --version ```
If any of those return "command not found", install the missing pieces first.
Step 1 — Install the base toolchain
The compiler is the foundation. Everything else here assumes you can build a C or C++ program without thinking about it.```bash sudo apt update sudo apt install -y build-essential gcc g++ cpp ```
`build-essential` pulls in `make`, `libc6-dev`, and `dpkg-dev` alongside the compilers. You could install `gcc` alone, but then every second project fails at link time and you spend an afternoon chasing headers. The full metapackage costs about 200 MB of disk. That is the trade.
Verify the versions landed where you expect:
```bash gcc --version | head -n1 g++ --version | head -n1 cpp --version | head -n1 ```
Step 2 — Install zutty as your terminal
`zutty` is a terminal emulator for X11 that renders with OpenGL and implements the VT100/VT102/VT220 and xterm control sequences directly. It does not use a widget toolkit.```bash sudo apt install -y zutty ```
The cost of that design choice: no tabs, no split panes, no built-in configuration file. You get one window per process, and you compose it with `tmux` or your window manager. If you want tabs out of the box, `gnome-terminal` or `kitty` are the honest alternatives. `zutty` is faster and smaller, and that is the entire pitch.
Launch it once to confirm it starts:
```bash zutty -e tmux new-session -A -s main ```
The `-e` flag takes the command to run inside the terminal. If you do not use `tmux`, drop that part.
Step 3 — Preprocess C sources without invoking the full compiler
`cpp` is the C preprocessor, and it is useful on its own when you want to see what the compiler actually sees before it compiles anything.```bash cpp -I/usr/include -DDEBUG=1 -P main.c > main.i ```
The `-P` flag suppresses line markers, so the output is readable source rather than a stream peppered with `# 1 "main.c"` comments. The `-D` flag defines a macro at the command line, same as you would in a build system.
This costs you nothing but disk space for the intermediate file. It saves you from guessing why a macro expanded the way it did.
Step 4 — Compile with warnings enabled by default
A compiler you run without warnings is a compiler you are not really using.```bash gcc -Wall -Wextra -O2 -o myprog main.c ```
`-Wall` and `-Wextra` together turn on the warnings that catch real bugs: uninitialized variables, unused parameters, signed/unsigned mismatches. `-O2` is the standard optimization level for release builds. `-O3` is faster on some workloads and slower on others; measure before you switch.
For C++ the same flags apply through `g++`:
```bash g++ -Wall -Wextra -O2 -std=c++20 -o myprog main.cpp ```
The cost of `-Wall -Wextra` is that third-party headers sometimes produce noise you cannot fix. Suppress those per-file with `-isystem` instead of `-I`, which tells the compiler not to warn about that include path.
Step 5 — Log GnuPG activity through a Unix socket
`watchgnupg` reads the log output that GnuPG writes to a Unix Domain socket. You point GnuPG at the socket, then watch it in a second terminal.First, create the socket path and tell `gpg-agent` to use it:
```bash mkdir -p ~/.gnupg echo "log-file socket:///home/$USER/.gnupg/S.log" >> ~/.gnupg/gpg-agent.conf gpg-connect-agent reloadagent /bye ```
Then start the watcher:
```bash watchgnupg --force ~/.gnupg/S.log ```
The `--force` flag removes a stale socket file before binding. Without it, `watchgnupg` refuses to start if a previous run crashed and left the file behind. That is a deliberate safety check, and it is mildly annoying the first three times it happens.
The trade-off: you now have a second terminal permanently occupied with log output. If you only debug GnuPG once a month, skip this and use `--debug-level expert` on the command line instead.
Step 6 — Use `_thread` for lightweight concurrency in Python
The `_thread` module is the low-level threading interface in Python. It is not the one you reach for by default — that is `threading`. But `_thread` exists for cases where the overhead of the higher-level module matters, or where you are writing a small script and do not want the abstraction.```python import _thread import time
def worker(name, delay): for i in range(3): time.sleep(delay) print(f"{name}: {i}")
_thread.start_new_thread(worker, ("fast", 0.1)) _thread.start_new_thread(worker, ("slow", 0.3)) time.sleep(1.5) ```
Two things to know. First, `_thread` gives you no way to join a thread or wait for it to finish; you sleep and hope. Second, exceptions in a thread started this way print to stderr and the thread dies silently otherwise. For anything beyond a throwaway script, use `threading`.
Step 7 — Build a small GUI with tkinter
`tkinter` is the Python binding to Tcl/Tk. It ships with CPython on most distributions, and on Debian you may need to install it separately:```bash sudo apt install -y python3-tk ```
A minimal window that does something useful:
```python import tkinter as tk
root = tk.Tk() root.title("Log tail") root.geometry("600x400")
text = tk.Text(root, wrap="none") text.pack(fill="both", expand=True)
with open("/home/user/.gnupg/S.log", "r") as f: text.insert("end", f.read())
root.mainloop() ```
`tkinter` is not pretty and it is not fast. What it is: available everywhere Python runs, with no pip install and no external runtime. For a small internal tool, that is usually the right trade.
Step 8 — Manage Ubuntu Pro services with `pro`
On Ubuntu, the `pro` command manages the subscription services: ESM, Livepatch, and the security coverage that comes with them.```bash sudo pro status sudo pro enable livepatch ```
`pro status` shows which services are active and which are available. `livepatch` applies kernel security patches without a reboot, which is the main reason to bother. The cost: Livepatch only covers the kernel, not userspace packages, and it requires an Ubuntu One account attached to the machine.
If you are not on Ubuntu, none of this applies. Skip to verification.
Verification
Confirm each piece is working:```bash zutty --version gcc --version | head -n1 g++ --version | head -n1 cpp --version | head -n1 python3 -c "import _thread; print('_thread ok')" python3 -c "import tkinter; print('tkinter ok')" watchgnupg --version 2>&1 | head -n1 ```
Every line should print a version or an "ok". If `tkinter` fails, you are missing `python3-tk`. If `watchgnupg` is not found, you need the `gnupg-utils` package on Debian or `gnupg2` on older releases.
Then compile something trivial to prove the toolchain end to end:
```bash
printf '#include
It should print `ok`.
Common problems
`zutty` fails to start with "cannot open display". You are running it outside an X11 session, or `DISPLAY` is unset. Check with `echo $DISPLAY`. If it is empty, you are on a TTY or a Wayland session without XWayland.`gpg-connect-agent` reports the agent is not running. Start it manually with `gpg-agent --daemon`, then re-run the reload command. The agent does not always start on its own after a fresh install.
`tkinter` imports but no window appears. On a headless machine there is no display to draw to. `tkinter` will import fine and then fail at `Tk()`. Run it under `xvfb-run` if you need to test without a screen.
The stack above is deliberately small. Every piece you add from here — a window manager, a shell prompt, a multiplexer configuration — costs startup time you will pay back fifty times a day. Whether that is worth it depends entirely on how much you notice the pause.