Merge pull request 'clanModule: Init iwd wifi credential module' (#1840) from Qubasa/clan-core:Qubasa-Qubasa-main into main

This commit is contained in:
clan-bot
2024-08-07 14:13:25 +00:00
10 changed files with 233 additions and 23 deletions

View File

@@ -1,6 +1,7 @@
{ ... }: { ... }:
{ {
flake.clanModules = { flake.clanModules = {
iwd = ./iwd;
borgbackup = ./borgbackup; borgbackup = ./borgbackup;
borgbackup-static = ./borgbackup-static; borgbackup-static = ./borgbackup-static;
deltachat = ./deltachat; deltachat = ./deltachat;

View File

@@ -0,0 +1,6 @@
---
description = "Automatically provisions wifi credentials"
---

View File

@@ -0,0 +1,77 @@
{ lib, config, ... }:
let
cfg = config.clan.iwd;
secret_path = ssid: config.clan.core.facts.services."iwd.${ssid}".secret."wifi-password".path or "";
secret_generator = name: value: {
name = "iwd.${value.ssid}";
value = {
secret."iwd.${value.ssid}" = { };
generator.prompt = "Wifi password for '${value.ssid}'";
generator.script = ''
config="
[Security]
Passphrase=$prompt_value
"
echo "$config" > $secrets/wifi-password
'';
};
};
in
{
options.clan.iwd = {
networks = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule (
{ name, ... }:
{
options = {
ssid = lib.mkOption {
type = lib.types.strMatching "^[a-zA-Z0-9._-]+$";
default = name;
description = "The name of the wifi network";
};
};
}
)
);
default = { };
description = "Wifi networks to predefine";
};
};
imports = [
(lib.mkRemovedOptionModule [
"clan"
"iwd"
"enable"
] "Just define clan.iwd.networks to enable it")
];
config = lib.mkMerge [
(lib.mkIf (cfg.networks != { }) {
# Systemd tmpfiles rule to create /var/lib/iwd/example.psk file
systemd.tmpfiles.rules = lib.mapAttrsToList (
_: value: "C /var/lib/iwd/${value.ssid}.psk 0600 root root - ${secret_path value.ssid}"
) cfg.networks;
clan.core.facts.services = lib.mapAttrs' secret_generator cfg.networks;
})
{
# disable wpa supplicant
networking.wireless.enable = false;
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
networking.wireless.iwd = {
enable = true;
settings = {
Network = {
EnableIPv6 = true;
RoutePriorityOffset = 300;
};
Settings.AutoConnect = true;
};
};
}
];
}

View File

@@ -58,6 +58,7 @@ nav:
- reference/clanModules/borgbackup-static.md - reference/clanModules/borgbackup-static.md
- reference/clanModules/borgbackup.md - reference/clanModules/borgbackup.md
- reference/clanModules/deltachat.md - reference/clanModules/deltachat.md
- reference/clanModules/iwd.md
- reference/clanModules/ergochat.md - reference/clanModules/ergochat.md
- reference/clanModules/localbackup.md - reference/clanModules/localbackup.md
- reference/clanModules/localsend.md - reference/clanModules/localsend.md

View File

@@ -43,6 +43,9 @@
"*.yaml" "*.yaml"
"*.yml" "*.yml"
]; ];
# plugins = [
# "${self'.packages.prettier-plugin-tailwindcss}/lib/node_modules/prettier-plugin-tailwindcss/dist/index.mjs"
# ];
}; };
treefmt.programs.mypy.directories = treefmt.programs.mypy.directories =
{ {

View File

@@ -60,6 +60,10 @@ class ImplFunc(GObject.Object, Generic[P, B]):
return result return result
# TODO: Reimplement this such that it uses a multiprocessing.Array of type ctypes.c_char
# all fn arguments are serialized to json and passed to the new process over the Array
# the new process deserializes the json and calls the function
# the result is serialized to json and passed back to the main process over another Array
class MethodExecutor(threading.Thread): class MethodExecutor(threading.Thread):
def __init__( def __init__(
self, function: Callable[..., Any], *args: Any, **kwargs: dict[str, Any] self, function: Callable[..., Any], *args: Any, **kwargs: dict[str, Any]

View File

@@ -54,6 +54,10 @@ class open_file(
op_key=op_key, data=selected_paths, status="success" op_key=op_key, data=selected_paths, status="success"
) )
) )
else:
self.returns(
SuccessDataClass(op_key=op_key, data=None, status="success")
)
except Exception as e: except Exception as e:
print(f"Error getting selected files: {e}") print(f"Error getting selected files: {e}")
@@ -67,6 +71,10 @@ class open_file(
op_key=op_key, data=selected_path, status="success" op_key=op_key, data=selected_path, status="success"
) )
) )
else:
self.returns(
SuccessDataClass(op_key=op_key, data=None, status="success")
)
except Exception as e: except Exception as e:
print(f"Error getting selected directory: {e}") print(f"Error getting selected directory: {e}")
@@ -80,6 +88,10 @@ class open_file(
op_key=op_key, data=selected_path, status="success" op_key=op_key, data=selected_path, status="success"
) )
) )
else:
self.returns(
SuccessDataClass(op_key=op_key, data=None, status="success")
)
except Exception as e: except Exception as e:
print(f"Error getting selected file: {e}") print(f"Error getting selected file: {e}")

View File

@@ -0,0 +1,127 @@
import dataclasses
import logging
import multiprocessing as mp
import os
import signal
import sys
import traceback
from collections.abc import Callable
from pathlib import Path
from typing import Any
log = logging.getLogger(__name__)
# Kill the new process and all its children by sending a SIGTERM signal to the process group
def _kill_group(proc: mp.Process) -> None:
pid = proc.pid
if proc.is_alive() and pid:
os.killpg(pid, signal.SIGTERM)
else:
log.warning(f"Process '{proc.name}' with pid '{pid}' is already dead")
@dataclasses.dataclass(frozen=True)
class MPProcess:
name: str
proc: mp.Process
out_file: Path
# Kill the new process and all its children by sending a SIGTERM signal to the process group
def kill_group(self) -> None:
_kill_group(proc=self.proc)
def _set_proc_name(name: str) -> None:
if sys.platform != "linux":
return
import ctypes
# Define the prctl function with the appropriate arguments and return type
libc = ctypes.CDLL("libc.so.6")
prctl = libc.prctl
prctl.argtypes = [
ctypes.c_int,
ctypes.c_char_p,
ctypes.c_ulong,
ctypes.c_ulong,
ctypes.c_ulong,
]
prctl.restype = ctypes.c_int
# Set the process name to "my_process"
prctl(15, name.encode(), 0, 0, 0)
def _init_proc(
func: Callable,
out_file: Path,
proc_name: str,
on_except: Callable[[Exception, mp.process.BaseProcess], None] | None,
**kwargs: Any,
) -> None:
# Create a new process group
os.setsid()
# Open stdout and stderr
with open(out_file, "w") as out_fd:
os.dup2(out_fd.fileno(), sys.stdout.fileno())
os.dup2(out_fd.fileno(), sys.stderr.fileno())
# Print some information
pid = os.getpid()
gpid = os.getpgid(pid=pid)
# Set the process name
_set_proc_name(proc_name)
# Close stdin
sys.stdin.close()
linebreak = "=" * 5
# Execute the main function
print(linebreak + f" {func.__name__}:{pid} " + linebreak, file=sys.stderr)
try:
func(**kwargs)
except Exception as ex:
traceback.print_exc()
if on_except is not None:
on_except(ex, mp.current_process())
# Kill the new process and all its children by sending a SIGTERM signal to the process group
pid = os.getpid()
gpid = os.getpgid(pid=pid)
print(f"Killing process group pid={pid} gpid={gpid}", file=sys.stderr)
os.killpg(gpid, signal.SIGTERM)
sys.exit(1)
# Don't use a finally block here, because we want the exitcode to be set to
# 0 if the function returns normally
def spawn(
*,
out_file: Path,
on_except: Callable[[Exception, mp.process.BaseProcess], None] | None,
func: Callable,
**kwargs: Any,
) -> MPProcess:
# Decouple the process from the parent
if mp.get_start_method(allow_none=True) is None:
mp.set_start_method(method="forkserver")
# Set names
proc_name = f"MPExec:{func.__name__}"
# Start the process
proc = mp.Process(
target=_init_proc,
args=(func, out_file, proc_name, on_except),
name=proc_name,
kwargs=kwargs,
)
proc.start()
# Return the process
mp_proc = MPProcess(name=proc_name, proc=proc, out_file=out_file)
return mp_proc

View File

@@ -36,10 +36,6 @@ disallow_untyped_calls = true
disallow_untyped_defs = true disallow_untyped_defs = true
no_implicit_optional = true no_implicit_optional = true
[[tool.mypy.overrides]]
module = "argcomplete.*"
ignore_missing_imports = true
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = "clan_cli.*" module = "clan_cli.*"
ignore_missing_imports = true ignore_missing_imports = true

View File

@@ -1,32 +1,15 @@
{ self, lib, ... }: { self, lib, ... }:
let let
wifiModule =
{ ... }:
{
# use iwd instead of wpa_supplicant
networking.wireless.enable = false;
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
networking.wireless.iwd = {
enable = true;
settings = {
Network = {
EnableIPv6 = true;
RoutePriorityOffset = 300;
};
Settings.AutoConnect = true;
};
};
};
flashInstallerModule = flashInstallerModule =
{ config, ... }: { config, ... }:
{ {
imports = [ imports = [
wifiModule self.clanModules.iwd
self.nixosModules.installer self.nixosModules.installer
]; ];
system.stateVersion = config.system.nixos.version; system.stateVersion = config.system.nixos.version;
nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux; nixpkgs.pkgs = self.inputs.nixpkgs.legacyPackages.x86_64-linux;
} }