nix: Make nix the root
Which means now I'll have to think about real prefixes in commit names.
This commit is contained in:
parent
550eed06e0
commit
ee178b7d57
190 changed files with 5 additions and 6 deletions
4
os/battery.nix
Normal file
4
os/battery.nix
Normal file
|
@ -0,0 +1,4 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = with pkgs; [ powertop ];
|
||||
}
|
78
os/common.nix
Normal file
78
os/common.nix
Normal file
|
@ -0,0 +1,78 @@
|
|||
{ pkgs, lib, ... }:
|
||||
{
|
||||
networking.domain = "geoffrey.frogeye.fr";
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
time.timeZone = "Europe/Amsterdam";
|
||||
|
||||
# TODO qwerty-fr for console
|
||||
|
||||
# Enable CUPS to print documents
|
||||
services.printing.enable = true;
|
||||
|
||||
# Enable passwordless sudo
|
||||
security.sudo.extraRules = [{
|
||||
groups = [ "wheel" ];
|
||||
commands = [{
|
||||
command = "ALL";
|
||||
options = [ "NOPASSWD" ];
|
||||
}];
|
||||
}];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
wget
|
||||
kexec-tools
|
||||
openvpn
|
||||
update-resolv-conf # TODO Is it what I think it is?
|
||||
|
||||
# android tools
|
||||
android-udev-rules
|
||||
|
||||
# Needed for all the fetchFromGit in this repo on nixos-rebuild
|
||||
git
|
||||
];
|
||||
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
|
||||
programs = {
|
||||
# Enable compilation cache
|
||||
ccache.enable = true;
|
||||
# TODO Not enough, see https://nixos.wiki/wiki/CCache.
|
||||
# Might want to see if it's worth using on NixOS
|
||||
gnupg.agent.enable = true;
|
||||
|
||||
# Let users mount disks
|
||||
udevil.enable = true;
|
||||
};
|
||||
|
||||
services = {
|
||||
# Enable the OpenSSH daemon
|
||||
openssh.enable = true;
|
||||
|
||||
# Time sychronisation
|
||||
chrony = {
|
||||
enable = true;
|
||||
servers = map (n: "${toString n}.europe.pool.ntp.org") (lib.lists.range 0 3);
|
||||
extraConfig = "rtcsync"; # See 23.11 release notes
|
||||
};
|
||||
|
||||
# Prevent power button from shutting down the computer.
|
||||
# On Pinebook it's too easy to hit,
|
||||
# on others I sometimes turn it off when unsuspending.
|
||||
logind.extraConfig = "HandlePowerKey=ignore";
|
||||
|
||||
};
|
||||
|
||||
# FIXME services.openvpn.servers.<name>.updateResolvConf=true
|
||||
# For profiles in the extensions
|
||||
|
||||
# TODO Hibernation?
|
||||
|
||||
# TEST
|
||||
system.copySystemConfiguration = true;
|
||||
|
||||
# Use defaults from
|
||||
system.stateVersion = "23.05";
|
||||
|
||||
}
|
38
os/desktop.nix
Normal file
38
os/desktop.nix
Normal file
|
@ -0,0 +1,38 @@
|
|||
{ pkgs, lib, config, ... }:
|
||||
{
|
||||
config = lib.mkIf config.frogeye.desktop.xorg {
|
||||
# Enable the X11 windowing system
|
||||
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
windowManager.i3.enable = true;
|
||||
displayManager.defaultSession = "none+i3";
|
||||
|
||||
# Keyboard layout
|
||||
extraLayouts.qwerty-fr = {
|
||||
description = "QWERTY-fr";
|
||||
languages = [ "fr" ];
|
||||
symbolsFile = "${pkgs.stdenv.mkDerivation {
|
||||
name = "qwerty-fr-keypad";
|
||||
src = builtins.fetchGit {
|
||||
url = "https://github.com/qwerty-fr/qwerty-fr.git";
|
||||
rev = "3a4d13089e8ef016aa20baf6b2bf3ea53de674b8";
|
||||
};
|
||||
patches = [ ./qwerty-fr-keypad.diff ];
|
||||
# TODO This doesn't seem to be applied... it's the whole point of the derivation :(
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/linux
|
||||
cp $src/linux/us_qwerty-fr $out/linux
|
||||
runHook postInstall
|
||||
'';
|
||||
}}/linux/us_qwerty-fr";
|
||||
};
|
||||
layout = "qwerty-fr";
|
||||
};
|
||||
|
||||
# Enable sound
|
||||
sound.enable = true;
|
||||
hardware.pulseaudio.enable = true;
|
||||
};
|
||||
}
|
36
os/geoffrey.nix
Normal file
36
os/geoffrey.nix
Normal file
|
@ -0,0 +1,36 @@
|
|||
{ pkgs, config, ... }:
|
||||
{
|
||||
imports = [
|
||||
<home-manager/nixos>
|
||||
];
|
||||
|
||||
users.users.geoffrey = {
|
||||
isNormalUser = true;
|
||||
extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
|
||||
shell = pkgs.zsh;
|
||||
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPE41gxrO8oZ5n3saapSwZDViOQphm6RzqgsBUyA88pU geoffrey@frogeye.fr"
|
||||
];
|
||||
};
|
||||
|
||||
# Won't allow to set the shell otherwise,
|
||||
# even though home-manager sets it
|
||||
programs.zsh.enable = true;
|
||||
|
||||
home-manager = {
|
||||
users.geoffrey = { pkgs, ... }: {
|
||||
imports = [
|
||||
../hm/loader.nix
|
||||
];
|
||||
frogeye = config.frogeye;
|
||||
};
|
||||
# Makes VMs able to re-run
|
||||
useUserPackages = true;
|
||||
# Adds consistency
|
||||
useGlobalPkgs = true;
|
||||
};
|
||||
|
||||
# FIXME Make sure I'm the only user & everything is encrypted
|
||||
services.xserver.displayManager.autoLogin.user = "geoffrey";
|
||||
}
|
11
os/loader.nix
Normal file
11
os/loader.nix
Normal file
|
@ -0,0 +1,11 @@
|
|||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
../options.nix
|
||||
./battery.nix
|
||||
./geoffrey.nix
|
||||
./common.nix
|
||||
./desktop.nix
|
||||
./wireless.nix
|
||||
];
|
||||
}
|
10
os/qwerty-fr-keypad.diff
Normal file
10
os/qwerty-fr-keypad.diff
Normal file
|
@ -0,0 +1,10 @@
|
|||
--- ./linux/us_qwerty-fr
|
||||
+++ ./linux/us_qwerty-fr
|
||||
@@ -4,6 +4,7 @@
|
||||
{
|
||||
include "us(basic)"
|
||||
include "level3(ralt_switch)"
|
||||
+ include "keypad(oss)"
|
||||
|
||||
name[Group1]= "US keyboard with french symbols - AltGr combination";
|
||||
|
14
os/wireless.nix
Normal file
14
os/wireless.nix
Normal file
|
@ -0,0 +1,14 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
# wireless support via wpa_supplicant
|
||||
# TODO This doesn't change anything, at least in the VM
|
||||
networking.wireless = {
|
||||
enable = true;
|
||||
networks = builtins.fromJSON (builtins.readFile ./wireless/networks.json); # If this file doesn't exist, run ./wireless/import.py
|
||||
extraConfig = ''
|
||||
country=NL
|
||||
'';
|
||||
};
|
||||
environment.systemPackages = with pkgs; [ wirelesstools ];
|
||||
services.chrony.serverOption = "offline";
|
||||
}
|
2
os/wireless/.gitignore
vendored
Normal file
2
os/wireless/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
networks.json
|
||||
networks.env
|
164
os/wireless/import.py
Executable file
164
os/wireless/import.py
Executable file
|
@ -0,0 +1,164 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Exports Wi-Fi networks configuration stored in pass into a format readable by Nix.
|
||||
"""
|
||||
|
||||
# TODO EAP ca_cert=/etc/ssl/... probably won't work. Example fix:
|
||||
# builtins.fetchurl {
|
||||
# url = "https://letsencrypt.org/certs/isrgrootx1.pem";
|
||||
# sha256 = "sha256:1la36n2f31j9s03v847ig6ny9lr875q3g7smnq33dcsmf2i5gd92";
|
||||
# }
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import yaml
|
||||
|
||||
# passpy doesn't handle encoding properly, so doing this with calls
|
||||
|
||||
PASSWORD_STORE = os.path.expanduser("~/.password-store")
|
||||
SUBFOLDER = "wifi"
|
||||
SEPARATE_PASSWORDS = False
|
||||
# TODO Find a way to make then env file available at whatever time it is needed
|
||||
|
||||
|
||||
class Password:
|
||||
all: list["Password"] = list()
|
||||
|
||||
def __init__(self, path: str, content: str):
|
||||
self.path = path
|
||||
self.content = content
|
||||
|
||||
Password.all.append(self)
|
||||
|
||||
def var(self) -> str:
|
||||
# return self.path.split("/")[-1].upper()
|
||||
m = hashlib.sha256()
|
||||
m.update(self.path.encode())
|
||||
return m.hexdigest().upper()
|
||||
|
||||
def val(self) -> str:
|
||||
return self.content
|
||||
|
||||
def exists(self) -> bool:
|
||||
return not not self.content
|
||||
|
||||
def key(self) -> str:
|
||||
if SEPARATE_PASSWORDS:
|
||||
return f"@{self.var()}@"
|
||||
else:
|
||||
return self.val()
|
||||
|
||||
@classmethod
|
||||
def vars(cls) -> dict[str, str]:
|
||||
vars = dict()
|
||||
for password in cls.all:
|
||||
if not password.content:
|
||||
continue
|
||||
var = password.var()
|
||||
assert var not in vars, f"Duplicate key: {var}"
|
||||
vars[var] = password.val()
|
||||
return vars
|
||||
|
||||
|
||||
def list_networks() -> list[str]:
|
||||
paths = []
|
||||
pass_folder = os.path.join(PASSWORD_STORE, SUBFOLDER)
|
||||
for filename in os.listdir(pass_folder):
|
||||
if not filename.endswith(".gpg"):
|
||||
continue
|
||||
filepath = os.path.join(pass_folder, filename)
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
|
||||
file = filename[:-4]
|
||||
path = os.path.join(SUBFOLDER, file)
|
||||
paths.append(path)
|
||||
return paths
|
||||
|
||||
|
||||
def format_wpa_supplicant_conf(conf: dict, indent: str = "") -> str:
|
||||
lines = []
|
||||
for k, v in conf.items():
|
||||
if isinstance(v, str):
|
||||
val = '"' + v.replace('"', '\\"') + '"'
|
||||
elif isinstance(v, Password):
|
||||
val = v.key()
|
||||
elif isinstance(v, list):
|
||||
assert all(
|
||||
map(lambda i: isinstance(i, str), v)
|
||||
), "Only list of strings supported"
|
||||
val = " ".join(v)
|
||||
else:
|
||||
val = str(v)
|
||||
lines.append(f"{indent}{k}={val}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
networks = {}
|
||||
for path in list_networks():
|
||||
proc = subprocess.run(["pass", path], stdout=subprocess.PIPE)
|
||||
proc.check_returncode()
|
||||
|
||||
raw = proc.stdout.decode()
|
||||
split = raw.split("\n")
|
||||
|
||||
password = Password(path, split[0])
|
||||
data = yaml.safe_load("\n".join(split[1:])) or dict()
|
||||
# print(path, data) # DEBUG
|
||||
|
||||
# Helpers to prevent repetition
|
||||
suffixes = data.pop("suffixes", [""])
|
||||
data.setdefault("key_mgmt", ["WPA-PSK"] if password.exists() else ["NONE"])
|
||||
if password:
|
||||
if any(map(lambda m: "PSK" in m.split("-"), data["key_mgmt"])):
|
||||
data["psk"] = password
|
||||
if "NONE" in data["key_mgmt"]:
|
||||
data["wep_key0"] = password
|
||||
if any(map(lambda m: "EAP" in m.split("-"), data["key_mgmt"])):
|
||||
data["password"] = password
|
||||
assert "ssid" in data, f"{path}: Missing SSID"
|
||||
|
||||
# # Output wpa_supplicant conf, for debug
|
||||
# for suffix in suffixes:
|
||||
# wpas = data.copy()
|
||||
# wpas["ssid"] += suffix
|
||||
# print(f"# {path}")
|
||||
# print("network={")
|
||||
# print(format_wpa_supplicant_conf(wpas, indent=" "))
|
||||
# print("}")
|
||||
# print()
|
||||
|
||||
# Convert to nix configuration
|
||||
ssid = data.pop("ssid")
|
||||
network = {}
|
||||
key_mgmt = data.pop("key_mgmt", None)
|
||||
psk = data.pop("psk", None)
|
||||
priority = data.pop("priority", None)
|
||||
# No support for hidden
|
||||
# No support for extraConfig (all is assumed to be auth)
|
||||
if key_mgmt:
|
||||
network["authProtocols"] = key_mgmt
|
||||
if psk:
|
||||
network["psk"] = psk.key()
|
||||
if data:
|
||||
raise NotImplementedError(f"{path}: Unhandled non-auth extra: {data}")
|
||||
else:
|
||||
if data:
|
||||
network["auth"] = format_wpa_supplicant_conf(data)
|
||||
if priority:
|
||||
network["priority"] = int(priority)
|
||||
|
||||
for suffix in suffixes:
|
||||
networks[ssid + suffix] = network
|
||||
|
||||
with open("networks.json", "w") as fd:
|
||||
json.dump(networks, fd, indent=4)
|
||||
|
||||
with open("networks.env", "w") as fd:
|
||||
if SEPARATE_PASSWORDS:
|
||||
for k, v in Password.vars().items():
|
||||
print(f"{k}={v}", file=fd)
|
Loading…
Add table
Add a link
Reference in a new issue