Compare commits
3 commits
a9fb912e92
...
5847c04acd
| Author | SHA1 | Date | |
|---|---|---|---|
| 5847c04acd | |||
| 248534bc35 | |||
| 4648d6b51a |
15 changed files with 670 additions and 80 deletions
|
|
@ -29,6 +29,7 @@ in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./modules/dev-tools.nix
|
./modules/dev-tools.nix
|
||||||
|
./users
|
||||||
];
|
];
|
||||||
|
|
||||||
networking.hostName = "dev";
|
networking.hostName = "dev";
|
||||||
|
|
|
||||||
3
hosts/dev/users/default.nix
Normal file
3
hosts/dev/users/default.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
imports = [ ./dominik.nix ];
|
||||||
|
}
|
||||||
6
hosts/dev/users/dominik.nix
Normal file
6
hosts/dev/users/dominik.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../utils/home-manager/claude-code/nixos.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -10,22 +10,104 @@ let
|
||||||
add_header Access-Control-Allow-Origin *;
|
add_header Access-Control-Allow-Origin *;
|
||||||
return 200 '${builtins.toJSON data}';
|
return 200 '${builtins.toJSON data}';
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
# Shared settings format for bridges
|
||||||
|
settingsFormat = pkgs.formats.json {};
|
||||||
in {
|
in {
|
||||||
sops.secrets.matrix-shared-secret = {
|
# Secrets for Synapse
|
||||||
};
|
sops.secrets.synapse-oidc-client-secret = {
|
||||||
sops.secrets.dendrite-private-key = {
|
owner = "matrix-synapse";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# PostgreSQL database for Synapse
|
||||||
services.postgresql = {
|
services.postgresql = {
|
||||||
enable = true;
|
enable = true;
|
||||||
ensureDatabases = [ "dendrite" ];
|
ensureDatabases = [ "matrix-synapse" ];
|
||||||
ensureUsers = [
|
ensureUsers = [
|
||||||
{
|
{
|
||||||
name = "dendrite";
|
name = "matrix-synapse";
|
||||||
|
ensureDBOwnership = true;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.postgresqlBackup.enable = true;
|
||||||
|
services.postgresqlBackup.databases = [ "matrix-synapse" ];
|
||||||
|
|
||||||
|
# Synapse homeserver
|
||||||
|
services.matrix-synapse = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
server_name = "cloonar.com";
|
||||||
|
public_baseurl = baseUrl;
|
||||||
|
|
||||||
|
listeners = [
|
||||||
|
{
|
||||||
|
port = 8008;
|
||||||
|
bind_addresses = [ "::1" ];
|
||||||
|
type = "http";
|
||||||
|
tls = false;
|
||||||
|
x_forwarded = true;
|
||||||
|
resources = [
|
||||||
|
{
|
||||||
|
compress = true;
|
||||||
|
names = [ "client" "federation" ];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
database = {
|
||||||
|
name = "psycopg2";
|
||||||
|
args = {
|
||||||
|
host = "/run/postgresql";
|
||||||
|
database = "matrix-synapse";
|
||||||
|
user = "matrix-synapse";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Disable registration - users created via OIDC
|
||||||
|
enable_registration = false;
|
||||||
|
allow_guest_access = false;
|
||||||
|
|
||||||
|
# OIDC SSO via Authelia
|
||||||
|
oidc_providers = [
|
||||||
|
{
|
||||||
|
idp_id = "authelia";
|
||||||
|
idp_name = "Authelia";
|
||||||
|
discover = true;
|
||||||
|
issuer = "https://auth.cloonar.com";
|
||||||
|
user_profile_method = "userinfo_endpoint";
|
||||||
|
client_id = "synapse";
|
||||||
|
client_secret_path = config.sops.secrets.synapse-oidc-client-secret.path;
|
||||||
|
scopes = [ "openid" "profile" "email" ];
|
||||||
|
allow_existing_users = true;
|
||||||
|
user_mapping_provider.config = {
|
||||||
|
subject_claim = "sub";
|
||||||
|
localpart_template = "{{ user.preferred_username }}";
|
||||||
|
display_name_template = "{{ user.name }}";
|
||||||
|
email_template = "{{ user.email }}";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Appservice registrations for bridges
|
||||||
|
app_service_config_files = [
|
||||||
|
"/var/lib/mautrix-whatsapp/whatsapp-registration.yaml"
|
||||||
|
"/var/lib/mautrix-signal/signal-registration.yaml"
|
||||||
|
"/var/lib/mautrix-discord/discord-registration.yaml"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Allow bridge users to read registration files
|
||||||
|
systemd.services.matrix-synapse.serviceConfig.SupplementaryGroups = [
|
||||||
|
"mautrix-whatsapp"
|
||||||
|
"mautrix-signal"
|
||||||
|
"mautrix-discord"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Element Web client
|
||||||
services.nginx.virtualHosts."element.cloonar.com" = {
|
services.nginx.virtualHosts."element.cloonar.com" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
|
|
@ -45,10 +127,8 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.postgresqlBackup.enable = true;
|
|
||||||
services.postgresqlBackup.databases = [ "dendrite" ];
|
|
||||||
|
|
||||||
|
# Synapse nginx reverse proxy
|
||||||
services.nginx.virtualHosts."${fqdn}" = {
|
services.nginx.virtualHosts."${fqdn}" = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
|
|
@ -56,60 +136,28 @@ in {
|
||||||
locations."/".extraConfig = ''
|
locations."/".extraConfig = ''
|
||||||
return 404;
|
return 404;
|
||||||
'';
|
'';
|
||||||
locations."/_dendrite".proxyPass = "http://[::1]:8008";
|
|
||||||
locations."/_matrix".proxyPass = "http://[::1]:8008";
|
locations."/_matrix".proxyPass = "http://[::1]:8008";
|
||||||
locations."/_synapse/client".proxyPass = "http://[::1]:8008";
|
locations."/_synapse/client".proxyPass = "http://[::1]:8008";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#
|
||||||
|
# Mautrix bridges
|
||||||
|
#
|
||||||
|
|
||||||
services.dendrite = {
|
# WhatsApp bridge
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
global = {
|
|
||||||
server_name = "cloonar.com";
|
|
||||||
private_key = "$CREDENTIALS_DIRECTORY/private_key";
|
|
||||||
database.connection_string = "postgresql:///dendrite?host=/run/postgresql";
|
|
||||||
};
|
|
||||||
client_api.registration_shared_secret = "$REGISTRATION_SHARED_SECRET";
|
|
||||||
app_service_api.config_files = [
|
|
||||||
"$CREDENTIALS_DIRECTORY/whatsapp_registration"
|
|
||||||
"$CREDENTIALS_DIRECTORY/signal_registration"
|
|
||||||
"$CREDENTIALS_DIRECTORY/discord_registration"
|
|
||||||
];
|
|
||||||
app_service_api.database.connection_string = "";
|
|
||||||
federation_api.database.connection_string = "";
|
|
||||||
key_server.database.connection_string = "";
|
|
||||||
relay_api.database.connection_string = "";
|
|
||||||
media_api.database.connection_string = "";
|
|
||||||
room_server.database.connection_string = "";
|
|
||||||
sync_api.database.connection_string = "";
|
|
||||||
user_api.account_database.connection_string = "";
|
|
||||||
user_api.device_database.connection_string = "";
|
|
||||||
mscs.database.connection_string = "";
|
|
||||||
};
|
|
||||||
loadCredential = [
|
|
||||||
"private_key:${config.sops.secrets.dendrite-private-key.path}"
|
|
||||||
"whatsapp_registration:/var/lib/mautrix-whatsapp/whatsapp-registration.yaml"
|
|
||||||
"signal_registration:/var/lib/mautrix-signal/signal-registration.yaml"
|
|
||||||
"discord_registration:/var/lib/mautrix-discord/discord-registration.yaml"
|
|
||||||
];
|
|
||||||
environmentFile = config.sops.secrets.matrix-shared-secret.path;
|
|
||||||
};
|
|
||||||
|
|
||||||
users.users.mautrix-whatsapp = {
|
users.users.mautrix-whatsapp = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = "mautrix-whatsapp";
|
group = "mautrix-whatsapp";
|
||||||
home = "/var/lib/mautrix-whatsapp";
|
home = "/var/lib/mautrix-whatsapp";
|
||||||
description = "Mautrix-WhatsApp bridge user";
|
description = "Mautrix-WhatsApp bridge user";
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.mautrix-whatsapp = {};
|
users.groups.mautrix-whatsapp = {};
|
||||||
|
|
||||||
systemd.services.mautrix-whatsapp = let
|
systemd.services.mautrix-whatsapp = let
|
||||||
dataDir = "/var/lib/mautrix-whatsapp";
|
dataDir = "/var/lib/mautrix-whatsapp";
|
||||||
registrationFile = "${dataDir}/whatsapp-registration.yaml";
|
registrationFile = "${dataDir}/whatsapp-registration.yaml";
|
||||||
settingsFile = "${dataDir}/config.json";
|
settingsFile = "${dataDir}/config.json";
|
||||||
settingsFileUnsubstituted = settingsFormat.generate "mautrix-whatsapp-config-unsubstituted.json" defaultConfig;
|
settingsFileUnsubstituted = settingsFormat.generate "mautrix-whatsapp-config-unsubstituted.json" defaultConfig;
|
||||||
settingsFormat = pkgs.formats.json {};
|
|
||||||
appservicePort = 29318;
|
appservicePort = 29318;
|
||||||
defaultConfig = {
|
defaultConfig = {
|
||||||
homeserver = {
|
homeserver = {
|
||||||
|
|
@ -154,10 +202,9 @@ in {
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
description = "Mautrix-WhatsApp Service - A WhatsApp bridge for Matrix";
|
description = "Mautrix-WhatsApp Service - A WhatsApp bridge for Matrix";
|
||||||
|
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = ["multi-user.target"];
|
||||||
wants = ["network-online.target"];
|
wants = ["network-online.target" "matrix-synapse.service"];
|
||||||
after = ["network-online.target"];
|
after = ["network-online.target" "matrix-synapse.service"];
|
||||||
|
|
||||||
preStart = ''
|
preStart = ''
|
||||||
test -f '${settingsFile}' && rm -f '${settingsFile}'
|
test -f '${settingsFile}' && rm -f '${settingsFile}'
|
||||||
|
|
@ -189,7 +236,6 @@ in {
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
User = "mautrix-whatsapp";
|
User = "mautrix-whatsapp";
|
||||||
Group = "mautrix-whatsapp";
|
Group = "mautrix-whatsapp";
|
||||||
# EnvironmentFile = cfg.environmentFile;
|
|
||||||
StateDirectory = baseNameOf dataDir;
|
StateDirectory = baseNameOf dataDir;
|
||||||
WorkingDirectory = dataDir;
|
WorkingDirectory = dataDir;
|
||||||
ExecStart = ''
|
ExecStart = ''
|
||||||
|
|
@ -225,19 +271,19 @@ in {
|
||||||
restartTriggers = [settingsFileUnsubstituted];
|
restartTriggers = [settingsFileUnsubstituted];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Signal bridge
|
||||||
users.users.mautrix-signal = {
|
users.users.mautrix-signal = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = "mautrix-signal";
|
group = "mautrix-signal";
|
||||||
home = "/var/lib/mautrix-signal";
|
home = "/var/lib/mautrix-signal";
|
||||||
description = "Mautrix-Signal bridge user";
|
description = "Mautrix-Signal bridge user";
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.mautrix-signal = {};
|
users.groups.mautrix-signal = {};
|
||||||
|
|
||||||
systemd.services.mautrix-signal = let
|
systemd.services.mautrix-signal = let
|
||||||
pkgswithsignal = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/fd698a4ab779fb7fb95425f1b56974ba9c2fa16c.tar.gz") {
|
pkgswithsignal = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/fd698a4ab779fb7fb95425f1b56974ba9c2fa16c.tar.gz") {
|
||||||
config = {
|
config = {
|
||||||
permittedInsecurePackages = [
|
permittedInsecurePackages = [
|
||||||
# needed for matrix
|
|
||||||
"olm-3.2.16"
|
"olm-3.2.16"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
@ -246,7 +292,6 @@ in {
|
||||||
registrationFile = "${dataDir}/signal-registration.yaml";
|
registrationFile = "${dataDir}/signal-registration.yaml";
|
||||||
settingsFile = "${dataDir}/config.json";
|
settingsFile = "${dataDir}/config.json";
|
||||||
settingsFileUnsubstituted = settingsFormat.generate "mautrix-signal-config-unsubstituted.json" defaultConfig;
|
settingsFileUnsubstituted = settingsFormat.generate "mautrix-signal-config-unsubstituted.json" defaultConfig;
|
||||||
settingsFormat = pkgs.formats.json {};
|
|
||||||
appservicePort = 29328;
|
appservicePort = 29328;
|
||||||
defaultConfig = {
|
defaultConfig = {
|
||||||
homeserver = {
|
homeserver = {
|
||||||
|
|
@ -295,10 +340,9 @@ in {
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
description = "Mautrix-Signal Service - A Signal bridge for Matrix";
|
description = "Mautrix-Signal Service - A Signal bridge for Matrix";
|
||||||
|
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = ["multi-user.target"];
|
||||||
wants = ["network-online.target"];
|
wants = ["network-online.target" "matrix-synapse.service"];
|
||||||
after = ["network-online.target"];
|
after = ["network-online.target" "matrix-synapse.service"];
|
||||||
|
|
||||||
preStart = ''
|
preStart = ''
|
||||||
test -f '${settingsFile}' && rm -f '${settingsFile}'
|
test -f '${settingsFile}' && rm -f '${settingsFile}'
|
||||||
|
|
@ -331,7 +375,6 @@ in {
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
User = "mautrix-signal";
|
User = "mautrix-signal";
|
||||||
Group = "mautrix-signal";
|
Group = "mautrix-signal";
|
||||||
# EnvironmentFile = cfg.environmentFile;
|
|
||||||
StateDirectory = baseNameOf dataDir;
|
StateDirectory = baseNameOf dataDir;
|
||||||
WorkingDirectory = dataDir;
|
WorkingDirectory = dataDir;
|
||||||
ExecStart = ''
|
ExecStart = ''
|
||||||
|
|
@ -367,20 +410,19 @@ in {
|
||||||
restartTriggers = [settingsFileUnsubstituted];
|
restartTriggers = [settingsFileUnsubstituted];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Discord bridge
|
||||||
users.users.mautrix-discord = {
|
users.users.mautrix-discord = {
|
||||||
isSystemUser = true;
|
isSystemUser = true;
|
||||||
group = "mautrix-discord";
|
group = "mautrix-discord";
|
||||||
home = "/var/lib/mautrix-discord";
|
home = "/var/lib/mautrix-discord";
|
||||||
description = "Mautrix-Discord bridge user";
|
description = "Mautrix-Discord bridge user";
|
||||||
};
|
};
|
||||||
|
|
||||||
users.groups.mautrix-discord = {};
|
users.groups.mautrix-discord = {};
|
||||||
|
|
||||||
systemd.services.mautrix-discord = let
|
systemd.services.mautrix-discord = let
|
||||||
pkgswithdiscord = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/5ed627539ac84809c78b2dd6d26a5cebeb5ae269.tar.gz") {
|
pkgswithdiscord = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/5ed627539ac84809c78b2dd6d26a5cebeb5ae269.tar.gz") {
|
||||||
config = {
|
config = {
|
||||||
permittedInsecurePackages = [
|
permittedInsecurePackages = [
|
||||||
# needed for matrix
|
|
||||||
"olm-3.2.16"
|
"olm-3.2.16"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
@ -389,7 +431,6 @@ in {
|
||||||
registrationFile = "${dataDir}/discord-registration.yaml";
|
registrationFile = "${dataDir}/discord-registration.yaml";
|
||||||
settingsFile = "${dataDir}/config.json";
|
settingsFile = "${dataDir}/config.json";
|
||||||
settingsFileUnsubstituted = settingsFormat.generate "mautrix-discord-config-unsubstituted.json" defaultConfig;
|
settingsFileUnsubstituted = settingsFormat.generate "mautrix-discord-config-unsubstituted.json" defaultConfig;
|
||||||
settingsFormat = pkgs.formats.json {};
|
|
||||||
appservicePort = 29329;
|
appservicePort = 29329;
|
||||||
defaultConfig = {
|
defaultConfig = {
|
||||||
homeserver = {
|
homeserver = {
|
||||||
|
|
@ -436,10 +477,9 @@ in {
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
description = "Mautrix-Discord Service - A Discord bridge for Matrix";
|
description = "Mautrix-Discord Service - A Discord bridge for Matrix";
|
||||||
|
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = ["multi-user.target"];
|
||||||
wants = ["network-online.target"];
|
wants = ["network-online.target" "matrix-synapse.service"];
|
||||||
after = ["network-online.target"];
|
after = ["network-online.target" "matrix-synapse.service"];
|
||||||
|
|
||||||
preStart = ''
|
preStart = ''
|
||||||
test -f '${settingsFile}' && rm -f '${settingsFile}'
|
test -f '${settingsFile}' && rm -f '${settingsFile}'
|
||||||
|
|
@ -472,7 +512,6 @@ in {
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
User = "mautrix-discord";
|
User = "mautrix-discord";
|
||||||
Group = "mautrix-discord";
|
Group = "mautrix-discord";
|
||||||
# EnvironmentFile = cfg.environmentFile;
|
|
||||||
StateDirectory = baseNameOf dataDir;
|
StateDirectory = baseNameOf dataDir;
|
||||||
WorkingDirectory = dataDir;
|
WorkingDirectory = dataDir;
|
||||||
ExecStart = ''
|
ExecStart = ''
|
||||||
|
|
@ -506,4 +545,5 @@ in {
|
||||||
};
|
};
|
||||||
restartTriggers = [settingsFileUnsubstituted];
|
restartTriggers = [settingsFileUnsubstituted];
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,20 @@
|
||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
let
|
let
|
||||||
|
sddmTheme = pkgs.where-is-my-sddm-theme.override {
|
||||||
|
themeConfig.General = {
|
||||||
|
showUsersByDefault = true;
|
||||||
|
background = "/nix/persist/system/wallpaper.png";
|
||||||
|
backgroundFill = "#252525";
|
||||||
|
backgroundFillMode = "Image.Pad";
|
||||||
|
passwordInputWidth = 0.25;
|
||||||
|
passwordInputBackground = "#60ffffff";
|
||||||
|
passwordFontSize = 28;
|
||||||
|
showSessionsByDefault = true;
|
||||||
|
sessionsFontSize = 24;
|
||||||
|
usersFontSize = 32;
|
||||||
|
};
|
||||||
|
variants = ["qt5"];
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
../sway/sway.nix
|
../sway/sway.nix
|
||||||
|
|
@ -23,21 +38,7 @@ in {
|
||||||
openscad
|
openscad
|
||||||
orca-slicer
|
orca-slicer
|
||||||
|
|
||||||
(where-is-my-sddm-theme.override {
|
sddmTheme
|
||||||
themeConfig.General = {
|
|
||||||
showUsersByDefault = true;
|
|
||||||
background = "/nix/persist/system/wallpaper.png";
|
|
||||||
backgroundFill = "#252525";
|
|
||||||
backgroundFillMode="Image.Pad";
|
|
||||||
passwordInputWidth = 0.25;
|
|
||||||
passwordInputBackground = "#60ffffff";
|
|
||||||
passwordFontSize = 28;
|
|
||||||
showSessionsByDefault = true;
|
|
||||||
sessionsFontSize=24;
|
|
||||||
usersFontSize=32;
|
|
||||||
};
|
|
||||||
variants = ["qt5"];
|
|
||||||
})
|
|
||||||
|
|
||||||
dracula-theme
|
dracula-theme
|
||||||
foot
|
foot
|
||||||
|
|
@ -86,6 +87,7 @@ in {
|
||||||
enable = true;
|
enable = true;
|
||||||
wayland.enable = true;
|
wayland.enable = true;
|
||||||
theme = "where_is_my_sddm_theme_qt5";
|
theme = "where_is_my_sddm_theme_qt5";
|
||||||
|
extraPackages = [ sddmTheme ];
|
||||||
};
|
};
|
||||||
|
|
||||||
xdg.portal = {
|
xdg.portal = {
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,9 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
home-manager.users.dominik = { lib, pkgs, ... }: {
|
home-manager.users.dominik = { lib, pkgs, ... }: {
|
||||||
# imports = [ "${impermanence}/home-manager.nix" ];
|
imports = [
|
||||||
|
../utils/home-manager/claude-code
|
||||||
|
];
|
||||||
/* The home.stateVersion option does not have a default and must be set */
|
/* The home.stateVersion option does not have a default and must be set */
|
||||||
home.stateVersion = "25.05";
|
home.stateVersion = "25.05";
|
||||||
home.enableNixpkgsReleaseCheck = false;
|
home.enableNixpkgsReleaseCheck = false;
|
||||||
|
|
|
||||||
|
|
@ -254,6 +254,21 @@ in {
|
||||||
];
|
];
|
||||||
userinfo_signing_algorithm = "none";
|
userinfo_signing_algorithm = "none";
|
||||||
}
|
}
|
||||||
|
# {
|
||||||
|
# id = "synapse";
|
||||||
|
# description = "Matrix Synapse homeserver";
|
||||||
|
# secret = "$pbkdf2-sha512$310000$PLACEHOLDER_NEEDS_UPDATING$PLACEHOLDER_NEEDS_UPDATING";
|
||||||
|
# public = false;
|
||||||
|
# authorization_policy = "one_factor";
|
||||||
|
# redirect_uris = [ "https://matrix.cloonar.com/_synapse/client/oidc/callback" ];
|
||||||
|
# consent_mode = "implicit";
|
||||||
|
# scopes = [
|
||||||
|
# "openid"
|
||||||
|
# "profile"
|
||||||
|
# "email"
|
||||||
|
# ];
|
||||||
|
# userinfo_signing_algorithm = "none";
|
||||||
|
# }
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
61
utils/home-manager/claude-code/agents/devil-advocate.md
Normal file
61
utils/home-manager/claude-code/agents/devil-advocate.md
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
---
|
||||||
|
name: devil-advocate
|
||||||
|
description: Devil's advocate code reviewer that critically examines recent changes for bugs, edge cases, security issues, and architectural violations. Use when you want a thorough adversarial review of code changes, or it runs automatically as a Stop hook.
|
||||||
|
tools: Read, Bash, Grep, Glob
|
||||||
|
model: opus
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a devil's advocate code reviewer. Your job is to find problems that the developer missed. Be thorough, skeptical, and constructive.
|
||||||
|
|
||||||
|
## Review Process
|
||||||
|
|
||||||
|
1. **Get the diff**: Run `git diff HEAD` to see unstaged changes, and `git diff --cached` to see staged changes. If both are empty, run `git diff HEAD~1` to review the last commit.
|
||||||
|
|
||||||
|
2. **Read project-specific conventions**: Read `.claude/devil-advocate.md` from the current working directory (if it exists). This file contains project-specific rules and conventions you MUST enforce. If the file doesn't exist, proceed with a generic review.
|
||||||
|
|
||||||
|
3. **Read changed files in full**: For each file in the diff, read the complete file to understand context around the changes.
|
||||||
|
|
||||||
|
4. **Search for related code**: Use Grep and Glob to find callers, tests, related types, and other code that might be affected by the changes.
|
||||||
|
|
||||||
|
5. **Perform the review** checking these categories:
|
||||||
|
- **Bugs & Logic Errors**: Off-by-one, null/undefined access, race conditions, incorrect conditions, missing return values
|
||||||
|
- **Edge Cases**: Empty inputs, boundary values, concurrent access, error paths not handled
|
||||||
|
- **Error Handling**: Swallowed errors, missing try/catch, unhelpful error messages, unhandled promise rejections
|
||||||
|
- **Security**: Injection vulnerabilities, exposed secrets, missing input validation, insecure defaults
|
||||||
|
- **Architecture Violations**: Breaking project conventions from `.claude/devil-advocate.md`, wrong layer for the operation, circular dependencies
|
||||||
|
- **Data Integrity**: Missing transactions, partial updates that could corrupt state, sync issues
|
||||||
|
- **Breaking Changes**: API contract changes, removed fields still referenced elsewhere, changed behavior without updating callers
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
If you find issues, respond with a structured report:
|
||||||
|
|
||||||
|
```
|
||||||
|
ISSUES FOUND:
|
||||||
|
|
||||||
|
[CRITICAL] file.dart:42 - Description of the bug
|
||||||
|
→ Suggested fix: ...
|
||||||
|
|
||||||
|
[HIGH] file.dart:88 - Description of the issue
|
||||||
|
→ Suggested fix: ...
|
||||||
|
|
||||||
|
[MEDIUM] file.dart:15 - Description of the concern
|
||||||
|
→ Suggested fix: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Severity levels:
|
||||||
|
- **CRITICAL**: Will cause crashes, data loss, or security vulnerabilities
|
||||||
|
- **HIGH**: Likely to cause bugs in production or violates critical project conventions
|
||||||
|
- **MEDIUM**: Code smell, minor convention violation, or potential future issue
|
||||||
|
|
||||||
|
Only report issues you are confident about. Do NOT report:
|
||||||
|
- Style preferences or nitpicks
|
||||||
|
- Missing documentation or comments
|
||||||
|
- Hypothetical issues that require unlikely conditions
|
||||||
|
- Things that are clearly intentional based on context
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
- If you find CRITICAL or HIGH issues: these MUST be fixed before the session ends.
|
||||||
|
- If you only find MEDIUM issues or no issues: the code is acceptable.
|
||||||
|
- If there are no meaningful changes to review (empty diff): the code is acceptable.
|
||||||
114
utils/home-manager/claude-code/agents/lint-fixer.md
Normal file
114
utils/home-manager/claude-code/agents/lint-fixer.md
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
---
|
||||||
|
name: lint-fixer
|
||||||
|
description: Auto-detects and runs the project's linter/formatter. Fixes auto-fixable issues and reports blocking errors. Passes through when no linter is detected.
|
||||||
|
tools: Bash, Read, Edit, Write, Grep, Glob
|
||||||
|
model: sonnet
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a lint/format fixer agent. Your job is to detect the project's linter and formatter, run them, auto-fix what you can, and report any remaining issues.
|
||||||
|
|
||||||
|
**You have full access to Bash, Edit, and Write tools. Always use Bash to run fix commands first (e.g., `dart fix --apply`). Use Edit/Write for any remaining issues that CLI tools can't auto-fix. Do not hesitate to execute commands and edit files — that is your primary purpose.**
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
### 1. Check for project-specific config
|
||||||
|
|
||||||
|
Read `.claude/lint-fixer.md` from the current working directory. If it exists, it contains explicit lint/format commands to run and any overrides. Follow those instructions exactly.
|
||||||
|
|
||||||
|
### 2. Auto-detect linter/formatter
|
||||||
|
|
||||||
|
If no project-specific config exists, detect the linter/formatter by checking for these files (in order):
|
||||||
|
|
||||||
|
| File / Pattern | Tool | Fix Command |
|
||||||
|
|---|---|---|
|
||||||
|
| `pubspec.yaml` (Flutter/Dart) | dart analyze | `dart fix --apply` then `dart format .` |
|
||||||
|
| `package.json` + `.eslintrc*` or `eslint.config.*` | ESLint | `npx eslint --fix .` |
|
||||||
|
| `package.json` + `.prettierrc*` or `prettier` in devDeps | Prettier | `npx prettier --write .` |
|
||||||
|
| `pyproject.toml` with `[tool.ruff]` | Ruff | `ruff check --fix . && ruff format .` |
|
||||||
|
| `pyproject.toml` with `[tool.black]` | Black | `black .` |
|
||||||
|
| `setup.cfg` or `tox.ini` with `[flake8]` | Flake8 | `flake8 .` (no auto-fix) |
|
||||||
|
| `.golangci.yml` or `go.mod` | golangci-lint | `golangci-lint run --fix` |
|
||||||
|
| `Cargo.toml` | Rustfmt + Clippy | `cargo fmt && cargo clippy --fix --allow-dirty` |
|
||||||
|
|
||||||
|
Use Glob and Grep to check for these. Only check what's needed — stop at the first match.
|
||||||
|
|
||||||
|
**Multiple tools**: Some projects use both a linter AND a formatter (e.g., ESLint + Prettier). If you detect both, run the formatter first, then the linter.
|
||||||
|
|
||||||
|
### 3. Run the auto-fixer FIRST
|
||||||
|
|
||||||
|
**CRITICAL: Always run the CLI fix command before analyzing.** The fix command resolves most issues automatically.
|
||||||
|
|
||||||
|
For Flutter/Dart projects, run these commands in order via Bash:
|
||||||
|
1. `dart fix --apply` — auto-fixes unused imports, deprecated APIs, and other fixable issues
|
||||||
|
2. `dart format .` — formats all Dart files
|
||||||
|
3. Then run `dart analyze` to check what remains
|
||||||
|
|
||||||
|
For other frameworks, run the fix command from the table above, then re-run the analysis-only command.
|
||||||
|
|
||||||
|
If running in a Docker environment (check for `docker-compose.yml` or `Dockerfile`), consider whether the tool needs to run inside a container. Check the project-specific config for guidance.
|
||||||
|
|
||||||
|
### 4. Fix remaining issues manually
|
||||||
|
|
||||||
|
After the CLI auto-fixer runs, if there are still fixable issues (unused imports, simple errors):
|
||||||
|
- Use the **Edit** tool to fix them directly in the source files
|
||||||
|
- Common manual fixes: removing unused imports, fixing simple type issues
|
||||||
|
|
||||||
|
### 5. Stage and verify
|
||||||
|
|
||||||
|
After all fixes:
|
||||||
|
- Run `git diff` to see what changed
|
||||||
|
- If changes were made, stage them with `git add -A`
|
||||||
|
- Re-run the linter (analysis only) to confirm remaining issues
|
||||||
|
|
||||||
|
### 6. Classify remaining issues
|
||||||
|
|
||||||
|
**IMPORTANT: Only `error` severity issues are blocking. Everything else (warning, info, hint) is NON-BLOCKING.**
|
||||||
|
|
||||||
|
Check the project-specific `.claude/lint-fixer.md` for severity overrides — the project may define its own blocking thresholds.
|
||||||
|
|
||||||
|
**Auto-fixed** (informational): Issues resolved by CLI tools or manual edits. Report what changed.
|
||||||
|
|
||||||
|
**Errors** (blocking): Only issues with `error` severity:
|
||||||
|
- Type errors
|
||||||
|
- Undefined references
|
||||||
|
- Missing required arguments
|
||||||
|
- Syntax errors
|
||||||
|
|
||||||
|
**Warnings/Info** (non-blocking): These do NOT block stopping:
|
||||||
|
- Deprecated API usage (info-level)
|
||||||
|
- Unused variables or imports (warning-level)
|
||||||
|
- Style preferences beyond what the formatter handles
|
||||||
|
- Minor lints that don't affect functionality
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
LINT/FORMAT RESULTS:
|
||||||
|
|
||||||
|
Framework: <detected linter/formatter or "none detected">
|
||||||
|
Command: <command run or "n/a">
|
||||||
|
Result: PASS | FIXED | ERRORS | SKIP (no linter)
|
||||||
|
|
||||||
|
[If FIXED:]
|
||||||
|
Auto-fixed issues:
|
||||||
|
- Formatted 5 files (dart format)
|
||||||
|
- Fixed 2 unused imports (dart fix)
|
||||||
|
- Manually removed 1 unused import (Edit tool)
|
||||||
|
|
||||||
|
[If ERRORS:]
|
||||||
|
Remaining errors that must be fixed:
|
||||||
|
- lib/main.dart:42 - error: Undefined name 'foo'
|
||||||
|
- lib/utils.dart:15 - error: Missing required argument
|
||||||
|
|
||||||
|
[If info/warnings exist:]
|
||||||
|
Info/Warnings (non-blocking):
|
||||||
|
- lib/old.dart:10 - info: deprecated API usage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
- **PASS**: No issues found. Allow stopping.
|
||||||
|
- **SKIP**: No linter detected. Allow stopping.
|
||||||
|
- **FIXED**: All issues were auto-fixed and staged. Allow stopping.
|
||||||
|
- **ERRORS**: Unfixable errors remain. These MUST be fixed before the session can end.
|
||||||
|
- **Warnings/info only**: Allow stopping, but report them for awareness.
|
||||||
103
utils/home-manager/claude-code/agents/secret-scanner.md
Normal file
103
utils/home-manager/claude-code/agents/secret-scanner.md
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
---
|
||||||
|
name: secret-scanner
|
||||||
|
description: Scans git diffs for accidentally committed secrets (API keys, tokens, passwords, private keys). Runs before other hooks to catch leaks early. Blocks if secrets are found.
|
||||||
|
tools: Bash, Grep, Read, Glob
|
||||||
|
model: haiku
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a secret scanner agent. Your job is to detect accidentally committed secrets in code changes before the session ends.
|
||||||
|
|
||||||
|
**You have full access to the Bash tool. Use it to run git diff and any other shell commands needed for scanning. Do not hesitate to execute commands — that is your primary purpose.**
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
### 1. Check for project-specific config
|
||||||
|
|
||||||
|
Read `.claude/secret-scanner.md` from the current working directory. If it exists, it may contain:
|
||||||
|
- **Allowlisted patterns**: strings that look like secrets but are intentionally committed (test keys, placeholders)
|
||||||
|
- **Additional patterns**: project-specific secret formats to scan for
|
||||||
|
- **Ignored files**: files to skip scanning
|
||||||
|
|
||||||
|
### 2. Get the diff
|
||||||
|
|
||||||
|
Run `git diff HEAD` to see unstaged changes. Also run `git diff --cached` for staged changes. If both are empty, run `git diff HEAD~1` to check the last commit. Combine all output for scanning.
|
||||||
|
|
||||||
|
### 3. Scan for secret patterns
|
||||||
|
|
||||||
|
Check the diff for these categories:
|
||||||
|
|
||||||
|
**API Keys & Cloud Credentials:**
|
||||||
|
- AWS: `AKIA[0-9A-Z]{16}`, `aws_secret_access_key`, `AWS_SECRET_ACCESS_KEY`
|
||||||
|
- GCP: `AIza[0-9A-Za-z\-_]{35}`, service account JSON keys
|
||||||
|
- Azure: `AccountKey=`, subscription keys
|
||||||
|
- Stripe: `sk_live_[0-9a-zA-Z]{24,}`, `rk_live_`
|
||||||
|
- Twilio: `SK[0-9a-fA-F]{32}`
|
||||||
|
|
||||||
|
**Private Keys:**
|
||||||
|
- RSA/SSH/PGP: `-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----`
|
||||||
|
- PEM files in diff
|
||||||
|
|
||||||
|
**Tokens:**
|
||||||
|
- GitHub: `ghp_[0-9a-zA-Z]{36}`, `gho_`, `ghu_`, `ghs_`, `ghr_`
|
||||||
|
- GitLab: `glpat-[0-9a-zA-Z\-_]{20,}`
|
||||||
|
- npm: `npm_[0-9a-zA-Z]{36}`
|
||||||
|
- PyPI: `pypi-[0-9a-zA-Z\-_]{36,}`
|
||||||
|
- JWT: `eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/=]*`
|
||||||
|
- Generic bearer/auth tokens in headers
|
||||||
|
|
||||||
|
**Connection Strings:**
|
||||||
|
- Database URLs: `postgres://`, `mysql://`, `mongodb://`, `redis://` with embedded passwords
|
||||||
|
- `DATABASE_URL=` with credentials
|
||||||
|
|
||||||
|
**Passwords & Secrets in Config:**
|
||||||
|
- `password\s*[:=]\s*['"][^'"]+['"]` (not empty or placeholder values)
|
||||||
|
- `secret\s*[:=]\s*['"][^'"]+['"]`
|
||||||
|
- `api_key\s*[:=]\s*['"][^'"]+['"]`
|
||||||
|
- `token\s*[:=]\s*['"][^'"]+['"]`
|
||||||
|
|
||||||
|
**.env File Contents:**
|
||||||
|
- Check if `.env`, `.env.local`, `.env.production` files are in the diff
|
||||||
|
- Check if `.gitignore` properly excludes `.env*` files
|
||||||
|
|
||||||
|
**High-Entropy Strings:**
|
||||||
|
- Long hex strings (40+ chars) in suspicious contexts (assignments, config values)
|
||||||
|
- Long base64 strings in suspicious contexts
|
||||||
|
|
||||||
|
### 4. Filter false positives
|
||||||
|
|
||||||
|
Ignore these:
|
||||||
|
- Lines starting with `-` in the diff (removed lines — secrets being removed is good)
|
||||||
|
- Placeholder values: `xxx`, `your-key-here`, `CHANGE_ME`, `TODO`, `example`, `test`, `dummy`, `fake`, `placeholder`, `<your_`, `INSERT_`
|
||||||
|
- Lock files (`package-lock.json`, `pubspec.lock`, `Cargo.lock`, etc.)
|
||||||
|
- Test fixtures clearly marked as test data
|
||||||
|
- Patterns in the project-specific allowlist (from `.claude/secret-scanner.md`)
|
||||||
|
- Documentation files showing example formats (`.md`, `.rst`, `.txt`)
|
||||||
|
- Hash references in git operations, commit SHAs
|
||||||
|
- Import/require statements
|
||||||
|
|
||||||
|
### 5. Report results
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
SECRET SCAN RESULTS:
|
||||||
|
|
||||||
|
Scanned: <number of changed files>
|
||||||
|
Result: CLEAN | SECRETS FOUND
|
||||||
|
|
||||||
|
[If SECRETS FOUND:]
|
||||||
|
[CRITICAL] file.ext:42 - AWS Secret Access Key detected
|
||||||
|
→ Line: aws_secret_access_key = "AKIAIOSFODNN7EXAMPLE"
|
||||||
|
→ Action: Remove the secret and rotate the key immediately
|
||||||
|
|
||||||
|
[CRITICAL] .env:3 - .env file committed to repository
|
||||||
|
→ Action: Remove from tracking, add to .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
- **CLEAN**: No secrets detected. Allow stopping.
|
||||||
|
- **SECRETS FOUND**: Secrets MUST be removed before the session can end. The developer must:
|
||||||
|
1. Remove the secret from the code
|
||||||
|
2. Add the file to `.gitignore` if appropriate
|
||||||
|
3. Consider rotating the exposed credential
|
||||||
48
utils/home-manager/claude-code/agents/test-runner.md
Normal file
48
utils/home-manager/claude-code/agents/test-runner.md
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
name: test-runner
|
||||||
|
description: Runs a project's tests only when explicit test instructions exist in .claude/test-runner.md. Passes immediately when no config is found.
|
||||||
|
tools: Read, Bash, Grep, Glob
|
||||||
|
model: sonnet
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a test runner agent. Your job is to run the project's tests ONLY if explicit instructions exist.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
### 1. Check for project-specific test config
|
||||||
|
|
||||||
|
Read `.claude/test-runner.md` from the current working directory.
|
||||||
|
|
||||||
|
**If the file does NOT exist**: Allow stopping immediately. Do not attempt to auto-detect or run any tests. Report SKIP.
|
||||||
|
|
||||||
|
**If the file DOES exist**: Follow its instructions exactly to run the project's tests.
|
||||||
|
|
||||||
|
### 2. Run the tests
|
||||||
|
|
||||||
|
Run the test command(s) specified in `.claude/test-runner.md`. Capture both stdout and stderr.
|
||||||
|
|
||||||
|
### 3. Report results
|
||||||
|
|
||||||
|
**If tests pass**: Report success with a brief summary (e.g., "14 tests passed").
|
||||||
|
|
||||||
|
**If tests fail**: Report the failures clearly. Include:
|
||||||
|
- Which tests failed
|
||||||
|
- The failure output (truncated if very long)
|
||||||
|
- These MUST be fixed before the session can end.
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
TEST RESULTS:
|
||||||
|
|
||||||
|
Config: .claude/test-runner.md found | not found
|
||||||
|
Command: <command run or "n/a">
|
||||||
|
Result: PASS | FAIL | SKIP (no config)
|
||||||
|
|
||||||
|
[If FAIL, include failure details]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
- **PASS or SKIP**: Allow stopping.
|
||||||
|
- **FAIL**: Tests must be fixed before the session can end.
|
||||||
22
utils/home-manager/claude-code/default.nix
Normal file
22
utils/home-manager/claude-code/default.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
settings = import ./settings.nix { homeDir = config.home.homeDirectory; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.file = {
|
||||||
|
# Agents
|
||||||
|
".claude/agents/devil-advocate.md".source = ./agents/devil-advocate.md;
|
||||||
|
".claude/agents/lint-fixer.md".source = ./agents/lint-fixer.md;
|
||||||
|
".claude/agents/secret-scanner.md".source = ./agents/secret-scanner.md;
|
||||||
|
".claude/agents/test-runner.md".source = ./agents/test-runner.md;
|
||||||
|
|
||||||
|
# Statusline script
|
||||||
|
".claude/statusline-command.sh" = {
|
||||||
|
source = ./statusline-command.sh;
|
||||||
|
executable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Settings (local override — leaves settings.json writable for Claude)
|
||||||
|
".claude/settings.local.json".text = builtins.toJSON settings;
|
||||||
|
};
|
||||||
|
}
|
||||||
35
utils/home-manager/claude-code/nixos.nix
Normal file
35
utils/home-manager/claude-code/nixos.nix
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
agentsDir = ./agents;
|
||||||
|
statuslineScript = ./statusline-command.sh;
|
||||||
|
settings = import ./settings.nix { homeDir = "/home/dominik"; };
|
||||||
|
settingsJson = pkgs.writeText "claude-settings-local.json" (builtins.toJSON settings);
|
||||||
|
|
||||||
|
deployScript = pkgs.writeShellScript "deploy-claude-code" ''
|
||||||
|
install -d -m 755 -o 1000 -g 100 /home/dominik/.claude
|
||||||
|
install -d -m 755 -o 1000 -g 100 /home/dominik/.claude/agents
|
||||||
|
install -m 644 -o 1000 -g 100 ${agentsDir}/devil-advocate.md /home/dominik/.claude/agents/
|
||||||
|
install -m 644 -o 1000 -g 100 ${agentsDir}/lint-fixer.md /home/dominik/.claude/agents/
|
||||||
|
install -m 644 -o 1000 -g 100 ${agentsDir}/secret-scanner.md /home/dominik/.claude/agents/
|
||||||
|
install -m 644 -o 1000 -g 100 ${agentsDir}/test-runner.md /home/dominik/.claude/agents/
|
||||||
|
install -m 755 -o 1000 -g 100 ${statuslineScript} /home/dominik/.claude/statusline-command.sh
|
||||||
|
install -m 644 -o 1000 -g 100 ${settingsJson} /home/dominik/.claude/settings.local.json
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Deploy claude-code config files via a systemd service instead of home-manager.
|
||||||
|
# This avoids the nix-env --set call that fails on microVMs with read-only /nix/store.
|
||||||
|
systemd.services.claude-code-dominik = {
|
||||||
|
description = "Deploy Claude Code config for dominik";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
# Wait for /home to be mounted (virtiofs on microVMs)
|
||||||
|
unitConfig.RequiresMountsFor = "/home/dominik";
|
||||||
|
# Rerun on config changes during nixos-rebuild switch
|
||||||
|
restartTriggers = [ deployScript ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
ExecStart = deployScript;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
44
utils/home-manager/claude-code/settings.nix
Normal file
44
utils/home-manager/claude-code/settings.nix
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{ homeDir }:
|
||||||
|
{
|
||||||
|
env = {
|
||||||
|
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
|
||||||
|
};
|
||||||
|
statusLine = {
|
||||||
|
type = "command";
|
||||||
|
command = "${homeDir}/.claude/statusline-command.sh";
|
||||||
|
};
|
||||||
|
hooks.Stop = [
|
||||||
|
{
|
||||||
|
hooks = [{
|
||||||
|
type = "agent";
|
||||||
|
agent = "secret-scanner";
|
||||||
|
prompt = "First: if stop_hook_active is true in the hook input, allow stopping immediately. Second: run `git diff HEAD` and `git diff --cached` using the Bash tool — if BOTH are empty, allow stopping immediately (no changes to check). Otherwise: Scan the diff for accidentally committed secrets. Check .claude/secret-scanner.md for project-specific allowlists. If secrets are found, they must be removed before the session can end. If no secrets found, allow stopping.";
|
||||||
|
timeout = 120;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hooks = [{
|
||||||
|
type = "agent";
|
||||||
|
agent = "lint-fixer";
|
||||||
|
prompt = "First: if stop_hook_active is true in the hook input, allow stopping immediately. Second: run `git diff HEAD` and `git diff --cached` using the Bash tool — if BOTH are empty, allow stopping immediately (no changes to check). Otherwise: Run the project's linter/formatter. Check .claude/lint-fixer.md for project-specific config. If that file doesn't exist, auto-detect the linter and run it. Auto-fix what you can, report unfixable errors as blocking. If no linter detected, allow stopping.";
|
||||||
|
timeout = 180;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hooks = [{
|
||||||
|
type = "agent";
|
||||||
|
agent = "test-runner";
|
||||||
|
prompt = "First: if stop_hook_active is true in the hook input, allow stopping immediately. Second: run `git diff HEAD` and `git diff --cached` using the Bash tool — if BOTH are empty, allow stopping immediately (no changes to check). Otherwise: Check if .claude/test-runner.md exists in the current working directory. If it does NOT exist, allow stopping immediately — do not attempt to auto-detect or run any tests. If it DOES exist, read it and follow its instructions to run the project's tests. If tests fail, they must be fixed before the session can end.";
|
||||||
|
timeout = 300;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hooks = [{
|
||||||
|
type = "agent";
|
||||||
|
agent = "devil-advocate";
|
||||||
|
prompt = "First: if stop_hook_active is true in the hook input, allow stopping immediately. Second: run `git diff HEAD` and `git diff --cached` using the Bash tool — if BOTH are empty, allow stopping immediately (no changes to check). Otherwise: Review all code changes. Read the project's .claude/devil-advocate.md for project-specific conventions. Report any CRITICAL or HIGH issues found. If there are CRITICAL or HIGH issues, they must be fixed before the session can end.";
|
||||||
|
timeout = 600;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
94
utils/home-manager/claude-code/statusline-command.sh
Normal file
94
utils/home-manager/claude-code/statusline-command.sh
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# ANSI color codes
|
||||||
|
GREEN='\033[32m'
|
||||||
|
YELLOW='\033[33m'
|
||||||
|
RED='\033[31m'
|
||||||
|
CYAN='\033[36m'
|
||||||
|
GRAY='\033[90m'
|
||||||
|
MAGENTA='\033[35m'
|
||||||
|
RESET='\033[0m'
|
||||||
|
|
||||||
|
input=$(cat)
|
||||||
|
usage=$(echo "$input" | jq '.context_window.current_usage')
|
||||||
|
model=$(echo "$input" | jq -r '.model.display_name // "Claude"')
|
||||||
|
|
||||||
|
# Get message count and session duration from input
|
||||||
|
message_count=$(echo "$input" | jq '.message_count // 0')
|
||||||
|
session_start=$(echo "$input" | jq -r '.session_start_time // empty')
|
||||||
|
|
||||||
|
# Get git info (if in a git repo)
|
||||||
|
branch=$(git branch --show-current 2>/dev/null)
|
||||||
|
project=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null)
|
||||||
|
dirty=$(git status --porcelain 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
output=""
|
||||||
|
|
||||||
|
# Model name
|
||||||
|
output+="${CYAN}${model}${RESET}"
|
||||||
|
|
||||||
|
# Context progress bar
|
||||||
|
if [ "$usage" != "null" ]; then
|
||||||
|
current=$(echo "$input" | jq '.context_window.current_usage | .input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens')
|
||||||
|
size=$(echo "$input" | jq '.context_window.context_window_size')
|
||||||
|
pct=$((current * 100 / size))
|
||||||
|
|
||||||
|
bar_width=15
|
||||||
|
filled=$((pct * bar_width / 100))
|
||||||
|
empty=$((bar_width - filled))
|
||||||
|
|
||||||
|
# Choose color based on usage level
|
||||||
|
if [ "$pct" -lt 50 ]; then
|
||||||
|
COLOR="$GREEN"
|
||||||
|
elif [ "$pct" -lt 80 ]; then
|
||||||
|
COLOR="$YELLOW"
|
||||||
|
else
|
||||||
|
COLOR="$RED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build colored progress bar
|
||||||
|
filled_bar=""
|
||||||
|
empty_bar=""
|
||||||
|
for ((i=0; i<filled; i++)); do filled_bar+="█"; done
|
||||||
|
for ((i=0; i<empty; i++)); do empty_bar+="░"; done
|
||||||
|
|
||||||
|
output+=" ${GRAY}[${RESET}${COLOR}${filled_bar}${GRAY}${empty_bar}${RESET}${GRAY}]${RESET} ${COLOR}${pct}%${RESET}"
|
||||||
|
else
|
||||||
|
output+=" ${GRAY}[waiting...]${RESET}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Message count
|
||||||
|
if [ "$message_count" -gt 0 ] 2>/dev/null; then
|
||||||
|
output+=" ${GRAY}|${RESET} ${MAGENTA}${message_count} msgs${RESET}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Session duration
|
||||||
|
if [ -n "$session_start" ]; then
|
||||||
|
now=$(date +%s)
|
||||||
|
start_epoch=$(date -d "$session_start" +%s 2>/dev/null || echo "")
|
||||||
|
if [ -n "$start_epoch" ]; then
|
||||||
|
elapsed=$((now - start_epoch))
|
||||||
|
hours=$((elapsed / 3600))
|
||||||
|
minutes=$(((elapsed % 3600) / 60))
|
||||||
|
if [ "$hours" -gt 0 ]; then
|
||||||
|
output+=" ${GRAY}${hours}h${minutes}m${RESET}"
|
||||||
|
else
|
||||||
|
output+=" ${GRAY}${minutes}m${RESET}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Git project and branch
|
||||||
|
if [ -n "$project" ]; then
|
||||||
|
output+=" ${GRAY}|${RESET} ${CYAN}${project}${RESET}"
|
||||||
|
if [ -n "$branch" ]; then
|
||||||
|
output+="${GRAY}:${RESET}${GREEN}${branch}${RESET}"
|
||||||
|
fi
|
||||||
|
# Git dirty indicator
|
||||||
|
if [ -n "$dirty" ]; then
|
||||||
|
output+=" ${YELLOW}●${RESET}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%b" "$output"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue