Merge pull request 'clanModule: Init iwd wifi credential module' (#1840) from Qubasa/clan-core:Qubasa-Qubasa-main into main
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
6
clanModules/iwd/README.md
Normal file
6
clanModules/iwd/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
description = "Automatically provisions wifi credentials"
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
77
clanModules/iwd/default.nix
Normal file
77
clanModules/iwd/default.nix
Normal 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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 =
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
127
pkgs/clan-app/clan_app/components/executor.py
Normal file
127
pkgs/clan-app/clan_app/components/executor.py
Normal 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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user