La configuración de mi PC usando NixOS
Hace poco decidí que, tal y como ahora estoy migrando mis servidores a NixOS, ¿por qué no migrar mis equipos de trabajo también a esta distribución? Actualmente uso 3 máquinas que usan Arch Linux y para actualizarlas/mantenerlas uso scripts de Ansible. Esto me funciona de forma correcta, ya que Ansible me permite crear una infinidad de módulos para configurar los programas que uso, los servicios que corro, los puertos que expongo,… de una forma idempotente.
El único problema que he encontrado con esta forma de trabajo es que si, por ejemplo, habilito un puerto en un módulo de Ansible y por lo que sea quiero que ese puerto no esté habilitado, tengo que definir un segundo módulo que cierre el puerto, es decir, aunque el módulo del habilitado del puerto no corra, no significa que el puerto no se vaya a cerrar.
Como posible solución a esto se tiene a NixOS, que tiene un archivo de configuración que define absolutamente todo, de forma que, si se quisiera abrir un puerto, se definiría en el archivo de configuración, pero si ese puerto ya no fuera necesario y se borrara de la configuración, el puerto quedaría cerrado.
Otros beneficios con los que contaría migrando a NixOS serían:
- Rolling release o Stable: Podría configurar mis equipos para que usaran la versión estable o la última versión disponible del software.
- Rollbacks: Cada versión de la configuración de NixOS se guarda como un estado del sistema, pudiendo volver a un estado anterior desde el menú de arranque en el caso de romper algo. Esto hace al sistema prácticamente irrompible.
- Repositorio: NixOS usa el gestor de paquetes Nix, el cual cuenta con la mayor selección de paquetes del mundo.
- Dependencias versionadas: Los paquetes de NixOS y sus dependencias son almacenadas con su HASH, de forma que, si dos programas distintos necesitan una misma dependencia sólo la usarán si es exactamente la misma versión, de esta forma se garantiza el funcionamiento del paquete. En el caso de dejar de utilizar un paquete, las dependencias de este sólo serán eliminadas si era el único que usaba esa dependencia en esa versión en concreta.
Instalación
Para la instalación he decidido usar el instalador gráfico de NixOS y usar el entorno de escritorio Gnome.
Eliminación de programas
Como ya he comentado, he seleccionado el escritorio Gnome, pero en la configuración por defecto de Gnome en NixOS cuenta con una infinidad de programas que o bien no voy a usar o bien prefiero usar otra alternativa. Es por ello que defino la eliminación de los paquetes de la siguiente forma:
1 # Remove the Bloat
2 environment.gnome.excludePackages = with pkgs; [
3 baobab
4 cheese
5 ...
6 }Variables Globales
A lo largo de la configuración de mi sistema podré usar un mismo valor muchas veces, como es posible que en un futuro quiera cambiar ese valor, prefiero definirlo como una variable, de forma que, si en un futuro decidiera cambiarlo, este cambio se replicaría en todos mis archivos de configuración. Para conseguir esto definiré un archivo de variables denominado globals.nix1 que tendrá la siguiente estructura:
1# ./modules/globals.nix
2{ lib, config, ... }:
3
4{
5 options.globals.username = lib.mkOption {
6 type = lib.types.str;
7 description = "Primary user name";
8 default = "gabriel";
9 };
10}Con esto tendría definida la variable de mi nombre de usuario (en este ejemplo), de forma que si quisiera usarlo en otro archivo ya no necesitaría usar:
1 users.users.gabriel = {
2 isNormalUser = true;
3 extraGroups = [ "networkmanager" "wheel" ];
4 };Sino que podría usar:
1 users.users.${config.globals.username} = {
2 isNormalUser = true;
3 extraGroups = [ "networkmanager" "wheel" ];
4 };Bootloader
Configuración usada en el arranque del sistema, almacenado en modules/boot.nix
Servicios
Defino los servicios como aquellos programas o procesos que deseo que corran en segundo plano, como son cronjobs, programas como syncthing o programas que corren al iniciar/apagar la máquina. Estos quedarán definidos en modules/services.nix. Un extracto de este directorio sería:
1{ config, pkgs, ... }:
2let
3 SaveNixOSConfig = pkgs.writeShellScript "SaveNixOSConfig" ''
4 rsync -av --no-owner --no-group --delete /etc/nixos/ /home/${config.globals.username}/Sync/NixOS/${config.globals.syncnixos}/
5 '';
6in
7{
8 ...
9
10 services.cron = {
11 enable = true;
12 systemCronJobs = [
13 "0 * * * * ${config.globals.username} ${SaveNixOSConfig}"
14 ];
15 };
16}Docker
Al igual que los servicios, aquí definiré todos los contenedores Docker que correrá mi máquina. Un ejemplo de esta archivo sería:
1{ config, pkgs, ... }:
2
3{
4 virtualisation.docker.enable = true;
5
6 virtualisation.oci-containers = {
7 backend = "docker";
8
9 containers.qbittorrent = {
10 image = "lscr.io/linuxserver/qbittorrent:latest";
11
12 environment = {
13 PUID = "1000";
14 PGID = "1000";
15 TZ = "Europe/Madrid";
16 WEBUI_PORT = "8080";
17 TORRENTING_PORT = "6881";
18 };
19
20 volumes = [
21 "/home/${config.globals.username}/Docker/qbittorrent:/config"
22 "/home/${config.globals.username}/Docker/Downloads:/downloads"
23 ];
24
25 ports = [
26 "127.0.0.1:8080:8080"
27 ];
28
29 # Equivalent to `restart: unless-stopped`
30 autoStart = true;
31 };
32 };
33}Programas
Para mis programas en NixOS tendré 2 grandes grupos.
Programas básicos
Guardados en la carpeta como modules/programs/programs.nix, almacenará todos los programas que no requieren de una configuración adicional, por ejemplo, la versión de cmus que uso no tiene ninguna configuración adicional, por lo que iría aquí.
1{ config, pkgs, ... }:
2{
3 nixpkgs.config.allowUnfree = true;
4 environment.systemPackages = with pkgs; [
5 # Gnome Extensions
6 gnomeExtensions.blur-my-shell
7 gnomeExtensions.vitals
8 ...
9}Programas modificados
Guardados en la carpeta modules/programs/ y con el nombre del programa que corren. Estos archivos definirán, además de la instalación del programa en sí, los dotfiles o configuraciones adicionales que requiere el programa. Por ejemplo, para la configuración de mpv uso la siguiente configuración:
{ config, pkgs, ... }:
let
mpvConf = pkgs.writeText "mpv.conf" ''
fullscreen
no-osd-bar
'';
in
{
environment.systemPackages = with pkgs; [
mpv
];
# DotFiles
systemd.tmpfiles.rules = [
"d /home/${config.globals.username}/.config/mpv 0755 ${config.globals.username} users -"
"r /home/${config.globals.username}/.config/mpv/mpv.conf"
"L+ /home/${config.globals.username}/.config/mpv/mpv.conf - - - - ${mpvConf}"
];
}Donde en primer lugar defino los datos del archivo de configuración, luego defino el paquete y finalmente borro y creo un vínculo simbólico del archivo en la dirección .config (en este caso en la dirección .config/mpv).
Conclusión
Espero que este post sirva como introducción a la creación de configuraciones propias en NixOS. En mi caso, sigo aprendiendo a usar este sistema operativo, y todo lo que aquí muestro son referencias que me hubiera gustado tener al empezar.
NixOS no es una distribución para todo el mundo, pero para quien valora la reproducibilidad y el control total del sistema, el esfuerzo inicial merece la pena. El código está disponible en mi GitHub para quien prefiera usarlo como referencia junto con este artículo.
Este archivo no aparecerá en el repositorio público por seguridad, ya que en él se podrán almacenar datos confidenciales como contraseñas. ↩︎