Moving to modules. Structural changes.

This commit is contained in:
kat witch 2021-07-05 22:47:28 +01:00
parent 3903bc1766
commit 060d4c6d1e
No known key found for this signature in database
GPG key ID: 1B477797DCA5EC72
258 changed files with 621 additions and 407 deletions

View file

@ -0,0 +1 @@
{ ... }: { imports = [ ./weechat.nix ]; }

View file

@ -0,0 +1,5 @@
{ config, pkgs, lib, ... }:
{
programs.weechat = { enable = true; };
}

View file

@ -0,0 +1,120 @@
{ lib, config, ... }: with lib; {
config = {
deploy.targets.infra = {
tf = {
resources.hcloud_ssh_key = {
provider = "hcloud";
type = "ssh_key";
inputs = {
name = "yubikey";
public_key =
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCocjQqiDIvzq+Qu3jkf7FXw5piwtvZ1Mihw9cVjdVcsra3U2c9WYtYrA3rS50N3p00oUqQm9z1KUrvHzdE+03ZCrvaGdrtYVsaeoCuuvw7qxTQRbItTAEsfRcZLQ5c1v/57HNYNEsjVrt8VukMPRXWgl+lmzh37dd9w45cCY1QPi+JXQQ/4i9Vc3aWSe4X6PHOEMSBHxepnxm5VNHm4PObGcVbjBf0OkunMeztd1YYA9sEPyEK3b8IHxDl34e5t6NDLCIDz0N/UgzCxSxoz+YJ0feQuZtud/YLkuQcMxW2dSGvnJ0nYy7SA5DkW1oqcy6CGDndHl5StOlJ1IF9aGh0gGkx5SRrV7HOGvapR60RphKrR5zQbFFka99kvSQgOZqSB3CGDEQGHv8dXKXIFlzX78jjWDOBT67vA/M9BK9FS2iNnBF5x6shJ9SU5IK4ySxq8qvN7Us8emkN3pyO8yqgsSOzzJT1JmWUAx0tZWG/BwKcFBHfceAPQl6pwxx28TM3BTBRYdzPJLTkAy48y6iXW6UYdfAPlShy79IYjQtEThTuIiEzdzgYdros0x3PDniuAP0KOKMgbikr0gRa6zahPjf0qqBnHeLB6nHAfaVzI0aNbhOg2bdOueE1FX0x48sjKqjOpjlIfq4WeZp9REr2YHEsoLFOBfgId5P3BPtpBQ== cardno:000612078454";
};
};
resources.athame = {
provider = "null";
type = "resource";
connection = {
port = 62954;
host = "athame.kittywit.ch";
};
};
};
};
network.nodes.athame = {
imports = lib.hostImport "athame";
networking = {
hostName = "athame";
};
};
};
}
# For the eventual migration
#resources.athame = {
#provider = "hcloud";
# type = "server";
# inputs = {
# name = "athame";
# image = "ubuntu-20.04";
# server_type = "cpx21";
# location = "nbg1";
# backups = false;
# ssh_keys = [ (hcloud_ssh_key.refAttr "id") ];
# };
# connection = { host = config.lib.tf.terraformSelf "ipv4_address"; };
# provisioners = [
# {
# file = {
# destination = "/tmp/sshportfix.nix";
# content = "{ config, ...}: { services.openssh.ports = [ 62954 ]; }";
# };
# }
# {
# remote-exec.command =
# "curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | NIXOS_IMPORT=/tmp/sshportfix.nix NO_REBOOT=true PROVIDER=hetznercloud NIX_CHANNEL=nixos-20.09 bash 2>&1 | tee /tmp/infect.log";
# }
# {
# remote-exec.command = "reboot";
# onFailure = "continue";
# }
# ];
#};
/* resources.athame_rdns = {
provider = "hcloud";
type = "rdns";
inputs = {
server_id = athame.refAttr "id";
dns_ptr = "athame.kittywit.ch";
ip_address = athame.refAttr "ipv4_address";
};
};
*/
#dns.records.kittywitch_athame = {
# tld = "kittywit.ch.";
# domain = "athame";
# a.address = athame.refAttr "ipv4_address";
#};
#dns.records.kittywitch_root = {
# tld = "kittywit.ch.";
# domain = "@";
# a.address = athame.refAttr "ipv4_address";
#};
#dns.records.kittywitch_athame_v6 = {
# tld = "kittywit.ch.";
# domain = "athame";
# aaaa.address = athame.refAttr "ipv6_address";
#};
#dns.records.kittywitch_root_v6 = {
# tld = "kittywit.ch.";
# domain = "@";
# aaaa.address = athame.refAttr "ipv6_address";
#};
#dns.records.kittywitch_www = {
# tld = "kittywit.ch.";
# domain = "www";
# cname.target = "athame.kittywit.ch.";
#};
# connection = {
# host = athame.refAttr "ipv4_address";
# port = 62954;
# };
#triggers.switch = lib.mapAttrs (name: record:
# {
# A = config.lib.tf.terraformExpr
# ''join(",", ${record.out.resource.namedRef}.addresses)'';
# AAAA = config.lib.tf.terraformExpr
# ''join(",", ${record.out.resource.namedRef}.addresses)'';
# CNAME = record.out.resource.refAttr "cname";
# SRV = record.out.resource.refAttr "id";
# }.${record.out.type}) config.dns.records;

View file

@ -0,0 +1,79 @@
{ config, users, lib, pkgs, profiles, ... }:
with lib;
{
imports = [
./hw.nix
# profiles
users.kat.server
# host-specific services
./virtualhosts.nix
# services
../../../services/fail2ban.nix
../../../services/logrotate.nix
../../../services/postgres.nix
../../../services/nginx.nix
../../../services/mail.nix
../../../services/calendar.nix
../../../services/xmpp.nix
../../../services/gitea
../../../services/syncplay.nix
../../../services/weechat.nix
../../../services/vaultwarden.nix
../../../services/taskserver.nix
../../../services/murmur.nix
../../../services/matrix.nix
../../../services/restic.nix
../../../services/grafana.nix
../../../services/prometheus.nix
../../../services/loki.nix
../../../services/node-exporter.nix
../../../services/promtail.nix
../../../services/netdata.nix
../../../services/znc.nix
../../../services/asterisk.nix
];
boot.loader.grub.enable = true;
boot.loader.grub.version = 2;
networking = {
hostName = "athame";
domain = "kittywit.ch";
hostId = "7b0ac74e";
useDHCP = false;
interfaces.enp1s0.useDHCP = true;
};
networking.interfaces.enp1s0.ipv6.addresses = [{
address = "2a01:4f8:c2c:b7a8::1";
prefixLength = 64;
}];
networking.defaultGateway6 = {
address = "fe80::1";
interface = "enp1s0";
};
kw.fw.public.interfaces = singleton "enp1s0";
kw.fw.private.interfaces = singleton "hexnet";
kw.fw.public.tcp.ports = singleton 52969;
deploy.tf.dns.records.kittywitch_athame_v6 = {
tld = "kittywit.ch.";
domain = "athame";
aaaa.address =
(lib.head config.networking.interfaces.enp1s0.ipv6.addresses).address;
};
hexchen.network = {
enable = true;
pubkey = "55e3f29c252d16e73ac849a6039824f94df1dee670c030b9e29f90584f935575";
listen.enable = true;
listen.endpoints = [ "tcp://168.119.126.111:52969" ];
};
system.stateVersion = "20.09";
}

View file

@ -0,0 +1,10 @@
{ sources, ... }: {
imports = [ (sources.nixpkgs + "/nixos/modules/profiles/qemu-guest.nix") ];
boot.initrd.availableKernelModules =
[ "ata_piix" "uhci_hcd" "virtio_pci" "sd_mod" "sr_mod" ];
boot.loader.grub.device = "/dev/sda";
fileSystems."/" = {
device = "/dev/sda1";
fsType = "ext4";
};
}

View file

@ -0,0 +1,23 @@
{ config, lib, pkgs, ... }:
with lib;
let
common = {
enableACME = true;
forceSSL = true;
};
in
{
services.nginx.virtualHosts = {
"kittywit.ch" = { root = pkgs.kat-website; } // common;
"athame.kittywit.ch" = { root = "/var/www/athame"; } // common;
"files.kittywit.ch" = { root = "/var/www/files"; } // common;
};
deploy.tf.dns.records.kittywitch_files = {
tld = "kittywit.ch.";
domain = "files";
cname.target = "athame.kittywit.ch.";
};
}

View file

@ -0,0 +1,48 @@
{ config, lib, pkgs, sources, ... }:
with lib;
let
hexchen = (import sources.hexchen) { };
hexYgg = filterAttrs (_: c: c.enable)
(mapAttrs (_: host: host.config.hexchen.network) hexchen.hosts);
in
{
# stuff so dummy host is buildable (you probably don't want/need this???)
# but idk your config sooooo
boot.isContainer = true;
networking.useDHCP = false;
users.users.root.hashedPassword = "";
hexchen.network = {
enable = true;
pubkey = "0000000000000000000000000000000000000000000000000000000000000000";
listen.enable = true;
listen.endpoints = flatten (map (c: c.listen.endpoints) (filter
(c:
c.listen.enable && (c.pubkey
!= "0000000000000000000000000000000000000000000000000000000000000000"))
(attrValues hexYgg)));
extra.pubkeys = {
satorin =
"53d99a74a648ff7bd5bc9ba68ef4f472fb4fb8b2e26dfecea33c781f0d5c9525";
shanghai =
"0cc3c26366cbfddfb1534b25c5655733d8f429edc941bcce674c46566fc87027";
grimoire =
"2a1567a2848540070328c9e938c58d40f2b1a3f08982c15c7edc5dcabfde3330";
boline =
"89684441745467da0d1bf7f47dc74ec3ca65e05c72f752298ef3c22a22024d43";
} // (mapAttrs (_: c: c.pubkey) hexYgg);
};
# snippet for single host
# hexchen.network = {
# enable = true;
# pubkey = "0000000000000000000000000000000000000000000000000000000000000000";
# # if server, enable this and set endpoint:
# listen.enable = false;
# listen.endpoints = [
# "tcp://0.0.0.0:0"
# ];
# };
}

View file

@ -0,0 +1 @@
{ ... }: { }

View file

View file

@ -0,0 +1,29 @@
{ config, users, pkgs, profiles, ... }:
{
imports = [
./hw.nix
profiles.gui
profiles.fvwm
profiles.laptop
users.kairi.guiFull
];
networking.wireless.interfaces = [ "wlp3s0" ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelPackages = pkgs.linuxPackages;
boot.kernelModules = [ "wl" ];
boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ];
networking.hostId = "d199ad70";
networking.hostName = "mabon";
networking.useDHCP = false;
networking.interfaces.enp1s0.useDHCP = false;
networking.interfaces.wlp2s0.useDHCP = true;
system.stateVersion = "20.09";
}

View file

@ -0,0 +1,38 @@
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot.initrd.availableKernelModules = [
"uhci_hcd"
"ehci_pci"
"ahci"
"firewire_ohci"
"usbhid"
"usb_storage"
"sd_mod"
"sr_mod"
"sdhci_pci"
];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" "wl" ];
boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/236f9363-19ee-46e3-8db4-5dd1e28b742d";
fsType = "ext4";
};
boot.initrd.luks.devices."cryptroot".device =
"/dev/disk/by-uuid/b0435b6c-fd76-44d0-8b63-2c2c059df814";
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/84FB-4F88";
fsType = "vfat";
};
swapDevices =
[{ device = "/dev/disk/by-uuid/926c41d6-c06a-4dcc-b55d-f4cfaafe4bac"; }];
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
}

12
config/hosts/mabon/tf.nix Normal file
View file

@ -0,0 +1,12 @@
{ config, hosts, ... }: {
config = {
resources.mabon = {
provider = "null";
type = "resource";
connection = {
port = 62954;
host = "192.168.1.119";
};
};
};
}

View file

@ -0,0 +1 @@
{ ... }: { }

View file

@ -0,0 +1,22 @@
{ lib, config, ... }: with lib; {
config = {
deploy.targets.personal = {
tf = {
resources.ostara = {
provider = "null";
type = "resource";
connection = {
port = 62954;
host = "192.168.1.245";
};
};
};
network.nodes.samhain = {
imports = lib.hostImport "samhain";
networking = {
hostName = "samhain";
};
};
};
};
}

View file

@ -0,0 +1,58 @@
{ lib, config, users, pkgs, profiles, ... }:
with lib;
{
imports = [ ./hw.nix profiles.laptop ];
boot.loader.grub.enable = true;
boot.loader.grub.version = 2;
boot.loader.grub.device = "/dev/sda";
networking.hostId = "9f89b327";
networking.hostName = "ostara";
networking.useDHCP = false;
networking.interfaces.enp1s0.useDHCP = true;
networking.interfaces.wlp2s0.useDHCP = true;
kw.fw.public.interfaces = singleton "wlp2s0";
kw.fw.public.tcp.ports = [ 9981 9982 ];
hardware.firmware = [ pkgs.libreelec-dvb-firmware ];
services.tvheadend.enable = true;
systemd.services.tvheadend.enable = lib.mkForce false;
systemd.services.tvheadend-kat = {
description = "Tvheadend TV streaming server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
script = ''
${pkgs.tvheadend}/bin/tvheadend \
--http_root /tvheadend \
--http_port 9981 \
--htsp_port 9982 \
-f \
-C \
-p ${config.users.users.tvheadend.home}/tvheadend.pid \
-u tvheadend \
-g video
'';
serviceConfig = {
Type = "forking";
PIDFile = "${config.users.users.tvheadend.home}/tvheadend.pid";
Restart = "always";
RestartSec = 5;
User = "tvheadend";
Group = "video";
ExecStop = "${pkgs.coreutils}/bin/rm ${config.users.users.tvheadend.home}/tvheadend.pid";
};
};
system.stateVersion = "20.09";
}

View file

@ -0,0 +1,20 @@
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot.initrd.availableKernelModules =
[ "uhci_hcd" "ehci_pci" "ahci" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "/dev/disk/by-uuid/469a684b-eb8f-48a8-8f98-be58528312c4";
fsType = "ext4";
};
swapDevices =
[{ device = "/dev/disk/by-uuid/2223e305-79c9-45b3-90d7-560dcc45775a"; }];
}

View file

@ -0,0 +1,12 @@
{ config, hosts, ... }: {
config = {
resources.ostara = {
provider = "null";
type = "resource";
connection = {
port = 62954;
host = "192.168.1.245";
};
};
};
}

View file

@ -0,0 +1 @@
{ ... }: { imports = [ ./sway ]; }

View file

@ -0,0 +1,79 @@
{ config, pkgs, ... }:
{
imports = [ ./swayidle.nix ];
wayland.windowManager.sway = {
config =
let
lockCommand = "swaylock -i HDMI-A-1:${../../../../users/kat/sway/wallpapers/main.png} -i DP-1:${
../../../../users/kat/sway/wallpapers/left.jpg
} -i DVI-D-1:${../../../../users/kat/sway/wallpapers/right.jpg} -s fill";
cfg = config.wayland.windowManager.sway.config;
in
{
#startup = [{ command = "${pkgs.ckb-next}/bin/ckb-next -b"; }];
output =
let
left = {
res = "1920x1080";
pos = "0 0";
};
middle = {
res = "1920x1200";
pos = "1920 0";
};
right = {
res = "1920x1080";
pos = "3840 0";
};
in
{
"DP-1" = left;
"DVI-D-1" = middle;
"HDMI-A-1" = right;
};
keybindings = {
"${cfg.modifier}+x" = "exec ${lockCommand}";
};
modes = {
"System (l) lock, (e) logout, (s) suspend, (h) hibernate, (r) reboot, (Shift+s) shutdown" =
{
"l" = "exec ${lockCommand}, mode default";
"e" = "exec swaymsg exit, mode default";
"s" = "exec systemctl suspend, mode default";
"h" = "exec systemctl hibernate, mode default";
"r" = "exec systemctl reboot, mode default";
"Shift+s" = "exec systemctl shutdown, mode default";
"Return" = "mode default";
"Escape" = "mode default";
};
};
input = {
"5824:1503:screenstub-tablet" = { events = "disabled"; };
"5824:1503:screenstub-mouse" = { events = "disabled"; };
"5824:1503:screenstub-kbd" = { events = "disabled"; };
"1386:215:Wacom_BambooPT_2FG_Small_Pen" = {
map_to_output = "HDMI-A-1";
};
"1386:215:Wacom_BambooPT_2FG_Small_Finger" = {
natural_scroll = "enabled";
middle_emulation = "enabled";
tap = "enabled";
dwt = "enabled";
accel_profile = "flat";
pointer_accel = "0.05";
};
};
};
extraConfig = ''
workspace "1" output "DP-1"
workspace "11:F1" output "DVI-1"
workspace "12:F2" output "HDMI-A-1"
'';
};
}

View file

@ -0,0 +1,32 @@
{ config, pkgs, lib, ... }:
{
systemd.user.services.swayidle = {
Unit = {
Description = "swayidle";
Documentation = [ "man:swayidle(1)" ];
PartOf = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = ''
${pkgs.swayidle}/bin/swayidle -w \
timeout 300 '${pkgs.swaylock}/bin/swaylock -f -i HDMI-A-1:${
../../../../users/kat/sway/wallpapers/main.png
} -i DP-1:${../../../../users/kat/sway/wallpapers/left.jpg} -i DVI-D-1:${
../../../../users/kat/sway/wallpapers/right.jpg
}' \
timeout 600 'swaymsg "output * dpms off"' \
resume 'swaymsg "output * dpms on"' \
before-sleep '${pkgs.swaylock}/bin/swaylock -f -i HDMI-A-1:${
../../../../users/kat/sway/wallpapers/main.png
} -i DP-1:${../../../../users/kat/sway/wallpapers/left.jpg} -i DVI-D-1:${
../../../../users/kat/sway/wallpapers/right.jpg
}'
'';
RestartSec = 3;
Restart = "always";
};
Install = { WantedBy = [ "sway-session.target" ]; };
};
}

View file

@ -0,0 +1,22 @@
{ lib, config, ... }: with lib; {
config = {
deploy.targets.personal = {
tf = {
resources.samhain = {
provider = "null";
type = "resource";
connection = {
port = 62954;
host = "192.168.1.135";
};
};
};
};
network.nodes.samhain = {
imports = lib.hostImport "samhain";
networking = {
hostName = "samhain";
};
};
};
}

View file

@ -0,0 +1,142 @@
{ tf, config, users, pkgs, lib, profiles, sources, ... }:
with lib;
let
hexchen = (import sources.hexchen) { };
hexYgg = filterAttrs (_: c: c.enable)
(mapAttrs (_: host: host.config.hexchen.network) hexchen.hosts);
in
{
imports = [
./hw.nix
profiles.gui
profiles.sway
users.kat.guiFull
../../../services/zfs.nix
../../../services/restic.nix
../../../services/nginx.nix
../../../services/node-exporter.nix
../../../services/promtail.nix
../../../services/netdata.nix
./transmission.nix
./jellyfin.nix
./virtualhosts.nix
];
home-manager.users.kat = {
imports = [
../home
];
};
deploy.tf.variables.dyn_username = {
type = "string";
value.shellCommand = "bitw get infra/hexdns-dynamic -f username";
};
deploy.tf.variables.dyn_password = {
type = "string";
value.shellCommand = "bitw get infra/hexdns-dynamic -f password";
};
deploy.tf.variables.dyn_hostname = {
type = "string";
value.shellCommand = "bitw get infra/hexdns-dynamic -f hostname";
};
security.acme.certs."samhain.net.kittywit.ch" = {
domain = "samhain.net.kittywit.ch";
dnsProvider = "rfc2136";
credentialsFile = config.secrets.files.dns_creds.path;
group = "nginx";
};
secrets.files.kat-glauca-dns = {
text = ''
user="${tf.variables.dyn_username.ref}"
pass="${tf.variables.dyn_password.ref}"
hostname="${tf.variables.dyn_hostname.ref}"
'';
owner = "kat";
group = "users";
};
systemd.services.kat-glauca-dns = {
serviceConfig = {
ExecStart = "${pkgs.kat-glauca-dns}/bin/kat-glauca-dns";
};
environment = { passFile = config.secrets.files.kat-glauca-dns.path; };
wantedBy = [ "default.target" ];
};
#hardware.ckb-next = {
# enable = true;
# package = pkgs.ckb-next;
#};
kw.fw.private.interfaces = singleton "hexnet";
kw.fw.public.interfaces = singleton "br";
hardware.openrazer = {
enable = true;
};
kw.fw.private.tcp.ports = [ 10445 ];
systemd.timers.kat-glauca-dns = {
timerConfig = {
Unit = "kat-glauca-dns.service";
OnBootSec = "5m";
OnUnitActiveSec = "30m";
};
wantedBy = [ "default.target" ];
};
# graphics tablet
services.xserver.wacom.enable = true;
environment.systemPackages = [ pkgs.stepmania pkgs.screenstub ];
# other stuffs
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.supportedFilesystems = [ "zfs" "xfs" ];
networking.hostName = "samhain";
networking.hostId = "617050fc";
networking.useDHCP = false;
networking.useNetworkd = true;
networking.firewall.allowPing = true;
systemd.network = {
networks.enp34s0 = {
matchConfig.Name = "enp34s0";
bridge = singleton "br";
};
networks.br = {
matchConfig.Name = "br";
address = [ "192.168.1.135/24" ];
gateway = [ "192.168.1.254" ];
};
netdevs.br = {
netdevConfig = {
Name = "br";
Kind = "bridge";
MACAddress = "00:d8:61:c7:f4:9d";
};
};
};
services.avahi.enable = true;
hexchen.network = {
enable = true;
pubkey = "a7110d0a1dc9ec963d6eb37bb6922838b8088b53932eae727a9136482ce45d47";
# if server, enable this and set endpoint:
listen.enable = false;
listen.endpoints = [ "tcp://0.0.0.0:0" ];
};
system.stateVersion = "20.09";
}

View file

@ -0,0 +1,51 @@
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usbhid" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" "nct6775" ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "rpool/safe/root";
fsType = "zfs";
};
fileSystems."/nix" = {
device = "rpool/local/nix";
fsType = "zfs";
};
fileSystems."/home" = {
device = "rpool/safe/home";
fsType = "zfs";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/AED6-D0D1";
fsType = "vfat";
};
fileSystems."/mnt/zraw" = {
device = "zstore/raw";
fsType = "zfs";
};
fileSystems."/mnt/zcomp" = {
device = "zstore/compress";
fsType = "zfs";
};
fileSystems."/mnt/zenc" = {
device = "zstore/protect";
fsType = "zfs";
};
swapDevices = [
{ device = "/dev/disk/by-uuid/89831a0f-93e6-4d30-85e4-09061259f140"; }
{ device = "/dev/disk/by-uuid/8f944315-fe1c-4095-90ce-50af03dd5e3f"; }
];
}

View file

@ -0,0 +1,15 @@
{ config, lib, pkgs, ... }:
{
kw.fw.public.tcp.ranges = [{
from = 32768;
to = 60999;
}];
kw.fw.private.tcp.ranges = [{
from = 32768;
to = 60999;
}];
services.jellyfin.enable = true;
}

View file

@ -0,0 +1,39 @@
{ config, pkgs, ... }:
{
boot.kernelParams = [ "amdgpu.ppfeaturemask=0xffffffff" ];
powerManagement = {
enable = true;
cpuFreqGovernor = "conservative";
};
systemd = {
services = {
kaede-thermals = {
wantedBy = [ "multi-user.target" ];
path = [ pkgs.bash pkgs.coreutils-full pkgs.gawk ];
serviceConfig = {
RemainAfterExit = "no";
Type = "simple";
ExecStart = "${pkgs.runtimeShell} ${./kaede-thermals.sh} start";
ExecStop = "${pkgs.runtimeShell} ${./kaede-thermals.sh} stop";
User = "root";
};
};
kaede-power = {
wantedBy = [ "multi-user.target" ];
path = [ pkgs.bash pkgs.linuxPackages.cpupower ];
serviceConfig = {
RemainAfterExit = "yes";
Type = "oneshot";
ExecStart = "${pkgs.runtimeShell} ${./kaede-power.sh} start";
ExecStop = "${pkgs.runtimeShell} ${./kaede-power.sh} stop";
User = "root";
};
};
};
};
services.thermald = {
enable = true;
configFile = "${./kaede-thermald.xml}";
};
}

View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
################################################################################
# Written by Kaede Fox <kaede@boxedfox.org>
##########
# Default governor, currently required to be "conservative".
METHOD=conservative
# Configuration parameters.
SCALE_UP=40
SCALE_DOWN=30
SCALE_STEP=1
SAMPLING_RATE=10000
SAMPLING_FACTOR=10
NO_NICE=1
##########
script_name=`basename $0`
case "$1" in
"start")
modprobe cpufreq_conservative
modprobe cpufreq_ondemand
# This also needs to be loaded, in case the service is stopped later
# we can fall back to it.
modprobe cpufreq_performance
# The governor must be selected for its relevant configuration entries
# to appear in sysfs.
cpupower frequency-set -g $METHOD
sleep .5s
CONFIG=/sys/devices/system/cpu/cpufreq/$METHOD
chmod $CONFIG 644
echo $SCALE_UP > $CONFIG/up_threshold
echo $SCALE_DOWN > $CONFIG/down_threshold
echo $SCALE_STEP > $CONFIG/freq_step
echo $SAMPLING_RATE > $CONFIG/sampling_rate
echo $SAMPLING_FACTOR > $CONFIG/sampling_down_factor
echo $NO_NICE > $CONFIG/ignore_nice_load
sleep .5s
# Force reload all configuration.
cpupower frequency-set -g $METHOD
echo "enabled cpupower"
echo "loaded ${script_name}"
;;
"stop")
cpupower frequency-set -g performance &&
echo "disabled cpupower" &
wait
sleep .5s
modprobe -r cpufreq_conservative
modprobe -r cpufreq_ondemand
echo "unloaded ${script_name}"
;;
*)
echo "Usage: ${script_name} (start | stop)"
exit 1
;;
esac

View file

@ -0,0 +1,308 @@
<?xml version="1.0"?>
<!--
use "man thermal-conf.xml" for details
-->
<!-- BEGIN -->
<ThermalConfiguration>
<Platform>
<Name>AMD Ryzen 5 3600 6-Core Processor</Name>
<ProductName>*</ProductName>
<UUID>*</UUID>
<Preference>quiet</Preference>
<ThermalSensors>
<ThermalSensor>
<!--CPU sensor on die -->
<Type>CPU_TEMP_CORE</Type>
<Path>/var/cache/kaede-thermals/cpu_core_temp</Path>
<AsyncCapable>1</AsyncCapable>
</ThermalSensor>
<ThermalSensor>
<!-- GPU sensor -->
<Type>GPU_TEMP</Type>
<Path>/var/cache/kaede-thermals/gpu_temp</Path>
<AsyncCapable>1</AsyncCapable>
</ThermalSensor>
</ThermalSensors>
<ThermalZones>
<ThermalZone>
<Type>CPU_CTRL</Type>
<TripPoints>
<!-- CPU PASSIVE CONTROL -->
<!-- Ideal temp: 70-75*C, Max temp: 95*C -->
<TripPoint>
<SensorType>CPU_TEMP_CORE</SensorType>
<Temperature>65000</Temperature>
<Type>passive</Type>
<ControlType>parallel</ControlType>
<CoolingDevice>
<index>1</index>
<type>CPU_FREQ0</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>2</index>
<type>CPU_FREQ1</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>3</index>
<type>CPU_FREQ2</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>4</index>
<type>CPU_FREQ3</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>5</index>
<type>CPU_FREQ4</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>6</index>
<type>CPU_FREQ5</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>7</index>
<type>CPU_FREQ6</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>8</index>
<type>CPU_FREQ7</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>9</index>
<type>CPU_FREQ8</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>10</index>
<type>CPU_FREQ9</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>11</index>
<type>CPU_FREQ10</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>12</index>
<type>CPU_FREQ11</type>
<influence>100</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
</TripPoint>
<!-- EMERGENCY THROTTLING. -->
<!-- HOT means suspend the system. -->
<!-- CRITICAL means turn off the system. -->
<TripPoint>
<SensorType>CPU_TEMP_CORE</SensorType>
<Temperature>85000</Temperature>
<Type>hot</Type>
</TripPoint>
<TripPoint>
<SensorType>CPU_TEMP_CORE</SensorType>
<Temperature>90000</Temperature>
<Type>critical</Type>
</TripPoint>
</TripPoints>
</ThermalZone>
<ThermalZone>
<Type>GPU_CTRL</Type>
<TripPoints>
<!-- GPU PASSIVE CONTROL -->
<!-- Ideal temp: 70-75*C, Max temp: 90*C -->
<TripPoint>
<SensorType>GPU_TEMP</SensorType>
<Temperature>70000</Temperature>
<Type>passive</Type>
<ControlType>parallel</ControlType>
<CoolingDevice>
<index>1</index>
<type>GPU_FREQ_CORE</type>
<influence>50</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
<CoolingDevice>
<index>2</index>
<type>GPU_FREQ_MEM</type>
<influence>50</influence>
<SamplingPeriod>1</SamplingPeriod>
</CoolingDevice>
</TripPoint>
</TripPoints>
</ThermalZone>
</ThermalZones>
<CoolingDevices>
<!-- CPU frequency scaling. -->
<!-- This allows finer control of the CPU scaling in comparison to -->
<!-- thermald's built in 'cpufreq' driver. -->
<CoolingDevice>
<Type>CPU_FREQ0</Type>
<Path>/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ1</Type>
<Path>/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ2</Type>
<Path>/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ3</Type>
<Path>/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ4</Type>
<Path>/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ5</Type>
<Path>/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ6</Type>
<Path>/sys/devices/system/cpu/cpu6/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ7</Type>
<Path>/sys/devices/system/cpu/cpu7/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ8</Type>
<Path>/sys/devices/system/cpu/cpu8/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ9</Type>
<Path>/sys/devices/system/cpu/cpu9/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ10</Type>
<Path>/sys/devices/system/cpu/cpu10/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<CoolingDevice>
<Type>CPU_FREQ11</Type>
<Path>/sys/devices/system/cpu/cpu7/cpufreq/scaling_max_freq</Path>
<MinState>3600000</MinState>
<MaxState>2200000</MaxState>
<IncDecStep>-100000</IncDecStep>
<ReadBack>1</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
</CoolingDevice>
<!-- GPU frequency scaling. -->
<!-- This is read by a custom service and translated into the format
expected by the video driver. -->
<CoolingDevice>
<Type>GPU_FREQ_CORE</Type>
<Path>/var/cache/kaede-thermals/gpu_power_core</Path>
<MinState>255</MinState>
<MaxState>0</MaxState>
<IncDecStep>-5</IncDecStep>
<!-- Make sure ReadBack is disabled as our servive needs to
reset the value periodically. -->
<ReadBack>0</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
<!-- As negative values are possible and have special meaning,
leave room for the sign. -->
<WritePrefix> </WritePrefix>
</CoolingDevice>
<CoolingDevice>
<Type>GPU_FREQ_MEM</Type>
<Path>/var/cache/kaede-thermals/gpu_power_mem</Path>
<MinState>255</MinState>
<MaxState>0</MaxState>
<IncDecStep>-5</IncDecStep>
<ReadBack>0</ReadBack>
<AutoOffMode>1</AutoOffMode>
<DebouncePeriod>10</DebouncePeriod>
<WritePrefix> </WritePrefix>
</CoolingDevice>
</CoolingDevices>
</Platform>
</ThermalConfiguration>

View file

@ -0,0 +1,157 @@
#!/usr/bin/env bash
################################################################################
# Written by Kaede Fox <kaede@boxedfox.org>
##########
# thermald can't handle hwmon* moving around, so we use symlinks to resolve
# dynamic paths to static paths.
THERMAL_PATH="/var/cache/kaede-thermals"
# CPU sensor paths.
CPU_SENSOR_CORE="/sys/devices/pci0000:00/0000:00:18.3/hwmon/hwmon*/temp1_input"
#CPU_SENSOR_SOCKET="/sys/devices/platform/nct6775.656/hwmon/hwmon*/temp2_input"
# GPU control and sensor paths (using amdgpu).
GPU_CONTROL="/sys/class/drm/card0/device/"
GPU_SENSOR="/sys/class/drm/card0/device/hwmon/hwmon*/temp1_input"
# WARNING: MAKE ABSOLUTELY SURE THESE ARE THE PERFORMANCE STATES AND NOT THE
# ACTUAL CORE/MEM CLOCKS OR WE COULD DAMAGE THE HARDWARE.
GPUCTRL_CORECLK="${GPU_CONTROL}/pp_dpm_sclk"
GPUCTRL_MEMCLK="${GPU_CONTROL}/pp_dpm_mclk"
# How often to resolve paths (in seconds). Changes are extremely rare, but they
# can occur even while the system is booted.
INTERVAL='300'
# How often to update GPU power settings from thermald.
TIMESLICE='1'
# Formatter used to round floats to integers via printf.
float_to_int='%'\''.0f'
##########
script_name=`basename $0`
# Our simple path resolver subroutine, which uses ls to replace hwmon* with
# whichever hwmon is currently active.
sub_resolve_paths() {
ln -s -f "`ls $CPU_SENSOR_CORE`" "./cpu_core_temp"
# ln -s -f "`ls $CPU_SENSOR_SOCKET`" "./cpu_socket_temp"
ln -s -f "`ls $GPU_SENSOR`" "./gpu_temp"
}
case "$1" in
"start")
echo "loaded ${script_name}"
mkdir -p "$THERMAL_PATH" 2>/dev/null
cd "$THERMAL_PATH"
# Switch the video driver into manual control mode for
# performance levels.
echo "manual" >${GPU_CONTROL}/power_dpm_force_performance_level
echo "enabled gpupower"
# Immediately resolve paths at startup.
sub_resolve_paths
# Followed by starting thermald in case the paths didn't
# exist, which would've made it fail to start.
sleep .5s
systemctl start thermald
# thermald will create these files with the wrong permissions.
rm "./gpu_power_core"; printf "%d\n" '-1'>./gpu_power_core
rm "./gpu_power_mem"; printf "%d\n" '-1'>./gpu_power_mem
# Enter service mode.
slice_counter='0'
while true; do
# Execute a timeslice. Paths are updated every time the
# counter wraps around.
slice_counter=$((slice_counter + TIMESLICE))
if [ "$slice_counter" -ge "$INTERVAL" ]; then
#printf "DEBUG: *** Updating paths after %d seconds ***\n" "$slice_counter"
slice_counter=$((slice_counter - INTERVAL))
# Resolve paths.
sub_resolve_paths
fi
# Read GPU thermal settings from thermald and apply
# them to the driver via sysfs.
#
# The core and memory clock lists are highly specific
# to the hardware, and need updating if the video card
# is ever changed.
#
gpu_power_core="`cat ./gpu_power_core`"
gpu_power_mem="`cat ./gpu_power_mem`"
if [ -z "$gpu_power_core" ]; then gpu_power_core='-1'; fi
if [ -z "$gpu_power_mem" ]; then gpu_power_mem='-1'; fi
# Check if we need to update the core clock.
if [ "$gpu_power_core" -ge '0' ]; then
#printf "DEBUG: *** Updating GPU core clock ***\n"
printf "%d\n" '-1'>./gpu_power_core
printf "$gpu_power_core\n">./gpu_power_core_cached
core_count_f="$(echo "$gpu_power_core" | awk '{ x=(($1/255.0)*(7-1))+1; printf("%f",x) }')"
core_count="$(printf "$float_to_int" "$core_count_f")"
if [ "$core_count" -lt '1' ]; then core_count='1'; fi
if [ "$core_count" -gt '7' ]; then core_count='7'; fi
gpu_list_core="1"
for ((i=2; i<=core_count; i++)); do
gpu_list_core="$gpu_list_core $i"; done
echo "$gpu_list_core" >${GPUCTRL_CORECLK}
#echo "dbg: gpu_power_core: $gpu_power_core"
#echo "dbg: core_count_f: $core_count_f, core_count: $core_count"
#echo "dbg: gpu_list_core: $gpu_list_core"
fi
# Check if we need to update the memory clock.
if [ "$gpu_power_mem" -ge '0' ]; then
#printf "DEBUG: *** Updating GPU memory clock ***\n"
printf "%d\n" '-1'>./gpu_power_mem
printf "$gpu_power_mem\n">./gpu_power_mem_cached
mem_count_f="$(echo "$gpu_power_mem" | awk '{ x=(($1/255.0)*(3-1))+1; printf("%f",x) }')"
mem_count="$(printf "$float_to_int" "$mem_count_f")"
if [ "$mem_count" -lt '1' ]; then mem_count='1'; fi
if [ "$mem_count" -gt '3' ]; then mem_count='3'; fi
gpu_list_mem="1"
for ((i=2; i<=mem_count; i++)); do
gpu_list_mem="$gpu_list_mem $i"; done
echo "$gpu_list_mem" >${GPUCTRL_MEMCLK}
#echo "dbg: gpu_power_mem: $gpu_power_mem"
#echo "dbg: mem_count_f: $mem_count_f, mem_count: $mem_count"
#echo "dbg: gpu_list_mem: $gpu_list_mem"
fi
# Sleep until the next cycle.
sleep "${TIMESLICE}s"
done
;;
"stop")
# Reset all performance level tunings.
echo "1 2 3 4 5 6 7" >${GPUCTRL_CORECLK}
echo "1 2 3" >${GPUCTRL_MEMCLK}
# Switch the video driver into automatic control mode for
# performance levels.
echo "auto" >${GPU_CONTROL}/power_dpm_force_performance_level
echo "disabled gpupower"
sleep .5s
echo "unloaded ${script_name}"
;;
*)
echo "Usage: ${script_name} (start | stop)"
exit 1
;;
esac

View file

@ -0,0 +1,35 @@
{ config, pkgs, ... }:
{
services.transmission =
let
transmission-done-script = pkgs.writeScriptBin "script" ''
#!${pkgs.bash}/bin/bash
set -e
if [ "$TR_TORRENT_DIR"/"$TR_TORRENT_NAME" != "/" ]; then
cd "$TR_TORRENT_DIR"/"$TR_TORRENT_NAME"
if [ ! -z "*.rar" ]; then
${pkgs.unrar}/bin/unrar x "*.rar"
fi
chmod ugo=rwX .
fi'';
in
{
enable = true;
home = "/mnt/zraw/transmission";
downloadDirPermissions = "777";
settings = {
download-dir = "/mnt/zraw/media/unsorted";
incomplete-dir = "/mnt/zraw/media/.incomplete";
incomplete-dir-enabled = true;
rpc-bind-address = "::";
rpc-whitelist-enabled = false;
rpc-host-whitelist-enabled = false;
script-torrent-done-enabled = true;
dht-enabled = true;
pex-enabled = true;
script-torrent-done-filename = "${transmission-done-script}/bin/script";
umask = 0;
};
};
}

View file

@ -0,0 +1,71 @@
{ config, lib, ... }:
with lib;
{
kw.fw.private.tcp.ports = singleton 1935;
kw.fw.public.tcp.ports = singleton 1935;
services.nginx.virtualHosts = {
"samhain.net.kittywit.ch" = {
useACMEHost = "samhain.net.kittywit.ch";
forceSSL = true;
locations = {
"/jellyfin/".proxyPass = "http://127.0.0.1:8096/jellyfin/";
"/jellyfin/socket" = {
proxyPass = "http://127.0.0.1:8096/jellyfin/";
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
"/tvheadend/".proxyPass = "http://192.168.1.245:9981";
"/" = {
root = "/mnt/zraw/media/";
extraConfig = "autoindex on;";
};
"/transmission" = {
proxyPass = "http://[::1]:9091";
extraConfig = "proxy_pass_header X-Transmission-Session-Id;";
};
};
};
"192.168.1.135" = {
locations = {
"/jellyfin/".proxyPass = "http://127.0.0.1:8096/jellyfin/";
"/jellyfin/socket" = {
proxyPass = "http://127.0.0.1:8096/jellyfin/";
extraConfig = ''
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
'';
};
"/share/" = {
alias = "/mnt/zraw/media/";
extraConfig = "autoindex on;";
};
};
};
"100.103.111.44" = {
locations."/share/" = {
alias = "/mnt/zraw/media/";
extraConfig = "autoindex on;";
};
};
};
services.nginx.appendConfig = ''
rtmp {
server {
listen [::]:1935 ipv6only=off;
application kattv {
live on;
allow publish all;
allow play all;
}
}
}
'';
}

View file

@ -0,0 +1 @@
{ ... }: { imports = [ ./sway ]; }

View file

@ -0,0 +1,51 @@
{ config, pkgs, ... }:
{
imports = [ ./swayidle.nix ];
wayland.windowManager.sway.config =
let
lockCommand = "swaylock -i eDP-1:${
../../../../users/kat/sway/wallpapers/main.png
} -s fill";
cfg = config.wayland.windowManager.sway.config;
in
{
output =
let
laptop = {
res = "1920x1080";
pos = "0 0";
};
in
{ "eDP-1" = laptop; };
keybindings = {
"${cfg.modifier}+x" = "exec ${lockCommand}";
};
modes = {
"System (l) lock, (e) logout, (s) suspend, (h) hibernate, (r) reboot, (Shift+s) shutdown" =
{
"l" = "exec ${lockCommand}, mode default";
"e" = "exec swaymsg exit, mode default";
"s" = "exec systemctl suspend, mode default";
"h" = "exec systemctl hibernate, mode default";
"r" = "exec systemctl reboot, mode default";
"Shift+s" = "exec systemctl shutdown, mode default";
"Return" = "mode default";
"Escape" = "mode default";
};
};
input = {
"1739:33362:Synaptics_TM3336-002" = {
dwt = "enabled";
tap = "enabled";
natural_scroll = "enabled";
middle_emulation = "enabled";
click_method = "clickfinger";
};
};
};
}

View file

@ -0,0 +1,24 @@
{ config, pkgs, lib, ... }:
{
systemd.user.services.swayidle = {
Unit = {
Description = "swayidle";
Documentation = [ "man:swayidle(1)" ];
PartOf = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = ''
${pkgs.swayidle}/bin/swayidle -w \
timeout 300 '${pkgs.swaylock}/bin/swaylock -f -i eDP-1:${../../../../users/kat/sway/wallpapers/main.png} \
timeout 600 'swaymsg "output * dpms off"' \
resume 'swaymsg "output * dpms on"' \
before-sleep '${pkgs.swaylock}/bin/swaylock -f -i eDP-1:${../../../../users/kat/sway/wallpapers/main.png}'
'';
RestartSec = 3;
Restart = "always";
};
Install = { WantedBy = [ "sway-session.target" ]; };
};
}

View file

@ -0,0 +1,22 @@
{ lib, config, ... }: with lib; {
config = {
deploy.targets.personal = {
tf = {
resources.yule = {
provider = "null";
type = "resource";
connection = {
port = 62954;
host = "192.168.1.92";
};
};
};
};
network.nodes.yule = {
imports = lib.hostImport "yule";
networking = {
hostName = "yule";
};
};
};
}

View file

@ -0,0 +1,52 @@
{ config, users, pkgs, lib, profiles, ... }:
with lib;
{
imports = [
./hw.nix
../../../services/zfs.nix
../../../services/restic.nix
../../../services/node-exporter.nix
../../../services/promtail.nix
../../../services/netdata.nix
../../../services/nginx.nix
profiles.gui
profiles.sway
profiles.laptop
users.kat.guiFull
];
home-manager.users.kat = {
imports = [
../home
];
};
networking.wireless.interfaces = [ "wlp2s0" ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.supportedFilesystems = [ "zfs" ];
kw.fw.private.interfaces = singleton "hexnet";
kw.fw.public.interfaces = [ "enp1s0" "wlp2s0" ];
networking.hostId = "dddbb888";
networking.hostName = "yule";
networking.useDHCP = false;
networking.interfaces.enp1s0.useDHCP = true;
networking.interfaces.wlp2s0.useDHCP = true;
hexchen.network = {
enable = true;
pubkey = "9779fd6b5bdba6b9e0f53c96e141f4b11ce5ef749d1b9e77a759a3fdbd33a653";
# if server, enable this and set endpoint:
listen.enable = false;
listen.endpoints = [ "tcp://0.0.0.0:0" ];
};
system.stateVersion = "20.09";
}

View file

@ -0,0 +1,35 @@
{ config, lib, pkgs, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot.initrd.availableKernelModules =
[ "xhci_pci" "ahci" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
fileSystems."/" = {
device = "rpool/safe/root";
fsType = "zfs";
};
fileSystems."/home" = {
device = "rpool/safe/home";
fsType = "zfs";
};
fileSystems."/nix" = {
device = "rpool/local/nix";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/2552-18F2";
fsType = "vfat";
};
swapDevices =
[{ device = "/dev/disk/by-uuid/87ff4f68-cc00-494e-8eba-050469c3bf03"; }];
}

View file

@ -0,0 +1,10 @@
{ sources, ... }:
{
disabledModules = [ "programs/vim.nix" ];
imports = with (import (sources.nixexprs + "/modules")).home-manager; [ base16 syncplay konawall i3gopher weechat shell ] ++ [
./vim.nix
./deploy-tf
(sources.tf-nix + "/modules/home/secrets.nix")
];
}

View file

@ -0,0 +1,34 @@
{ config, lib, ... }:
with lib;
let
cfg = config.deploy.tf;
unmergedValues = types.mkOptionType {
name = "unmergedValues";
merge = loc: defs: map (def: def.value) defs;
};
in
{
options.deploy.tf = mkOption {
type = types.submodule {
freeformType = types.attrsOf unmergedValues;
options = {
attrs = mkOption {
type = types.listOf types.str;
default = [ ];
};
out.set = mkOption { type = types.unspecified; };
};
};
};
config = {
deploy.tf = {
attrs = [ "out" "attrs" ];
out.set = removeAttrs cfg cfg.attrs;
};
};
}

194
config/modules/home/vim.nix Normal file
View file

@ -0,0 +1,194 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.vim;
defaultPlugins = [ pkgs.vimPlugins.vim-sensible ];
knownSettings = {
background = types.enum [ "dark" "light" ];
backupdir = types.listOf types.str;
copyindent = types.bool;
directory = types.listOf types.str;
expandtab = types.bool;
hidden = types.bool;
history = types.int;
ignorecase = types.bool;
modeline = types.bool;
mouse = types.enum [ "n" "v" "i" "c" "h" "a" "r" ];
mousefocus = types.bool;
mousehide = types.bool;
mousemodel = types.enum [ "extend" "popup" "popup_setpos" ];
number = types.bool;
relativenumber = types.bool;
shiftwidth = types.int;
smartcase = types.bool;
tabstop = types.int;
undodir = types.listOf types.str;
undofile = types.bool;
};
vimSettingsType = types.submodule {
options =
let
opt = name: type:
mkOption {
type = types.nullOr type;
default = null;
visible = false;
};
in
mapAttrs opt knownSettings;
};
setExpr = name: value:
let
v =
if isBool value then
(if value then "" else "no") + name
else
"${name}=${
if isList value then concatStringsSep "," value else toString value
}";
in
optionalString (value != null) ("set " + v);
plugins =
let
vpkgs = pkgs.vimPlugins;
getPkg = p:
if isDerivation p then
[ p ]
else
optional (isString p && hasAttr p vpkgs) vpkgs.${p};
in
concatMap getPkg cfg.plugins;
in
{
options = {
programs.vim = {
enable = mkEnableOption "Vim";
package = mkOption {
type = types.package;
default = pkgs.vim_configurable;
defaultText = literalExample "pkgs.vim_configurable";
description = "The package to use for the vim binary.";
};
finalPackage = mkOption {
type = types.package;
visible = false;
readOnly = true;
description = "Resulting customized vim package.";
};
plugins = mkOption {
type = with types; listOf (either str package);
default = defaultPlugins;
example = literalExample "[ pkgs.vimPlugins.YankRing ]";
description = ''
List of vim plugins to install. To get a list of supported plugins run:
<command>nix-env -f '&lt;nixpkgs&gt;' -qaP -A vimPlugins</command>.
</para><para>
Note: String values are deprecated, please use actual packages.
'';
};
settings = mkOption {
type = vimSettingsType;
default = { };
example = literalExample ''
{
expandtab = true;
history = 1000;
background = "dark";
}
'';
description = ''
At attribute set of Vim settings. The attribute names and
corresponding values must be among the following supported
options.
<informaltable frame="none"><tgroup cols="1"><tbody>
${concatStringsSep "\n" (mapAttrsToList (n: v: ''
<row>
<entry><varname>${n}</varname></entry>
<entry>${v.description}</entry>
</row>
'') knownSettings)}
</tbody></tgroup></informaltable>
See the Vim documentation for detailed descriptions of these
options. Note, use <varname>extraConfig</varname> to
manually set any options not listed above.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
example = ''
set nocompatible
set nobackup
'';
description = "Custom .vimrc lines";
};
};
};
config = (
let
customRC = ''
${concatStringsSep "\n" (filter (v: v != "") (mapAttrsToList setExpr
(builtins.intersectAttrs knownSettings cfg.settings)))}
${cfg.extraConfig}
'';
vim = cfg.package.customize {
name = "vim";
vimrcConfig = {
inherit customRC;
packages.home-manager.start = plugins;
};
};
in
mkIf cfg.enable {
assertions =
let
packagesNotFound =
filter (p: isString p && (!hasAttr p pkgs.vimPlugins)) cfg.plugins;
in
[{
assertion = packagesNotFound == [ ];
message = "Following VIM plugin not found in pkgs.vimPlugins: ${
concatMapStringsSep ", " (p: ''"${p}"'') packagesNotFound
}";
}];
warnings =
let stringPlugins = filter isString cfg.plugins;
in
optional (stringPlugins != [ ]) ''
Specifying VIM plugins using strings is deprecated, found ${
concatMapStringsSep ", " (p: ''"${p}"'') stringPlugins
} as strings.
'';
home.packages = [ cfg.finalPackage ];
programs.vim = {
finalPackage = vim;
plugins = defaultPlugins;
};
}
);
}

View file

@ -0,0 +1,8 @@
{ ... }:
{
imports = [
./deploy.nix
./network.nix
];
}

View file

@ -0,0 +1,69 @@
{ tf, target, config, lib, ... }:
with lib;
let
cfg = config.deploy.tf;
unmergedValues = types.mkOptionType {
name = "unmergedValues";
merge = loc: defs: map (def: def.value) defs;
};
in
{
options.deploy.target = mkOption {
type = with types; str;
default = "";
};
options.deploy.tf = mkOption {
type = types.submodule {
freeformType = types.attrsOf unmergedValues;
options = {
attrs = mkOption {
type = types.listOf types.str;
default = [ ];
};
out.set = mkOption { type = types.unspecified; };
};
};
};
config = {
deploy.tf = mkMerge (singleton
{
attrs = [ "out" "attrs" ];
out.set = removeAttrs cfg cfg.attrs;
deploy.systems.${config.networking.hostName} =
with tf.resources; {
isRemote =
(config.networking.hostName != builtins.getEnv "HOME_HOSTNAME");
nixosConfig = config;
connection = tf.resources.${config.networking.hostName}.connection.set;
triggers.copy.${config.networking.hostName} =
tf.resources.${config.networking.hostName}.refAttr "id";
triggers.secrets.${config.networking.hostName} =
tf.resources.${config.networking.hostName}.refAttr "id";
};
dns.records."kittywitch_net_${config.networking.hostName}" =
mkIf (config.hexchen.network.enable) {
tld = "kittywit.ch.";
domain = "${config.networking.hostName}.net";
aaaa.address = config.hexchen.network.address;
};
} ++ mapAttrsToList
(_: user:
mapAttrs (_: mkMerge) user.deploy.tf.out.set)
config.home-manager.users);
security.acme.certs."${config.networking.hostName}.net.kittywit.ch" =
mkIf (config.services.nginx.enable && config.hexchen.network.enable) {
domain = "${config.networking.hostName}.net.kittywit.ch";
dnsProvider = "rfc2136";
credentialsFile = config.secrets.files.dns_creds.path;
group = "nginx";
};
_module.args.tf = target.${config.deploy.target};
};
}

View file

@ -0,0 +1,99 @@
{ sources, config, pkgs, lib, ... }: with lib; let
cfg = config.deploy;
meta = config;
tfModule = { lib, ... }: with lib; {
config._module.args = {
pkgs = mkDefault pkgs;
};
};
tfType = types.submoduleWith {
modules = [
tfModule
"${toString sources.tf-nix}/modules"
];
};
in {
imports = [
(toString (sources.tf-nix + "/modules/run.nix"))
] ++ (optional (builtins.pathExists ../../trusted/tf/tf.nix) (../../trusted/tf/tf.nix));
options = {
deploy = {
dataDir = mkOption {
type = types.path;
};
local = {
isRoot = mkOption {
type = types.bool;
default = builtins.getEnv "HOME_UID" == "0";
};
hostName = mkOption {
type = types.nullOr types.str;
default = let
hostName = builtins.getEnv "HOME_HOSTNAME";
in if hostName == "" then null else hostName;
};
};
targets = let
type = types.submodule ({ config, name, ... }: {
options = {
name = mkOption {
type = types.str;
default = name;
};
nodeNames = mkOption {
type = types.listOf types.str;
default = [ ];
};
tf = mkOption {
type = tfType;
default = { };
};
};
config.tf = mkMerge (singleton {
imports = [
../../targets/common
];
deps = {
select.allProviders = true;
enable = true;
};
terraform = {
version = "1.0";
logPath = cfg.dataDir + "/terraform-${config.name}.log";
dataDir = cfg.dataDir + "/tfdata/${config.name}";
environment.TF_CLI_ARGS_apply = "-backup=-";
environment.TF_CLI_ARGS_taint = "-backup=-";
};
state = {
file = cfg.dataDir + "/terraform-${config.name}.tfstate";
};
runners = {
lazy = {
inherit (meta.runners.lazy) file args;
attrPrefix = "deploy.targets.${name}.tf.runners.run.";
};
run = {
apply.name = "${name}-apply";
terraform.name = "${name}-tf";
};
};
continue.envVar = "TF_NIX_CONTINUE_${replaceStrings [ "-" ] [ "_" ] config.name}";
} ++ map (nodeName: mapAttrs (_: mkMerge) meta.network.nodes.${nodeName}.deploy.tf.out.set) config.nodeNames);
});
in mkOption {
type = types.attrsOf type;
default = { };
};
};
};
config = {
runners = {
run = mkMerge (mapAttrsToList (targetName: target: mapAttrs' (k: run:
nameValuePair run.name run.set
) target.tf.runners.run) cfg.targets);
lazy.run = mkMerge (mapAttrsToList (targetName: target: mapAttrs' (k: run:
nameValuePair run.name run.set
) target.tf.runners.lazy.run) cfg.targets);
};
};
}

View file

@ -0,0 +1,59 @@
{ pkgs, sources, users, profiles, lib, config, ... }: with lib;
{
options.network = {
nixos = {
extraModules = mkOption {
type = types.listOf types.unspecified;
default = [ ];
};
specialArgs = mkOption {
type = types.attrsOf types.unspecified;
default = { };
};
modulesPath = mkOption {
type = types.path;
default = toString (pkgs.path + "/nixos/modules");
};
};
nodes = let
nixosModule = { name, config, meta, modulesPath, lib, ... }: with lib; {
config = {
nixpkgs = {
system = mkDefault pkgs.system;
pkgs = mkDefault pkgs;
#inherit (pkgs) config;
};
};
};
nixosType = let
baseModules = import (config.network.nixos.modulesPath + "/module-list.nix");
in types.submoduleWith {
modules = baseModules
++ singleton nixosModule
++ config.network.nixos.extraModules;
specialArgs = {
inherit baseModules;
inherit (config.network.nixos) modulesPath;
} // config.network.nixos.specialArgs;
};
in mkOption {
type = types.attrsOf nixosType;
default = { };
};
};
config.network = {
nixos = {
extraModules = [
"${toString sources.home-manager}/nixos"
../../modules/nixos
];
specialArgs = {
inherit (config.network) nodes;
inherit sources profiles users;
meta = config;
};
};
};
}

View file

@ -0,0 +1,21 @@
{ meta, sources, lib, ... }:
{
imports = with (import (sources.nixexprs + "/modules")).nixos; [ base16 base16-shared ] ++ [
./nftables.nix
./fw-abstraction.nix
./deploy-tf.nix
(sources.tf-nix + "/modules/nixos/secrets.nix")
(sources.tf-nix + "/modules/nixos/secrets-users.nix")
(sources.hexchen + "/modules/hexnet")
];
# stubs for hexchens modules, until more generalized
options.hexchen.dns = lib.mkOption { };
options.hexchen.deploy = lib.mkOption { };
# shim
config = {
_module.args.hosts = lib.mapAttrs (_: config: { inherit config; } ) meta.network.nodes;
};
}

View file

@ -0,0 +1,91 @@
{ tf, target, name, meta, config, lib, ... }:
with lib;
let
cfg = config.deploy;
unmergedValues = types.mkOptionType {
name = "unmergedValues";
merge = loc: defs: map (def: def.value) defs;
};
in
{
options.deploy = {
targetName = mkOption {
type = types.nullOr types.str;
default = null;
};
system = mkOption {
type = types.unspecified;
readOnly = true;
};
};
options.deploy.tf = mkOption {
type = types.submodule {
freeformType = types.attrsOf unmergedValues;
options = {
import = mkOption {
type = types.attrsOf types.unspecified;
default = [ ];
};
imports = mkOption {
type = types.listOf types.str;
description = "Other targets to depend on";
default = [ ];
};
attrs = mkOption {
type = types.listOf types.str;
default = [ ];
};
out.set = mkOption { type = types.unspecified; };
};
};
};
config = {
deploy = {
system = config.system.build.toplevel;
targetName = if (meta.deploy.targets ? ${name}) then
(mkDefault name)
else
head (attrNames ((filterAttrs(targetName: target: elem config.networking.hostName target.nodeNames) meta.deploy.targets)));
};
deploy.tf = mkMerge (singleton
{
attrs = [ "import" "imports" "out" "attrs" ];
import = genAttrs cfg.tf.imports (target: meta.deploy.targets.${target}.tf);
out.set = removeAttrs cfg.tf cfg.tf.attrs;
deploy.systems.${config.networking.hostName} =
with tf.resources; {
isRemote =
(config.networking.hostName != builtins.getEnv "HOME_HOSTNAME");
nixosConfig = config;
connection = tf.resources.${config.networking.hostName}.connection.set;
triggers.copy.${config.networking.hostName} =
tf.resources.${config.networking.hostName}.refAttr "id";
triggers.secrets.${config.networking.hostName} =
tf.resources.${config.networking.hostName}.refAttr "id";
};
dns.records."kittywitch_net_${config.networking.hostName}" =
mkIf (config.hexchen.network.enable) {
tld = "kittywit.ch.";
domain = "${config.networking.hostName}.net";
aaaa.address = config.hexchen.network.address;
};
} ++ mapAttrsToList
(_: user:
mapAttrs (_: mkMerge) user.deploy.tf.out.set)
config.home-manager.users);
security.acme.certs."${config.networking.hostName}.net.kittywit.ch" =
mkIf (config.services.nginx.enable && config.hexchen.network.enable) {
domain = "${config.networking.hostName}.net.kittywit.ch";
dnsProvider = "rfc2136";
credentialsFile = config.secrets.files.dns_creds.path;
group = "nginx";
};
_module.args.target = mapNullable (targetName: meta.deploy.targets.${targetName}) cfg.targetName;
_module.args.tf = mapNullable (target: target.tf) target;
};
}

View file

@ -0,0 +1,80 @@
{ config, lib, pkgs, ... }:
with lib;
let cfg = config.kw.fw;
in
{
options.kw.fw = {
public.tcp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
public.udp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
private.tcp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
private.udp.ports = mkOption {
type = types.listOf types.port;
default = [ ];
};
public.tcp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
public.udp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
private.tcp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
private.udp.ranges = mkOption {
type = types.listOf (types.attrsOf types.port);
default = [ ];
};
public.interfaces = mkOption {
type = types.listOf types.str;
description = "Public firewall interfaces";
default = [ ];
};
private.interfaces = mkOption {
type = types.listOf types.str;
description = "Private firewall interfaces";
default = [ ];
};
};
config = {
networking.firewall.interfaces =
let
fwTypes = {
ports = "Ports";
ranges = "PortRanges";
};
interfaceDef = visibility:
listToAttrs (flatten (mapAttrsToList
(type: typeString:
map
(proto: {
name = "allowed${toUpper proto}${typeString}";
value = cfg.${visibility}.${proto}.${type};
}) [ "tcp" "udp" ])
fwTypes));
interfaces = visibility:
listToAttrs
(map (interface: nameValuePair interface (interfaceDef visibility))
cfg.${visibility}.interfaces);
in
mkMerge (map (visibility: interfaces visibility) [ "public" "private" ]);
};
}

View file

@ -0,0 +1,134 @@
{ pkgs, lib, config, modulesPath, ... }:
let
fwcfg = config.networking.firewall;
cfg = config.kw.nftables;
doDocker = config.virtualisation.docker.enable && cfg.generateDockerRules;
mkPorts = cond: ports: ranges: action: let
portStrings = (map (range: "${toString range.from}-${toString range.to}") ranges)
++ (map toString ports);
in lib.optionalString (portStrings != []) ''
${cond} dport { ${lib.concatStringsSep ", " portStrings} } ${action}
'';
ruleset = ''
table inet filter {
chain input {
type filter hook input priority filter
policy ${cfg.inputPolicy}
icmpv6 type { echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, packet-too-big } accept
icmp type echo-request accept
ct state invalid drop
ct state established,related accept
iifname { ${
lib.concatStringsSep "," (["lo"] ++ fwcfg.trustedInterfaces)
} } accept
${mkPorts "tcp" fwcfg.allowedTCPPorts fwcfg.allowedTCPPortRanges "accept"}
${mkPorts "udp" fwcfg.allowedUDPPorts fwcfg.allowedUDPPortRanges "accept"}
${
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: ifcfg:
mkPorts "iifname ${name} tcp" ifcfg.allowedTCPPorts ifcfg.allowedTCPPortRanges "accept"
+ mkPorts "iifname ${name} udp" ifcfg.allowedUDPPorts ifcfg.allowedUDPPortRanges "accept"
) fwcfg.interfaces)
}
# DHCPv6
ip6 daddr fe80::/64 udp dport 546 accept
${cfg.extraInput}
counter
}
chain output {
type filter hook output priority filter
policy ${cfg.outputPolicy}
${cfg.extraOutput}
counter
}
chain forward {
type filter hook forward priority filter
policy ${cfg.forwardPolicy}
${lib.optionalString doDocker ''
oifname docker0 ct state invalid drop
oifname docker0 ct state established,related accept
iifname docker0 accept
''}
${cfg.extraForward}
counter
}
}
${lib.optionalString doDocker ''
table ip nat {
chain docker-postrouting {
type nat hook postrouting priority 10
iifname docker0 masquerade
}
}
''}
${cfg.extraConfig}
'';
in {
options = with lib; {
kw.nftables = {
enable = mkEnableOption "nftables firewall";
extraConfig = mkOption {
type = types.lines;
default = "";
};
extraInput = mkOption {
type = types.lines;
default = "";
};
extraOutput = mkOption {
type = types.lines;
default = "";
};
extraForward = mkOption {
type = types.lines;
default = "";
};
inputPolicy = mkOption {
type = types.str;
default = "drop";
};
outputPolicy = mkOption {
type = types.str;
default = "accept";
};
forwardPolicy = mkOption {
type = types.str;
default = "accept";
};
generateDockerRules = mkOption {
type = types.bool;
default = true;
};
};
};
config = lib.mkIf cfg.enable {
networking.firewall.enable = false;
networking.nftables = {
enable = true;
inherit ruleset;
};
virtualisation.docker = lib.mkIf doDocker {
extraOptions = "--iptables=false";
};
};
}

52
config/nixos.nix Normal file
View file

@ -0,0 +1,52 @@
{ pkgs, config, lib, tf, sources, options, profiles, ... }:
{
imports = [
profiles/common
];
options = {
deploy.profile.gui = lib.mkEnableOption "graphical system";
deploy.profile.fvwm = lib.mkEnableOption "fvwm";
deploy.profile.sway = lib.mkEnableOption "sway wm";
deploy.profile.laptop = lib.mkEnableOption "lappytop";
home-manager.users = lib.mkOption {
type = lib.types.attrsOf (lib.types.submoduleWith {
modules = [ ./modules/home ];
specialArgs = {
inherit sources tf;
superConfig = config;
modulesPath = sources.home-manager + "/modules";
};
});
};
};
config = {
home-manager = {
useUserPackages = true;
useGlobalPkgs = true;
users = {
kat = {
imports = lib.optional (builtins.pathExists ./trusted/users/kat) (import ./trusted/users/kat);
options = {
deploy.profile.gui = lib.mkEnableOption "graphical system";
deploy.profile.sway = lib.mkEnableOption "sway wm";
deploy.profile.laptop = lib.mkEnableOption "lappytop";
};
};
kairi = {
imports = lib.optional (builtins.pathExists ./trusted/users/kairi) (import ./trusted/users/kairi);
options = {
deploy.profile.gui = lib.mkEnableOption "graphical system";
deploy.profile.fvwm = lib.mkEnableOption "fvwm";
deploy.profile.laptop = lib.mkEnableOption "lappytop";
};
};
};
};
};
}

View file

@ -0,0 +1,16 @@
{ config, lib, pkgs, ... }:
{
security.sudo.wheelNeedsPassword = lib.mkForce false;
users.users.root = {
openssh.authorizedKeys.keys = with pkgs.lib;
concatLists (mapAttrsToList
(name: user:
if elem "wheel" user.extraGroups then
user.openssh.authorizedKeys.keys
else
[ ])
config.users.users);
};
}

View file

@ -0,0 +1,10 @@
{ config, ... }:
{
base16 = {
console = {
enable = true;
scheme = "rebecca.rebecca";
};
};
}

View file

@ -0,0 +1,19 @@
{ config, users, lib, pkgs, ... }:
{
imports = [
users.kat.base
users.kairi.base
users.arc
users.hexchen
./system.nix
./base16.nix
./net.nix
./access.nix
./locale.nix
./nix.nix
./ssh.nix
./packages.nix
./secrets.nix
];
}

View file

@ -0,0 +1,13 @@
{ config, pkgs, ... }:
{
fonts.fonts = [
pkgs.tamzen
];
i18n.defaultLocale = "en_GB.UTF-8";
time.timeZone = "Europe/London";
console = {
packages = [ pkgs.tamzen ];
keyMap = "uk";
};
}

View file

@ -0,0 +1,5 @@
{ config, lib, ... }:
{
kw.nftables.enable = lib.mkDefault true;
}

View file

@ -0,0 +1,21 @@
{ config, lib, pkgs, sources, ... }:
{
boot.loader.grub.configurationLimit = 8;
boot.loader.systemd-boot.configurationLimit = 8;
nix = {
nixPath = [
"nixpkgs=${sources.nixpkgs}"
"nur=${sources.nur}"
"arc=${sources.nixexprs}"
"ci=${sources.ci}"
];
binaryCaches = [ "https://arc.cachix.org" "https://kittywitch.cachix.org" ];
binaryCachePublicKeys =
[ "arc.cachix.org-1:DZmhclLkB6UO0rc0rBzNpwFbbaeLfyn+fYccuAy7YVY=" "kittywitch.cachix.org-1:KIzX/G5cuPw5WgrXad6UnrRZ8UDr7jhXzRTK/lmqyK0=" ];
gc.automatic = lib.mkDefault true;
gc.options = lib.mkDefault "--delete-older-than 1w";
trustedUsers = [ "root" "@wheel" ];
};
}

View file

@ -0,0 +1,14 @@
{ config, lib, pkgs, ... }:
{
environment.systemPackages = with pkgs; [
smartmontools
hddtemp
lm_sensors
cachix
pinentry-curses
wezterm-terminfo
kitty.terminfo
gnupg
];
}

View file

@ -0,0 +1,9 @@
{ config, lib, pkgs, ... }:
{
secrets = {
root = "/var/lib/kat/secrets";
persistentRoot = "/var/lib/kat/secrets";
external = true;
};
}

View file

@ -0,0 +1,26 @@
{ config, lib, pkgs, ... }:
{
kw.fw.public.udp.ranges = [{
from = 60000;
to = 61000;
}];
kw.fw.private.udp.ranges = [{
from = 60000;
to = 61000;
}];
services.openssh = {
enable = true;
ports = lib.mkDefault [ 62954 ];
passwordAuthentication = false;
challengeResponseAuthentication = false;
permitRootLogin = lib.mkDefault "prohibit-password";
kexAlgorithms = [ "curve25519-sha256@libssh.org" ];
extraConfig = ''
StreamLocalBindUnlink yes
LogLevel VERBOSE
'';
};
programs.mosh.enable = true;
}

View file

@ -0,0 +1,9 @@
{ config, lib, pkgs, ... }:
{
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
boot.tmpOnTmpfs = true;
boot.zfs.enableUnstable = true;
services.journald.extraConfig = "SystemMaxUse=512M";
users.mutableUsers = false;
}

View file

@ -0,0 +1,7 @@
{ config, pkgs, ... }:
{
imports = [ ./fvwm.nix ];
deploy.profile.fvwm = true;
}

View file

@ -0,0 +1,10 @@
{ config, pkgs, lib, ... }:
{
services.xserver.enable = true;
services.xserver.displayManager.startx.enable = true;
services.xserver.windowManager.fvwm = {
enable = true;
gestures = true;
};
}

View file

@ -0,0 +1,6 @@
{ config, ... }:
{
programs.adb.enable = true;
users.users.kat.extraGroups = [ "adbusers" ];
}

View file

@ -0,0 +1,23 @@
{ config, pkgs, ... }:
{
imports = [
./adb.nix
./fonts.nix
./gpg.nix
./firefox.nix
./dns.nix
./nfs.nix
./nix-doc.nix
./mpd.nix
./nixpkgs.nix
./mingetty.nix
./sound.nix
];
hardware.opengl.extraPackages = with pkgs; [ libvdpau-va-gl ];
services.tumbler.enable = true;
environment.systemPackages = with pkgs; [ ntfs3g exfat-utils ];
deploy.profile.gui = true;
}

View file

@ -0,0 +1,37 @@
{ config, lib, pkgs, ... }: {
networking = {
# networkmanager.enable = true;
resolvconf.useLocalResolver = true;
networkmanager.dns = "none";
};
services.dnscrypt-proxy2 = {
enable = true;
settings = {
ipv6_servers = true;
require_dnssec = true;
sources.public-resolvers = {
urls = [
"https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md"
"https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md"
];
cache_file = "/var/lib/dnscrypt-proxy2/public-resolvers.md";
minisign_key =
"RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3";
};
# You can choose a specific set of servers from https://github.com/DNSCrypt/dnscrypt-resolvers/blob/master/v2/public-resolvers.md
server_names = [
"acsacsar-ams-ipv4"
"acsacsar-ams-ipv6"
"dnscrypt.eu-dk"
"dnscrypt.eu-dk-ipv6"
"dnscrypt.eu-nl"
"dnscrypt.eu-nl-ipv6"
"meganerd"
"meganerd-ipv6"
];
};
};
}

View file

@ -0,0 +1,16 @@
{ config, pkgs, ... }:
{
environment.variables = { BROWSER = "firefox"; };
xdg = {
portal = {
enable = true;
extraPortals = with pkgs; [
xdg-desktop-portal-wlr
xdg-desktop-portal-gtk
];
gtkUsePortal = true;
};
};
}

View file

@ -0,0 +1,18 @@
{ config, pkgs, lib, ... }:
{
fonts.fontconfig = {
enable = true;
defaultFonts = {
emoji = [
"Twitter Color Emoji"
];
};
};
fonts.fonts = with pkgs; [
font-awesome
nerdfonts
emacs-all-the-icons-fonts
twitter-color-emoji
];
}

View file

@ -0,0 +1,12 @@
{ config, pkgs, lib, ... }:
{
services.pcscd.enable = true;
services.udev.packages = [ pkgs.yubikey-personalization ];
programs.gnupg.agent = {
enable = true;
enableSSHSupport = true;
pinentryFlavor = "gtk2";
};
}

View file

@ -0,0 +1,46 @@
{ config, lib, pkgs, ... }:
let
c1 = "\\e[22;34m";
c2 = "\\e[1;35m";
nixos = [
" ${c1} ::::. ${c2}'::::: ::::' "
" ${c1} '::::: ${c2}':::::. ::::' "
" ${c1} ::::: ${c2}'::::.::::: "
" ${c1} .......:::::..... ${c2}:::::::: "
" ${c1} ::::::::::::::::::. ${c2}:::::: ${c1}::::. "
" ${c1} ::::::::::::::::::::: ${c2}:::::. ${c1}.::::' "
" ${c2} ..... ::::' ${c1}:::::' "
" ${c2} ::::: '::' ${c1}:::::' "
" ${c2} ........::::: ' ${c1}:::::::::::. "
" ${c2}::::::::::::: ${c1}::::::::::::: "
" ${c2} ::::::::::: ${c1}.. ${c1}::::: "
" ${c2} .::::: ${c1}.::: ${c1}::::: "
" ${c2} .::::: ${c1}::::: ${c1}''''' ${c2}..... "
" ${c2} ::::: ${c1}':::::. ${c2}......:::::::::::::' "
" ${c2} ::: ${c1}::::::. ${c2}':::::::::::::::::' "
" ${c1} .:::::::: ${c2}':::::::::: "
" ${c1} .::::''::::. ${c2}'::::. "
" ${c1} .::::' ::::. ${c2}'::::. "
" ${c1} .:::: :::: ${c2}'::::. "
];
in
{
console = {
font = "Tamzen7x14";
earlySetup = true;
getty = {
greetingPrefix =
''\e[H\e[2J'' + # topleft
''\e[9;10]''; # setterm blank/powersave = 10 minutes
greeting =
"\n" +
lib.concatStringsSep "\n" nixos +
"\n\n" +
''\e[1;32m>>> NixOS ${config.system.nixos.label} (Linux \r) - \l\e[0m'';
};
};
services.getty = {
helpLine = lib.mkForce "";
};
}

View file

@ -0,0 +1,8 @@
{ config, lib, ... }:
with lib;
{
kw.fw.private.tcp.ports = [ 6600 32101 ];
kw.fw.public.tcp.ports = [ 6600 32101 ];
}

View file

@ -0,0 +1,23 @@
{ config, ... }:
{
boot.supportedFilesystems = [ "nfs" ];
fileSystems."/mnt/kat-nas" = {
device = "samhain.net.kittywit.ch:/mnt/zraw/media";
fsType = "nfs";
options = [ "x-systemd.automount" "noauto" ];
};
fileSystems."/mnt/hex-corn" = {
device = "storah.net.lilwit.ch:/data/cornbox";
fsType = "nfs";
options = [ "x-systemd.automount" "noauto" ];
};
fileSystems."/mnt/hex-tor" = {
device = "storah.net.lilwit.ch:/data/torrents";
fsType = "nfs";
options = [ "x-systemd.automount" "noauto" ];
};
}

View file

@ -0,0 +1,11 @@
{ pkgs, ... }:
{
nix.extraOptions = ''
plugin-files = ${pkgs.nix-doc}/lib/libnix_doc_plugin.so
'';
environment.systemPackages = with pkgs; [
nix-doc
];
}

View file

@ -0,0 +1,8 @@
{ pkgs, config, lib, ... }:
{
nixpkgs.config = {
allowUnfree = true;
pulseaudio = true;
};
}

View file

@ -0,0 +1,43 @@
{ config, lib, pkgs, ... }:
{
sound = {
enable = true;
extraConfig = ''
defaults.pcm.rate_converter "speexrate_best"
'';
};
environment.systemPackages = with pkgs; [ pavucontrol ];
security.rtkit.enable = true;
services.pipewire = {
enable = true;
config = {
pipewire = {
"context.properties" = {
"log.level" = 2;
"default.clock.min-quantum" =
32; # default; going lower may cause crackles and distorted audio
};
pipewire-pulse = {
"context.modules" = [{
name = "libpipewire-module-protocol-pulse";
args = {
"pulse.min.quantum" = 32; # controls minimum playback quant
"pulse.min.req" = 32; # controls minimum recording quant
"pulse.min.frag" = 32; # controls minimum fragment size
"server.address" =
[ "unix:native" ]; # the default address of the server
};
}];
};
};
};
pulse.enable = true;
alsa.support32Bit = true;
jack.enable = true;
alsa.enable = true;
};
}

View file

@ -0,0 +1,7 @@
{ ... }:
{
imports = [ ./light.nix ];
deploy.profile.laptop = true;
}

View file

@ -0,0 +1,5 @@
{ config, lib, pkgs, ... }:
{
programs.light.enable = true;
}

View file

@ -0,0 +1,7 @@
{ config, pkgs, ... }:
{
imports = [ ./sway.nix ];
deploy.profile.sway = true;
}

View file

@ -0,0 +1,8 @@
{ config, pkgs, lib, ... }:
{
programs.sway = {
enable = true;
extraPackages = with pkgs; lib.mkForce [ xwayland swaylock swayidle ];
};
}

View file

@ -0,0 +1,57 @@
{ config, lib, pkgs, tf, ... }:
with lib;
{
kw.fw.public.tcp.ports = [ 5160 5060 ];
kw.fw.public.udp.ports = [ 5160 5060 ];
kw.fw.public.tcp.ranges = [{
from = 10000;
to = 20000;
}];
kw.fw.public.udp.ranges = [{
from = 10000;
to = 20000;
}];
services.fail2ban.jails = {
asterisk = ''
enabled = true
filter = asterisk
action = nftables-allports
logpath = /var/log/asterisk/messages
maxretry = 4
'';
};
environment.systemPackages = with pkgs; [ asterisk ];
users.groups.asterisk = {
name = "asterisk";
};
users.users.asterisk = {
name = "asterisk";
group = "asterisk";
home = "/var/lib/asterisk";
isSystemUser = true;
};
systemd.services.asterisk = {
description = "Asterisk PBX Server";
wantedBy = [ "multi-user.target" ];
restartIfChanged = false;
serviceConfig = {
ExecStart = "${pkgs.asterisk}/bin/asterisk -U asterisk -C /etc/asterisk/asterisk.conf -F";
ExecReload = "${pkgs.asterisk}/bin/asterisk -x 'core reload'";
Type = "forking";
PIDFile = "/run/asterisk/asterisk.pid";
};
};
}

View file

@ -0,0 +1,44 @@
{ config, pkgs, lib, ... }:
with lib;
let
mailAccounts = config.mailserver.loginAccounts;
htpasswd = pkgs.writeText "radicale.users" (concatStrings
(flip mapAttrsToList mailAccounts
(mail: user: mail + ":" + user.hashedPassword + "\n")));
in
{
services.radicale = {
enable = true;
settings = {
auth = {
type = "htpasswd";
htpasswd_filename = toString htpasswd;
htpasswd_encryption = "bcrypt";
};
};
};
services.nginx.virtualHosts = {
"cal.kittywit.ch" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://localhost:5232/";
extraConfig = ''
proxy_set_header X-Script-Name /;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass_header Authorization;
'';
};
};
};
deploy.tf.dns.records.kittywitch_cal = {
tld = "kittywit.ch.";
domain = "cal";
cname.target = "athame.kittywit.ch.";
};
}

View file

@ -0,0 +1,41 @@
{ config, pkgs, ... }:
{
services.fail2ban = {
enable = true;
packageFirewall = pkgs.nftables;
banaction = "nftables-multiport";
banaction-allports = "nftables-allports";
jails = {
default = ''
bantime = 7d
blocktype = DROP
action = nftables-allports
logpath = /var/log/auth.log
'';
ssh = ''
enabled = true
filter = sshd
maxretry = 4
action = nftables-multiport[name=SSH, port=ssh, protocol=tcp]
'';
sshd-ddos = ''
enabled = true
filter = sshd-ddos
maxretry = 4
action = nftables-multiport[name=ssh, port=ssh, protocol=tcp]
'';
};
};
environment.etc."fail2ban/filter.d/sshd-ddos.conf" = {
enable = true;
text = ''
[Definition]
failregex = sshd(?:\[\d+\])?: Did not receive identification string from <HOST>$
ignoreregex =
'';
};
systemd.services.fail2ban.serviceConfig.LimitSTACK = 128 * 1024;
}

View file

@ -0,0 +1,59 @@
{ config, pkgs, ... }:
{
services.postgresql = {
enable = true;
ensureDatabases = [ "gitea" ];
ensureUsers = [{
name = "gitea";
ensurePermissions."DATABASE gitea" = "ALL PRIVILEGES";
}];
};
services.gitea = {
enable = true;
disableRegistration = true;
domain = "git.kittywit.ch";
rootUrl = "https://git.kittywit.ch";
httpAddress = "127.0.0.1";
appName = "kittywitch git";
ssh = { clonePort = 62954; };
database = {
type = "postgres";
name = "gitea";
user = "gitea";
};
settings = {
security = { DISABLE_GIT_HOOKS = false; };
api = { ENABLE_SWAGGER = true; };
mailer = {
ENABLED = true;
MAILER_TYPE = "sendmail";
FROM = "gitea@kittywit.ch";
SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
};
ui = {
THEMES = "gitea,arc-green";
DEFAULT_THEME = "gitea";
THEME_COLOR_META_TAG = "#222222";
};
};
};
systemd.services.gitea.preStart = ''
${pkgs.coreutils}/bin/ln -sfT ${./public} /var/lib/gitea/custom/public
${pkgs.coreutils}/bin/ln -sfT ${./templates} /var/lib/gitea/custom/templates
'';
services.nginx.virtualHosts."git.kittywit.ch" = {
enableACME = true;
forceSSL = true;
locations = { "/".proxyPass = "http://127.0.0.1:3000"; };
};
deploy.tf.dns.records.kittywitch_git = {
tld = "kittywit.ch.";
domain = "git";
cname.target = "athame.kittywit.ch.";
};
}

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 171.19"><defs><style>.cls-1,.cls-3{stroke:#000;stroke-miterlimit:10;}.cls-2{fill:#ccc;}.cls-3,.cls-4{fill:#f2f2f2;}</style></defs><path class="cls-1" d="M403.84,463.72s13.51-20.52,9.51-44.75c0,0-22.62-2.63-38,12.5C361.41,402.18,345.28,369,369.47,345c0,0,8,5.45,8.47,12.71,2,31.08-1.18,31.53-1.18,31.53,25.3-3.77,32.51-45.12,9.89-59.77-55.53-26.12-63.59-4.21-71.3,42.35-3.2,27.07-3.83,41.66-8.19,62.74-13.46-15.72-38.59-14.19-38-14-3.4,25,7.88,45.73,9.45,46.09-15,4.55-37.67,6.71-73,11.28,75.87,17.66,199,11.15,274.58-4.94C468.23,470.85,414.31,471.71,403.84,463.72Z" transform="translate(-202.9 -317.79)"/><polygon class="cls-2" points="98.68 121.45 90.04 110.5 90.44 120.65 75.53 115.4 83.03 130.62 71.94 130.38 81.58 144.37 98.68 121.45"/><path class="cls-3" d="M305.65,436.85" transform="translate(-202.9 -317.79)"/><path class="cls-4" d="M301.65,439.44l-.07-.2a85.43,85.43,0,0,0-6.65-5.65l0,6.41s-6,1.47-10.83-1.78c0,0,7.18,11.33,1.07,11.54a40.37,40.37,0,0,0-5.48.5l4.78,11.9C299.42,459.81,301.81,444.69,301.65,439.44Z" transform="translate(-202.9 -317.79)"/><polygon class="cls-2" points="179.03 119.28 187.68 108.33 187.28 118.47 202.19 113.22 194.69 128.44 205.78 128.21 196.13 142.19 179.03 119.28"/><path class="cls-4" d="M381.87,437.27l.07-.2a83.13,83.13,0,0,1,6.65-5.66l0,6.41s6,1.47,10.83-1.78c0,0-7.18,11.33-1.07,11.55a38.39,38.39,0,0,1,5.48.5L399,460C384.1,457.63,381.71,442.51,381.87,437.27Z" transform="translate(-202.9 -317.79)"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,29 @@
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar">
<a class="item {{if .PageIsAdminDashboard}}acty{{end}}" href="{{AppSubUrl}}/admin">
{{.i18n.Tr "admin.dashboard"}}
</a>
<a class="item {{if .PageIsAdminUsers}}acty{{end}}" href="{{AppSubUrl}}/admin/users">
{{.i18n.Tr "admin.users"}}
</a>
<a class="item {{if .PageIsAdminOrganizations}}acty{{end}}" href="{{AppSubUrl}}/admin/orgs">
{{.i18n.Tr "admin.organizations"}}
</a>
<a class="item {{if .PageIsAdminRepositories}}acty{{end}}" href="{{AppSubUrl}}/admin/repos">
{{.i18n.Tr "admin.repositories"}}
</a>
<a class="item {{if .PageIsAdminHooks}}acty{{end}}" href="{{AppSubUrl}}/admin/hooks">
{{.i18n.Tr "admin.hooks"}}
</a>
<a class="item {{if .PageIsAdminAuthentications}}acty{{end}}" href="{{AppSubUrl}}/admin/auths">
{{.i18n.Tr "admin.authentication"}}
</a>
<a class="item {{if .PageIsAdminConfig}}acty{{end}}" href="{{AppSubUrl}}/admin/config">
{{.i18n.Tr "admin.config"}}
</a>
<a class="item {{if .PageIsAdminNotices}}acty{{end}}" href="{{AppSubUrl}}/admin/notices">
{{.i18n.Tr "admin.notices"}}
</a>
<a class="item {{if .PageIsAdminMonitor}}acty{{end}}" href="{{AppSubUrl}}/admin/monitor">
{{.i18n.Tr "admin.monitor"}}
</a>
</div>

View file

@ -0,0 +1,176 @@
<!DOCTYPE html>
<html>
<head data-suburl="{{AppSubUrl}}">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
<link rel="manifest" href="{{AppSubUrl}}/manifest.json">
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('{{AppSubUrl}}/serviceworker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
</script>
<meta name="theme-color" content="{{ThemeColorMetaTag}}">
<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}" />
<meta name="description" content="{{if .Repository}}{{.Repository.Name}}{{if .Repository.Description}} - {{.Repository.Description}}{{end}}{{else}}{{MetaDescription}}{{end}}" />
<meta name="keywords" content="{{MetaKeywords}}">
<meta name="referrer" content="no-referrer" />
<meta name="_csrf" content="{{.CsrfToken}}" />
<meta name="_suburl" content="{{AppSubUrl}}" />
{{if .IsSigned}}
<meta name="_uid" content="{{.SignedUser.ID}}" />
{{end}}
{{if .ContextUser}}
<meta name="_context_uid" content="{{.ContextUser.ID}}" />
{{end}}
{{if .SearchLimit}}
<meta name="_search_limit" content="{{.SearchLimit}}" />
{{end}}
{{if .GoGetImport}}
<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">
<meta name="go-source" content="{{.GoGetImport}} _ {{.GoDocDirectory}} {{.GoDocFile}}">
{{end}}
<script>
{{SafeJS `/*
@licstart The following is the entire license notice for the
JavaScript code in this page.
Copyright (c) 2016 The Gitea Authors
Copyright (c) 2015 The Gogs Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---
Licensing information for additional javascript libraries can be found at:
{{AppSubUrl}}/vendor/librejs.html
@licend The above is the entire license notice
for the JavaScript code in this page.
*/`}}
</script>
<link rel="shortcut icon" href="{{AppSubUrl}}/img/favicon.png" />
<link rel="mask-icon" href="{{AppSubUrl}}/img/gitea-safari.svg" color="#609926">
<link rel="preload" href="{{AppSubUrl}}/vendor/assets/font-awesome/css/font-awesome.min.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{AppSubUrl}}/vendor/assets/font-awesome/css/font-awesome.min.css"></noscript>
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/assets/octicons/octicons.min.css">
{{if .RequireSimpleMDE}}
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/simplemde/simplemde.min.css">
{{end}}
{{if .RequireGitGraph}}
<!-- graph -->
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/gitgraph/gitgraph.css">
{{end}}
{{if .RequireTribute}}
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/tribute/tribute.css">
{{end}}
<!-- Stylesheet -->
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/semantic/semantic.min.css">
<link rel="stylesheet" href="{{AppSubUrl}}/css/index.css?v={{MD5 AppVer}}">
<noscript>
<style>
.dropdown:hover > .menu { display: block; }
.ui.secondary.menu .dropdown.item > .menu { margin-top: 0; }
</style>
</noscript>
{{if .RequireHighlightJS}}
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/highlight/github.css">
{{end}}
{{if .RequireMinicolors}}
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/jquery.minicolors/jquery.minicolors.css">
{{end}}
{{if .RequireDatetimepicker}}
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/jquery.datetimepicker/jquery.datetimepicker.css">
{{end}}
{{if .RequireDropzone}}
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/dropzone/dropzone.css">
{{end}}
{{if .EnableHeatmap}}
<link rel="stylesheet" href="{{AppSubUrl}}/vendor/plugins/vue-calendar-heatmap/vue-calendar-heatmap.css">
{{end}}
<style class="list-search-style"></style>
<script src="{{AppSubUrl}}/vendor/plugins/cssrelpreload/loadCSS.min.js"></script>
<script src="{{AppSubUrl}}/vendor/plugins/cssrelpreload/cssrelpreload.min.js"></script>
{{if .PageIsUserProfile}}
<meta property="og:title" content="{{.Owner.Name}}" />
<meta property="og:type" content="profile" />
<meta property="og:image" content="{{.Owner.AvatarLink}}" />
<meta property="og:url" content="{{.Owner.HTMLURL}}" />
<meta property="og:site_name" content="{{AppName}}" />
{{else if .Repository}}
<meta property="og:title" content="{{.Repository.Name}}" />
<meta property="og:type" content="object" />
<meta property="og:image" content="{{.Repository.Owner.AvatarLink}}" />
<meta property="og:url" content="{{.Repository.HTMLURL}}" />
{{if .Repository.Description}}
<meta property="og:description" content="{{.Repository.Description}}" />
{{end}}
<meta property="og:site_name" content="{{AppName}}" />
{{else}}
<meta property="og:title" content="{{AppName}}">
<meta property="og:type" content="website" />
<meta property="og:image" content="{{AppSubUrl}}/img/gitea-lg.png" />
<meta property="og:url" content="{{AppUrl}}" />
<meta property="og:description" content="{{MetaDescription}}">
{{end}}
{{if .IsSigned }}
{{ if ne .SignedUser.Theme "gitea" }}
<link rel="stylesheet" href="{{AppSubUrl}}/css/theme-{{.SignedUser.Theme}}.css">
{{end}}
{{else if ne DefaultTheme "gitea"}}
<link rel="stylesheet" href="{{AppSubUrl}}/css/theme-{{DefaultTheme}}.css">
{{end}}
{{template "custom/header" .}}
</head>
<body>
{{template "custom/body_outer_pre" .}}
<div class="full height">
<noscript>{{.i18n.Tr "enable_javascript"}}</noscript>
{{template "custom/body_inner_pre" .}}
{{if not .PageIsInstall}}
<div class="ui top secondary stackable main menu following bar light">
{{template "base/head_navbar" .}}
</div><!-- end bar -->
{{end}}
{{/*
</div>
</body>
</html>
*/}}

View file

@ -0,0 +1,16 @@
<div class="ui secondary pointing tabular top attached borderless stackable menu navbar">
<a class="item {{if .PageIsExploreRepositories}}acty{{end}}" href="{{AppSubUrl}}/explore/repos">
{{svg "octicon-repo" 16}} {{.i18n.Tr "explore.repos"}}
</a>
<a class="item {{if .PageIsExploreUsers}}acty{{end}}" href="{{AppSubUrl}}/explore/users">
{{svg "octicon-person" 16}} {{.i18n.Tr "explore.users"}}
</a>
<a class="item {{if .PageIsExploreOrganizations}}acty{{end}}" href="{{AppSubUrl}}/explore/organizations">
{{svg "octicon-organization" 16}} {{.i18n.Tr "explore.organizations"}}
</a>
{{if .IsRepoIndexerEnabled}}
<a class="item {{if .PageIsExploreCode}}acty{{end}}" href="{{AppSubUrl}}/explore/code">
{{svg "octicon-code" 16}} {{.i18n.Tr "explore.code"}}
</a>
{{end}}
</div>

View file

@ -0,0 +1,18 @@
{{template "base/head" .}}
<div class="home">
<div class="ui stackable middle very relaxed page grid">
<div class="sixteen wide center aligned centered column">
<div>
<img class="logo" src="{{StaticUrlPrefix}}/img/gitea-lg.png" />
</div>
<div class="hero">
<br />
<h1 class="ui icon header title">
{{AppName}}
</h1>
<a href="https://kittywit.ch"><h2>back to home</h2></a>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View file

@ -0,0 +1,143 @@
<div class="header-wrapper">
{{with .Repository}}
<div class="ui container">
<div class="repo-header">
<div class="ui huge breadcrumb repo-title">
{{if .RelAvatarLink}}
<img class="ui avatar image" src="{{.RelAvatarLink}}">
{{else if .IsTemplate}}
{{if .IsPrivate}}
{{svg "octicon-repo-template-private" 32}}
{{else}}
{{svg "octicon-repo-template" 32}}
{{end}}
{{else}}
{{if .IsPrivate}}
{{svg "octicon-lock" 32}}
{{else if .IsMirror}}
{{svg "octicon-repo-clone" 32}}
{{else if .IsFork}}
{{svg "octicon-repo-fork" 32}}
{{else}}
{{svg "octicon-repo" 32}}
{{end}}
{{end}}
<a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a>
<div class="divider"> / </div>
<a href="{{$.RepoLink}}">{{.Name}}</a>
{{if .RelAvatarLink}}
{{if .IsTemplate}}
{{if .IsPrivate}}
{{svg "octicon-repo-template-private" 32}}
{{else}}
{{svg "octicon-repo-template" 32}}
{{end}}
{{else}}
{{if .IsPrivate}}
{{svg "octicon-lock" 32}}
{{else if .IsMirror}}
{{svg "octicon-repo-clone" 32}}
{{else if .IsFork}}
{{svg "octicon-repo-fork" 32}}
{{else}}
{{svg "octicon-repo" 32}}
{{end}}
{{end}}
{{end}}
{{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}}
{{if .IsMirror}}<div class="fork-flag">{{$.i18n.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{MirrorAddress $.Mirror}}{{end}}">{{if .SanitizedOriginalURL}}{{.SanitizedOriginalURL}}{{else}}{{MirrorAddress $.Mirror}}{{end}}</a></div>{{end}}
{{if .IsFork}}<div class="fork-flag">{{$.i18n.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{SubStr .BaseRepo.RelLink 1 -1}}</a></div>{{end}}
{{if .IsGenerated}}<div class="fork-flag">{{$.i18n.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{SubStr .TemplateRepo.RelLink 1 -1}}</a></div>{{end}}
</div>
{{if not .IsBeingCreated}}
<div class="repo-buttons">
<div class="ui labeled button" tabindex="0">
<a class="ui compact basic button" href="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}un{{end}}watch?redirect_to={{$.Link}}">
<i class="icon fa-eye{{if not $.IsWatchingRepo}}-slash{{end}}"></i>{{if $.IsWatchingRepo}}{{$.i18n.Tr "repo.unwatch"}}{{else}}{{$.i18n.Tr "repo.watch"}}{{end}}
</a>
<a class="ui basic label" href="{{.Link}}/watchers">
{{.NumWatches}}
</a>
</div>
<div class="ui labeled button" tabindex="0">
<a class="ui compact basic button" href="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}">
<i class="icon star{{if not $.IsStaringRepo}} outline{{end}}"></i>{{if $.IsStaringRepo}}{{$.i18n.Tr "repo.unstar"}}{{else}}{{$.i18n.Tr "repo.star"}}{{end}}
</a>
<a class="ui basic label" href="{{.Link}}/stars">
{{.NumStars}}
</a>
</div>
{{if and (not .IsEmpty) ($.Permission.CanRead $.UnitTypeCode)}}
<div class="ui labeled button {{if and ($.IsSigned) (not $.CanSignedUserFork)}}disabled-repo-button{{end}}" tabindex="0">
<a class="ui compact basic button {{if or (not $.IsSigned) (not $.CanSignedUserFork)}}poping up{{end}}" {{if $.CanSignedUserFork}}href="{{AppSubUrl}}/repo/fork/{{.ID}}"{{else if $.IsSigned}} data-content="{{$.i18n.Tr "repo.fork_from_self"}}" {{ else }} data-content="{{$.i18n.Tr "repo.fork_guest_user" }}" rel="nofollow" href="{{AppSubUrl}}/user/login?redirect_to={{AppSubUrl}}/repo/fork/{{.ID}}" {{end}} data-position="top center" data-variation="tiny">
{{svg "octicon-repo-forked" 16}}{{$.i18n.Tr "repo.fork"}}
</a>
<a class="ui basic label" href="{{.Link}}/forks">
{{.NumForks}}
</a>
</div>
{{end}}
</div>
{{end}}
</div><!-- end grid -->
</div><!-- end container -->
{{end}}
<div class="ui tabs container">
{{if not .Repository.IsBeingCreated}}
<div class="ui tabular stackable menu navbar">
{{if .Permission.CanRead $.UnitTypeCode}}
<a class="item {{if .PageIsViewCode}}acty{{end}}" href="{{.RepoLink}}{{if (ne .BranchName .Repository.DefaultBranch)}}/src/{{.BranchNameSubURL | EscapePound}}{{end}}">
{{svg "octicon-code" 16}} {{.i18n.Tr "repo.code"}}
</a>
{{end}}
{{if .Permission.CanRead $.UnitTypeIssues}}
<a class="item {{if .PageIsIssueList}}acty{{end}}" href="{{.RepoLink}}/issues">
{{svg "octicon-issue-opened" 16}} {{.i18n.Tr "repo.issues"}} <span class="ui {{if not .Repository.NumOpenIssues}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenIssues}}</span>
</a>
{{end}}
{{if .Permission.CanRead $.UnitTypeExternalTracker}}
<a class="item {{if .PageIsIssueList}}acty{{end}}" href="{{.RepoExternalIssuesLink}}" target="_blank" rel="noopener noreferrer">
{{svg "octicon-link-external" 16}} {{.i18n.Tr "repo.issues"}} </span>
</a>
{{end}}
{{if and .Repository.CanEnablePulls (.Permission.CanRead $.UnitTypePullRequests)}}
<a class="item {{if .PageIsPullList}}acty{{end}}" href="{{.RepoLink}}/pulls">
{{svg "octicon-git-pull-request" 16}} {{.i18n.Tr "repo.pulls"}} <span class="ui {{if not .Repository.NumOpenPulls}}gray{{else}}blue{{end}} small label">{{.Repository.NumOpenPulls}}</span>
</a>
{{end}}
{{if and (.Permission.CanRead $.UnitTypeReleases) (not .IsEmptyRepo) }}
<a class="item {{if .PageIsReleaseList}}acty{{end}}" href="{{.RepoLink}}/releases">
{{svg "octicon-tag" 16}} {{.i18n.Tr "repo.releases"}} <span class="ui {{if not .NumReleases}}gray{{else}}blue{{end}} small label">{{.NumReleases}}</span>
</a>
{{end}}
{{if or (.Permission.CanRead $.UnitTypeWiki) (.Permission.CanRead $.UnitTypeExternalWiki)}}
<a class="item {{if .PageIsWiki}}acty{{end}}" href="{{.RepoLink}}/wiki" {{if (.Permission.CanRead $.UnitTypeExternalWiki)}} target="_blank" rel="noopener noreferrer" {{end}}>
{{svg "octicon-book" 16}} {{.i18n.Tr "repo.wiki"}}
</a>
{{end}}
{{if and (.Permission.CanReadAny $.UnitTypePullRequests $.UnitTypeIssues $.UnitTypeReleases) (not .IsEmptyRepo)}}
<a class="item {{if .PageIsActivity}}acty{{end}}" href="{{.RepoLink}}/activity">
{{svg "octicon-pulse" 16}} {{.i18n.Tr "repo.activity"}}
</a>
{{end}}
{{template "custom/extra_tabs" .}}
{{if .Permission.IsAdmin}}
<div class="right menu">
<a class="item {{if .PageIsSettings}}acty{{end}}" href="{{.RepoLink}}/settings">
{{svg "octicon-tools" 16}} {{.i18n.Tr "repo.settings"}}
</a>
</div>
{{end}}
</div>
{{end}}
</div>
<div class="ui tabs divider"></div>
</div>

View file

@ -0,0 +1,153 @@
{{template "base/head" .}}
<div class="repository file list">
{{template "repo/header" .}}
<div class="ui container">
{{template "base/alert" .}}
<div class="ui repo-description">
<div id="repo-desc">
{{if .Repository.DescriptionHTML}}<span class="description has-emoji">{{.Repository.DescriptionHTML}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>{{end}}
<a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
</div>
{{if .RepoSearchEnabled}}
<div class="ui repo-search">
<form class="ui form ignore-dirty" action="{{.RepoLink}}/search" method="get">
<div class="field">
<div class="ui action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "repo.search.search_repo"}}">
<button class="ui icon button" type="submit">
<i class="search icon"></i>
</button>
</div>
</div>
</form>
</div>
{{end}}
</div>
<div class="ui" id="repo-topics">
{{range .Topics}}<a class="ui repo-topic small label topic" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<a id="manage_topic">{{.i18n.Tr "repo.topic.manage_topics"}}</a>{{end}}
</div>
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
<div class="ui repo-topic-edit grid form segment error" id="topic_edit" style="display:none">
<div class="fourteen wide column">
<div class="field">
<div class="ui fluid multiple search selection dropdown">
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if lt (Add $i 1) (len $.Topics)}},{{end}}{{end}}">
{{range .Topics}}
<div class="ui small label topic transition visible" data-value="{{.Name}}" style="display: inline-block !important; cursor: default;">{{.Name}}<i class="delete icon"></i></div>
{{end}}
<div class="text"></div>
</div>
</div>
</div>
<div class="two wide column">
<a class="ui button primary" href="javascript:;" id="save_topic"
data-link="{{.RepoLink}}/topics">{{.i18n.Tr "repo.topic.done"}}</a>
</div>
</div>
{{end}}
<div class="hide" id="validate_prompt">
<span id="count_prompt">{{.i18n.Tr "repo.topic.count_prompt"}}</span>
<span id="format_prompt">{{.i18n.Tr "repo.topic.format_prompt"}}</span>
</div>
{{if .Repository.IsArchived}}
<div class="ui warning message">
{{.i18n.Tr "repo.archive.title"}}
</div>
{{end}}
{{template "repo/sub_menu" .}}
<div class="ui stackable secondary menu mobile--margin-between-items mobile--no-negative-margins">
{{template "repo/branch_dropdown" .}}
{{ $n := len .TreeNames}}
{{ $l := Subtract $n 1}}
<!-- If home page, show new PR. If not, show breadcrumb -->
{{if eq $n 0}}
{{if and .PullRequestCtx.Allowed .IsViewBranch (not .Repository.IsArchived)}}
<div class="fitted item">
<a href="{{.BaseRepo.Link}}/compare/{{.BaseRepo.DefaultBranch | EscapePound}}...{{if ne .Repository.Owner.Name .BaseRepo.Owner.Name}}{{.Repository.Owner.Name}}:{{end}}{{.BranchName | EscapePound}}">
<button id="new-pull-request" class="ui compact basic button">{{.i18n.Tr "repo.pulls.compare_changes"}}</button>
</a>
</div>
{{end}}
{{else}}
<div class="fitted item"><span class="ui breadcrumb repo-path"><a class="section" href="{{.RepoLink}}/src/{{EscapePound .BranchNameSubURL}}" title="{{.Repository.Name}}">{{EllipsisString .Repository.Name 30}}</a>{{range $i, $v := .TreeNames}}<span class="divider">/</span>{{if eq $i $l}}<span class="active section" title="{{$v}}">{{EllipsisString $v 30}}</span>{{else}}{{ $p := index $.Paths $i}}<span class="section"><a href="{{EscapePound $.BranchLink}}/{{EscapePound $p}}" title="{{$v}}">{{EllipsisString $v 30}}</a></span>{{end}}{{end}}</span></div>
{{end}}
<div class="right fitted item" id="file-buttons">
<div class="ui tiny blue buttons">
{{if .Repository.CanEnableEditor}}
{{if .CanAddFile}}
<a href="{{.RepoLink}}/_new/{{EscapePound .BranchName}}/{{EscapePound .TreePath}}" class="ui button">
{{.i18n.Tr "repo.editor.new_file"}}
</a>
{{end}}
{{if .CanUploadFile}}
<a href="{{.RepoLink}}/_upload/{{EscapePound .BranchName}}/{{EscapePound .TreePath}}" class="ui button">
{{.i18n.Tr "repo.editor.upload_file"}}
</a>
{{end}}
{{end}}
{{if and (ne $n 0) (not .IsViewFile) (not .IsBlame) }}
<a href="{{.RepoLink}}/commits/{{EscapePound .BranchNameSubURL}}/{{EscapePound .TreePath}}" class="ui button">
{{.i18n.Tr "repo.file_history"}}
</a>
{{end}}
</div>
</div>
<div class="fitted item">
{{if eq $n 0}}
{{if .Repository.IsTemplate}}
<div class="ui tiny blue buttons">
<a href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}" class="ui button">
{{.i18n.Tr "repo.use_template"}}
</a>
</div>
{{end}}
{{end}}
</div>
<div class="fitted item">
<!-- Only show clone panel in repository home page -->
{{if eq $n 0}}
<div class="ui action tiny input" id="clone-panel">
{{if not $.DisableHTTP}}
<button class="ui basic clone button" id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}">
{{if UseHTTPS}}HTTPS{{else}}HTTP{{end}}
</button>
{{end}}
{{if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
<button class="ui basic clone button" id="repo-clone-ssh" data-link="{{.CloneLink.SSH}}">
SSH
</button>
{{end}}
{{if not $.DisableHTTP}}
<input id="repo-clone-url" value="{{$.CloneLink.HTTPS}}" readonly>
{{else if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
<input id="repo-clone-url" value="{{$.CloneLink.SSH}}" readonly>
{{end}}
{{if or (not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH))}}
<button class="ui basic icon button poping up clipboard" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}" data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny" data-clipboard-target="#repo-clone-url">
{{svg "octicon-clippy" 16}}
</button>
{{end}}
<div class="ui basic jump dropdown icon button poping up" data-content="{{.i18n.Tr "repo.download_archive"}}" data-variation="tiny inverted" data-position="top right">
<i class="download icon"></i>
<div class="menu">
<a class="item" href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.zip">{{svg "octicon-file-zip" 16}}&nbsp;ZIP</a>
<a class="item" href="{{$.RepoLink}}/archive/{{EscapePound $.BranchName}}.tar.gz">{{svg "octicon-file-zip" 16}}&nbsp;TAR.GZ</a>
</div>
</div>
</div>
{{end}}
</div>
</div>
{{if .IsViewFile}}
{{template "repo/view_file" .}}
{{else if .IsBlame}}
{{template "repo/blame" .}}
{{else}}
{{template "repo/view_list" .}}
{{end}}
</div>
</div>
{{template "base/footer" .}}

View file

@ -0,0 +1,29 @@
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar">
<a class="item {{if .PageIsSettingsOptions}}acty{{end}}" href="{{.RepoLink}}/settings">
{{.i18n.Tr "repo.settings.options"}}
</a>
<a class="item {{if .PageIsSettingsCollaboration}}acty{{end}}" href="{{.RepoLink}}/settings/collaboration">
{{.i18n.Tr "repo.settings.collaboration"}}
</a>
{{if not .Repository.IsEmpty}}
<a class="item {{if .PageIsSettingsBranches}}acty{{end}}" href="{{.RepoLink}}/settings/branches">
{{.i18n.Tr "repo.settings.branches"}}
</a>
{{end}}
<a class="item {{if .PageIsSettingsHooks}}acty{{end}}" href="{{.RepoLink}}/settings/hooks">
{{.i18n.Tr "repo.settings.hooks"}}
</a>
{{if .SignedUser.CanEditGitHook}}
<a class="item {{if .PageIsSettingsGitHooks}}acty{{end}}" href="{{.RepoLink}}/settings/hooks/git">
{{.i18n.Tr "repo.settings.githooks"}}
</a>
{{end}}
<a class="item {{if .PageIsSettingsKeys}}acty{{end}}" href="{{.RepoLink}}/settings/keys">
{{.i18n.Tr "repo.settings.deploy_keys"}}
</a>
{{if .LFSStartServer}}
<a class="item {{if .PageIsSettingsLFS}}acty{{end}}" href="{{.RepoLink}}/settings/lfs">
{{.i18n.Tr "repo.settings.lfs"}}
</a>
{{end}}
</div>

View file

@ -0,0 +1,132 @@
{{template "base/head" .}}
<div class="dashboard feeds">
{{template "user/dashboard/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}
<div class="ui mobile reversed stackable grid">
<div class="ui container ten wide column">
{{if .EnableHeatmap}}
<div id="user-heatmap" style="padding-right: 40px">
<activity-heatmap :locale="locale" :suburl="suburl" :user="heatmapUser">
<div slot="loading">
<div class="ui active centered inline indeterminate text loader" id="loading-heatmap">{{.i18n.Tr "user.heatmap.loading"}}</div>
</div>
</activity-heatmap>
<div class="ui divider"></div>
</div>
{{end}}
{{template "user/dashboard/feeds" .}}
</div>
<div id="app" class="six wide column">
<repo-search
:search-limit="searchLimit"
:suburl="suburl"
:uid="uid"
:more-repos-link="'{{.ContextUser.HomeLink}}'"
{{if not .ContextUser.IsOrganization}}
:organizations="[
{{range .ContextUser.Orgs}}
{name: '{{.Name}}', num_repos: '{{.NumRepos}}'},
{{end}}
]"
:is-organization="false"
:organizations-total-count="{{.ContextUser.GetOrganizationCount}}"
:can-create-organization="{{.SignedUser.CanCreateOrganization}}"
{{end}}
inline-template
v-cloak
>
<div>
<div v-if="!isOrganization" class="ui two item tabable menu">
<a :class="{item: true, acty: tab === 'repos'}" @click="changeTab('repos')">{{.i18n.Tr "repository"}}</a>
<a :class="{item: true, acty: tab === 'organizations'}" @click="changeTab('organizations')">{{.i18n.Tr "organization"}}</a>
</div>
<div v-show="tab === 'repos'" class="ui tab active list dashboard-repos">
<h4 class="ui top attached header">
{{.i18n.Tr "home.my_repos"}} <span class="ui grey label">${reposTotalCount}</span>
{{if or (not .ContextUser.IsOrganization) .IsOrganizationOwner}}
<div class="ui right">
<a class="poping up" :href="suburl + '/repo/create{{if .ContextUser.IsOrganization}}?org={{.ContextUser.ID}}{{end}}'" data-content="{{.i18n.Tr "new_repo"}}" data-variation="tiny inverted" data-position="left center">
<i class="plus icon"></i>
<span class="sr-only">{{.i18n.Tr "new_repo"}}</span>
</a>
</div>
{{end}}
</h4>
<div class="ui attached secondary segment repos-search">
<div class="ui fluid icon input" :class="{loading: isLoading}">
<input @input="searchRepos(reposFilter)" v-model="searchQuery" ref="search" placeholder="{{.i18n.Tr "home.search_repos"}}">
<i class="search icon"></i>
</div>
<div class="ui secondary tiny pointing borderless menu center aligned grid repos-filter">
<a class="item" :class="{acty: reposFilter === 'all'}" @click="changeReposFilter('all')">
{{.i18n.Tr "all"}}
<div v-show="reposFilter === 'all'" class="ui circular mini grey label">${repoTypeCount}</div>
</a>
<a class="item" :class="{acty: reposFilter === 'sources'}" @click="changeReposFilter('sources')">
{{.i18n.Tr "sources"}}
<div v-show="reposFilter === 'sources'" class="ui circular mini grey label">${repoTypeCount}</div>
</a>
<a class="item" :class="{acty: reposFilter === 'forks'}" @click="changeReposFilter('forks')">
{{.i18n.Tr "forks"}}
<div v-show="reposFilter === 'forks'" class="ui circular mini grey label">${repoTypeCount}</div>
</a>
<a class="item" :class="{acty: reposFilter === 'mirrors'}" @click="changeReposFilter('mirrors')">
{{.i18n.Tr "mirrors"}}
<div v-show="reposFilter === 'mirrors'" class="ui circular mini grey label">${repoTypeCount}</div>
</a>
<a class="item" :class="{acty: reposFilter === 'collaborative'}" @click="changeReposFilter('collaborative')">
{{.i18n.Tr "collaborative"}}
<div v-show="reposFilter === 'collaborative'" class="ui circular mini grey label">${repoTypeCount}</div>
</a>
</div>
</div>
<div class="ui attached table segment">
<ul class="repo-owner-name-list">
<li v-for="repo in repos" :class="{'private': repo.private}" v-show="showRepo(repo, reposFilter)">
<a :href="suburl + '/' + repo.full_name">
<svg :class="'svg ' + repoClass(repo)" width="16" height="16" aria-hidden="true"><use :xlink:href="staticPrefix + '/img/svg/icons.svg#' + repoClass(repo)" /></svg>
<strong class="text truncate item-name">${repo.full_name}</strong>
<i v-if="repo.archived" class="archive icon archived-icon"></i>
<span class="ui right text light grey">
${repo.stars_count} <span class="rear">{{svg "octicon-star" 16}}</span>
</span>
</a>
</li>
<li v-if="showMoreReposLink">
<a :href="moreReposLink">{{.i18n.Tr "home.show_more_repos"}}</a>
</li>
</ul>
</div>
</div>
<div v-if="!isOrganization" v-show="tab === 'organizations'" class="ui tab active list">
<h4 class="ui top attached header">
{{.i18n.Tr "home.my_orgs"}} <span class="ui grey label">${organizationsTotalCount}</span>
<div v-if="canCreateOrganization" class="ui right">
<a class="poping up" :href="suburl + '/org/create'" data-content="{{.i18n.Tr "new_org"}}" data-variation="tiny inverted" data-position="left center">
<i class="plus icon"></i>
<span class="sr-only">{{.i18n.Tr "new_org"}}</span>
</a>
</div>
</h4>
<div class="ui attached table segment">
<ul class="repo-owner-name-list">
<li v-for="org in organizations">
<a :href="suburl + '/' + org.name">
{{svg "octicon-organization" 16}}
<strong class="text truncate item-name">${org.name}</strong>
<span class="ui right text light grey">
${org.num_repos} <span class="rear">{{svg "octicon-repo" 16}}</span>
</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</repo-search>
</div>
</div>
</div>
</div>
{{template "base/footer" .}}

View file

@ -0,0 +1,23 @@
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar">
<a class="item {{if .PageIsSettingsProfile}}acty{{end}}" href="{{AppSubUrl}}/user/settings">
{{.i18n.Tr "settings.profile"}}
</a>
<a class="item {{if .PageIsSettingsAccount}}acty{{end}}" href="{{AppSubUrl}}/user/settings/account">
{{.i18n.Tr "settings.account"}}
</a>
<a class="item {{if .PageIsSettingsSecurity}}acty{{end}}" href="{{AppSubUrl}}/user/settings/security">
{{.i18n.Tr "settings.security"}}
</a>
<a class="item {{if .PageIsSettingsApplications}}acty{{end}}" href="{{AppSubUrl}}/user/settings/applications">
{{.i18n.Tr "settings.applications"}}
</a>
<a class="item {{if .PageIsSettingsKeys}}acty{{end}}" href="{{AppSubUrl}}/user/settings/keys">
{{.i18n.Tr "settings.ssh_gpg_keys"}}
</a>
<a class="item {{if .PageIsSettingsOrganization}}acty{{end}}" href="{{AppSubUrl}}/user/settings/organization">
{{.i18n.Tr "settings.organization"}}
</a>
<a class="item {{if .PageIsSettingsRepos}}acty{{end}}" href="{{AppSubUrl}}/user/settings/repos">
{{.i18n.Tr "settings.repos"}}
</a>
</div>

View file

@ -0,0 +1,36 @@
{ config, ... }:
{
services.postgresql = {
ensureDatabases = [ "grafana" ];
ensureUsers = [{
name = "grafana";
ensurePermissions."DATABASE grafana" = "ALL PRIVILEGES";
}];
};
services.grafana = {
enable = true;
port = 3001;
domain = "graph.kittywit.ch";
rootUrl = "https://graph.kittywit.ch/";
database = {
type = "postgres";
host = "/run/postgresql/";
user = "grafana";
name = "grafana";
};
};
services.nginx.virtualHosts."graph.kittywit.ch" = {
enableACME = true;
forceSSL = true;
locations = { "/".proxyPass = "http://127.0.0.1:3001"; };
};
deploy.tf.dns.records.kittywitch_graph = {
tld = "kittywit.ch.";
domain = "graph";
cname.target = "athame.kittywit.ch.";
};
}

View file

@ -0,0 +1,25 @@
{ config, lib, ... }:
with lib;
{
services.logrotate = {
enable = true;
paths = {
nginx = mkIf config.services.nginx.enable {
path = "/var/log/nginx/*.log";
user = "nginx";
group = "nginx";
frequency = "weekly";
keep = 2;
};
asterisk = mkIf config.systemd.services.asterisk.enable {
path = "/var/log/asterisk/messages";
user = "asterisk";
group = "asterisk";
frequency = "daily";
keep = 2;
};
};
};
}

62
config/services/loki.nix Normal file
View file

@ -0,0 +1,62 @@
{ config, pkgs, ... }:
{
kw.fw.private.tcp.ports = [ 3100 ];
services.loki = {
enable = true;
configuration = {
auth_enabled = false;
chunk_store_config = { max_look_back_period = "0s"; };
ingester = {
chunk_idle_period = "1h";
chunk_retain_period = "30s";
chunk_target_size = 1048576;
lifecycler = {
address = "0.0.0.0";
final_sleep = "0s";
ring = {
kvstore = { store = "inmemory"; };
replication_factor = 1;
};
};
max_chunk_age = "1h";
max_transfer_retries = 0;
};
limits_config = {
reject_old_samples = true;
reject_old_samples_max_age = "168h";
};
schema_config = {
configs = [{
from = "2020-10-24";
index = {
period = "24h";
prefix = "index_";
};
object_store = "filesystem";
schema = "v11";
store = "boltdb-shipper";
}];
};
compactor = {
working_directory = "/tmp/loki-compactor-boltdb";
shared_store = "filesystem";
};
server = { http_listen_port = 3100; };
storage_config = {
boltdb_shipper = {
active_index_directory = "/var/lib/loki/boltdb-shipper-active";
cache_location = "/var/lib/loki/boltdb-shipper-cache";
cache_ttl = "24h";
shared_store = "filesystem";
};
filesystem = { directory = "/var/lib/loki/chunks"; };
};
table_manager = {
retention_deletes_enabled = false;
retention_period = "0s";
};
};
};
}

106
config/services/mail.nix Normal file
View file

@ -0,0 +1,106 @@
{ config, lib, tf, pkgs, sources, ... }:
with lib;
{
imports = [ sources.nixos-mailserver.outPath ];
services.fail2ban.jails = {
postfix = ''
enabled = true
filter = postfix
maxretry = 3
action = nftables-multiport[name=postfix, port=smtp, protocol=tcp]
'';
postfix-sasl = ''
enabled = true
filter = postfix-sasl
port = postfix,imap3,imaps,pop3,pop3s
maxretry = 3
action = nftables-multiport[name=postfix, port=smtp, protocol=tcp]
'';
postfix-ddos = ''
enabled = true
filter = postfix-ddos
maxretry = 3
action = nftables-multiport[name=postfix, port=submission, protocol=tcp]
bantime = 7200
'';
};
environment.etc."fail2ban/filter.d/postfix-sasl.conf" = {
enable = true;
text = ''
# Fail2Ban filter for postfix authentication failures
[INCLUDES]
before = common.conf
[Definition]
daemon = postfix/smtpd
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$
'';
};
environment.etc."fail2ban/filter.d/postfix-ddos.conf" = {
enable = true;
text = ''
[Definition]
failregex = lost connection after EHLO from \S+\[<HOST>\]
'';
};
deploy.tf.variables.domainkey_kitty = {
type = "string";
value.shellCommand = "bitw get infra/domainkey-kitty";
};
deploy.tf.dns.records.kittywitch_mx = {
tld = "kittywit.ch.";
domain = "@";
mx = {
priority = 10;
target = "athame.kittywit.ch.";
};
};
deploy.tf.dns.records.kittywitch_spf = {
tld = "kittywit.ch.";
domain = "@";
txt.value = "v=spf1 ip4:168.119.126.111 ip6:${
(head config.networking.interfaces.enp1s0.ipv6.addresses).address
} -all";
};
deploy.tf.dns.records.kittywitch_dmarc = {
tld = "kittywit.ch.";
domain = "_dmarc";
txt.value = "v=DMARC1; p=none";
};
deploy.tf.dns.records.kittywitch_domainkey = {
tld = "kittywit.ch.";
domain = "mail._domainkey";
txt.value = tf.variables.domainkey_kitty.ref;
};
mailserver = {
enable = true;
fqdn = "athame.kittywit.ch";
domains = [ "kittywit.ch" "dork.dev" ];
# Use Let's Encrypt certificates. Note that this needs to set up a stripped
# down nginx and opens port 80.
certificateScheme = 3;
# Enable IMAP and POP3
enableImap = true;
enablePop3 = true;
enableImapSsl = true;
enablePop3Ssl = true;
# Enable the ManageSieve protocol
enableManageSieve = true;
# whether to scan inbound emails for viruses (note that this requires at least
# 1 Gb RAM for the server. Without virus scanning 256 MB RAM should be plenty)
virusScanning = false;
};
}

148
config/services/matrix.nix Normal file
View file

@ -0,0 +1,148 @@
{ config, pkgs, lib, ... }:
with lib;
{
environment.systemPackages = [ pkgs.mx-puppet-discord pkgs.mautrix-whatsapp ];
services.postgresql.initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
'';
services.matrix-synapse = {
enable = true;
max_upload_size = "512M";
server_name = "kittywit.ch";
app_service_config_files = [
"/var/lib/matrix-synapse/telegram-registration.yaml"
"/var/lib/matrix-synapse/discord-registration.yaml"
"/var/lib/matrix-synapse/whatsapp-registration.yaml"
];
rc_messages_per_second = mkDefault "0.1";
rc_message_burst_count = mkDefault "25.0";
url_preview_enabled = mkDefault true;
enable_registration = mkDefault false;
enable_metrics = mkDefault false;
report_stats = mkDefault false;
dynamic_thumbnails = mkDefault true;
allow_guest_access = mkDefault true;
extraConfig = ''
suppress_key_server_warning: true
'';
listeners = [{
port = 8008;
bind_address = "::1";
type = "http";
tls = false;
x_forwarded = true;
resources = [{
names = [ "client" "federation" ];
compress = false;
}];
}];
};
services.mautrix-telegram = {
enable = true;
settings = {
homeserver = {
address = "http://localhost:8008";
domain = "kittywit.ch";
};
appservice = {
provisioning.enabled = false;
id = "telegram";
public = {
enabled = false;
prefix = "/public";
external = "https://kittywit.ch/public";
};
};
bridge = {
relaybot.authless_portals = false;
permissions = {
"@kat:kittywit.ch" = "admin";
"kittywit.ch" = "full";
};
};
};
};
systemd.services.mx-puppet-discord = {
serviceConfig = {
Type = "simple";
Restart = "always";
ExecStart =
"${pkgs.mx-puppet-discord}/bin/mx-puppet-discord -c /var/lib/mx-puppet-discord/config.yaml -f /var/lib/mx-puppet-discord/discord-registration.yaml";
WorkingDirectory = "/var/lib/mx-puppet-discord";
DynamicUser = true;
StateDirectory = "mx-puppet-discord";
UMask = 27;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
};
requisite = [ "matrix-synapse.service" ];
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
};
systemd.services.mautrix-whatsapp = {
serviceConfig = {
Type = "simple";
Restart = "always";
ExecStart =
"${pkgs.mautrix-whatsapp}/bin/mautrix-whatsapp -c /var/lib/mautrix-whatsapp/config.yaml -r /var/lib/mautrix-whatsapp/registration.yaml";
WorkingDirectory = "/var/lib/mautrix-whatsapp";
DynamicUser = true;
StateDirectory = "mautrix-whatsapp";
UMask = 27;
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
};
requisite = [ "matrix-synapse.service" ];
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
};
services.nginx.virtualHosts."kittywit.ch" = {
# allegedly fixes https://github.com/poljar/weechat-matrix/issues/240
extraConfig = ''
keepalive_requests 100000;
'';
locations = {
"/_matrix" = { proxyPass = "http://[::1]:8008"; };
"= /.well-known/matrix/server".extraConfig =
let server = { "m.server" = "kittywit.ch:443"; };
in
''
add_header Content-Type application/json;
return 200 '${builtins.toJSON server}';
'';
"= /.well-known/matrix/client".extraConfig =
let
client = {
"m.homeserver" = { "base_url" = "https://kittywit.ch"; };
"m.identity_server" = { "base_url" = "https://vector.im"; };
};
in
''
add_header Content-Type application/json;
add_header Access-Control-Allow-Origin *;
return 200 '${builtins.toJSON client}';
'';
};
};
}

View file

@ -0,0 +1,60 @@
{ config, lib, pkgs, ... }:
with lib;
{
kw.fw.public.tcp.ports = singleton 64738;
kw.fw.public.udp.ports = singleton 64738;
services.murmur = {
enable = true;
hostName = "voice.kittywit.ch";
bandwidth = 130000;
welcometext = "mew!";
extraConfig = ''
sslCert=/var/lib/acme/voice.kittywit.ch/fullchain.pem
sslKey=/var/lib/acme/voice.kittywit.ch/key.pem
'';
};
services.nginx.virtualHosts."voice.kittywit.ch" = {
enableACME = true;
forceSSL = true;
};
users.groups."voice-cert".members = [ "nginx" "murmur" ];
security.acme.certs = { "voice.kittywit.ch" = { group = "voice-cert"; }; };
deploy.tf.dns.records.kittywitch_voice = {
tld = "kittywit.ch.";
domain = "voice";
cname.target = "athame.kittywit.ch.";
};
deploy.tf.dns.records.kittywitch_voice_tcp = {
tld = "kittywit.ch.";
domain = "@";
srv = {
service = "mumble";
proto = "tcp";
priority = 0;
weight = 5;
port = 64738;
target = "voice.kittywit.ch.";
};
};
deploy.tf.dns.records.kittywitch_voice_udp = {
tld = "kittywit.ch.";
domain = "@";
srv = {
service = "mumble";
proto = "udp";
priority = 0;
weight = 5;
port = 64738;
target = "voice.kittywit.ch.";
};
};
}

View file

@ -0,0 +1,18 @@
{ config, ... }:
{
kw.fw.private.tcp.ports = [ 19999 ];
services.netdata = { enable = true; };
services.nginx = {
enable = true;
virtualHosts = {
"${config.networking.hostName}.net.kittywit.ch" = {
useACMEHost = "${config.networking.hostName}.net.kittywit.ch";
forceSSL = true;
locations = { "/netdata" = { proxyPass = "http://[::1]:19999/"; }; };
};
};
};
}

43
config/services/nginx.nix Normal file
View file

@ -0,0 +1,43 @@
{ config, lib, pkgs, tf, ... }:
with lib;
{
secrets.files.dns_creds = {
text = ''
RFC2136_NAMESERVER='ns1.as207960.net'
RFC2136_TSIG_ALGORITHM='hmac-sha512.'
RFC2136_TSIG_KEY='${tf.variables.glauca_key.ref}'
RFC2136_TSIG_SECRET='${tf.variables.glauca_secret.ref}'
'';
};
kw.fw.public.tcp.ports = [ 443 80 ];
kw.fw.private.tcp.ports = [ 443 80 ];
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
commonHttpConfig = ''
map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload";
}
add_header Strict-Transport-Security $hsts_header;
#add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
add_header 'Referrer-Policy' 'origin-when-cross-origin';
#add_header X-Frame-Options DENY;
#add_header X-Content-Type-Options nosniff;
#add_header X-XSS-Protection "1; mode=block";
#proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
'';
clientMaxBodySize = "512m";
};
security.acme = {
email = "acme@kittywit.ch";
acceptTerms = true;
};
}

Some files were not shown because too many files have changed in this diff Show more