Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre linux (publicaciones antiguas, página 23)

Getting started with Ansible

I have a server, her name is Pinky

Pinky does a lot of things but pinky has one prob­lem: Pinky is to­tal­ly hand-­made. Ev­ery­thing in it has been in­stalled by hand, con­fig­ured by hand, and main­tained by hand. This is ok.

I mean, it's ok, un­til it's not ok. It has back­ups and ev­ery­thing, but when a chance presents to, for ex­am­ple, move to a new server, be­cause I just got a nice new com­put­er ... I would need to do ev­ery­thing by hand again.

So, let's fix this us­ing tech­nol­o­gy. I have known about an­si­ble for a long time, I have used things like an­si­ble. I have used pack­er, and salt, and pup­pet, and (re­lat­ed) dock­er, and ku­ber­netes, and ter­rafor­m, and cloud­for­ma­tion, and chef, and ... you get the idea.

But I have nev­er used an­si­ble!

So, here's my plan:

  • I will start do­ing an­si­ble play­books for pinky.
  • Since an­si­ble is idem­po­ten­t, I can run the play­books on pinky and noth­ing should change.
  • I can al­so run them on the new server, and ev­ery­thing should be set up.
  • At some point the new serv­er will be suf­fi­cient­ly pinky-­like and I can switch.

So, what is ansible?

In non-tech­ni­cal terms: An­si­ble is a tool to change things on ma­chines. An­si­ble can:

  • Set­up a us­er
  • Copy a file
  • In­stall a pack­age
  • Con­fig­ure a thing
  • En­able a ser­vice
  • Run a com­mand

And so on.

Ad­di­tion­al­ly:

  • It will on­ly do things that need to be done.
  • It will do things in the re­quest­ed or­der.
  • It will do things in mul­ti­ple ma­chines.

First: inventory

The first thing I need to do is to tell an­si­ble where to run things. This is done us­ing an in­ven­to­ry file. The in­ven­to­ry file is a list of ma­chi­nes, and groups of ma­chi­nes, that an­si­ble can run things on.

Mine is very sim­ple, a file called hosts in the same di­rec­to­ry as the play­book:

[servers]
pinky ansible_user=ralsina
rocky ansible_user=rock

[servers:vars]
ansible_connection=ssh 

This defines two machines, called pinky (current server) and rocky (new server). Since rocky is still in pretty much brand new shape it has only the default user it came with, called rock. I have logged into it and done some things ansible needs:

  • En­abled ssh
  • Made it so my per­son­al ma­chine where an­si­ble runs can log in with­out a pass­word
  • In­stalled python
  • Made rock a sudoer so it can run commands as root using sudo

So, I tell ansible I can log in as ralsina in pinky and as rock in rocky, in both cases using ssh.

First playbook

I want to be able to log into these machines using my user ralsina and my ssh key. So, I will create a playbook that does that. Additionally, I want my shell fish and my prompt starship to be installed and enabled.

A play­book is just a YAML file that lists tasks to be done. We start with some gener­ic stuff like "what ma­chines to run this on" and "how do I be­come root?"

# Setup my user with some QoL packages and settings
- name: Basic Setup
  hosts: servers
  become_method: ansible.builtin.sudo
  tasks:

And then guess what? Tasks. Each task is a thing to do. Here's the first one:

    - name: Install some packages
      become: true
      ansible.builtin.package:
        name:
          - git
          - vim
          - htop
          - fish
          - rsync
          - restic
          - vim
        state: present

There "an­si­ble.builtin.­pack­age" is a mod­ule that in­stalls pack­ages. An­si­ble has tons of mod­ules, and they are all doc­u­ment­ed in the an­si­ble doc­u­men­ta­tion.

Each task can take parameters, which depend on what the module does. In this case, as you can see there's a list of packages to install, and the state means I want them to be there.

BUT while rocky is a Debian, pinky is arch (btw), so there is at least one package I need to install only in rocky. That's the next task:

    - name: Install Debian-specific packages
      become: true
      when: ansible_os_family == 'Debian'
      ansible.builtin.apt:
        name:
          - ncurses-term
        state: present

Same thing, ex­cep­t:

  • It uses a debian-specific package thing, called ansible.builtin.apt
  • It has a when clause that only runs the task if the OS family is Debian.

What nex­t? Well, more tasks! Here they are, you can un­der­stand what each one does by look­ing up the docs for each an­si­ble mod­ule.

    - name: Add the user ralsina
      become: true
      ansible.builtin.user:
        name: ralsina
        create_home: true
        password_lock: true
        shell: /usr/bin/fish
    - name: Authorize ssh
      become: true
      ansible.posix.authorized_key:
        user: ralsina
        state: present
        key: "{{ lookup('file', '/home/ralsina/.ssh/id_rsa.pub') }}"
    - name: Make ralsina a sudoer
      become: true
      community.general.sudoers:
        name: ralsina
        user: ralsina
        state: present
        commands: ALL
        nopassword: true
    - name: Create fish config directory
      ansible.builtin.file:
        path: /home/ralsina/.config/fish/conf.d
        recurse: true
        state: directory
        mode: '0755'
    - name: Get starship installer
      ansible.builtin.get_url:
        url: https://starship.rs/install.sh
        dest: /tmp/starship.sh
        mode: '0755'
    - name: Install starship
      become: true
      ansible.builtin.command:
        cmd: sh /tmp/starship.sh -y
        creates: /usr/local/bin/starship
    - name: Enable starship
      ansible.builtin.copy:
        dest: /home/ralsina/.config/fish/conf.d/starship.fish
        mode: '0644'
        content: |
          starship init fish | source

And that's it! I can run this playbook using ansible-playbook -i hosts setup_user.yml and it will do all those things on both pinky and rocky, if needed:

> ansible-playbook -i hosts setup_user.yml

PLAY [Basic Setup] ******************************

TASK [Gathering Facts] **************************
ok: [rocky]
ok: [pinky]

TASK [Install some packages] ********************
ok: [rocky]
ok: [pinky]

TASK [Install Debian-specific packages] *********
skipping: [pinky]
ok: [rocky]

TASK [Add the user ralsina] *********************
ok: [rocky]
ok: [pinky]

TASK [Authorize ssh] ****************************
ok: [rocky]
ok: [pinky]

TASK [Make ralsina a sudoer] ********************
ok: [rocky]
ok: [pinky]

TASK [Create fish config directory] *************
changed: [rocky]
changed: [pinky]

TASK [Get starship installer] *******************
ok: [rocky]
ok: [pinky]

TASK [Install starship] *************************
ok: [rocky]
ok: [pinky]

TASK [Enable starship] **************************
changed: [rocky]
changed: [pinky]

PLAY RECAP **************************************
pinky : ok=9    changed=2    unreachable=0    failed=0    skipped=1 
        rescued=0    ignored=0
rocky : ok=10   changed=2    unreachable=0    failed=0    skipped=0 
        rescued=0    ignored=0

If you look care­ful­ly you can see rocky ran one more task, and pinky skipped one (the de­bian-spe­cif­ic pack­age in­stal­la­tion), and that on­ly two things got ac­tu­al­ly ex­e­cut­ed on each ma­chine.

I could run this a dozen times from now on, and it would not do any­thing.

Did it work?

Sure, I can ssh into rocky and everything is nice:

> ssh rocky
Linux rock-5c 5.10.110-37-rockchip #27a257394 SMP Thu May 23 02:38:59 UTC 2024 aarch64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Jun 26 15:32:33 2024 from 100.73.196.129
Welcome to fish, the friendly interactive shell
Type `help` for instructions on how to use fish

ralsina in 🌐 rock-5c in ~ 

There is a star­ship promp­t, and I can use fish. And I can su­do. Nice!

I can now change the inventory so rocky also uses the ralsina user and delete the rock user.

Next steps

There is a lot more to an­si­ble, specif­i­cal­ly roles but this is al­ready enough to get use­ful things done, and hope­ful­ly it will be use­ful to you too.

Cheap man's secret handling

I run a very cheap home serv­er. How cheap? Very, very cheap. Sub Rasp­ber­ry Pi 4 cheap.

It runs a ton of ser­vices, and it al­so works as my "Func­tion­s" serv­er.

What is a func­tions server?

It's a cheap man's AWS Lamb­da which al­lows me to cre­ate small "ser­vices" like this, and de­ploy them au­to­mat­i­cal­ly. It's re­al­ly a game chang­er for sim­ple code avail­abil­i­ty, and it's my favourite way to share func­tion­al­i­ty with oth­er­s.

But some­times a ser­vice re­lies on a 3rd par­ty API, and it needs things like a to­ken to be avail­able. Faasd sup­ports this us­ing their se­cret API. You cre­ate a se­cret like this:

faas-cli secret create whatever

And when you declare in your functions.yml that your function needs a secret:

myfunc:
  lang: python3-fastapi
  handler: ./myfunc
  secrets:
  - whatever

Your code reads a file in /var/openfaas/secrets/whatever and that's all, there is a secret on the server, your app can see it, it's not in the app's code, all good.

Ex­cept ... what hap­pens if you need to re­de­ploy faas­d? You will need to cre­ate the se­cret again! So you need to keep the se­cret some­where.

So­lu­tion: pass

I already use pass to keep many passwords, it's easy to also put secrets there. It manages everything using a git repo, so it's a known factor. You can even do things like add them all inside a faasd/ folder and then recreate them using scripts, like this:

pass faasd/whatever | faas-cli secret create whatever

pass will ask for your mas­ter passphrase, se­cret cre­at­ed. You can even pub­lish your pass re­po since ev­ery­thing in it is en­crypt­ed with gpg, so no­body can re­al­ly read it (don't do that).

So, this so­lu­tion us­es:

  • pass
  • gpg
  • git
  • faasd
  • shell
  • what­ev­er lan­guage and frame­work you use in your code

And ev­ery­thing is seam­less!

I think this is a nice ex­am­ple of how ran­dom tools can con­nect with each oth­er be­cause they all fol­low the unix con­ven­tion about mov­ing things around as tex­t.

Home Server Update May 2023

This is a longer-term up­date on the state of my home serv­er. You can read more about it in these 1 2 3 4 5 6 post­s.

Hardware

  • Got some SS­Ds for cheap, so mi­grat­ed it from HDDs to SDDs. Thanks to btrf­s, I could even do that with­out turn­ing the thing of­f.
  • Did some ex­per­i­ments to mea­sure pow­er us­age. It's around 8W when it goes full throt­tle with heavy disk us­age.
  • Work­ing on im­ple­ment­ing a UPS for the whole sys­tem.

Software

Since the last up­date I have added a few new ser­vices:

Filebrowser

I added a we­b-based file­brows­er. It's ... File­brows­er

Why? Be­cause some­times I need to man­age some files. No big deal, al­most nev­er use it.

Ebook Server

It's Kavi­ta and it's pret­ty good, if a lit­tle too ba­sic. I use it to man­age an epub col­lec­tion, and it work­s. Writ­ing scripts to au­to­mat­i­cal­ly tag and add meta­da­ta to ran­dom crap­py ebooks was fun.

Snips

A snip­pet/­paste­bin thing called snip­s.sh and it's re­al­ly nice. I added a small shell script so I can just pipe things to it. I am mak­ing the HTTPS pub­lic at snip­s.ralsi­na.me and keep­ing the SSH pri­vate in my VPN thank you very much.

Had some trou­ble mak­ing it work on ARM be­cause of a ten­sor­flow de­pen­den­cy, but it's run­ning fine.

WatchTower

Watch­tow­er is a tool that mon­i­tors your run­ning con­tain­ers and will dai­ly check if there are new ver­sion­s. If there are, it will up­grade them. Nice to have things up­date unat­tend­ed.

Conclusion

Serv­er is sta­ble

Color Coordination Using Base16

The Problem

A few days ago I wrote about how I moved from a al­l-in­clu­sive en­vi­ron­ment (KDE Plas­ma) to Qtile, a tiling win­dow man­ager, and all the things I did to have the ameni­ties I missed from Plas­ma.

One thing there that may need fur­ther ex­pla­na­tion is the sec­tion about col­or schemes be­cause the con­cepts them­selves are fair­ly un­usu­al.

So, let's dig in­to it a bit more.

This is how a few apps look by de­fault if you don't con­fig­ure things:

unconfigured apps

One is a Qt ap­p, and the oth­er is a GTK app and they look ... bad?

I mean, I don't re­al­ly care all that much, but not on­ly are the wid­get styles to­tal­ly dif­fer­en­t, but the col­ors are all over the place.

And the same hap­pens to Qtile's bar, and my ter­mi­nal (alacrit­ty) and my tmux­er (zel­li­j) and my web brows­er and so on.

Ev­ery­thing is a dif­fer­ent col­or.

In Plas­ma, a comon col­or scheme is en­forced on most apps (not in chrome, though) and it makes things much nicer.

So, what's the equiv­a­lent in the Win­dow Man­ag­er world? There is­n't one, re­al­ly, but there are bits and pieces you can make in­to ... 80% of it.

Here's what I did.

My Solution

First, you need a source of con­sis­tent col­or schemes. If you just re­al­ly, re­al­ly, re­al­ly like a sin­gle one, then they may have a site full of con­fig­u­ra­tions for dif­fer­ent apps to force them to fol­low it.

For ex­am­ple: Drac­u­la or Nord

But I want­ed to be able to try a bunch and see which one I liked.

For this, there is noth­ing like base16

Base16 de­fines a stan­dard. You will have 16 col­ors. Those col­ors mean things.

  • Col­or 0 is the back­ground.
  • Col­or 1 is the fore­ground.

And so on.

Then it de­fines some oth­er things, like "If you are do­ing a 'dark' scheme, col­ors go from dark­er to lighter" and so on.

Then they pro­vide a col­lec­tion of themes, which are each 16 col­ors fol­low­ing those rules.

For ex­am­ple, this is the Bro­gram­mer Theme:

scheme: "Brogrammer"
author: "Vik Ramanujam (http://github.com/piggyslasher)"
base00: "1f1f1f"
base01: "f81118"
base02: "2dc55e"
base03: "ecba0f"
base04: "2a84d2"
base05: "4e5ab7"
base06: "1081d6"
base07: "d6dbe5"
base08: "d6dbe5"
base09: "de352e"
base0A: "1dd361"
base0B: "f3bd09"
base0C: "1081d6"
base0D: "5350b9"
base0E: "0f7ddb"
base0F: "ffffff"

Now, sup­pose you want an app to fol­low the "base 16 stan­dard" to see how it looks in Bro­gram­mer style.

All you need to do is take that ap­p's con­fig file and put the right col­or where it needs it.

So, for ex­am­ple, for the Alacrit­ty ter­mi­nal:

colors:
  # Default colors
  primary:
    background: '0x1b1918'
    foreground: '0xa8a19f'

  # Colors the cursor will use if `custom_cursor_colors` is true
  cursor:
    text: '0x1b1918'
    cursor: '0xa8a19f'

  # Normal colors
  normal:
    black:   '0x1b1918'
    red:     '0xf22c40'
    green:   '0x7b9726'
    yellow:  '0xc38418'
    blue:    '0x407ee7'
    magenta: '0x6666ea'
    cyan:    '0x3d97b8'
    white:   '0xa8a19f'

Of course edit­ing all the con­fig files ev­ery time you want to change your col­or scheme is a pain.

So the base16 project al­so col­lects tem­plates. Those are files that when com­bined with a theme gen­er­ate the con­fig­u­ra­tion file for an ap­pli­ca­tion.

This is the Alacrit­ty tem­plate:

# Base16 {{scheme-name}} - alacritty color config
# {{scheme-author}}
colors:
  # Default colors
  primary:
    background: '0x{{base00-hex}}'
    foreground: '0x{{base05-hex}}'

  # Colors the cursor will use if `custom_cursor_colors` is true
  cursor:
    text: '0x{{base00-hex}}'
    cursor: '0x{{base05-hex}}'

  # Normal colors
  normal:
    black:   '0x{{base00-hex}}'
    red:     '0x{{base08-hex}}'
    green:   '0x{{base0B-hex}}'
    yellow:  '0x{{base0A-hex}}'
    blue:    '0x{{base0D-hex}}'
    magenta: '0x{{base0E-hex}}'
    cyan:    '0x{{base0C-hex}}'
    white:   '0x{{base05-hex}}'

See those bits like {{base05-hex}}? That one gets replaced with your theme's color 5.

But again, us­ing the tem­plate for each app you want to theme is bor­ing.

So you need a tool to do that. The one I use is called flavours

What flavours does is:

  • Get all the base16 col­or themes
  • Get all the base16 app con­fig tem­plates
  • Fol­low­ing a con­fig­u­ra­tion file, gen­er­ate all the con­fig files you need.
  • Op­tion­al­ly: run com­mands so the apps no­tice their con­figs have changed.

For ex­am­ple, this is my con­fig­u­ra­tion for Alacrit­ty:

[[items]]
file = "~/.config/alacritty/alacritty.yml"
template = "alacritty"
subtemplate = "default-256"
rewrite = false

It says:

  • Generate ~/.config/alacritty/alacritty.yml
  • Use the alacritty template
  • From that template use the version called default-256 (don't worry)
  • DO NOT RECRE­ATE THE WHOLE FILE

That last bit is im­por­tan­t. That file does­n't just have the the­me, it has a lot of oth­er im­por­tant stuff, so I don't want it to just have the theme in it.

So, I had to ed­it it once and put these lines be­fore and af­ter where the theme goes:

# Start flavours
... theme goes here
# End flavours

So when I use flavours to apply a theme it will only replace that bit and leave the rest.

Since alacritty notices when the config has changed, I don't need a hook entry. In other cases you can use it. Here's mine for qtile, to let it know it should reread its config:

hook = "killall -USR1 qtile"

So, I configured this for a bunch of things, and at this point, I can just run something like flavours apply atelier-forest and ...

color coordinated apps

Yeah, they are not per­fec­t. I still need to tweak some stuff, but it's get­ting there.

Cur­rent­ly my flavours con­fig­u­ra­tion co­or­di­nates the­se:

  • Alacrit­ty
  • Qtile
  • Zel­lij
  • Rofi
  • Qt
  • Gtk (us­ing Flat­col­or)
  • VS Code
  • Qute­Brows­er

That's rough­ly ev­ery app I use (yes, I am mi­grat­ing from Chrome to Qute­Brows­er for no good rea­son)

And be­cause I am not ok, I wrote a cou­ple of tiny things to make things eas­i­er for me.

A rofi-based theme pick­er called rofi-base16:

rofi-base16 in action

And a base 16 tem­plate for Zel­li­j.

I al­so wrote an ug­ly script to set VS Code's theme but it's shame­ful, so will not be shown here.

Hope­ful­ly this ex­plains some­thing to some­one. If it does­n't, well, it will help me when I in­evitably for­get how this works :-)

Desktop setup

Introduction

I am a long-term KDE us­er. How long ter­m? Well, since 1997 or so with in­ter­mit­ten­cies. A cou­ple of weeks ago, I switched my per­son­al desk­tops to QTile.

Manda­to­ry screen­shot:

A desktop

Why?

Well, I like to switch things around ev­ery few years. I have used Uni­ty, and MATE, and even LxQt at some point, and I have no­ticed over the years that I don't tend to over­lap win­dows, so tiling win­dow man­agers are the nat­u­ral pro­gres­sion of my us­age.

For a few years I have used Krohnkite an awe­some KWin ex­ten­sion that has re­al­ly been a plea­sure to use and proved me that a tiling WM, with a few rea­son­able key­board short­cut­s, was a work­able so­lu­tion.

Late­ly I have been build­ing a home­brew lap­top which has fair­ly lim­it­ed hard­ware, but I want to use for re­al. So I al­so want­ed to ex­plore ways to low­er re­source us­age.

Oth­er re­lat­ed con­cern­s:

  • I have very dy­nam­ic mon­i­tor con­fig­u­ra­tions. My com­put­ers gain and lose mon­i­tor all the time, and this needs to be han­dled au­to­mat­i­cal­ly.
  • I have mul­ti­ple com­put­ers (work, home, home­brew) and they all have very dif­fer­ent set­ups and con­fig­u­ra­tions, but I want them all to work the same.
  • I use chez­moi to man­age my con­fig­u­ra­tions so I want con­fig to be sim­ple text files I can store with a min­i­mum of tem­plat­ing.
  • I want com­fort. I want disk au­to­moun­ing, vol­ume keys, blue­tooth menus, wifi con­fig­u­ra­tion, a clock, bat­tery in­for­ma­tion, no­ti­fi­ca­tions and so on.

But why QTile specif­i­cal­ly:

  • It's tiling but with a nice at­ti­tude. The whole "hard­core" thing in both tiling WM users and projects is a bit tire­some.
  • It's writ­ten, con­fig­urable and ex­tend­able in Python, which I like.
  • It works rea­son­ably out of the box. No, a black screen where you can do noth­ing with­out look­ing up short­cuts in your phone at first is too much.
  • It's "dy­nam­ic". This is one of those minute sub­cat­e­gories you can see in WM fan­dom, but ba­si­cal­ly: win­dows get placed more or less cor­rect­ly by de­fault, while oth­er WMs will make you ex­plic­it­ly han­dle each win­dow as it ap­pears, which sounds sort of like tor­ture to me.

So what?

So I will now de­scribe painstak­ing­ly ev­ery de­ci­sion I made as of to­day, and de­scribe the im­por­tant con­fig­u­ra­tion, and bore any­one who tries to read this to tears, that's what.

In an at­tempt to make this a bit less an­noy­ing I will set­up a prob­lem, then de­scribe the so­lu­tion I found.

Uniform UI for utilities

The Problem

There are a num­ber of ways you in­ter­act with your "desk­top". You may want to lo­gout. Or start an ap­p. Or run a com­mand. Or con­fig­ure a blue­tooth speak­er. Or see the clip­board his­to­ry. Or con­fig­ure wifi.

In the desk­top world, this all comes in­clud­ed in the pack­age deal. In the Win­dow Man­ag­er uni­verse, you have to roll your own, and most set­ups end with a dis­parate col­lec­tion of util­i­ties, like blue­man and net­work-­man­ag­er and clip­it and so on plus what­ev­er the WM it­self pro­vides.

Well, but what if they could be the same thing?

What if in ad­di­tion they could all share a love­ly, cus­tom­iz­a­ble UI?

The Solution

En­ter rofi

the most com­mon us­age for rofi is as an app launcher, and it's great at that, but it's just scratch­ing the sur­face:

rofi as launcher

Be­cause of its flex­i­ble ar­chi­tec­ture, it can be used to con­fig­ure your net­work us­ing net­work­man­ager-d­menu:

rofi as network manager

As your pow­er/l­o­gout menu via rofi-pow­er-­menu

rofi as power menu

And much more. I al­so use it as a front­ed to a pas­word man­ag­er, a fron­tend to ssh, a sim­ple To­do list, an emo­ji pick­er, as a fan­cy win­dow switcher, and as a clip­board his­to­ry man­ag­er us­ing rofi-­green­clip

rofi as clipboard manager

Rofi starts so fast that it's of­ten im­posi­ble to no­tice the dif­fer­ence be­tween hav­ing a tool al­ways run­ning and with an icon in the systray and just hav­ing a but­ton that launch­es rofi.

So, how do I use it in my ~/.config/qtile/config.py?

Bound to some key short­cut­s:

keys = [
    # Launch things
    Key([mod], 'p', lazy.spawn('rofi -show drun'), desc="Run app"),
    Key([mod, "shift"], 'p', lazy.spawn('rofi -show run'), desc="Run Command"),
    Key([mod], 's', lazy.spawn('rofi -show ssh'), desc="Ssh"),
    Key([mod], 'e', lazy.spawn('rofi -show emoji'), desc="Emoji"),
    Key([mod], 'v', lazy.spawn('rofi -modi "clipboard:greenclip print" -show'), desc="Clipboard")
    Key([mod, alt], "Tab", lazy.spawn('rofi -show window'), desc="Move focus to next window"),

I al­so added a Launch­Bar wid­get to my QTile bar where it re­places some­thing like clip­it, the blue­man ap­plet, a pass­word man­ag­er and a to­do ap­p:

    widget.LaunchBar(padding=0, text_only=True, font="Font Awesome 6 Free", fontsize=16, foreground="2e3440", progs=[
        ("", "rofi-bluetooth", "Bluetooth"),
        ("", "rofi -modi 'clipboard:greenclip print' -show", "Clipboard History"),
        ("", "rofi-pass", "Passwords"),
        ("", "rofi-todo -f /home/ralsina/todo.txt", "Todo"),
        ("", "flameshot gui", "Screenshot"),
    ]),

The first field that prob­a­bly looks like a square in there are uni­code char­ac­ters dis­played on the desk­top us­ing font awe­some:

launchbar that uss rofi

Al­so for mouse in­ter­ac­tions with the Wlan wid­get:

widget.Wlan(format=" {percent:2.0%}",width=54, mouse_callbacks={'Button1': lazy.spawn('networkmanager_dmenu')}),

I even use it to show me the short­cut list in case I for­got any!

rofi showing shortcuts

I could write some mi­nor code and use it for oth­er things too. Rofi is a mag­nif­i­cent ex­am­ple of a tool with a nice API that you can lever­age to make it do many dif­fer­ent things.

Display Configuration

The Problem

My dis­play con­fig­u­ra­tions are many. I use note­book­s, so they all have one "de­fault" con­fig­u­ra­tion where they use their own screen.

In one case, it's all it can use.

In an­oth­er, it's per­ma­nent­ly docked to a mon­i­tor above its screen, but some­times I close the note­book and the on­ly dis­play is the mon­i­tor.

In an­oth­er it's usu­al­ly docked to a mon­i­tor be­sides its screen, but it's shared with my work com­put­er, so it los­es and re­cov­ers that mon­i­tor dozens of times via a HD­MI switch through the day as I switch tasks.

While QTile han­dles grace­ful­ly the ad­di­tion and re­moval of dis­plays in gen­er­al, there are some is­sues.

  • Us­ing the HD­MI switch did­n't make the mon­i­tor "go away" which caused some is­sues with bar­ri­er
  • The con­fig file de­scribes the "bars" for each screen. So I need to put the main bar that has the systray on a screen that is al­ways on. But the screens are one per-out­put and in a ma­chine-de­pen­dent or­der, and things got com­pli­cat­ed re­al­ly quick. Here Plas­ma does the right thing ef­fort­less­ly.

The Solution

En­ter au­toran­dr an awe­some tool that will re­act to changes in your sit­u­a­tion and do the right thing.

How?

  • Set your machine as you normally use it say, with two monitors using arandr or something. Then autorandr -s two-monitors
  • Set your machine in its alternate config, say, with the lid closed. Then autorandr -s closed-lid
  • Put autorandr -c in your ~.xprofile

This is enough for most things, ex­cept the bar sit­u­a­tion in QTile.

For that, I wrote two things. First a shell script that goes in ~/.config/autorandr/postswitch This is a "hook" that gets executed after the display configuration changes. Here's mine:

#!/bin/bash -x

config="$AUTORANDR_CURRENT_PROFILE"
notify-send "Switched to config $AUTORANDR_CURRENT_PROFILE"
killall -USR1 qtile  # Make qtile reread configuration

And why do I want qtile to reread its con­fig­u­ra­tion? Be­cause I want the systray to be on the "last" screen, which is al­ways larg­er :-)

Here's the relevant fragment from ~/.config/qtile/conf.py

screen_count = len(subprocess.check_output(shlex.split("xrandr --listmonitors")).splitlines()) -1

screens = [
    Screen(
        bottom=bar.Bar(
            [ ... # List of widgets for this bar
            # Add systray in the LAST screen, otherwise invisible widget
            widget.Systray(padding=10) if i==screen_count-1 else widget.Sep(linewidth=0),),
    ) for i in range(screen_count)]  

This so­lu­tion re­quires a min­i­mum of set­up for my stan­dards (just con­fig­ure your screens once and a bit of code) and a max­i­mum of ben­e­fit:

  • The same con­fig works on all my ma­chi­nes, ex­cept for au­toran­dr pro­files, which have to be done on each com­put­er
  • It works in a to­tal­ly au­to­mat­ed man­ner, I nev­er need to move screens around
  • It will work on any fu­ture com­put­ers thank to chez­moi

Chrome hates WMs

The Problem

I have seen it a few times in the past. I change WMs or desk­tops and sud­den­ly chrome has no idea where my pass­words wen­t. They are there in pass­word­s.­google.­com but chrome nev­er aut­ofill­s, and they are not list­ed in my lo­cal pro­file.

The Solution

Make sure you are running gnome-keyring-daemon and add this in ~/.config/chrome-flags.conf

--password-store=gnome

The Other Solution

You want a pass­word store that is not chrome. I have one in ad­di­tion of chrome, us­ing pass

This keeps my pass­words en­crypt­ed in Git (y­ou can use GitHub, I use my per­son­al gitea) and you can ac­cess them from the com­mand line, from a Chrome ex­ten­sion, or from a but­ton in my desk­top bar us­ing rofi-­pass

This en­sures I nev­er will lose ac­cess to my pass­word­s, and max­i­mizes the ways I can get to them while keep­ing things nice and se­cure.

Automounting Devices

The Problem

While I know per­fect­ly well how to mount de­vices in Lin­ux, I'd rather they mount them­selves.

The Solution

Use ud­iskie it's lightweight, nice­ly in­te­grat­ed, and it just works well by de­fault.

Notifications

The Problem

I want desk­top no­ti­fi­ca­tion­s.

The solution

Use dun­st it's lightweight, nice­ly in­te­grat­ed, and it just works well by de­fault.

Screen Locking

The Problem

I want my screen to lock, but don't want any­thing fan­cy.

The Solution

Use xss-lock and slock

While slock in itself works great, xss-lock integrates it with loginctl so it works better. Just add this to your ~/.xprofile:

xss-lock slock &

Pretty Icons and Colors

The Problem

I want sim­ple icons that are not very col­or­ful, so they in­te­grate with what­ev­er col­or scheme I am us­ing.

The Solution

Since I am fol­low­ing more or less a nord col­or scheme I am us­ing a com­bi­na­tion of Nordzy icons and just us­ing font-awe­some

Here are some snip­pets from the qtile con­fig. Where I am point­ing icons to a spe­cif­ic fold­er, that fold­er con­tains copies of nordzy icons con­vert­ed to PNG with trans­par­ent back­ground­s:

bottom=bar.Bar(
    [ ...
        widget.GroupBox(highlight_method="block", foreground="2e3440", active="d08770", inactive="4c566a", this_current_screen_border="242831",this_screen_border="88c0d0"),
        widget.BatteryIcon(theme_path="/home/ralsina/.config/qtile/battery_icons", background="d8dee9", mouse_callbacks=power_callbacks),
        
        widget.Volume(fmt="",padding=0, theme_path="/home/ralsina/.config/qtile/battery_icons"),
        
        widget.LaunchBar(padding=0, text_only=True, font="Font Awesome 6 Free", fontsize=16, foreground="2e3440", progs=[
                    ("", "rofi-bluetooth", "Bluetooth"),
                    ("", "rofi -modi 'clipboard:greenclip print' -show", "Clipboard History"),
                    ("", "rofi-pass", "Passwords"),
                    ("", "rofi-todo -f /home/ralsina/todo.txt", "Todo"),
                    ("", "flameshot gui", "Screenshot"),
                ]),
    ],
    background=["d8dee9"],
    border_width=[1, 0, 0, 0],  # Draw top and bottom borders
    border_color=["b48ead","b48ead","b48ead","b48ead" ],  # Borders are magenta

Then I configured styles and icons for gtk apps (using lxappearance) and Qt apps (using qt5ct) and things now look decent.

Color Schemes

The Problem

I want a co­or­di­nat­ed col­or scheme in the dis­parate tools I am us­ing.

The Solution

Up­date: I wrote a more tu­to­ri­al-­like thing about this called Col­or Co­or­di­na­tion Us­ing Base16

En­ter flavours, a tool to gen­er­ate con­fig­u­ra­tions for dif­fer­ent things out of sim­ple tem­plates and pre­de­fined col­or schemes.

It al­ready sup­port­ed my ter­mi­nal (alacrit­ty) and rofi, just need­ed to cre­ate tem­plates for my qtile con­fig:

colors = {
    "background": "#{{base00-hex}}",
    "background-lighter": "#{{base01-hex}}",
    "selection-background": "#{{base02-hex}}",
    "comments": "#{{base03-hex}}",
    "foreground-dark": "#{{base04-hex}}",
    "foreground": "#{{base05-hex}}",
    "foreground-light": "#{{base06-hex}}",
    "background-light": "#{{base07-hex}}",
    "variables": "#{{base08-hex}}",
    "integers": "#{{base09-hex}}",
    "bold": "#{{base0A-hex}}",
    "strings": "#{{base0B-hex}}",
    "quotes": "#{{base0C-hex}}",
    "headings": "#{{base0D-hex}}",
    "italic": "#{{base0E-hex}}",
    "tags": "#{{base0F-hex}}",
}

And my tmux­er (zel­li­j):

themes {
    default {
        fg "#{{base05-hex}}"
        bg "#{{base00-hex}}"
        black "#{{base00-hex}}"
        red "#{{base08-hex}}"
        green "#{{base0B-hex}}"
        yellow "#{{base0A-hex}}"
        blue "#{{base0D-hex}}"
        magenta "#{{base0E-hex}}"
        cyan "#{{base0C-hex}}"
        white "#{{base05-hex}}"
        orange "#{{base09-hex}}"
    }
}

And put the right in­can­ta­tions in the flavours con­fig (par­tial):

[[items]]
file = "~/.config/qtile/colors.py"
template = "ralsina"
subtemplate = "qtile"
rewrite = true
hook = "killall -USR1 qtile"

[[items]]
file = "~/.config/zellij/themes/default.kdl"
template = "ralsina"
subtemplate = "zellij"
rewrite = true

I will have to configure everything I want to follow my colour scheme but I can apply it to everything with a simple command like flavours apply monokai

As an ex­tra, here's a script that lets you choose the col­or scheme us­ing rofi:

#!/bin/sh

LAST_SCHEME=$(cat ~/.local/share/flavours/lastscheme)
SELECTED=$(flavours list | sed 's/\s/\n/g' | grep -n $LAST_SCHEME | cut -d: -f1)

flavours apply $(flavours list| sed 's/\s/\n/g' | rofi -dmenu -selected-row $SELECTED)

rofi choosing a color scheme

Conclusion

What can I say, I like it, it's not ug­ly, has all the func­tion­al­i­ty I want­ed and us­es around 400MB of RAM be­fore I start chrome. I am quite en­joy­ing it.

While this site (in­ten­tion­al­ly) has no com­ments, feel free to dis­cuss this in Red­dit


Contents © 2000-2025 Roberto Alsina