These are my notes on setting and maintaining a desktop/workstation system, a successor to the older CentOS 7 workstation, to be used--among other things--with the private server setup and simpler server setup.
My goals were a working setup, along with an old system, simple
and close to the standard one, and with
encrypted /home
(see also: personal data
storage). To avoid possible confusion during installation or
when some repairs are needed, I keep a sheet of paper with
partitions listed on it.
I went for Unofficial non-free images including firmware
packages, since I need GNU documentation and the Nvidia
proprietary driver anyway (unnecessary as of Debian 12, since
proprietary firmware is included into official images, and that
Nvidia card is not supported anymore), and it is more suitable
for a rescue USB stick. Picked a live Xfce image, to be able to
poke it briefly (and ensure that it works fine with the
hardware) before installation, as well as for possible later use
as a rescue system. Though live images come with a drawback of
installing live-task-*
packages, including
localization ones for all the supported languages, so you end up
with hundreds of additional and unused packages to upgrade
regularly; netinst
produces a cleaner system, but
they can also be removed manually afterwards. Xfce is not as
bloated and broken as GNOME and KDE, but not as half-baked and
broken as most of the others. Apparently MATE and Cinnamon aim a
similar level of complexity, and I hear good things about those,
too. I downloaded the image via BitTorrent, and as
the Installation Guide suggests, did the equivalent of cp
debian.iso /dev/sdX && sync
.
There is a graphical installer available from the live system itself, which is handy for looking up documentation on the web while installing, but its functionality differs from that of the regular installer: there is no option to make an EFI system partition (ESP) explicitly, so I rebooted and used the regular installer. Although while installing Debian on another machine a bit later, I noticed that it would handle fine a FAT32 partition mounted into /boot/efi, without requiring to mark it explicitly as ESP.
As usual, I wanted to keep the old system usable and independent, so I have set this one on a separate disk, with a separate ESP, which I had to add (about 500 MB in size); the installer presented a warning about possibly making other systems hard to boot into if EFI is forced, but I've installed it on a separate disk (and adjusted UEFI boot priorities accordingly), so it was fine.
I used btrfs for a while, but decided to go with ext4 this time, since I use btrfs's advanced features less and less, while a simpler filesystem may be more reliable. Decided to minimize dealing with partitioning in the installer, and just made a single 500 GB partition for everything (not counting ESP, and while having 1.5 TB unpartitioned on the disk). No swap partition either, since in my experience it's not helpful and only freezes the system when something goes wrong. Didn't choose a network mirror to download new packages either, so the installation went quickly and smoothly.
While the en_US.UTF_8
locale is very
common, C.UTF_8
may be better to set at once, since
it has 24-hour time format, sensible string sorting, and DBMSes
(particularly PostgreSQL) are more portable when set with it,
not running into collation version mismatches on replication
between databases hosted on different operating systems. This is
simply adjusted in /etc/default/locale
.
As with CentOS about 7 years ago, apparently the nouveau driver was causing the system to freeze, so I installed the NVIDIA Proprietary Driver.
Then I've added my user into the sudo
group, have
set the keyboard layout to colemak with sudo
dpkg-reconfigure keyboard-configuration
(since the
installer doesn't provide that option), have set it in Xfce's
settings to use the system layout (actually in a couple of
places, not sure why there are so many). While at it, removed
the useless bottom panel (application launcher), have set a dark
theme, nicer icons, disabled icons on the desktop.
As with servers, and perhaps more importantly than with those,
decent and varied nameservers should be set. In this
case /etc/resolv.conf
mentions that it's generated
by NetworkManager (which is rather awkward and unnecessary, and
an example of little bloat task-xfce-desktop
pulls), so one can adjust nameservers with nm-connection-editor.
Then I've set the previously mentioned
encrypted /home
(this method is a bit verbose,
since I've checked that things work as intended):
sudo fdisk /dev/sda # created another 500 GB partition for /home, sda3 sudo apt install cryptsetup sudo cryptsetup luksFormat /dev/sda3 sudo cryptsetup luksOpen /dev/sda3 enchome sudo mkfs.ext4 -L home /dev/mapper/enchome sudo cryptsetup close enchome sudo blkid | grep sda3 sudo -e /etc/crypttab # added the following: # enchome UUID=PARTITION_UUID_HERE none luks sudo -e /etc/fstab # added the following: # /dev/mapper/enchome /mnt/home ext4 defaults 0 2
Then rebooted to ensure that /mnt/home
mounts fine,
moved the files from /home
there (with cp
-a
), renamed /home
, have
set fstab
to mount it
into /home
. Rebooted again, checked again that
everything is fine, and removed the old /home
.
One may also mount /tmp
into memory, reducing the
data leaking to the unencrypted root filesystem, slightly
speeding up some tasks, and reducing disk usage; it works for me
and I like it, but there is plenty of criticizm and possible
issues with that:
tmpfs /tmp tmpfs size=1g,nosuid 0 0
Moved/imported my SSH and GPG keys, ~/.authinfo
,
some other files.
I had to remap the "menu" key (keycode 135) to left alt, which
is always awkward and different; in Xfce I had to enter the GUI
settings, then "session and startup", and add the xmodmap
-e "keycode 135 = Alt_L"
command there. Also had to unmap
C-M-f to be able to use it in Emacs, in "settings" - "keyboard"
- "application shortcuts".
XFCE's default key bindings for basic tiling functionality aim a numpad, which I do not have, but those can be adjusted in "settings" - "window manager" - "keyboard".
More software: sudo apt install emacs
emacs-common-non-dfsg telnet vlc tor mu4e isync rsync xsltproc
clementine git elpa-magit elpa-haskell-mode cabal-install lynx
whois nmap ncat dnsutils knot-dnsutils tmux fbreader inkscape
blender godot3 gimp darktable lmms musescore texlive
texlive-plain-generic auctex texlive-latex-extra texlive-science
python3-sympy octave octave-symbolic
,
and better-defaults
, mu4e-alert
,
and cdlatex
via Emacs's package manager (since they
weren't in the system repositories). Generally it's a good idea
to stick to a single package manager, since then you shouldn't
run into version mismatches. update-alternatives --config
editor
to set vim as the default editor (running a new
emacs instance may be a bit slow for quick sudo -e
editos, emacsclient won't always work, setting a small emacs
clone just for that seems excessive, and the default nano is
just awkward, so vim is an okay option; though perhaps one can
also set emacs -Q -nw
). Over time a bunch of other
things were added, including mpd (running as a user service) and
mpc, strongSwan, likely more development tools.
Then I set xterm and Emacs themes (.Xresources
,
Elisp), from my dotfiles repository.
By 2022, I had to start using Tor bridges (since Tor is being
blocked around here, and Internet connectivity is crippled in
general, with Tor helping to fix some of it):
install obfs4proxy
, then append
to /etc/tor/torrc
:
UseBridges 1 ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy managed
And bridge records received from bridges.torproject.org or by
other means, prefixed with "Bridge" (Bridge obfs4
...
). Though by 2024, many of those are blocked.
Configured Firefox: Sans Serif font, disallowed pages to choose their own fonts, increasing monospace font size to be the same as others (16), setting a minimal font size equal to those, "wp" keyword for Wikipedia search and "wt" for Wiktionary search, installing uBlock Origin (with "annoyance" lists additionally enabled) to cut out junk, NoScript to cut out more junk, FoxyProxy to use Tor for websites blacklisted around here and the ones I don't want to track me, HTTPS everywhere to mitigate local data retention practices (superceded by the Firefox's built-in HTTPS-Only Mode, which should be enabled in settings), Stylus to set a global dark theme for comfortable browsing when it is dark around.
Configured isync and Emacs, later installed rexmpp's
xmpp.el. Attempted a minimal Emacs configuration this time
(though most likely it'll grow), so used the built-in rcirc
(with rcirc-track-minor-mode
and just
setting rcirc-server-alist
), not much of mu4e
configuration. Something like this:
(require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) (require 'better-defaults) (global-set-key [mode-line mouse-4] 'previous-buffer) (global-set-key [mode-line mouse-5] 'next-buffer) ;; https://github.com/defanor/cyrillic-colemak (require 'cyrillic-colemak) (add-to-list 'custom-theme-load-path "~/.emacs.d/elisp/") (load-theme 'blueish t) (setq org-preview-latex-default-process 'dvisvgm org-babel-python-command "python3" org-src-preserve-indentation t) (with-eval-after-load 'org (plist-put org-format-latex-options :scale 1.5) (require 'ob-python)) (rcirc-track-minor-mode t) (setq rcirc-buffer-maximum-lines 2000 rcirc-server-alist '(("irc.libera.chat" :port 6697 :encryption tls :user-name "defanor" :channels ("#emacs"))) rcirc-authinfo '(("libera.chat" sasl "defanor" "password-here"))) (require 'haskell-interactive-mode) (require 'haskell-process) (add-hook 'haskell-mode-hook 'interactive-haskell-mode) (add-hook 'haskell-mode-hook 'haskell-decl-scan-mode) (require 'html-wysiwyg) (add-hook 'html-mode-hook 'html-wysiwyg-mode) (add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display) (setq mail-user-agent 'mu4e-user-agent read-mail-command 'mu4e) (with-eval-after-load "mu4e" (require 'smtpmail) (setq mml-secure-openpgp-encrypt-to-self t) (defun suppress-messages (old-fun &rest args) (cl-flet ((silence (&rest args1) (ignore))) (advice-add 'message :around #'silence) (unwind-protect (apply old-fun args) (advice-remove 'message #'silence)))) (advice-add 'mu4e-update-mail-and-index :around #'suppress-messages) (advice-add 'mu4e-index-message :around #'suppress-messages) (advice-add 'progress-reporter-done :around #'suppress-messages) (setq mu4e-change-filenames-when-moving t) (add-to-list 'mu4e-contexts (make-mu4e-context :name "thunix" :enter-func (lambda () (mu4e-message "Switch to the thunix IMAP context") ;; (mu4e~request-contacts) ) :leave-func (lambda () (mu4e-clear-caches)) :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg :to "defanor@thunix.net"))) :vars '( (user-mail-address . "defanor@thunix.net") (user-full-name . "defanor") (smtpmail-default-smtp-server . "thunix.net") (smtpmail-local-domain . "thunix.net") (smtpmail-smtp-user . "defanor") (smtpmail-smtp-server . "thunix.net") (smtpmail-stream-type . starttls) (smtpmail-smtp-service . 25) (message-send-mail-function . message-smtpmail-send-it) (mu4e-get-mail-command . "mbsync -q thunix") (mu4e-update-interval . 300) (mu4e-view-show-addresses . t) (mu4e-maildir . "~/Maildir/thunix/") (mu4e-mu-home . "~/.mu/thunix") (mu4e-user-mail-address-list . ("defanor@thunix.net")) ))) ;; more contexts here )
And .mbsyncrc
records like this:
IMAPAccount thunix Host thunix.net Port 993 User defanor SSLType IMAPS Pass "password-here" AuthMechs * IMAPStore thunix-remote Account thunix MaildirStore thunix-local Path ~/Maildir/thunix/ Inbox ~/Maildir/thunix/inbox/ Channel thunix Far :thunix-remote: Near :thunix-local: Patterns * !drafts Create Both Remove Both Expunge Both SyncState *
Then mu stores can be initialized with commands like mu
init --muhome=~/.mu/thunix --maildir=~/Maildir/thunix
--my-address=defanor@thunix.net
.
This was a sufficient setup to listen to a radio (vlc
'http://s3.radionetz.de/1a-rock.mp3'
), local music
collection (which I keep on a separate partition, so just
mounted it via fstab
into the same path as before,
and the playlist also stored on it contained correct paths),
communicate (IRC, XMPP, email), do Haskell programming, browse
WWW relatively comfortably, play Discworld MUD over telnet, and
publish these notes. At that point I've adjusted dwproxy to be
able to build it using only dependencies from the system
repositories (for related rants and musings, see the notes
on software packaging and deployment and everyday programming in
Haskell), and built a few work projects: since it's Cabal 3 now,
had to set cabal.project in order to use internal libraries, and
made some other minor adjustments to handle newer versions of
dependencies. C projects (rexmpp in particular) also required
minor adjustments to handle newer versions of the compiler and
libraries, but fairly straightforward.
Realtime Policy and Watchdog Daemon (rtkit) can be quite spammy
in the logs with its debug messages, but that can be fixed by
overriding its systemd service (sudo systemctl edit
rtkit-daemon.service
, followed by sudo systemctl
daemon-reload
and sudo systemctl restart
rtkit-daemon.service
to apply it) with the following:
[Service] LogLevelMax=info
Following the instructions (Chapter 4. Upgrades from Debian 11
(bullseye)), I executed apt full-upgrade
to
find out that my graphics card (GTX 660) is not supported by the
NVIDIA proprietary driver anymore. Chose to not install the new
nvidia-driver, but that interrupted the process, so had
to apt --fix-broken install
, and then apt
full-upgrade
again. Afterwards
removed nvidia-driver
,
chose mesa-diverted
in update-glx --config
glx
in order to de-blacklist nouveau drivers, rebooted,
the system only worked for some minutes before freezing,
rendering it unusable. Fortunately I have integrated graphics
here (Xeon E3-1275 v2 on ASUS P8C WS), which I picked precisely
because this sort of thing keeps happening; took the graphics
card out, connected the display to the motherboard's DVI
output. Apparently I disconnected the system disk while taking
the graphics card out, so failed to boot; then reconnected it,
and saw it via UEFI, but failed to boot still, with different
priorities (possibly messed up the UEFI boot settings while
poking them without the disk connected properly). Managed to
boot into the system by booting grub from a live USB stick, then
pointing it to the system's grub.cfg using grub shell's
configfile
command. Tried to fix it with
efibootmgr, that did not work, but it worked to just
do grub-install
and update-grub
,
leading to a working system into which I can boot directly,
albeit without a graphics card. See GrubEFIReinstall for more
options.
Additionally, some texlive packages failed to update, and some fcitx5 ones were kept back.
Afterwards I did apt autoremove
, which removed
telnet, so had to apt install telnet
again.
mu4e broke as well: had to update mu4e-alert via Emacs, since it
came from melpa, but then it kept failing with "Mu server
process ended with exit code 1". Dug the approximate command out
of the sources (/usr/bin/mu server --debug
--muhome=~/.mu/thunix
), executed it manually, saw the
error message: "error: expected schema-version 465, but got 451;
cannot auto-upgrade; please use 'mu init'", "Please
(re)initialize mu with 'mu init' see mu-init(1) for
details". Did mv ~/.mu/ ~/.mu-old/
, then mu
init --muhome=~/.mu/thunix --maildir=~/Maildir/thunix
--my-address=defanor@thunix.net
(and similar ones, for
other mailboxes), and then it worked. As many other programs,
mbsync deprecated "master/slave" terminology, introducing its
unique alternative: "far/near".
Had to M-x customize-group RET ansi-colors RET
,
since ansi-color-names-vector
became obsolete.
I had an unused PostgreSQL 13 (used primarily for local
testing), and PostgreSQL 15 was installed by the system upgrade,
so I just cleaned up the old version: sudo pg_dropcluster
--stop 13 main
, sudo apt remove
postgresql-13 postgresql-client-13
.
Then I was left with a bunch of other "installed,local" packages
(apt list '?narrow(?installed,
?not(?origin(Debian)))'
), so cleaned some of those up,
after checking that they do not seem to be necessary: sudo
apt remove haskell-platform gcc-10 gcc-9-base gcc-10-base
clang-11 python-numpy-doc openjdk-11-jre openjdk-11-jdk
openjdk-11-jre-headless openjdk-11-jdk-headless libx264-160
libx265-192 libwebp6 libvpx6 libswresample3 libssl1.1 libsepol1
firmware-intelwimax linux-image-5.10.0-8-amd64
linux-image-5.10.0-23-amd64 iukrainian libffi7 libbpf0
libprocps8
.
Had to use a workaround for the FBReader's hyphenation-after-each-word bug.
Eventually I decided that having a properly configured XMPP
server locally is useful as a backup, for lower-latency calls,
and to decrease load on remote servers. Having just an A record
pointing to my static IP address (a free dyndns service in this
case, to avoid dependencies on domain names at once), and port
forwarding configured on the router for ports 80, 5222, 5269,
5281, 3478, 49152-49155, I have set nginx and uacme to obtain an
X.509 certificate for TLS, configured nftables to decrease spam
in the logs (only accepting connections on port 80 when renewing
a certificate), then configured Prosody and coturn. sudo
apt install nginx uacme nftables prosody
coturn
. My /etc/nftables.conf
, slightly
abridged to focus on relevant parts:
#!/usr/sbin/nft -f flush ruleset table inet filter { set not-clients { type ipv4_addr flags interval elements = { 1.0.0.0/8 } } set blocks { type ipv4_addr flags interval elements = { 1.1.1.1 } } set open-ports-s2s { type inet_service flags interval elements = { 5269 } } set open-ports-c2s { type inet_service flags interval elements = { 5222, 5281, 3478, 49152-49155 } } chain input { type filter hook input priority 0; policy drop; # Mitigate TCP reset attacks performed by the ISP. ip saddr @blocks tcp sport 443 tcp flags rst drop; # Allow traffic from established and related packets. ct state established,related accept # Allow loopback traffic. iifname lo accept # Allow incoming TCP and UDP packets on @open-ports-s2s. tcp dport @open-ports-s2s accept; udp dport @open-ports-s2s accept; # Drop connections from spammy addresses. ip saddr @not-clients drop; # Allow incoming TCP and UDP packets on @open-ports-c2s. tcp dport @open-ports-c2s accept; udp dport @open-ports-c2s accept; } chain forward { type filter hook forward priority 0; } chain output { type filter hook output priority 0; } }
Then set /usr/local/bin/uacme-hook.sh
,
modifying /usr/share/uacme/uacme.sh
:
--- /usr/share/uacme/uacme.sh 2023-02-15 23:31:43.000000000 +0300 +++ /usr/local/bin/uacme-hook.sh 2024-01-30 09:49:06.505761694 +0300 @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. -CHALLENGE_PATH="${UACME_CHALLENGE_PATH:-/var/www/.well-known/acme-challenge}" +CHALLENGE_PATH="${UACME_CHALLENGE_PATH:-/var/www/html/.well-known/acme-challenge}" ARGS=5 E_BADARGS=85 @@ -37,6 +37,8 @@ case "$TYPE" in http-01) printf "%s" "${AUTH}" > "${CHALLENGE_PATH}/${TOKEN}" + # Temporarily allow connections to port 80 + sudo nft add element inet filter open-ports-s2s {80} exit $? ;; *) @@ -48,7 +50,10 @@ "done"|"failed") case "$TYPE" in http-01) + sudo nft delete element inet filter open-ports-s2s {80} rm "${CHALLENGE_PATH}/${TOKEN}" exit $? ;; *)
Then:
sudo mkdir -p /var/www/html/.well-known/acme-challenge sudo mkdir /etc/prosody/certs/example.com/ sudo touch /etc/prosody/certs/example.com/{fullchain,privkey}.pem sudo chmod 640 /etc/prosody/certs/example.com/{fullchain,privkey}.pem sudo chown root:prosody /etc/prosody/certs/example.com/{fullchain,privkey}.pem sudo uacme -v new sudo uacme -h /usr/local/bin/uacme-hook.sh issue example.com sudo -e /etc/cron.daily/uacme-cert-update sudo chmod +x /etc/cron.daily/uacme-cert-update
With the following in /etc/cron.daily/uacme-cert-update
:
#!/bin/sh set -e /usr/bin/uacme -h /usr/local/bin/uacme-hook.sh issue example.com cp /etc/ssl/uacme/example.com/cert.pem /etc/prosody/certs/example.com/fullchain.pem cp /etc/ssl/uacme/private/example.com/key.pem /etc/prosody/certs/example.com/privkey.pem
In /etc/turnserver.conf
I have only set external-ip
, static-auth-secret
, use-auth-secret
, max-port=49154
.
Relevant lines of /etc/prosody/prosody.cfg.lua
:
interfaces = { "192.168.1.8", "127.0.0.1", "::1" } modules_enabled = { --- [...] -- Other modules "turn_external"; "http"; } -- TURN turn_external_host = "example.com" turn_external_secret = "secret here" -- HTTP http_host = "example.com" VirtualHost "example.com" Component "upload.example.com" "http_file_share"
Then restart or reload the services, add users with sudo
prosodyctl adduser <jid>
, and it works.
For voice conferences, apparently a particularly easy to set and
properly working option is Mumble. sudo apt install
mumble-server mumble
, set a password
in /etc/mumble-server.init
, open UDP and TCP ports,
and it is ready to use with desktop clients or Mumla or Android.