Compare commits
10 Commits
fix/waybar
...
8462b3a5a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 8462b3a5a8 | |||
| d0872e7d8c | |||
| 9be9ad7675 | |||
| 5ee328cdf8 | |||
| 6532a822cd | |||
| 20a3990979 | |||
| b7ab518f58 | |||
| 3f1e34ef95 | |||
| d0abfc1625 | |||
| 031090e4ea |
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.guests.actualbudget; # NOLINT(let_order)
|
|
||||||
dataDir = "/data";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.guests.actualbudget = {
|
|
||||||
dataPath = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
example = "/srv/actualbudget";
|
|
||||||
description = "Host path bind-mounted as Actual Budget's data directory.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
assertions = [
|
|
||||||
{
|
|
||||||
assertion = cfg.dataPath != null;
|
|
||||||
message = "guests.actualbudget.dataPath must point at the host directory that stores Actual Budget data.";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
registry.homepage.${config.networking.hostName}.actualbudget = {
|
|
||||||
tab = "Apps";
|
|
||||||
section = "Other";
|
|
||||||
name = "Actual Budget";
|
|
||||||
description = "Finance";
|
|
||||||
icon = "actual-budget.png";
|
|
||||||
};
|
|
||||||
|
|
||||||
guests.actualbudget = {
|
|
||||||
interior = {
|
|
||||||
modules.services.actualbudget = {
|
|
||||||
enable = true;
|
|
||||||
inherit dataDir;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
mounts.${dataDir}.directory = cfg.dataPath;
|
|
||||||
limits = {
|
|
||||||
memory = lib.mkDefault "2G";
|
|
||||||
cpu = lib.mkDefault "200%";
|
|
||||||
tasksMax = lib.mkDefault 512;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,31 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.guests.copyparty; # NOLINT(let_order)
|
cfg = config.guests.copyparty; # NOLINT(let_order)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.guests.copyparty = {
|
||||||
|
passwordSecret = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "services/copyparty/users/${config.user.name}/password";
|
||||||
|
description = "SOPS secret name that contains the Copyparty account password.";
|
||||||
|
};
|
||||||
|
|
||||||
volumeType = lib.types.submodule {
|
statePath = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
example = "/srv/copyparty";
|
||||||
|
description = "Host path bind-mounted as Copyparty's state directory.";
|
||||||
|
};
|
||||||
|
|
||||||
|
reverseProxy.trustedSources = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "IP ranges that may supply Copyparty's forwarded client IP header.";
|
||||||
|
};
|
||||||
|
|
||||||
|
volumes = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
hostPath = lib.mkOption {
|
hostPath = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -31,48 +54,13 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
flags = lib.mkOption {
|
flags = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.attrs;
|
||||||
default = [ ];
|
default = { };
|
||||||
description = "Copyparty volume flags.";
|
description = "Copyparty volume flags.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
in
|
);
|
||||||
{
|
|
||||||
options.guests.copyparty = {
|
|
||||||
accountName = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "alexion";
|
|
||||||
description = "Copyparty account name configured for password authentication.";
|
|
||||||
};
|
|
||||||
|
|
||||||
passwordSecret = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "services/copyparty/users/alexion/password";
|
|
||||||
description = "SOPS secret name containing the Copyparty account password.";
|
|
||||||
};
|
|
||||||
|
|
||||||
stateDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/copyparty";
|
|
||||||
description = "Guest path where Copyparty stores service state.";
|
|
||||||
};
|
|
||||||
|
|
||||||
statePath = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
example = "/srv/copyparty";
|
|
||||||
description = "Host path bind-mounted as Copyparty's state directory.";
|
|
||||||
};
|
|
||||||
|
|
||||||
reverseProxy.trustedSources = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = "IP ranges that may supply Copyparty's forwarded client IP header.";
|
|
||||||
};
|
|
||||||
|
|
||||||
volumes = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf volumeType;
|
|
||||||
default = { };
|
default = { };
|
||||||
description = "Copyparty volumes keyed by their URL path.";
|
description = "Copyparty volumes keyed by their URL path.";
|
||||||
};
|
};
|
||||||
@@ -102,24 +90,26 @@ in
|
|||||||
guests.copyparty = {
|
guests.copyparty = {
|
||||||
interior.modules.services.copyparty = {
|
interior.modules.services.copyparty = {
|
||||||
enable = true;
|
enable = true;
|
||||||
inherit (cfg) accountName stateDir;
|
|
||||||
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
||||||
reverseProxy = {
|
reverseProxy = {
|
||||||
enable = cfg.reverseProxy.enable;
|
enable = cfg.reverseProxy.enable;
|
||||||
inherit (cfg.reverseProxy) trustedSources;
|
inherit (cfg.reverseProxy) trustedSources;
|
||||||
};
|
};
|
||||||
volumes = lib.mapAttrs (_urlPath: volume: {
|
volumes = lib.mapAttrs (
|
||||||
|
_urlPath: volume:
|
||||||
|
{
|
||||||
inherit (volume)
|
inherit (volume)
|
||||||
path
|
path
|
||||||
access
|
|
||||||
flags
|
|
||||||
readOnly
|
readOnly
|
||||||
;
|
;
|
||||||
}) cfg.volumes;
|
}
|
||||||
|
// lib.optionalAttrs (volume.access != { }) { inherit (volume) access; }
|
||||||
|
// lib.optionalAttrs (volume.flags != { }) { inherit (volume) flags; }
|
||||||
|
) cfg.volumes;
|
||||||
};
|
};
|
||||||
|
|
||||||
mounts = {
|
mounts = {
|
||||||
${cfg.stateDir} = {
|
"/var/lib/copyparty" = {
|
||||||
directory = cfg.statePath;
|
directory = cfg.statePath;
|
||||||
owner = config.registry."stable-uid".${config.networking.hostName}.copyparty;
|
owner = config.registry."stable-uid".${config.networking.hostName}.copyparty;
|
||||||
group = "storage";
|
group = "storage";
|
||||||
|
|||||||
@@ -71,22 +71,6 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
guests.actualbudget = {
|
|
||||||
enable = true;
|
|
||||||
vlan = 20;
|
|
||||||
mac = "BC:24:11:C1:CD:28";
|
|
||||||
dataPath = "/pikachu/data/actualbudget";
|
|
||||||
address = "10.23.20.42";
|
|
||||||
reverseProxy = {
|
|
||||||
enable = true;
|
|
||||||
route = {
|
|
||||||
domain = "budget.alexion.dev";
|
|
||||||
protocol = "https";
|
|
||||||
port = 5006;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
guests.navidrome = {
|
guests.navidrome = {
|
||||||
enable = true;
|
enable = true;
|
||||||
vlan = 20;
|
vlan = 20;
|
||||||
@@ -145,35 +129,21 @@
|
|||||||
"/" = {
|
"/" = {
|
||||||
hostPath = "/pikachu/data/copyparty/root";
|
hostPath = "/pikachu/data/copyparty/root";
|
||||||
path = "/srv";
|
path = "/srv";
|
||||||
access.r = [ "alexion" ];
|
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
};
|
};
|
||||||
"/inbox" = {
|
"/inbox" = {
|
||||||
hostPath = "/pikachu/__inbox";
|
hostPath = "/pikachu/__inbox";
|
||||||
path = "/pikachu/__inbox";
|
path = "/pikachu/__inbox";
|
||||||
access.A = [ "alexion" ];
|
|
||||||
flags = [
|
|
||||||
"d2d"
|
|
||||||
"d2t"
|
|
||||||
"dthumb"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
"/media" = {
|
"/media" = {
|
||||||
hostPath = "/pikachu/media";
|
hostPath = "/pikachu/media";
|
||||||
path = "/pikachu/media";
|
path = "/pikachu/media";
|
||||||
access.A = [ "alexion" ];
|
|
||||||
flags = [
|
|
||||||
"d2d"
|
|
||||||
"d2t"
|
|
||||||
"dthumb"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
"/opds" = {
|
"/opds" = {
|
||||||
hostPath = "/pikachu/data/opds";
|
hostPath = "/pikachu/data/opds";
|
||||||
path = "/pikachu/data/opds";
|
path = "/pikachu/data/opds";
|
||||||
access.r = [ "alexion" ];
|
|
||||||
readOnly = true;
|
readOnly = true;
|
||||||
flags = [ "opds" ];
|
flags.opds = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ in
|
|||||||
|
|
||||||
home-manager.users.${config.user.name}.programs.waybar = {
|
home-manager.users.${config.user.name}.programs.waybar = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
package = pkgs.alexion.waybar;
|
||||||
|
|
||||||
# A user service bound to graphical-session.target, which uwsm activates,
|
# A user service bound to graphical-session.target, which uwsm activates,
|
||||||
# so the bar comes up with the session and no compositor exec-once.
|
# so the bar comes up with the session and no compositor exec-once.
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
}:
|
}:
|
||||||
# PipeWire as the desktop audio server.
|
# PipeWire as the desktop audio server.
|
||||||
let
|
let
|
||||||
|
volumeKey = keys: command: {
|
||||||
|
inherit keys;
|
||||||
|
repeating = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${lib.getExe' pkgs.swayosd "swayosd-client"} --output-volume ${command}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
mediaKey = keys: command: {
|
mediaKey = keys: command: {
|
||||||
inherit keys;
|
inherit keys;
|
||||||
locked = true;
|
locked = true;
|
||||||
@@ -30,6 +39,17 @@ in
|
|||||||
};
|
};
|
||||||
|
|
||||||
registry.keybind.${config.networking.hostName} = {
|
registry.keybind.${config.networking.hostName} = {
|
||||||
|
volume-up = volumeKey "XF86AudioRaiseVolume" "raise --max-volume 100";
|
||||||
|
volume-down = volumeKey "XF86AudioLowerVolume" "lower";
|
||||||
|
mute = {
|
||||||
|
keys = "XF86AudioMute";
|
||||||
|
locked = true;
|
||||||
|
action = {
|
||||||
|
kind = "exec";
|
||||||
|
command = "${lib.getExe' pkgs.swayosd "swayosd-client"} --output-volume mute-toggle";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
media-play = mediaKey "XF86AudioPlay" "play-pause";
|
media-play = mediaKey "XF86AudioPlay" "play-pause";
|
||||||
media-next = mediaKey "XF86AudioNext" "next";
|
media-next = mediaKey "XF86AudioNext" "next";
|
||||||
media-previous = mediaKey "XF86AudioPrev" "previous";
|
media-previous = mediaKey "XF86AudioPrev" "previous";
|
||||||
|
|||||||
@@ -5,39 +5,41 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
# SwayOSD: a transient on-screen popup for volume and brightness changes.
|
# SwayOSD: a transient on-screen popup for volume and brightness changes.
|
||||||
let
|
|
||||||
user = config.user.name;
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
options.modules.desktop.utilities.osd.enable =
|
options.modules.desktop.utilities.osd.enable =
|
||||||
lib.mkEnableOption "the SwayOSD volume and brightness popup";
|
lib.mkEnableOption "the SwayOSD volume and brightness popup";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.utilities.osd.enable {
|
config = lib.mkIf config.modules.desktop.utilities.osd.enable {
|
||||||
|
environment.systemPackages = [ pkgs.swayosd ];
|
||||||
|
|
||||||
|
# The udev rule chgrps each backlight's brightness node to `video` and adds
|
||||||
|
# group write, so the server dims the panel without root.
|
||||||
|
# Membership below grants the running session that access.
|
||||||
|
services.udev.packages = [ pkgs.swayosd ];
|
||||||
|
users.users.${config.user.name}.extraGroups = [ "video" ];
|
||||||
|
|
||||||
|
# The server draws the popups, so it runs for the whole graphical session.
|
||||||
|
# It binds to the target uwsm activates.
|
||||||
|
home-manager.users.${config.user.name}.systemd.user.services.swayosd = {
|
||||||
|
Unit = {
|
||||||
|
Description = "SwayOSD on-screen display server";
|
||||||
|
PartOf = [ "graphical-session.target" ];
|
||||||
|
After = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
ExecStart = lib.getExe' pkgs.swayosd "swayosd-server";
|
||||||
|
Restart = "on-failure";
|
||||||
|
};
|
||||||
|
Install.WantedBy = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
registry.keybind.${config.networking.hostName} = {
|
registry.keybind.${config.networking.hostName} = {
|
||||||
volume-up = {
|
|
||||||
keys = "XF86AudioRaiseVolume";
|
|
||||||
repeating = true;
|
|
||||||
action = {
|
|
||||||
kind = "exec";
|
|
||||||
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
volume-down = {
|
|
||||||
keys = "XF86AudioLowerVolume";
|
|
||||||
repeating = true;
|
|
||||||
action = {
|
|
||||||
kind = "exec";
|
|
||||||
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume lower";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
brightness-up = {
|
brightness-up = {
|
||||||
keys = "XF86MonBrightnessUp";
|
keys = "XF86MonBrightnessUp";
|
||||||
repeating = true;
|
repeating = true;
|
||||||
action = {
|
action = {
|
||||||
kind = "exec";
|
kind = "exec";
|
||||||
command = "${pkgs.swayosd}/bin/swayosd-client --brightness raise";
|
command = "${lib.getExe' pkgs.swayosd "swayosd-client"} --brightness raise";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,41 +48,9 @@ in
|
|||||||
repeating = true;
|
repeating = true;
|
||||||
action = {
|
action = {
|
||||||
kind = "exec";
|
kind = "exec";
|
||||||
command = "${pkgs.swayosd}/bin/swayosd-client --brightness lower";
|
command = "${lib.getExe' pkgs.swayosd "swayosd-client"} --brightness lower";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
mute = {
|
|
||||||
keys = "XF86AudioMute";
|
|
||||||
locked = true;
|
|
||||||
action = {
|
|
||||||
kind = "exec";
|
|
||||||
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume mute-toggle";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [ pkgs.swayosd ];
|
|
||||||
|
|
||||||
# The udev rule chgrps each backlight's brightness node to `video` and adds
|
|
||||||
# group write, so the server dims the panel without root.
|
|
||||||
# Membership below grants the running session that access.
|
|
||||||
services.udev.packages = [ pkgs.swayosd ];
|
|
||||||
users.users.${user}.extraGroups = [ "video" ];
|
|
||||||
|
|
||||||
# The server draws the popups, so it runs for the whole graphical session.
|
|
||||||
# It binds to the target uwsm activates.
|
|
||||||
home-manager.users.${user}.systemd.user.services.swayosd = {
|
|
||||||
Unit = {
|
|
||||||
Description = "SwayOSD on-screen display server";
|
|
||||||
PartOf = [ "graphical-session.target" ];
|
|
||||||
After = [ "graphical-session.target" ];
|
|
||||||
};
|
|
||||||
Service = {
|
|
||||||
ExecStart = "${pkgs.swayosd}/bin/swayosd-server";
|
|
||||||
Restart = "on-failure";
|
|
||||||
};
|
|
||||||
Install.WantedBy = [ "graphical-session.target" ];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
}:
|
}:
|
||||||
# Keybound screen recording: pick a region, then toggle a video-only capture.
|
# Keybound screen recording: pick a region, then toggle a video-only capture.
|
||||||
let
|
let
|
||||||
# A single key both starts and stops.
|
# A single key starts and stops recording.
|
||||||
# A running wf-recorder is stopped with SIGINT so it finalises the file.
|
# Waybar refreshes on state changes instead of polling.
|
||||||
# Otherwise slurp picks a region and wf-recorder runs in the foreground until
|
recordingToggle = pkgs.writeShellScript "screen-record-toggle" ''
|
||||||
# that stop arrives, so this instance lives for the whole recording and then
|
refresh_waybar() {
|
||||||
# reports it saved.
|
${lib.getExe' pkgs.procps "pkill"} -RTMIN+10 waybar >/dev/null 2>&1 || true
|
||||||
# No -a means no audio is captured.
|
}
|
||||||
toggle = pkgs.writeShellScript "screen-record-toggle" ''
|
|
||||||
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
||||||
${lib.getExe' pkgs.procps "pkill"} -INT -x wf-recorder
|
${lib.getExe' pkgs.procps "pkill"} -INT -x wf-recorder
|
||||||
exit 0
|
exit 0
|
||||||
@@ -25,12 +25,15 @@ let
|
|||||||
file="$dir/recording-$(${lib.getExe' pkgs.coreutils "date"} +%Y%m%d-%H%M%S).mp4"
|
file="$dir/recording-$(${lib.getExe' pkgs.coreutils "date"} +%Y%m%d-%H%M%S).mp4"
|
||||||
|
|
||||||
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording started" "Region capture, no audio."
|
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording started" "Region capture, no audio."
|
||||||
${lib.getExe pkgs.wf-recorder} -g "$region" -f "$file"
|
${lib.getExe pkgs.wf-recorder} -g "$region" -f "$file" &
|
||||||
|
recorder=$!
|
||||||
|
refresh_waybar
|
||||||
|
wait "$recorder" || true
|
||||||
|
refresh_waybar
|
||||||
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording saved" "$file"
|
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording saved" "$file"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Lit while a wf-recorder capture is running.
|
# Empty text collapses the widget when recording is idle.
|
||||||
# Empty text collapses the widget when idle, so it shows nothing until then.
|
|
||||||
recordingStatus = pkgs.writeShellScript "waybar-recording-status" ''
|
recordingStatus = pkgs.writeShellScript "waybar-recording-status" ''
|
||||||
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
||||||
printf '{"text":"${util.glyph "f03d"}","tooltip":"Recording","class":"recording"}\n'
|
printf '{"text":"${util.glyph "f03d"}","tooltip":"Recording","class":"recording"}\n'
|
||||||
@@ -48,7 +51,7 @@ in
|
|||||||
keys = "SUPER + SHIFT + R";
|
keys = "SUPER + SHIFT + R";
|
||||||
action = {
|
action = {
|
||||||
kind = "exec";
|
kind = "exec";
|
||||||
command = "${toggle}";
|
command = "${recordingToggle}";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -57,7 +60,8 @@ in
|
|||||||
settings = {
|
settings = {
|
||||||
return-type = "json";
|
return-type = "json";
|
||||||
exec = "${recordingStatus}";
|
exec = "${recordingStatus}";
|
||||||
interval = 1;
|
interval = "once";
|
||||||
|
signal = 10;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,26 +7,26 @@
|
|||||||
# Keyboard-driven screenshots copy to the clipboard before Satty opens for
|
# Keyboard-driven screenshots copy to the clipboard before Satty opens for
|
||||||
# annotation. Satty copies annotated screenshots when it saves them.
|
# annotation. Satty copies annotated screenshots when it saves them.
|
||||||
let
|
let
|
||||||
user = config.user.name;
|
screenshotKey = keys: target: {
|
||||||
|
inherit keys;
|
||||||
grimblast = "${pkgs.grimblast}/bin/grimblast";
|
action = {
|
||||||
satty = "${pkgs.satty}/bin/satty";
|
kind = "exec";
|
||||||
tee = "${pkgs.coreutils}/bin/tee";
|
command = "${capture target}";
|
||||||
wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy";
|
};
|
||||||
xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir";
|
};
|
||||||
|
|
||||||
# Copy the original screenshot before Satty opens.
|
# Copy the original screenshot before Satty opens.
|
||||||
# Satty copies and saves annotated screenshots when the user presses Enter.
|
# Satty copies and saves annotated screenshots when the user presses Enter.
|
||||||
capture =
|
capture =
|
||||||
target:
|
target:
|
||||||
pkgs.writeShellScript "screenshot-${target}" ''
|
pkgs.writeShellScript "screenshot-${target}" ''
|
||||||
dir="$(${xdgUserDir} PICTURES)/Screenshots"
|
dir="$(${lib.getExe' pkgs.xdg-user-dirs "xdg-user-dir"} PICTURES)/Screenshots"
|
||||||
mkdir -p "$dir"
|
${lib.getExe' pkgs.coreutils "mkdir"} -p "$dir"
|
||||||
${grimblast} save ${target} - \
|
${lib.getExe pkgs.grimblast} save ${target} - \
|
||||||
| ${tee} >(${wl-copy}) \
|
| ${lib.getExe' pkgs.coreutils "tee"} >(${lib.getExe' pkgs.wl-clipboard "wl-copy"}) \
|
||||||
| ${satty} --filename - \
|
| ${lib.getExe pkgs.satty} --filename - \
|
||||||
--output-filename "$dir/screenshot-%Y%m%d-%H%M%S.png" \
|
--output-filename "$dir/screenshot-%Y%m%d-%H%M%S.png" \
|
||||||
--copy-command ${wl-copy} \
|
--copy-command ${lib.getExe' pkgs.wl-clipboard "wl-copy"} \
|
||||||
--save-after-copy \
|
--save-after-copy \
|
||||||
--early-exit \
|
--early-exit \
|
||||||
--actions-on-enter save-to-clipboard
|
--actions-on-enter save-to-clipboard
|
||||||
@@ -37,33 +37,7 @@ in
|
|||||||
lib.mkEnableOption "screenshots via grimblast and satty";
|
lib.mkEnableOption "screenshots via grimblast and satty";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.utilities.screenshot.enable {
|
config = lib.mkIf config.modules.desktop.utilities.screenshot.enable {
|
||||||
registry.keybind.${config.networking.hostName} = {
|
home-manager.users.${config.user.name} = {
|
||||||
screenshot-area = {
|
|
||||||
keys = "Print";
|
|
||||||
action = {
|
|
||||||
kind = "exec";
|
|
||||||
command = "${capture "area"}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
screenshot-active = {
|
|
||||||
keys = "SHIFT + Print";
|
|
||||||
action = {
|
|
||||||
kind = "exec";
|
|
||||||
command = "${capture "active"}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
screenshot-screen = {
|
|
||||||
keys = "CTRL + Print";
|
|
||||||
action = {
|
|
||||||
kind = "exec";
|
|
||||||
command = "${capture "screen"}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.${user} = {
|
|
||||||
# satty is a one-shot annotation surface, so float it rather than letting
|
# satty is a one-shot annotation surface, so float it rather than letting
|
||||||
# it claim a tile in the layout.
|
# it claim a tile in the layout.
|
||||||
wayland.windowManager.hyprland.settings.window_rule = [
|
wayland.windowManager.hyprland.settings.window_rule = [
|
||||||
@@ -72,7 +46,12 @@ in
|
|||||||
float = true;
|
float = true;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.keybind.${config.networking.hostName} = {
|
||||||
|
screenshot-area = screenshotKey "Print" "area";
|
||||||
|
screenshot-active = screenshotKey "SHIFT + Print" "active";
|
||||||
|
screenshot-screen = screenshotKey "CTRL + Print" "screen";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,13 @@
|
|||||||
}:
|
}:
|
||||||
# Nord theming for the graphical layer, driven from one base16 scheme by Stylix.
|
# Nord theming for the graphical layer, driven from one base16 scheme by Stylix.
|
||||||
let
|
let
|
||||||
|
|
||||||
# A single static Nord wallpaper, drawn as a vertical gradient across the
|
# A single static Nord wallpaper, drawn as a vertical gradient across the
|
||||||
# Polar Night shades so it needs no committed binary and no network fetch.
|
# Polar Night shades so it needs no committed binary and no network fetch.
|
||||||
wallpaper = pkgs.runCommand "nord-wallpaper.png" { } ''
|
wallpaper = pkgs.runCommand "nord-wallpaper.png" { } ''
|
||||||
${pkgs.imagemagick}/bin/magick \
|
${lib.getExe pkgs.imagemagick} \
|
||||||
-size 2560x1440 gradient:'#2E3440'-'#3B4252' \
|
-size 2560x1440 gradient:'#2E3440'-'#3B4252' \
|
||||||
"$out"
|
"$out"
|
||||||
'';
|
'';
|
||||||
user = config.user.name;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.desktop.utilities.theming.enable =
|
options.modules.desktop.utilities.theming.enable =
|
||||||
@@ -58,12 +56,8 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
registry.env.${config.networking.hostName} = {
|
|
||||||
HYPRCURSOR_THEME = config.stylix.cursor.name;
|
|
||||||
HYPRCURSOR_SIZE = toString config.stylix.cursor.size;
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${config.user.name} = {
|
||||||
# Stylix drives the cursor through home-manager's pointer-cursor config,
|
# Stylix drives the cursor through home-manager's pointer-cursor config,
|
||||||
# whose generation must be switched on explicitly.
|
# whose generation must be switched on explicitly.
|
||||||
home.pointerCursor.enable = true;
|
home.pointerCursor.enable = true;
|
||||||
@@ -72,5 +66,10 @@ in
|
|||||||
# base16 mapping Stylix would apply, so its target stays off.
|
# base16 mapping Stylix would apply, so its target stays off.
|
||||||
stylix.targets.nixvim.enable = false;
|
stylix.targets.nixvim.enable = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
registry.env.${config.networking.hostName} = {
|
||||||
|
HYPRCURSOR_THEME = config.stylix.cursor.name;
|
||||||
|
HYPRCURSOR_SIZE = toString config.stylix.cursor.size;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
# Declares the XDG user directories, so a tool that queries them lands in a
|
# Declares XDG user directories, so tools that query them use predictable
|
||||||
# well-known folder a host can relocate from one place.
|
# folders a host can relocate from one place.
|
||||||
let
|
let
|
||||||
user = config.user.name;
|
homeDirectory = "/home/${config.user.name}";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.desktop.utilities.userdirs.enable = lib.mkEnableOption "XDG user directories";
|
options.modules.desktop.utilities.userdirs.enable = lib.mkEnableOption "XDG user directories";
|
||||||
|
|
||||||
config = lib.mkIf config.modules.desktop.utilities.userdirs.enable {
|
config = lib.mkIf config.modules.desktop.utilities.userdirs.enable {
|
||||||
# Write ~/.config/user-dirs.dirs from the option defaults.
|
home-manager.users.${config.user.name}.xdg.userDirs = {
|
||||||
home-manager.users.${user}.xdg.userDirs.enable = true;
|
enable = true;
|
||||||
|
createDirectories = true;
|
||||||
|
setSessionVariables = true;
|
||||||
|
|
||||||
|
desktop = "${homeDirectory}/.ignoreme";
|
||||||
|
documents = "${homeDirectory}/docs";
|
||||||
|
download = "${homeDirectory}/dwn";
|
||||||
|
music = "${homeDirectory}/mus";
|
||||||
|
pictures = "${homeDirectory}/pic";
|
||||||
|
publicShare = "${homeDirectory}/.ignoreme";
|
||||||
|
templates = "${homeDirectory}/.ignoreme";
|
||||||
|
videos = "${homeDirectory}/vid";
|
||||||
|
projects = "${homeDirectory}/.ignoreme";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.modules.services.actualbudget; # NOLINT(let_order)
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.modules.services.actualbudget = {
|
|
||||||
enable = lib.mkEnableOption "Actual Budget";
|
|
||||||
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 5006;
|
|
||||||
description = "Port Actual Budget listens on.";
|
|
||||||
};
|
|
||||||
|
|
||||||
dataDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/data";
|
|
||||||
description = "Directory where Actual Budget stores server and user files.";
|
|
||||||
};
|
|
||||||
|
|
||||||
user = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "actual";
|
|
||||||
description = "User account that runs Actual Budget.";
|
|
||||||
};
|
|
||||||
|
|
||||||
group = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "storage";
|
|
||||||
description = "Group account that owns Actual Budget's writable state.";
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkMerge [
|
|
||||||
{
|
|
||||||
registry."stable-uid".${config.networking.hostName}.actualbudget = 20001;
|
|
||||||
}
|
|
||||||
|
|
||||||
(lib.mkIf cfg.enable {
|
|
||||||
users.users.${cfg.user} = {
|
|
||||||
isSystemUser = true;
|
|
||||||
uid = config.registry."stable-uid".${config.networking.hostName}.actualbudget;
|
|
||||||
inherit (cfg) group;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.actual = {
|
|
||||||
enable = true;
|
|
||||||
openFirewall = true;
|
|
||||||
inherit (cfg) user;
|
|
||||||
inherit (cfg) group;
|
|
||||||
settings = {
|
|
||||||
inherit (cfg) port dataDir;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -2,16 +2,105 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
|
util,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.modules.services.copyparty; # NOLINT(let_order)
|
cfg = config.modules.services.copyparty; # NOLINT(let_order)
|
||||||
|
|
||||||
volumeType = lib.types.submodule {
|
accountName = config.user.name;
|
||||||
|
|
||||||
|
serviceGroup = "storage";
|
||||||
|
|
||||||
|
serviceStateDir = "/var/lib/copyparty";
|
||||||
|
|
||||||
|
serviceUser = "copyparty";
|
||||||
|
|
||||||
|
writableVolumes = lib.filter (volume: !volume.readOnly) (lib.attrValues cfg.volumes);
|
||||||
|
|
||||||
|
port = 3923;
|
||||||
|
defaultAccess = {
|
||||||
|
A = [ accountName ];
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultWritableFlags = {
|
||||||
|
d2d = true;
|
||||||
|
d2t = true;
|
||||||
|
dthumb = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
credentialHash = builtins.substring 0 16 (builtins.hashString "sha256" accountName);
|
||||||
|
|
||||||
|
credentialName = "password-${credentialHash}";
|
||||||
|
|
||||||
|
passwordPlaceholder = "@COPYPARTY_PASSWORD_${credentialHash}@";
|
||||||
|
|
||||||
|
globalSettings = {
|
||||||
|
i = "0.0.0.0";
|
||||||
|
usernames = true;
|
||||||
|
"http-only" = true;
|
||||||
|
"no-crt" = true;
|
||||||
|
"no-thumb" = true;
|
||||||
|
"no-snap" = true;
|
||||||
|
"no-rescan" = true;
|
||||||
|
"ses-db" = "${serviceStateDir}/sessions.db";
|
||||||
|
"chpw-db" = "${serviceStateDir}/chpw.json";
|
||||||
|
"shr-db" = "${serviceStateDir}/shares.db";
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs cfg.reverseProxy.enable {
|
||||||
|
rproxy = 1;
|
||||||
|
"xff-hdr" = "x-forwarded-for";
|
||||||
|
};
|
||||||
|
|
||||||
|
globalExtraConfig = lib.optionalString cfg.reverseProxy.enable (
|
||||||
|
lib.concatMapStringsSep "\n" (source: "xff-src: ${source}") cfg.reverseProxy.trustedSources
|
||||||
|
);
|
||||||
|
|
||||||
|
volumeFlags =
|
||||||
|
flags:
|
||||||
|
lib.optionalAttrs (flags != { }) (
|
||||||
|
{
|
||||||
|
chmod_d = "2770";
|
||||||
|
chmod_f = "660";
|
||||||
|
}
|
||||||
|
// flags
|
||||||
|
);
|
||||||
|
|
||||||
|
renderedVolumes = lib.mapAttrs (_urlPath: volume: {
|
||||||
|
inherit (volume) path;
|
||||||
|
access = if volume.readOnly then { r = [ accountName ]; } else defaultAccess // volume.access;
|
||||||
|
flags = volumeFlags (lib.optionalAttrs (!volume.readOnly) defaultWritableFlags // volume.flags);
|
||||||
|
}) cfg.volumes;
|
||||||
|
|
||||||
|
configText = util.formats.copyparty.render {
|
||||||
|
inherit globalExtraConfig;
|
||||||
|
accounts.${accountName} = passwordPlaceholder;
|
||||||
|
global = globalSettings;
|
||||||
|
volumes = renderedVolumes;
|
||||||
|
};
|
||||||
|
|
||||||
|
configFile = pkgs.writeText "copyparty.conf" configText;
|
||||||
|
|
||||||
|
replacePassword = ''
|
||||||
|
${lib.getExe pkgs.replace-secret} ${lib.escapeShellArg passwordPlaceholder} "$CREDENTIALS_DIRECTORY/${credentialName}" /run/copyparty/copyparty.conf
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.modules.services.copyparty = {
|
||||||
|
enable = lib.mkEnableOption "Copyparty";
|
||||||
|
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Sets the runtime file that contains the Copyparty account password.";
|
||||||
|
};
|
||||||
|
|
||||||
|
volumes = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
path = lib.mkOption {
|
path = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.externalPath;
|
||||||
description = "Filesystem path served by this volume.";
|
description = "Sets the filesystem path served by this volume.";
|
||||||
};
|
};
|
||||||
|
|
||||||
access = lib.mkOption {
|
access = lib.mkOption {
|
||||||
@@ -20,129 +109,28 @@ let
|
|||||||
example = {
|
example = {
|
||||||
A = [ "alexion" ];
|
A = [ "alexion" ];
|
||||||
};
|
};
|
||||||
description = "Copyparty access map from permission string to account names.";
|
description = "Maps Copyparty permission strings to account names.";
|
||||||
};
|
};
|
||||||
|
|
||||||
readOnly = lib.mkOption {
|
readOnly = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = "Serve this volume read-only.";
|
description = "Serves this volume read-only.";
|
||||||
};
|
};
|
||||||
|
|
||||||
flags = lib.mkOption {
|
flags = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.attrs;
|
||||||
default = [ ];
|
|
||||||
example = [ "dthumb" ];
|
|
||||||
description = "Copyparty volume flags.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
configScript = pkgs.writeShellScript "copyparty-config" ''
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
umask 077
|
|
||||||
password="$(${pkgs.coreutils}/bin/tr -d '\n' < "$CREDENTIALS_DIRECTORY/password")"
|
|
||||||
|
|
||||||
cat > /run/copyparty/copyparty.conf <<EOF
|
|
||||||
[global]
|
|
||||||
p: ${toString cfg.port}
|
|
||||||
usernames
|
|
||||||
http-only
|
|
||||||
no-crt
|
|
||||||
no-thumb
|
|
||||||
no-snap
|
|
||||||
no-rescan
|
|
||||||
ses-db: ${cfg.stateDir}/sessions.db
|
|
||||||
chpw-db: ${cfg.stateDir}/chpw.json
|
|
||||||
shr-db: ${cfg.stateDir}/shares.db
|
|
||||||
${renderedReverseProxy}
|
|
||||||
[accounts]
|
|
||||||
${cfg.accountName}: $password
|
|
||||||
|
|
||||||
${renderedVolumes}
|
|
||||||
EOF
|
|
||||||
'';
|
|
||||||
renderAccess = permission: accounts: " ${permission}: ${lib.concatStringsSep ", " accounts}";
|
|
||||||
|
|
||||||
hasFlag =
|
|
||||||
name: flags: lib.any (flag: lib.hasPrefix "${name}:" flag || lib.hasPrefix "${name}=" flag) flags;
|
|
||||||
|
|
||||||
defaultPermissionFlags =
|
|
||||||
flags:
|
|
||||||
lib.optionals (!(hasFlag "chmod_d" flags)) [ "chmod_d: 2770" ]
|
|
||||||
++ lib.optionals (!(hasFlag "chmod_f" flags)) [ "chmod_f: 660" ];
|
|
||||||
|
|
||||||
renderFlags =
|
|
||||||
flags: lib.concatMapStringsSep "\n" (flag: " ${flag}") (flags ++ defaultPermissionFlags flags);
|
|
||||||
|
|
||||||
renderVolume = urlPath: volume: ''
|
|
||||||
[${urlPath}]
|
|
||||||
${volume.path}
|
|
||||||
accs:
|
|
||||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList renderAccess volume.access)}
|
|
||||||
${lib.optionalString (volume.flags != [ ]) ''
|
|
||||||
flags:
|
|
||||||
${renderFlags volume.flags}
|
|
||||||
''}
|
|
||||||
'';
|
|
||||||
|
|
||||||
renderTrustedSource = source: " xff-src: ${source}";
|
|
||||||
|
|
||||||
renderedReverseProxy = lib.optionalString cfg.reverseProxy.enable ''
|
|
||||||
rproxy: 1
|
|
||||||
xff-hdr: x-forwarded-for
|
|
||||||
${lib.concatStringsSep "\n" (map renderTrustedSource cfg.reverseProxy.trustedSources)}
|
|
||||||
'';
|
|
||||||
|
|
||||||
renderedVolumes = lib.concatStringsSep "\n" (lib.mapAttrsToList renderVolume cfg.volumes);
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.modules.services.copyparty = {
|
|
||||||
enable = lib.mkEnableOption "Copyparty";
|
|
||||||
|
|
||||||
package = lib.mkPackageOption pkgs "copyparty-min" { };
|
|
||||||
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 3923;
|
|
||||||
description = "Port Copyparty listens on.";
|
|
||||||
};
|
|
||||||
|
|
||||||
user = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "copyparty";
|
|
||||||
description = "User account that runs Copyparty.";
|
|
||||||
};
|
|
||||||
|
|
||||||
group = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "storage";
|
|
||||||
description = "Group account that owns Copyparty's writable state and served files.";
|
|
||||||
};
|
|
||||||
|
|
||||||
accountName = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "alexion";
|
|
||||||
description = "Copyparty account name configured for password authentication.";
|
|
||||||
};
|
|
||||||
|
|
||||||
passwordFile = lib.mkOption {
|
|
||||||
type = lib.types.path;
|
|
||||||
description = "Runtime file containing the Copyparty account password.";
|
|
||||||
};
|
|
||||||
|
|
||||||
stateDir = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/copyparty";
|
|
||||||
description = "Directory where Copyparty stores service state.";
|
|
||||||
};
|
|
||||||
|
|
||||||
volumes = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf volumeType;
|
|
||||||
default = { };
|
default = { };
|
||||||
description = "Copyparty volumes keyed by their URL path.";
|
example = {
|
||||||
|
dthumb = true;
|
||||||
|
};
|
||||||
|
description = "Sets Copyparty volume flags.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
description = "Defines Copyparty volumes by URL path.";
|
||||||
};
|
};
|
||||||
|
|
||||||
reverseProxy = {
|
reverseProxy = {
|
||||||
@@ -151,7 +139,7 @@ in
|
|||||||
trustedSources = lib.mkOption {
|
trustedSources = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = "IP ranges that may supply Copyparty's forwarded client IP header.";
|
description = "Lists proxy sources that can set Copyparty's forwarded client IP header.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -173,46 +161,49 @@ in
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
users.users.${cfg.user} = {
|
users.users.${serviceUser} = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
uid = config.registry."stable-uid".${config.networking.hostName}.copyparty;
|
uid = config.registry."stable-uid".${config.networking.hostName}.copyparty;
|
||||||
inherit (cfg) group;
|
group = serviceGroup;
|
||||||
home = cfg.stateDir;
|
home = serviceStateDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.copyparty = {
|
systemd.services.copyparty = {
|
||||||
description = "Copyparty file server";
|
description = "Copyparty file server";
|
||||||
after = [ "network.target" ];
|
after = [ "network.target" ];
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
preStart = ''
|
||||||
|
${lib.getExe' pkgs.coreutils "install"} -m 600 ${configFile} /run/copyparty/copyparty.conf
|
||||||
|
${replacePassword}
|
||||||
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ReloadSignal = "SIGUSR1";
|
ReloadSignal = "SIGUSR1";
|
||||||
Type = "simple";
|
Type = "simple";
|
||||||
User = cfg.user;
|
User = serviceUser;
|
||||||
Group = cfg.group;
|
Group = serviceGroup;
|
||||||
WorkingDirectory = cfg.stateDir;
|
WorkingDirectory = serviceStateDir;
|
||||||
Environment = [
|
Environment = [
|
||||||
"HOME=${cfg.stateDir}"
|
"HOME=${serviceStateDir}"
|
||||||
"XDG_CONFIG_HOME=${cfg.stateDir}"
|
"XDG_CONFIG_HOME=${serviceStateDir}"
|
||||||
];
|
];
|
||||||
StateDirectory = "copyparty";
|
StateDirectory = "copyparty";
|
||||||
RuntimeDirectory = "copyparty";
|
RuntimeDirectory = "copyparty";
|
||||||
RuntimeDirectoryMode = "0700";
|
RuntimeDirectoryMode = "0700";
|
||||||
LoadCredential = [ "password:${cfg.passwordFile}" ];
|
LoadCredential = [ "${credentialName}:${cfg.passwordFile}" ];
|
||||||
ExecStartPre = configScript;
|
ExecStart = "${lib.getExe pkgs.copyparty-min} -c /run/copyparty/copyparty.conf";
|
||||||
ExecStart = "${lib.getExe cfg.package} -c /run/copyparty/copyparty.conf";
|
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
NoNewPrivileges = true;
|
NoNewPrivileges = true;
|
||||||
PrivateTmp = true;
|
PrivateTmp = true;
|
||||||
ProtectSystem = "strict";
|
ProtectSystem = "strict";
|
||||||
ProtectHome = true;
|
ProtectHome = true;
|
||||||
ReadWritePaths = [
|
ReadWritePaths = [
|
||||||
cfg.stateDir
|
serviceStateDir
|
||||||
]
|
]
|
||||||
++ map (volume: volume.path) (lib.filter (volume: !volume.readOnly) (lib.attrValues cfg.volumes));
|
++ map (volume: volume.path) writableVolumes;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ cfg.port ];
|
networking.firewall.allowedTCPPorts = [ port ];
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ let
|
|||||||
let
|
let
|
||||||
sectionOrder = sectionOrderByTab.${tab} or [ ];
|
sectionOrder = sectionOrderByTab.${tab} or [ ];
|
||||||
in
|
in
|
||||||
sectionOrder ++ lib.filter (section: !lib.elem section sectionOrder) (lib.attrNames sections);
|
lib.filter (section: lib.hasAttr section sections) sectionOrder
|
||||||
|
++ lib.filter (section: !lib.elem section sectionOrder) (lib.attrNames sections);
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,18 +12,14 @@ let
|
|||||||
piDir = "${config.users.users.${user}.home}/.pi/agent";
|
piDir = "${config.users.users.${user}.home}/.pi/agent";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.modules.shell.pi = {
|
options.modules.shell.pi.enable =
|
||||||
enable = lib.mkEnableOption "Pi, a terminal coding agent, configured through home-manager";
|
lib.mkEnableOption "Pi, a terminal coding agent, configured through home-manager";
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkIf config.modules.shell.pi.enable {
|
config = lib.mkIf config.modules.shell.pi.enable {
|
||||||
home-manager.users.${user} = {
|
home-manager.users.${user} = {
|
||||||
programs.pi-coding-agent = {
|
programs.pi-coding-agent = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.pi-coding-agent.overrideAttrs {
|
package = pkgs.alexion.pi-coding-agent;
|
||||||
src = ../../../../../third_party/pi;
|
|
||||||
patches = [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
defaultProvider = "openai-codex";
|
defaultProvider = "openai-codex";
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ let
|
|||||||
registry = registry.collected;
|
registry = registry.collected;
|
||||||
};
|
};
|
||||||
|
|
||||||
util = import ./util.nix { inherit lib; };
|
util = import ./util { inherit lib; };
|
||||||
|
|
||||||
registry = import ./registry.nix {
|
registry = import ./registry.nix {
|
||||||
inherit lib util;
|
inherit lib util;
|
||||||
@@ -33,7 +33,12 @@ let
|
|||||||
repositoryModules = modules;
|
repositoryModules = modules;
|
||||||
root = root + "/guests";
|
root = root + "/guests";
|
||||||
};
|
};
|
||||||
modules.imports = util.collectNixFiles (root + "/modules");
|
modules.imports = [
|
||||||
|
{
|
||||||
|
nixpkgs.overlays = [ self.overlays.custom ];
|
||||||
|
}
|
||||||
|
]
|
||||||
|
++ util.collectNixFiles (root + "/modules");
|
||||||
|
|
||||||
in
|
in
|
||||||
import ./hosts.nix {
|
import ./hosts.nix {
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ let
|
|||||||
value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries;
|
value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit collectNixFiles;
|
formats.copyparty = import ./format-copyparty.nix { inherit lib; };
|
||||||
|
|
||||||
|
inherit collectNixFiles;
|
||||||
removeNixSuffix = path: lib.removeSuffix ".nix" (builtins.baseNameOf path);
|
removeNixSuffix = path: lib.removeSuffix ".nix" (builtins.baseNameOf path);
|
||||||
|
|
||||||
glyph =
|
glyph =
|
||||||
58
src/dotfiles/util/format-copyparty.nix
Normal file
58
src/dotfiles/util/format-copyparty.nix
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{ lib }:
|
||||||
|
let
|
||||||
|
renderLine =
|
||||||
|
indent: name: value:
|
||||||
|
if value == true then
|
||||||
|
"${indent}${name}"
|
||||||
|
else if value == false then
|
||||||
|
null
|
||||||
|
else if lib.isList value then
|
||||||
|
"${indent}${name}: ${lib.concatStringsSep ", " (map toString value)}"
|
||||||
|
else
|
||||||
|
"${indent}${name}: ${toString value}";
|
||||||
|
|
||||||
|
renderAttrs =
|
||||||
|
indent: attrs:
|
||||||
|
lib.pipe attrs [
|
||||||
|
lib.attrsToList
|
||||||
|
(map ({ name, value }: renderLine indent name value))
|
||||||
|
(lib.filter (line: line != null))
|
||||||
|
(lib.concatStringsSep "\n")
|
||||||
|
];
|
||||||
|
|
||||||
|
renderSectionName = name: "[${lib.escape [ "[" "]" ] name}]";
|
||||||
|
|
||||||
|
renderSection = name: attrs: ''
|
||||||
|
${renderSectionName name}
|
||||||
|
${renderAttrs "" attrs}
|
||||||
|
'';
|
||||||
|
|
||||||
|
renderSubsection =
|
||||||
|
name: attrs:
|
||||||
|
lib.optionalString (attrs != { }) ''
|
||||||
|
${name}:
|
||||||
|
${renderAttrs " " attrs}
|
||||||
|
'';
|
||||||
|
|
||||||
|
renderVolume = urlPath: volume: ''
|
||||||
|
${renderSectionName urlPath}
|
||||||
|
${volume.path}
|
||||||
|
${renderSubsection "accs" volume.access}
|
||||||
|
${renderSubsection "flags" volume.flags}
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
render =
|
||||||
|
{
|
||||||
|
accounts,
|
||||||
|
global,
|
||||||
|
volumes,
|
||||||
|
globalExtraConfig ? "",
|
||||||
|
}:
|
||||||
|
''
|
||||||
|
${renderSection "global" global}
|
||||||
|
${globalExtraConfig}
|
||||||
|
${renderSection "accounts" accounts}
|
||||||
|
${lib.concatStringsSep "\n" (lib.mapAttrsToList renderVolume volumes)}
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -6,6 +6,19 @@
|
|||||||
system,
|
system,
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
overlays.custom = final: _prev: {
|
||||||
|
alexion = import ./packages {
|
||||||
|
pkgs = final;
|
||||||
|
inherit root;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
packages.${system} =
|
||||||
|
(import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.custom ];
|
||||||
|
}).alexion;
|
||||||
|
|
||||||
nixosConfigurations = import ../dotfiles {
|
nixosConfigurations = import ../dotfiles {
|
||||||
inherit
|
inherit
|
||||||
inputs
|
inputs
|
||||||
|
|||||||
8
src/flake/packages/default.nix
Normal file
8
src/flake/packages/default.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{ pkgs, root }:
|
||||||
|
{
|
||||||
|
pi-coding-agent = pkgs.callPackage ./pi-coding-agent.nix {
|
||||||
|
inherit root;
|
||||||
|
};
|
||||||
|
|
||||||
|
waybar = pkgs.callPackage ./waybar.nix { };
|
||||||
|
}
|
||||||
8
src/flake/packages/pi-coding-agent.nix
Normal file
8
src/flake/packages/pi-coding-agent.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
pi-coding-agent,
|
||||||
|
root,
|
||||||
|
}:
|
||||||
|
pi-coding-agent.overrideAttrs {
|
||||||
|
src = root + "/third_party/pi";
|
||||||
|
patches = [ ];
|
||||||
|
}
|
||||||
28
src/flake/packages/waybar.nix
Normal file
28
src/flake/packages/waybar.nix
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
fetchFromGitHub,
|
||||||
|
waybar,
|
||||||
|
}:
|
||||||
|
waybar.overrideAttrs {
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "Alexays";
|
||||||
|
repo = "Waybar";
|
||||||
|
rev = "05945748dccce28bf96d26d8f64a9e69a8dd49ba";
|
||||||
|
hash = "sha256-51R3mIt8cLNvh/X5qe9vOqeJCj0U9KRyemVE5y+OhiU=";
|
||||||
|
};
|
||||||
|
|
||||||
|
postUnpack =
|
||||||
|
let
|
||||||
|
libcava = fetchFromGitHub {
|
||||||
|
owner = "LukashonakV";
|
||||||
|
repo = "cava";
|
||||||
|
tag = "0.10.7";
|
||||||
|
hash = "sha256-zkyj1vBzHtoypX4Bxdh1Vmwh967DKKxN751v79hzmgQ=";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
''
|
||||||
|
pushd "$sourceRoot"
|
||||||
|
cp -R --no-preserve=mode,ownership ${libcava} subprojects/cava-0.10.7
|
||||||
|
patchShebangs .
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user