├── README.md ├── ansible.cfg ├── doc ├── debian.png ├── motd.png └── tasksel.png ├── inventory.ini ├── playbook.yml └── roles ├── X ├── files │ ├── 10-intel.conf │ ├── Xresources │ │ ├── lcd │ │ └── urxvt │ ├── Xsession.d │ │ └── 90custom_essentials │ ├── dotfiles │ │ ├── .Xaliases │ │ ├── .Xresources │ │ ├── .config │ │ │ ├── mpv │ │ │ │ └── mpv.conf │ │ │ └── user-dirs.dirs │ │ ├── .mailcap │ │ ├── .xmonad │ │ │ └── xmonad.hs │ │ └── .xsession │ └── profile.d │ │ └── startx.sh ├── tasks │ └── main.yml └── templates │ └── override.conf ├── Xworkstation ├── files │ └── Xsession.d │ │ └── 90custom_autolock └── tasks │ └── main.yml ├── common ├── files │ ├── 20-stats │ ├── 30-packages │ ├── dotfiles │ │ └── .bashrc │ └── inputrc └── tasks │ └── main.yml ├── database ├── files │ ├── daily_backup │ ├── monthly_backup │ └── pg_hba.conf ├── handlers │ └── main.yml └── tasks │ └── main.yml ├── gamestation ├── files │ ├── 10-intel.conf │ └── dotfiles │ │ └── .xsession └── tasks │ └── main.yml ├── laptop ├── files │ ├── Xsession.d │ │ ├── 90custom_trackpad │ │ ├── 90custom_xbattbar │ │ └── 90custom_xbindkeys │ ├── brightness │ ├── dotfiles │ │ └── .xbindkeysrc │ └── volume └── tasks │ └── main.yml ├── mailclient ├── files │ └── add_alias ├── tasks │ └── main.yml └── templates │ └── muttrc ├── mailserver ├── handlers │ └── main.yml ├── tasks │ └── main.yml └── templates │ └── update-exim4.conf.conf ├── ssh ├── tasks │ └── main.yml └── templates │ └── 10-convenience.conf ├── webserver ├── files │ └── nginx.conf ├── handlers │ └── main.yml └── tasks │ └── main.yml ├── wol ├── README.md ├── files │ └── override.conf └── tasks │ └── main.yml └── workstation ├── files └── dotfiles │ └── .aliases ├── handlers └── main.yml └── tasks └── main.yml /README.md: -------------------------------------------------------------------------------- 1 | # rtts/Debian 2 | 3 | ### *A complete, minimalist Debian setup for power users* 4 | 5 | **This repository contains the exact configuration of all my 6 | workstations, gaming computers, laptops and even a couple of VPSes. Of 7 | course, your situation will differ, but I believe the installation 8 | instructions and Ansible roles contained herein will be a great 9 | starting point for anyone who wants to enjoy using a minimalist setup 10 | to its maximum potential.** 11 | 12 | ## FAQ 13 | 14 | ### Is this a GNU/Linux distribution? 15 | 16 | Yes, it kind of is! Except that all that I'm distributing is a bunch 17 | of [Ansible](https://docs.ansible.com/ansible/latest/index.html) roles 18 | and the installation instructions. Think of this as a dotfiles 19 | repository that also includes the playbook to install [the 20 | dotfiles](https://github.com/search?q=repo%3Artts%2Fdebian+path%3Adotfiles&type=code). 21 | 22 | ### Why should I use this instead of distribution X? 23 | 24 | You should not use this at all, this system is tailored exactly to my 25 | own needs and nothing more. However, you may find some configuration 26 | gems here that you can use in your own setup. Find an old laptop or 27 | spin up a VM and give it a try if you want to see how a fellow 28 | GNU/Linux enthusiast has configured their computer! 29 | 30 | ### How can I install it? 31 | 32 | Keep reading! This README contains all the steps needed to install 33 | Debian, set up the base system, and run the Ansible playbook that will 34 | take care of the remaining configuration. 35 | 36 | ## The starting point 37 | 38 | ![Debian family tree](https://upload.wikimedia.org/wikipedia/commons/6/69/DebianFamilyTree1210.svg) 39 | 40 | Let's return to the source and start with a minimal, vanilla Debian 41 | installation. Visit [Debian.org](https://www.debian.org/) and click 42 | "Download", then write the image to a USB flash drive with: 43 | 44 | sudo dd if=debian-12.8.0-amd64-netinst.iso of=/dev/sdX 45 | 46 | Make sure you substitute `X` with the correct letter of your USB 47 | drive). Then, do your best to reboot your computer in such a way 48 | that you arrive at the following screen: 49 | 50 | ![Debian installer](https://raw.githubusercontent.com/rtts/debian/main/doc/debian.png) 51 | 52 | Hurrah! The hardest part – getting your computer to successfully boot 53 | from a USB stick – is over! Note that the screenshot says "BIOS mode", 54 | but if you manage to boot the "UEFI Installer menu" that is probably 55 | even better. The second hardest part is choosing a hostname for the 56 | new system. Get your inspiration at https://namingschemes.com/. 57 | 58 | Carefully follow the installation instructions and choose the 59 | partitioning method "Use entire disk and set up encrypted LVM" for 60 | maximum security. At the "Set up users and passwords" prompt, supply 61 | an empty password for the root user and create an initial user 62 | account. This account will be given the power to become root using the 63 | `sudo` command. One day, I will add a [Debian 64 | Preseed](https://wiki.debian.org/DebianInstaller/Preseed) to this 65 | repository which will make all this happen automatically. 66 | 67 | At the end of the installation you'll see this: 68 | 69 | ![Tasksel](https://raw.githubusercontent.com/rtts/debian/main/doc/tasksel.png) 70 | 71 | Select nothing except for "SSH Server" and "standard system 72 | utilities". After the installation is complete, boot into the new 73 | system and log in using the username and password you have set during 74 | installation. There is no graphical environment, yet, but be patient 75 | because we'll configure the important things first. 76 | 77 | > **Note** 78 | > 79 | > Even when you have configured a WiFi connection during the 80 | > installation, the resulting system may not have WiFi. 81 | > 82 | > One way to solve this is to boot the installer again and enter 83 | > "Rescue mode", follow the steps to get a root shell, and install 84 | > NetworkManager with `apt install network-manager`. 85 | > 86 | > Then, remove the USB drive, reboot into your system and execute: 87 | > `nmcli dev wifi connect password ` 88 | 89 | ## Give yourself (remote) access 90 | 91 | On the target computer, run: 92 | 93 | $ sudo apt install libnss-mdns 94 | 95 | On the target computer or another computer, run: 96 | 97 | $ ssh-copy-id .local 98 | 99 | Finally, on the target computer, run: 100 | 101 | $ sudo visudo 102 | 103 | And change the line containing `%sudo` to: 104 | 105 | %sudo ALL=(ALL:ALL) NOPASSWD:ALL 106 | 107 | This is everything that's required to run the Ansible playbook from 108 | this repository, which will take care of the rest of the installation. 109 | 110 | ## Run the playbook 111 | 112 | On either the target computer or on another computer, install 113 | [Git](https://git-scm.com/) and 114 | [Ansible](https://docs.ansible.com/ansible/latest/index.html) and 115 | clone this repository: 116 | 117 | $ sudo apt install git ansible 118 | $ git clone https://github.com/rtts/debian 119 | $ cd debian 120 | 121 | The playbook is divided into a number of hosts, with each host having 122 | a number of roles. The roles are meant to be composable, so you can, 123 | for example, configure a host with the `common` and `X` roles for use 124 | as a [media player](https://github.com/rtts/median). 125 | 126 | For now, however, let's assume you are setting up a personal computer 127 | that is in your physical possesion, such as a laptop or a desktop 128 | computer. Open the file `inventory.ini` and add your hostname to the 129 | `[workstations]` section and, if it's a laptop, to the `[laptops]` 130 | section. If you want the system to be able to send and receive email, 131 | provide your email credentials (optional). 132 | 133 | Now run the playbook! 134 | 135 | $ ./playbook.yml 136 | 137 | ## Using the system 138 | 139 | Congratulations! Your system has been fully set up for general use, 140 | After the system boots, you will be greeted with the following message 141 | of the day: 142 | 143 | ![Message of the day](https://raw.githubusercontent.com/rtts/debian/main/doc/motd.png) 144 | 145 | This message is shown in 146 | [rxvt-unicode](http://software.schmorp.de/pkg/rxvt-unicode.html) 147 | displayed by the tiling window manager [xmonad](https://xmonad.org/). 148 | A single terminal is automatically launched at startup. You can 149 | specify which program(s) launch at startup by editing `~/.xsession`. 150 | Also have look at the other dotfiles that were installed. 151 | 152 | > **Note** 153 | > 154 | > All dotfiles (except `.bashrc`) are placed by Ansible with the 155 | > [force](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/copy_module.html#parameter-force) 156 | > parameter set to `false`, which means that local changes will not be 157 | > overwritten when you re-run the playbook. The dotfiles are also 158 | > written to the `/etc/skel` directory, so they will be installed for 159 | > new users created by `adduser`. This is also a convenient location 160 | > to check for updates to dotfiles. 161 | 162 | Here are some useful keyboard shortcuts (note: `Mod` is mapped to the 163 | windows key by the `X` role): 164 | 165 | - `Mod` `Shift` `Enter` opens a new terminal. 166 | - `Mod` `[1-9]` switches to the virtual desktop 1 through 9. 167 | - `Mod` `P` launches `dmenu`. Type the starting letters of a 168 | graphical program, such as `chromium` or `firefox` and press Enter 169 | to launch it. 170 | - `Mod` `Tab` cycles between the windows on the current virtual 171 | desktop. 172 | - `Mod` `Space` switches between fullscreen and tiled window 173 | layouts. 174 | - `Mod` `Enter` moves a window to the top of the window stack. 175 | - When in tiled layout, `Mod` `,` and `Mod` `.` do something 176 | useful that's hard to explain. Try it out! 177 | - To change the size of tiled windows, `Mod` `H` and `Mod` `L` 178 | You can also change the size of any window by holding `Mod` 179 | and drag it using the right mouse button. 180 | - Dragging a window while holding the `Mod` key will make it 181 | floating. You can make it tiled again with `Mod` `T`. 182 | 183 | You can view all available keybindings with `Mod` `Shift` `/`. 184 | 185 | ## More possibilities 186 | 187 | Here are the things that I use this setup for: 188 | 189 | ### Web browsing 190 | 191 | The playbook has installed the web browsers Chromium and Firefox. 192 | Personally, I like to edit `/etc/chromium.d/default-flags` to add the 193 | `--incognito` flag so that Chromium will always browse incognito, and 194 | then use Firefox for all my non-incognito browsing. 195 | 196 | Both browsers include uBlock Origin through the `webext-ublock-origin-*` 197 | Debian packages. I have also heard good things about the 198 | [Firefox Multi-Account Containers](https://addons.mozilla.org/en-US/firefox/addon/multi-account-containers/) 199 | extension so you might want to give that a try. The recently introduced 200 | [Total Cookie Protection](https://blog.mozilla.org/en/products/firefox/firefox-rolls-out-total-cookie-protection-by-default-to-all-users-worldwide/) 201 | sounds very promising. 202 | 203 | I highly recommend setting your default search engine to DuckDuckGo so 204 | you have access to their incredible [Bang 205 | syntax](https://duckduckgo.com/bang). In practice, however, most of my 206 | web searches still use the `!g` bang to search Google. 207 | 208 | ### Email 209 | 210 | Email configuration is split up into two roles: 211 | 212 | 1. `mailserver` 213 | 2. `mailclient` 214 | 215 | The `mailserver` role configures Exim4 to send all outgoing emails 216 | through a smarthost of your choosing, solving the mystery of [xkcd 217 | 838](https://xkcd.com/838/): 218 | 219 | ![XKCD 838](https://imgs.xkcd.com/comics/incident.png) 220 | 221 | The `mailclient` role installs [mutt](http://www.mutt.org/) for a 222 | single user only, assuming that user is you. Launch it by typing 223 | `mutt` at the command line and be amazed at the usability of it. It 224 | has a [lengthy manual](http://www.mutt.org/doc/manual/), but for basic 225 | usage all you need is the arrow keys and the following shortcuts: 226 | 227 | - `m`: Send new email 228 | - `r`: Reply to the current email 229 | - `t`: Mark the current email for deletion/archival 230 | - `d`: Delete the marked emails 231 | - `A`: Archive the marked emails 232 | 233 | ### Lego CAD 234 | 235 | I use [LDCad](http://www.melkert.net/LDCad) to create building 236 | instructions for [my Lego models](https://jj.created.today/). It's 237 | closed-source, but I've emailed the author and he's assured me he 238 | would open source it before his death. You can also install `leocad` 239 | and `ldraw-parts` from the Debian repositories. For more information 240 | visit [LDraw.org](https://ldraw.org/). 241 | 242 | ### Photography 243 | 244 | I use [Geeqie](https://www.geeqie.org/) (`apt install geeqie`) to cull 245 | the photos after a shoot, then use 246 | [darktable](https://www.darktable.org/) (`apt install darktable`) to 247 | post-process them. Finally, I use 248 | [Photog!](https://pypi.org/project/photog/) (`pip install photog`) to 249 | generate [my photography website](https://www.superformosa.nl/). 250 | 251 | ### Audio 252 | 253 | I use [Audacity](https://www.audacityteam.org/) (`apt install 254 | audacity`) to record high-quality audio using a Focusrite Scarlet 2i2, 255 | which works phenomenally well under Linux and PulseAudio. All I needed 256 | to do was plug the device in and attach speakers, and all audio was 257 | routed correctly by default. 258 | 259 | ### Video 260 | 261 | Have a look at [FFTok](https://github.com/rtts/fftok) for some handy 262 | `ffmpeg` shortcuts to cut, split, crop, scale, combine and transcode 263 | video files. 264 | 265 | ### Programming 266 | 267 | The terminal-first computing environment configured by this playbook 268 | naturally lends itself well to all kinds of programming. Try for 269 | example Bash, Python, Perl, Ruby, or Haskell (I dare you to edit the 270 | `xmonad` configuration file!). 271 | 272 | ### Media 273 | 274 | [mpv](https://mpv.io/) is included in the `X` role by default. Read 275 | `man mpv` to find out all the available options of this grand 276 | successor to `mplayer`. 277 | 278 | Streaming services work in Firefox after [enabling 279 | DRM](https://support.mozilla.org/en-US/kb/enable-drm) but not in 280 | Chromium, because it lacks the required Widevine DRM. Please [fight 281 | for alternatives to DRM](https://www.defectivebydesign.org/). 282 | 283 | ### Gaming 284 | 285 | If you're a gamer, you probably want to install the [Steam 286 | Client](https://store.steampowered.com/about/) even though it's not 287 | open source. Personally I like playing titles that are available on 288 | the Internet Archive, using my own [custom games launcher for 289 | DOSBox](https://ialauncher.created.today/) that will automatically 290 | download and run a large number of MS-DOS games. Here's how to install 291 | and run it: 292 | 293 | $ sudo apt install dosbox 294 | $ pip install ialauncher 295 | $ ialauncher --no-fullscreen 296 | 297 | The `--no-fullscreen` argument is there because `xmonad` will already 298 | tile the IA Launcher window to be fullscreen. Alternatively, you can 299 | add your computer to the `gamestations` group to configure it as a 300 | gaming kiosk. 301 | 302 | ### Kiosks 303 | 304 | The `kiosks` group only installs the `common` and `X` roles intended 305 | for single-use setups, such as: 306 | 307 | - Digital signage 308 | - Public library 309 | - [Media player](https://github.com/rtts/median) 310 | 311 | You can turn your computer into a locked down Chromebook by adding the 312 | following to the end of `~/.xsession`: 313 | 314 | ``` 315 | exec chromium --kiosk 316 | ``` 317 | 318 | ### Work 319 | 320 | To satisfy my employer's ISO 27001 requirement, the screens of all 321 | workstations are set to lock after 15 minutes of inactivity. This 322 | is accomplished with `xautolock`, which is officially hosted by [one 323 | of the first web sites on the 324 | internet](https://en.wikipedia.org/wiki/Ibiblio#History): 325 | `http://sunsite.unc.edu/pub/Linux/X11/screensavers/`. To get rid of 326 | the auto-locking behavior, remove the file 327 | `/etc/X11/Xsession.d/90custom_autolock` and restart X with `Mod` 328 | `Shift` `Q`. 329 | 330 | It's possible to [configure mutt to connect to an Exchange 331 | server](https://jonathanh.co.uk/blog/mutt-setup/#connecting-to-exchange---davmail), 332 | but according to the linked blog: 333 | 334 | > get yourself a beer, this will probably take a couple of hours to 335 | > set up 336 | 337 | ## Maintenance 338 | 339 | Like any operating system, Debian publishes regular updates. Make it 340 | a habit to run the following commands regularly: 341 | 342 | $ sudo apt update 343 | $ sudo apt upgrade 344 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | interpreter_python = /usr/bin/python3 3 | inventory = inventory.ini 4 | stdout_callback = unixy 5 | retry_files_enabled = False 6 | strategy = free 7 | 8 | [ssh_connection] 9 | pipelining = True 10 | ssh_args = -o ControlMaster=auto -o ControlPersist=yes 11 | -------------------------------------------------------------------------------- /doc/debian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtts/debian/7261ea763cb9cd20983408fbe4898741a6de755f/doc/debian.png -------------------------------------------------------------------------------- /doc/motd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtts/debian/7261ea763cb9cd20983408fbe4898741a6de755f/doc/motd.png -------------------------------------------------------------------------------- /doc/tasksel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtts/debian/7261ea763cb9cd20983408fbe4898741a6de755f/doc/tasksel.png -------------------------------------------------------------------------------- /inventory.ini: -------------------------------------------------------------------------------- 1 | # Ansible inventory file, deliberately empty. Please add the hostnames 2 | # of your computers to the appropriate sections. 3 | 4 | [all:vars] 5 | domain_name = 6 | email_address = 7 | alternates = 8 | smtp_server = 9 | smtp_username = 10 | smtp_password = 11 | imap_server = 12 | imap_username = 13 | imap_password= 14 | 15 | [webservers] 16 | 17 | [workstations] 18 | 19 | [gamestations] 20 | 21 | [kiosks] 22 | 23 | # Note: laptops must also be workstations, gamestations, or kiosks 24 | [laptops] 25 | -------------------------------------------------------------------------------- /playbook.yml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ansible-playbook 2 | 3 | # Execute this playbook as `./playbook.yml` 4 | 5 | - hosts: all 6 | roles: 7 | - role: common 8 | tags: common 9 | 10 | - hosts: webservers 11 | roles: 12 | - role: mailserver 13 | tags: mailserver 14 | - role: database 15 | tags: database 16 | - role: webserver 17 | tags: webserver 18 | 19 | - hosts: workstations 20 | roles: 21 | - role: ssh 22 | tags: ssh 23 | - role: mailserver 24 | tags: mailserver 25 | - role: mailclient 26 | tags: mailclient 27 | - role: database 28 | tags: database 29 | - role: workstation 30 | tags: workstation 31 | - role: X 32 | tags: X 33 | - role: Xworkstation 34 | tags: Xworkstation 35 | 36 | - hosts: gamestations 37 | roles: 38 | - role: X 39 | tags: X 40 | - role: gamestation 41 | tags: gamestation 42 | 43 | - hosts: kiosks 44 | roles: 45 | - role: X 46 | tags: X 47 | 48 | - hosts: laptops 49 | roles: 50 | - role: laptop 51 | tags: laptop 52 | 53 | - hosts: wol 54 | roles: 55 | - role: wol 56 | tags: wol 57 | 58 | - hosts: ssh 59 | roles: 60 | - role: ssh 61 | tags: ssh 62 | -------------------------------------------------------------------------------- /roles/X/files/10-intel.conf: -------------------------------------------------------------------------------- 1 | # See `man intel` for all the other exciting options! 2 | Section "InputClass" 3 | Identifier "Enable TearFree option on Intel cards" 4 | MatchDriver "intel" 5 | Option "TearFree" "true" 6 | EndSection 7 | -------------------------------------------------------------------------------- /roles/X/files/Xresources/lcd: -------------------------------------------------------------------------------- 1 | Xft.autohint: 0 2 | Xft.lcdfilter: lcddefault 3 | Xft.hintstyle: hintfull 4 | Xft.hinting: 1 5 | Xft.antialias: 1 6 | Xft.rgba: rgb 7 | -------------------------------------------------------------------------------- /roles/X/files/Xresources/urxvt: -------------------------------------------------------------------------------- 1 | URxvt.font: *-terminus-medium-*-*-20-* 2 | URxvt.background: rgb:0/0/0 3 | URxvt.foreground: rgb:b2/b2/b2 4 | URxvt.color4: rgb:66/66/ff 5 | URxvt.color7: rgb:b2/b2/b2 6 | URxvt.color12: rgb:99/99/ff 7 | URxvt.color15: rgb:b2/b2/b2 8 | URxvt.scrollBar: False 9 | URxvt.visualBell: True 10 | URxvt.perl-ext: default,matcher 11 | URxvt.url-launcher: /usr/bin/x-www-browser 12 | URxvt.matcher.Button: 1 13 | -------------------------------------------------------------------------------- /roles/X/files/Xsession.d/90custom_essentials: -------------------------------------------------------------------------------- 1 | xsetroot -solid black 2 | xsetroot -cursor_name left_ptr 3 | xset b off 4 | xset s off 5 | xset s 0 0 6 | xset -dpms 7 | gsettings set org.gnome.desktop.interface color-scheme prefer-dark 8 | -------------------------------------------------------------------------------- /roles/X/files/dotfiles/.Xaliases: -------------------------------------------------------------------------------- 1 | # Show message of the day in every terminal 2 | if [[ ! $0 == -* ]]; then clear; /etc/update-motd.d/20-stats; fi 3 | 4 | alias eog='eog --fullscreen' 5 | alias xclip='xclip -selection clipboard' 6 | -------------------------------------------------------------------------------- /roles/X/files/dotfiles/.Xresources: -------------------------------------------------------------------------------- 1 | ! Leave unset to let X guess the DPI. 2 | !Xft.dpi: 96 3 | 4 | ! Leave unset to let X guess the cursor size. 5 | !Xcursor.size: 16 6 | 7 | ! This is already the default. Override as needed. 8 | !URxvt.font: *-terminus-medium-*-*-20-* 9 | -------------------------------------------------------------------------------- /roles/X/files/dotfiles/.config/mpv/mpv.conf: -------------------------------------------------------------------------------- 1 | vo=gpu 2 | hwdec=auto 3 | save-position-on-quit 4 | 5 | # Dynamic Audio Normalizer turns the volume knob up during quiet sections. 6 | af=dynaudnorm=maxgain=4 7 | -------------------------------------------------------------------------------- /roles/X/files/dotfiles/.config/user-dirs.dirs: -------------------------------------------------------------------------------- 1 | XDG_DESKTOP_DIR="$HOME/" 2 | XDG_DOWNLOAD_DIR="$HOME/" 3 | XDG_TEMPLATES_DIR="$HOME/" 4 | XDG_PUBLICSHARE_DIR="$HOME/" 5 | XDG_DOCUMENTS_DIR="$HOME/" 6 | XDG_MUSIC_DIR="$HOME/" 7 | XDG_PICTURES_DIR="$HOME/" 8 | XDG_VIDEOS_DIR="$HOME/" 9 | -------------------------------------------------------------------------------- /roles/X/files/dotfiles/.mailcap: -------------------------------------------------------------------------------- 1 | application/pdf; evince %s; test=test -n "$DISPLAY" 2 | image/*; eog %s; test=test -n "$DISPLAY" 3 | -------------------------------------------------------------------------------- /roles/X/files/dotfiles/.xmonad/xmonad.hs: -------------------------------------------------------------------------------- 1 | import XMonad 2 | import XMonad.Layout.Gaps 3 | import XMonad.Layout.Spacing 4 | import XMonad.Layout.NoBorders 5 | 6 | myManageHook = composeAll 7 | [ className =? "Gimp" --> doFloat 8 | , className =? "Clock" --> doIgnore 9 | , className =? "trayer" --> doIgnore 10 | ] 11 | 12 | myLayout = full ||| tiled 13 | where full = noBorders $ (spacing 0) $ gaps [(D,5)] $ Full 14 | tiled = smartBorders $ (spacing 2) $ gaps [(D,5),(U,35)] $ Tall 1 (1/80) 0.5 15 | 16 | main = xmonad $ def 17 | { borderWidth = 2 18 | , normalBorderColor = "#000099" 19 | , focusedBorderColor = "#ff0000" 20 | , modMask = mod4Mask 21 | , manageHook = myManageHook 22 | , layoutHook = myLayout 23 | , focusFollowsMouse = False 24 | , clickJustFocuses = True 25 | } 26 | -------------------------------------------------------------------------------- /roles/X/files/dotfiles/.xsession: -------------------------------------------------------------------------------- 1 | nm-applet & 2 | oclock -transparent -bd white -fg white -geometry 30x30-2+0 & 3 | trayer --edge top --widthtype request --align left --transparent true --alpha 255 & 4 | x-terminal-emulator & 5 | exec xmonad 6 | -------------------------------------------------------------------------------- /roles/X/files/profile.d/startx.sh: -------------------------------------------------------------------------------- 1 | # Start X if running on VT1 2 | if [ -z "$DISPLAY" ] && [ -n "$XDG_VTNR" ] && [ "$XDG_VTNR" -eq 1 ]; then 3 | # Ideally, X would be started after both the /etc/profile and ~/.profile 4 | # invocations so the PATH would be correct for everything running in X. 5 | # However, since we're starting X from /etc/profile it's necessary to first 6 | source ~/.profile 7 | exec startx 8 | fi 9 | -------------------------------------------------------------------------------- /roles/X/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install graphical packages 2 | become: true 3 | apt: 4 | state: present 5 | name: 6 | - chromium 7 | - dconf-cli 8 | - eog 9 | - evince 10 | - dconf-cli 11 | - file-roller 12 | - ghc 13 | - libghc-xmonad-contrib-dev 14 | - mpv 15 | - network-manager-gnome 16 | - pavucontrol 17 | - pulseaudio 18 | - pulseaudio-module-zeroconf 19 | - rxvt-unicode 20 | - suckless-tools 21 | - thunar 22 | - trayer 23 | - webext-ublock-origin-chromium 24 | - webext-ublock-origin-firefox 25 | - x11-apps 26 | - x11-xserver-utils 27 | - xcalib 28 | - xclip 29 | - xfonts-terminus 30 | - xinit 31 | - xinput 32 | - xmonad 33 | 34 | - block: 35 | - name: Install Firefox 36 | become: true 37 | apt: 38 | state: present 39 | name: 40 | - firefox 41 | rescue: 42 | - name: Install Firefox ESR instead 43 | become: true 44 | apt: 45 | state: present 46 | name: 47 | - firefox-esr 48 | 49 | - name: Setup autologin for user {{ ansible_user_id }} 50 | become: true 51 | template: 52 | src: override.conf 53 | dest: /etc/systemd/system/getty@tty1.service.d/ 54 | 55 | - name: Fix screen tearing on Intel 56 | become: true 57 | copy: 58 | src: 10-intel.conf 59 | dest: /etc/X11/xorg.conf.d/ 60 | 61 | - name: Enable bitmap fonts 62 | become: true 63 | file: 64 | path: /usr/share/fontconfig/conf.avail/70-no-bitmaps.conf 65 | state: absent 66 | 67 | - name: Xsession root background, root cursor, disable blanking 68 | become: true 69 | copy: 70 | src: Xsession.d/ 71 | dest: /etc/X11/Xsession.d/ 72 | 73 | - name: Xresources for fonts, colors, and LCD hinting 74 | become: true 75 | copy: 76 | src: Xresources/ 77 | dest: /etc/X11/Xresources/ 78 | 79 | - name: Bash profile to automatically start X 80 | become: true 81 | copy: 82 | src: profile.d/ 83 | dest: /etc/profile.d/ 84 | 85 | - name: Set default terminal emulator to rxvt-unicode 86 | become: true 87 | alternatives: 88 | name: x-terminal-emulator 89 | path: /usr/bin/urxvt 90 | 91 | - name: PulseAudio Zeroconf 92 | become: true 93 | lineinfile: 94 | dest: /etc/pulse/default.pa 95 | line: load-module module-zeroconf-discover 96 | 97 | - name: Dark mode 98 | command: dconf write /org/gnome/desktop/interface/color-scheme "'prefer-dark'" 99 | 100 | - name: Copy graphical dotfiles 101 | become: true 102 | copy: 103 | src: dotfiles/ 104 | dest: "{{ item.dest }}" 105 | owner: "{{ item.owner }}" 106 | force: "{{ item.force }}" 107 | loop: 108 | - dest: /home/{{ ansible_user_id }}/ 109 | owner: "{{ ansible_user_id }}" 110 | force: false 111 | - dest: /etc/skel/ 112 | owner: root 113 | force: true 114 | -------------------------------------------------------------------------------- /roles/X/templates/override.conf: -------------------------------------------------------------------------------- 1 | [Service] 2 | ExecStart= 3 | ExecStart=-/sbin/agetty --autologin {{ ansible_user_id }} --noclear %I $TERM 4 | -------------------------------------------------------------------------------- /roles/Xworkstation/files/Xsession.d/90custom_autolock: -------------------------------------------------------------------------------- 1 | # xss-lock locks the screen on suspend (it should also lock when the 2 | # screensaver is triggered, but that doesn't work, unfortunately) 3 | xss-lock xtrlock & 4 | 5 | # xautolock locks the screen after inactivity (as a workaround for the 6 | # failing xss-lock) 7 | xautolock -locker xtrlock -time 15 & 8 | -------------------------------------------------------------------------------- /roles/Xworkstation/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install graphical workstation packages 2 | become: true 3 | apt: 4 | state: present 5 | name: 6 | - gimp 7 | - inkscape 8 | - libreoffice 9 | - xautolock 10 | - xss-lock 11 | - xtrlock 12 | 13 | - name: Xsession autolock 14 | become: true 15 | copy: 16 | src: Xsession.d/ 17 | dest: /etc/X11/Xsession.d/ 18 | -------------------------------------------------------------------------------- /roles/common/files/20-stats: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | name=$(hostname) 3 | figlet -f smslant ${name^} 4 | free -m | awk 'NR==2 {printf "Memory: %s/%sMB (%.0f%%)\n", $3,$2,$3*100/$2 }' 5 | df -h -t ext4 | awk '/^\// {printf "Disk %s: %d/%dGB (%s)\n", $6,$3,$2,$5}' 6 | echo "Load average: $(cat /proc/loadavg | cut -d' ' -f1-3)" 7 | 8 | -------------------------------------------------------------------------------- /roles/common/files/30-packages: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | upgradable=$(apt list --upgradable 2> /dev/null | wc -l) 3 | if ((upgradable-- > 1)) 4 | then 5 | echo "$upgradable packages can be upgraded!" 6 | fi 7 | -------------------------------------------------------------------------------- /roles/common/files/dotfiles/.bashrc: -------------------------------------------------------------------------------- 1 | # If not running interactively, don't do anything 2 | case $- in 3 | *i*) ;; 4 | *) return ;; 5 | esac 6 | 7 | # Enable Bash completion 8 | . /usr/share/bash-completion/bash_completion 9 | 10 | # Infinite, global shell history, inspired by 11 | # https://unix.stackexchange.com/q/1288 12 | shopt -s histappend 13 | HISTSIZE=-1 14 | HISTFILESIZE=-1 15 | HISTCONTROL=ignorespace:erasedups 16 | PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND" 17 | 18 | # Some handy aliases 19 | alias cd..='cd ..' 20 | alias ls='ls -F --group-directories-first --color=auto' 21 | alias ll='ls -lh' 22 | alias la='l -a' 23 | alias l='ll' 24 | alias e=jmacs 25 | alias get='sudo apt install' 26 | alias s='apt search' 27 | ssh () { command ssh "$@" && /etc/update-motd.d/20-stats; } 28 | 29 | # Show current Git branch, if available 30 | git_prompt() { 31 | if git rev-parse --is-inside-work-tree &> /dev/null 32 | then 33 | echo -n '['$(git rev-parse --abbrev-ref HEAD 2> /dev/null) 34 | git diff --quiet && echo '] ' || echo '*] ' 35 | fi 36 | } 37 | PS1="\$(git_prompt)\u@\h:\w\$ " 38 | 39 | # Source more stuff, if available. 40 | [[ -f ~/.aliases ]] && . ~/.aliases 41 | [[ -n "$DISPLAY" ]] && [[ -f ~/.Xaliases ]] &&. ~/.Xaliases 42 | -------------------------------------------------------------------------------- /roles/common/files/inputrc: -------------------------------------------------------------------------------- 1 | # Configuration file for GNU Readline, see `man 3 readline` 2 | # for more information. 3 | 4 | set input-meta on 5 | set output-meta on 6 | 7 | # Complete both upper- and lowercase 8 | set completion-ignore-case On 9 | 10 | # Autocomplete immediately 11 | set show-all-if-ambiguous on 12 | set show-all-if-unmodified on 13 | 14 | # Don't treat symlinked directories as files 15 | set mark-symlinked-directories on 16 | -------------------------------------------------------------------------------- /roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Disable ssh password login 2 | become: true 3 | lineinfile: 4 | path: /etc/ssh/sshd_config 5 | regexp: 'PasswordAuthentication' 6 | line: 'PasswordAuthentication no' 7 | 8 | - name: Set hostname to {{ inventory_hostname_short }} 9 | become: true 10 | hostname: 11 | name: '{{ inventory_hostname_short }}' 12 | 13 | - name: Set FQDN hostname to {{ inventory_hostname_short }}.{{ domain_name | default("local", true) }} 14 | become: true 15 | lineinfile: 16 | path: /etc/hosts 17 | regexp: '^127\.0\.1\.1' 18 | line: '127.0.1.1 {{ inventory_hostname_short }}.{{ domain_name | default("local", true) }} {{ inventory_hostname_short }}' 19 | 20 | - name: Set timezone to Europe/Amsterdam 21 | become: true 22 | timezone: 23 | name: Europe/Amsterdam 24 | 25 | - name: Install common packages 26 | become: true 27 | apt: 28 | state: present 29 | name: 30 | - bash-completion 31 | - build-essential 32 | - figlet 33 | - git 34 | - joe 35 | - libnss-mdns 36 | - rsync 37 | 38 | - name: Configure keyboard 39 | become: true 40 | lineinfile: 41 | path: /etc/default/keyboard 42 | regexp: XKBOPTIONS 43 | line: 'XKBOPTIONS="ctrl:nocaps,compose:prsc,lv3:ralt_switch"' 44 | 45 | - name: Set default editor to Jmacs 46 | become: true 47 | alternatives: 48 | name: editor 49 | path: /usr/bin/jmacs 50 | 51 | - name: Configure Jmacs 52 | become: true 53 | lineinfile: 54 | path: /etc/joe/jmacsrc 55 | regexp: '^[^-]*-{{ item }}' 56 | backrefs: yes 57 | line: '-{{ item }}' 58 | loop: 59 | - nobackups 60 | - nodeadjoe 61 | - french 62 | 63 | - name: Generate SSH keys for root 64 | become: true 65 | user: 66 | name: root 67 | generate_ssh_key: true 68 | 69 | - name: Generate SSH keys for {{ ansible_user_id }} 70 | user: 71 | name: '{{ ansible_user_id }}' 72 | generate_ssh_key: true 73 | 74 | - name: Find pre-existing message-of-the-day scripts 75 | find: 76 | paths: /etc/update-motd.d 77 | file_type: any 78 | excludes: 79 | - 20-stats 80 | - 30-packages 81 | register: found_files 82 | 83 | - name: ...and delete them 84 | become: true 85 | file: 86 | path: '{{ item }}' 87 | state: absent 88 | loop: '{{ found_files["files"] | map(attribute="path") + ["/etc/motd"] }}' 89 | 90 | - name: Custom message-of-the-day 91 | become: true 92 | copy: 93 | src: '{{ item }}' 94 | dest: /etc/update-motd.d 95 | mode: 0755 96 | loop: 97 | - 20-stats 98 | - 30-packages 99 | 100 | - name: GNU Readline completion 101 | become: true 102 | copy: 103 | src: inputrc 104 | dest: /etc/ 105 | 106 | - name: Copy dotfiles 107 | become: true 108 | copy: 109 | src: dotfiles/ 110 | dest: "{{ item.dest }}" 111 | owner: "{{ item.owner }}" 112 | force: true 113 | loop: 114 | - dest: /home/{{ ansible_user_id }}/ 115 | owner: "{{ ansible_user_id }}" 116 | - dest: /etc/skel/ 117 | owner: root 118 | -------------------------------------------------------------------------------- /roles/database/files/daily_backup: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | cd /srv 4 | day=$(date +%A) 5 | su postgres -c pg_dumpall > "$day.sql" 6 | -------------------------------------------------------------------------------- /roles/database/files/monthly_backup: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | cd /srv 4 | month=$(date +%B) 5 | su postgres -c pg_dumpall > "$month.sql" 6 | -------------------------------------------------------------------------------- /roles/database/files/pg_hba.conf: -------------------------------------------------------------------------------- 1 | # TYPE DATABASE USER ADDRESS METHOD 2 | 3 | # Full access for local administrator 4 | local all postgres peer 5 | 6 | # Reject any other kind of access by administrator 7 | host all postgres all reject 8 | 9 | # Passwordless access for regular users on localhost 10 | local all all trust 11 | host all all 127.0.0.1/32 trust 12 | host all all ::1/128 trust 13 | -------------------------------------------------------------------------------- /roles/database/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: restart postgres 2 | become: true 3 | service: 4 | name: postgresql 5 | state: restarted 6 | -------------------------------------------------------------------------------- /roles/database/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install database packages 2 | become: true 3 | apt: 4 | state: present 5 | name: 6 | - postgresql 7 | - postgresql-server-dev-all 8 | 9 | - name: Passwordless database access for local users 10 | become: true 11 | copy: 12 | src: pg_hba.conf 13 | dest: /etc/postgresql/{{ item }}/main/ 14 | owner: postgres 15 | mode: 0640 16 | notify: restart postgres 17 | loop: 18 | - 15 19 | - 16 20 | 21 | - name: Daily database backups 22 | become: true 23 | copy: 24 | src: daily_backup 25 | dest: /etc/cron.daily 26 | mode: 0755 27 | 28 | - name: Monthly database backups 29 | become: true 30 | copy: 31 | src: monthly_backup 32 | dest: /etc/cron.monthly 33 | mode: 0755 34 | -------------------------------------------------------------------------------- /roles/gamestation/files/10-intel.conf: -------------------------------------------------------------------------------- 1 | Section "Screen" 2 | Identifier "Screen0" 3 | Device "Card0" 4 | Monitor "Monitor0" 5 | SubSection "Display" 6 | Modes "640x480" 7 | EndSubSection 8 | EndSection 9 | 10 | Section "Device" 11 | Identifier "Card0" 12 | Driver "intel" 13 | EndSection 14 | 15 | Section "Monitor" 16 | Identifier "Monitor0" 17 | EndSection 18 | -------------------------------------------------------------------------------- /roles/gamestation/files/dotfiles/.xsession: -------------------------------------------------------------------------------- 1 | exec ialauncher --slideshow 10 2 | -------------------------------------------------------------------------------- /roles/gamestation/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install DOSBox 2 | become: true 3 | apt: 4 | state: present 5 | name: dosbox 6 | 7 | - name: Force 640x480 resolution 8 | become: true 9 | copy: 10 | src: 10-intel.conf 11 | dest: /etc/X11/xorg.conf.d/ 12 | 13 | - name: Install IA Launcher 14 | pip: 15 | name: ialauncher 16 | state: present 17 | 18 | - name: Use IA Launcher as window manager 19 | copy: 20 | src: dotfiles/ 21 | dest: /home/{{ ansible_user_id }}/ 22 | -------------------------------------------------------------------------------- /roles/laptop/files/Xsession.d/90custom_trackpad: -------------------------------------------------------------------------------- 1 | # Eee PC 2 | xinput set-prop 'ETPS/2 Elantech Touchpad' 'libinput Tapping Enabled' 1 3 | 4 | # X260 5 | xinput set-prop 'SynPS/2 Synaptics TouchPad' 'libinput Tapping Enabled' 1 6 | 7 | # T440 8 | xinput set-prop 'Synaptics TM2668-001' 'libinput Tapping Enabled' 1 9 | 10 | # T14 11 | xinput set-prop 'Synaptics TM3471-020' 'libinput Tapping Enabled' 1 12 | 13 | # Vivobook 14 | xinput set-prop 'ELAN1200:00 04F3:303E Touchpad' 'libinput Tapping Enabled' 1 15 | 16 | # IdeaPad 17 | xinput set-prop 'FTCS0038:00 2808:0106 Touchpad' 'libinput Tapping Enabled' 1 18 | -------------------------------------------------------------------------------- /roles/laptop/files/Xsession.d/90custom_xbattbar: -------------------------------------------------------------------------------- 1 | xbattbar -c -t1 & 2 | -------------------------------------------------------------------------------- /roles/laptop/files/Xsession.d/90custom_xbindkeys: -------------------------------------------------------------------------------- 1 | xbindkeys 2 | -------------------------------------------------------------------------------- /roles/laptop/files/brightness: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dir=`find /sys/class/backlight/ -type l` 4 | device=$dir/brightness 5 | max=`cat $dir/max_brightness` 6 | stepsize=4 7 | 8 | current=`cat $device` 9 | step=$((current / stepsize)) 10 | if [ $step -eq 0 ]; then step=1; fi 11 | 12 | [ $1 == "up" ] && new=$((current + step)) 13 | [ $1 == "down" ] && new=$((current - step)) 14 | 15 | if [ $new -lt 1 ]; then new=1; fi 16 | if [ $new -gt $max ]; then new=$max; fi 17 | 18 | echo $new | sudo tee $device 19 | -------------------------------------------------------------------------------- /roles/laptop/files/dotfiles/.xbindkeysrc: -------------------------------------------------------------------------------- 1 | "brightness up" 2 | m:0x0 + c:233 3 | XF86MonBrightnessUp 4 | 5 | "brightness down" 6 | m:0x0 + c:232 7 | XF86MonBrightnessDown 8 | 9 | "volume up" 10 | m:0x0 + c:123 11 | XF86AudioRaiseVolume 12 | 13 | "volume down" 14 | m:0x0 + c:122 15 | XF86AudioLowerVolume 16 | 17 | "volume mute" 18 | m:0x0 + c:121 19 | XF86AudioMute 20 | -------------------------------------------------------------------------------- /roles/laptop/files/volume: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sink=$(pacmd list-sinks | sed -En 's/ \* index: (.)/\1/p') 4 | increment_by="10%" 5 | [ "$1" == "up" ] && pactl set-sink-volume "$sink" +"$increment_by" 6 | [ "$1" == "down" ] && pactl set-sink-volume "$sink" -"$increment_by" 7 | [ "$1" == "mute" ] && pactl set-sink-mute "$sink" toggle 8 | -------------------------------------------------------------------------------- /roles/laptop/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install laptop packages 2 | become: true 3 | apt: 4 | state: present 5 | name: 6 | - xbattbar 7 | - xbindkeys 8 | 9 | - name: Trackpad, battery bar, multimedia keys 10 | become: true 11 | copy: 12 | src: Xsession.d/ 13 | dest: /etc/X11/Xsession.d/ 14 | 15 | - name: Volume and brightness keys 16 | become: true 17 | copy: 18 | src: "{{ item }}" 19 | dest: /usr/local/bin/ 20 | mode: 0755 21 | loop: 22 | - volume 23 | - brightness 24 | 25 | - name: Lid switch 26 | become: true 27 | lineinfile: 28 | path: /etc/systemd/logind.conf 29 | regexp: HandleLidSwitch=suspend 30 | line: HandleLidSwitch=suspend 31 | state: present 32 | 33 | - name: Copy laptop dotfiles 34 | become: true 35 | copy: 36 | src: dotfiles/ 37 | dest: "{{ item.dest }}" 38 | owner: "{{ item.owner }}" 39 | force: "{{ item.force }}" 40 | loop: 41 | - dest: /home/{{ ansible_user_id }}/ 42 | owner: "{{ ansible_user_id }}" 43 | force: false 44 | - dest: /etc/skel/ 45 | owner: root 46 | force: true 47 | -------------------------------------------------------------------------------- /roles/mailclient/files/add_alias: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Adapted from http://wcm1.web.rice.edu/mutt-tips.html 3 | 4 | MESSAGE=$(cat) 5 | 6 | NEWALIAS=$(echo "${MESSAGE}" | grep ^"From: " | sed s/[\,\"\']//g | awk ' 7 | { 8 | email = $NF 9 | $1 = "" 10 | $NF = "" 11 | name = $0 12 | alias = tolower(name) 13 | gsub(/^ +/, "", alias) 14 | gsub(/ +$/, "", alias) 15 | gsub(/\. +/, "\.", alias) 16 | gsub(/ /, "_", alias) 17 | 18 | if (alias) { 19 | print "alias", alias, name, email 20 | } 21 | else { 22 | alias = email 23 | gsub(//, "", alias) 25 | print "alias", alias, email 26 | } 27 | } 28 | ' 2> /dev/null) 29 | 30 | ALIAS=$(echo "$NEWALIAS" | cut -d' ' -f2) 31 | 32 | if ! grep -q "^alias $ALIAS" ~/.mutt/aliases 33 | then 34 | echo "$NEWALIAS" >> ~/.mutt/aliases 35 | fi 36 | 37 | echo "Sender-Alias: $ALIAS" 38 | echo "${MESSAGE}" 39 | -------------------------------------------------------------------------------- /roles/mailclient/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install mutt 2 | become: true 3 | apt: 4 | state: present 5 | name: mutt 6 | 7 | - name: Place mutt configuration file 8 | template: 9 | src: muttrc 10 | dest: /home/{{ ansible_user_id }}/.mutt/ 11 | 12 | - name: Create mutt aliases file 13 | copy: 14 | content: "" 15 | dest: /home/{{ ansible_user_id }}/.mutt/aliases 16 | force: no 17 | 18 | - name: Automatically populate aliases 19 | copy: 20 | src: add_alias 21 | dest: /home/{{ ansible_user_id }}/.mutt/ 22 | mode: 0755 23 | -------------------------------------------------------------------------------- /roles/mailclient/templates/muttrc: -------------------------------------------------------------------------------- 1 | set quit = ask-yes 2 | set header_cache = ~/.mutt/header_cache 3 | set sort = reverse-date-received 4 | set mail_check = 10 5 | set timeout = 10 6 | set alias_file = "~/.mutt/aliases" 7 | set sort_alias = alias 8 | set reverse_alias = yes 9 | set auto_tag = yes 10 | set include = yes 11 | set smart_wrap = yes 12 | set mark_old = no 13 | set beep = no 14 | set beep_new = no 15 | set display_filter = ~/.mutt/add_alias 16 | set ssl_starttls = yes 17 | set ssl_force_tls = yes 18 | set reverse_name = yes 19 | set reverse_realname = no 20 | set arrow_cursor = yes 21 | set fast_reply = yes 22 | set forward_format = "FW: %s" 23 | set status_format="%m messages (%u unread) - %l" 24 | {% raw %}set index_format = "%4C %Z %{%b %d} %-25.25f (%?l?%4l&%4c?) %s"{% endraw %} 25 | 26 | auto_view text/html 27 | alternative_order text/plain text/enriched text/html 28 | bind pager previous-line 29 | bind pager k previous-line 30 | bind pager next-line 31 | bind pager j next-line 32 | bind pager previous-unread 33 | bind pager next-unread 34 | color index brightred black "~N|~O" 35 | color index brightblack black ~D 36 | color indicator black red 37 | 38 | # From https://unix.stackexchange.com/questions/290331/in-mutt-how-can-i-easily-attach-files-which-contain-spaces-in-their-name 39 | macro editor "\Cv " 40 | bind editor \e\ buffy-cycle 41 | 42 | # Shortcut to archive mail: 43 | macro index A '=Archive' 44 | 45 | {% if imap_server %} 46 | set imap_user = {{ imap_username }} 47 | set imap_pass = {{ imap_password }} 48 | set folder = imaps://{{ imap_server }}/ 49 | set spoolfile = +INBOX 50 | set record = "+Sent Items" 51 | set postponed = +Drafts 52 | mailboxes +INBOX "+Sent Items" +Drafts +Archive 53 | {% endif %} 54 | 55 | {% if email_address %} 56 | set from = {{ email_address }} 57 | {% endif %} 58 | 59 | {% if alternates %} 60 | alternates = {{ alternates }} 61 | {% endif %} 62 | 63 | {% if smtp_server and smtp_username and smtp_password %} 64 | set smtp_url="smtps://{{ smtp_username | regex_replace('@', '\@') }}:{{ smtp_password }}@{{ smtp_server }}" 65 | {% endif %} 66 | 67 | source $alias_file 68 | -------------------------------------------------------------------------------- /roles/mailserver/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: update exim4 2 | become: true 3 | command: update-exim4.conf 4 | -------------------------------------------------------------------------------- /roles/mailserver/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install mail server 2 | become: true 3 | apt: 4 | state: present 5 | name: exim4 6 | 7 | - name: Set mailname 8 | become: true 9 | copy: 10 | dest: /etc/mailname 11 | content: "{{ inventory_hostname_short }}.{{ domain_name }}\n" 12 | when: domain_name 13 | 14 | - name: Configure mail server 15 | become: true 16 | template: 17 | src: update-exim4.conf.conf 18 | dest: /etc/exim4/ 19 | notify: update exim4 20 | when: domain_name and smtp_server 21 | 22 | - name: Set SMTP credentials 23 | become: true 24 | copy: 25 | content: "{{ smtp_server }}:{{ smtp_username }}:{{ smtp_password }}\n" 26 | dest: /etc/exim4/passwd.client 27 | when: smtp_server and smtp_username and smtp_password 28 | 29 | - name: Forward all system mail to {{ email_address }} 30 | become: true 31 | lineinfile: 32 | path: /etc/aliases 33 | regexp: 'root:' 34 | line: 'root: {{ email_address }}' 35 | when: email_address 36 | -------------------------------------------------------------------------------- /roles/mailserver/templates/update-exim4.conf.conf: -------------------------------------------------------------------------------- 1 | dc_eximconfig_configtype='satellite' 2 | dc_other_hostnames='' 3 | dc_local_interfaces='127.0.0.1 ; ::1' 4 | dc_readhost='{{ inventory_hostname_short}}.{{ domain_name }}' 5 | dc_relay_domains='' 6 | dc_minimaldns='false' 7 | dc_relay_nets='' 8 | dc_smarthost='{{ smtp_server }}::{{ smtp_port | default(587) }}' 9 | CFILEMODE='644' 10 | dc_use_split_config='false' 11 | dc_hide_mailname='true' 12 | dc_mailname_in_oh='true' 13 | dc_localdelivery='mail_spool' 14 | -------------------------------------------------------------------------------- /roles/ssh/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Fetch public ssh key 2 | command: cat ~/.ssh/id_rsa.pub 3 | register: public_key 4 | changed_when: false 5 | no_log: true 6 | 7 | - name: Allow this host to access all others 8 | authorized_key: 9 | user: '{{ ansible_user_id }}' 10 | state: present 11 | key: '{{ public_key.stdout }}' 12 | delegate_to: '{{ item }}' 13 | loop: '{{ groups.all }}' 14 | failed_when: false 15 | ignore_unreachable: yes 16 | no_log: true 17 | 18 | - name: Canonicalize hostnames and forward agent 19 | become: true 20 | template: 21 | src: 10-convenience.conf 22 | dest: /etc/ssh/ssh_config.d/ 23 | when: domain_name 24 | -------------------------------------------------------------------------------- /roles/ssh/templates/10-convenience.conf: -------------------------------------------------------------------------------- 1 | CanonicalizeHostname yes 2 | CanonicalDomains {{ domain_name }} local 3 | Host *.local 4 | ForwardAgent yes 5 | StrictHostKeyChecking no 6 | UserKnownHostsFile /dev/null 7 | -------------------------------------------------------------------------------- /roles/webserver/files/nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes auto; 3 | pid /run/nginx.pid; 4 | 5 | include /etc/nginx/modules-enabled/*.conf; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | sendfile on; 13 | tcp_nopush on; 14 | tcp_nodelay on; 15 | client_max_body_size 25m; 16 | server_tokens off; 17 | include /etc/nginx/mime.types; 18 | default_type application/octet-stream; 19 | access_log /var/log/nginx/access.log; 20 | error_log /var/log/nginx/error.log; 21 | gzip on; 22 | 23 | include /etc/nginx/conf.d/*.conf; 24 | include /etc/nginx/sites-enabled/*; 25 | 26 | server { 27 | listen 80 default_server; 28 | 29 | location / { 30 | return 301 https://$host$request_uri; 31 | } 32 | 33 | location /.well-known/acme-challenge { 34 | alias /etc/nginx/challenges/$host; 35 | } 36 | } 37 | 38 | server { 39 | listen 443 ssl default_server; 40 | ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; 41 | ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; 42 | return 444; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /roles/webserver/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: reload nginx 2 | become: true 3 | service: 4 | name: nginx 5 | state: reloaded 6 | -------------------------------------------------------------------------------- /roles/webserver/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install webserver packages 2 | become: true 3 | apt: 4 | state: present 5 | name: 6 | - nginx 7 | - ssl-cert 8 | 9 | - name: Place nginx configuration file 10 | become: true 11 | copy: 12 | src: nginx.conf 13 | dest: /etc/nginx/ 14 | notify: reload nginx 15 | 16 | - name: Remove default site 17 | become: true 18 | file: 19 | path: /etc/nginx/sites-enabled/default 20 | state: absent 21 | -------------------------------------------------------------------------------- /roles/wol/README.md: -------------------------------------------------------------------------------- 1 | # Wake-on-LAN 2 | 3 | This is a rant about enabling Wake-on-LAN, a feature so forgotten that 4 | discovering it almost feels like re-inventing it. When it finally 5 | works, you can switch on the computer via the network, which is a 6 | useful and responsible thing to do when you want to conserve power by 7 | spinning up machines on demand. 8 | 9 | Unfortunately, hardware and software suppliers alike conspire against 10 | users to make this as hard as possible to enable. First, you need to 11 | scour the UEFI/BIOS settings to find something opaquely named "Power 12 | on by PCIE" or some other phrase that never uses the official term 13 | "Wake-on-LAN" as coined by the *Advanced Manageability Alliance* in 14 | 1997. 15 | 16 | Then, you need to install some obscure package called `ethtool` that's 17 | distributed by kernel.org 18 | [separately](https://mirrors.edge.kernel.org/pub/software/) from the 19 | Linux kernel, alongside forgotten beauties such as 20 | [DeCSS](https://en.wikipedia.org/wiki/DeCSS): 21 | 22 | > DeCSS is a utility for stripping Cascading Style Sheet (CSS) tags 23 | > from an HTML page. That's all it does. It has no relationship 24 | > whatsoever to encryption, copy protection, movies, software freedom, 25 | > oppressive industry cartels, Web site witch hunts, or any other bad 26 | > things that could get you in trouble. 27 | 28 | Next, you need to run `ethtool` to toggle the `g` capability of your 29 | network card, which is the single-letter abbreviation of the 30 | trademarked term MagicPacket™. I'm not kidding, read `man ethtool` if 31 | you don't believe me: 32 | 33 | wol p|u|m|b|a|g|s|f|d... 34 | Sets Wake-on-LAN options. Not all devices support 35 | this. The argument to this option is a string of 36 | characters specifying which options to enable. 37 | 38 | p Wake on PHY activity 39 | u Wake on unicast messages 40 | m Wake on multicast messages 41 | b Wake on broadcast messages 42 | a Wake on ARP 43 | g Wake on MagicPacket™ 44 | s Enable SecureOn™ password for MagicPacket™ 45 | f Wake on filter(s) 46 | d Disable (wake on nothing). This option 47 | clears all previous options. 48 | 49 | Here is the command to accomplish this: 50 | 51 | # ethtool -s wol g 52 | 53 | Now waking up the machine by sending it a MagicPacket™ should work, 54 | but only once. To persist the `g` option, you will have to re-enable 55 | it on every boot. In the old days, you could simply add the `ethtool` 56 | command to `/etc/rc.local` but nowadays even a single command like 57 | this needs their own standalone systemd service configuration: 58 | 59 | ```ini 60 | [Unit] 61 | Description=Wake-on-LAN for %i 62 | Requires=network.target 63 | After=network.target 64 | 65 | [Service] 66 | ExecStart=/usr/bin/ethtool -s %i wol g 67 | Type=oneshot 68 | 69 | [Install] 70 | WantedBy=multi-user.target 71 | ``` 72 | 73 | So that is what I initially set out to configure: Add this 74 | configuration to some hard-to-find subdirectory of systemd, then call 75 | `systemctl daemon-reload`, `systemctl enable wol`, `systemctl start 76 | wol`, and `systemctl pretty-please-with-sugar-on-top` to have the darn 77 | command run every time the computer boots. However, it turned out that 78 | supplying the correct value for the placeholder `%i` (or even finding 79 | the correct `man` page out of the 200+ systemd `man` pages that 80 | documents it) is impossible to do from a generic playbook role, 81 | because the name of the network device is unknown. (Again, in the good 82 | old days the name of the primary interface would simply be `eth0`, but 83 | they have changed it to a gibberish name like `enp1s2` to "helpfully" 84 | indicate that the device is present on bus number 2 in slot number 3, 85 | or whatever.) 86 | 87 | Moreover, the `ethtool` command will not work at all when 88 | NetworkManager is installed, according to the following warning in 89 | [this AUR package](https://aur.archlinux.org/packages/wol-systemd) 90 | that sets out to do the same thing: 91 | 92 | WARNING: You seem to have Network Manager installed. 93 | As of 1.0.6, this no longer works with NetworkManager. 94 | Use the solution from 95 | https://wiki.archlinux.org/index.php/Wake-on-LAN#NetworkManager 96 | instead. 97 | 98 | Even though that statement is not entirely correct (NetworkManager, 99 | even if installed, ignores interfaces that have been configured 100 | through `/etc/network/interfaces`), to maximize the chance of 101 | successfully enabling Wake-on-LAN I chose a different approach. I 102 | enable WoL of *every single network device* by overriding 103 | `/usr/lib/systemd/network/99-default.link` to add the `WakeOnLan` 104 | option to the `[Link]` section, as proposed by the Arch Wiki page 105 | [here](https://wiki.archlinux.org/title/Wake-on-LAN#Make_it_persistent) 106 | and in the way recommended by this comment in the [default 107 | configuration 108 | file](https://github.com/systemd/systemd/blob/main/network/99-default.link): 109 | 110 | ``` 111 | # To make local modifications, one of the following methods may be used: 112 | # 1. add a drop-in file that extends this file by creating the 113 | # /etc/systemd/network/99-default.link.d/ directory and creating a 114 | # new .conf file there. 115 | ``` 116 | 117 | Et voilà! Every network interface will now support Wake-on-LAN, if 118 | available, out-of-the-box. Was that so hard? 119 | -------------------------------------------------------------------------------- /roles/wol/files/override.conf: -------------------------------------------------------------------------------- 1 | [Link] 2 | WakeOnLan=magic 3 | -------------------------------------------------------------------------------- /roles/wol/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Enable Wake-on-LAN 2 | become: true 3 | copy: 4 | src: override.conf 5 | dest: /etc/systemd/network/99-default.link.d/ 6 | -------------------------------------------------------------------------------- /roles/workstation/files/dotfiles/.aliases: -------------------------------------------------------------------------------- 1 | # SSH Agent 2 | ssh-add -l > /dev/null || ssh-add 3 | 4 | # Emacs 5 | unalias e 2> /dev/null 6 | e() { 7 | if [[ $(emacsclient -ne '(length (frame-list))') -gt 1 ]] 8 | then 9 | emacsclient -n "$@" 10 | else 11 | emacsclient -nc "$@" 12 | fi 13 | } 14 | export EDITOR="emacsclient -c" 15 | 16 | # Python 17 | workon() { . ~/.virtualenvs/"$1"/bin/activate; } 18 | mkvirtualenv() { [[ -n $1 ]] && python3 -m venv ~/.virtualenvs/$1 && workon $1; } 19 | rmvirtualenv() { [[ -n $1 ]] && rm -r ~/.virtualenvs/$1; } 20 | resetvirtualenv() { [[ -n $1 ]] && { rmvirtualenv "$1"; mkvirtualenv $1; } } 21 | alias cv='pushd $VIRTUAL_ENV/lib/python*/site-packages' 22 | alias run='workon $(basename $(pwd)); ./manage.py runserver' 23 | 24 | # Perl 25 | eval $(perl -I$HOME/.perl5/lib/perl5 -Mlocal::lib=~/.perl5) 26 | 27 | # Ruby 28 | eval "$(rbenv init -)" 29 | 30 | # Make less understand pdf, tar, gif, etc. 31 | eval "$(lesspipe)" 32 | 33 | # When you're bored 34 | alias n='w3m news.ycombinator.com' 35 | -------------------------------------------------------------------------------- /roles/workstation/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: emacs 2 | systemd: 3 | scope: user 4 | name: emacs 5 | enabled: true 6 | state: started 7 | -------------------------------------------------------------------------------- /roles/workstation/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Install workstation packages 2 | become: true 3 | apt: 4 | state: present 5 | name: 6 | - ack 7 | - ansible 8 | - apt-file 9 | - bc 10 | - curl 11 | - emacs 12 | - jq 13 | - liblocal-lib-perl 14 | - pwgen 15 | - python3-dev 16 | - python3-pip 17 | - python3-venv 18 | - rbenv 19 | - texlive-latex-recommended 20 | - tidy 21 | - unzip 22 | - vim 23 | - w3m 24 | - whois 25 | notify: emacs 26 | 27 | - name: Copy workstation dotfiles 28 | become: true 29 | copy: 30 | src: dotfiles/ 31 | dest: "{{ item.dest }}" 32 | owner: "{{ item.owner }}" 33 | force: "{{ item.force }}" 34 | loop: 35 | - dest: /home/{{ ansible_user_id }}/ 36 | owner: "{{ ansible_user_id }}" 37 | force: false 38 | - dest: /etc/skel/ 39 | owner: root 40 | force: true 41 | --------------------------------------------------------------------------------