Compare commits

..

1 commit

Author SHA1 Message Date
Natsu Kagami 7400e2a377
Add local networking functionality 2023-07-09 17:32:39 +02:00
113 changed files with 3080 additions and 7988 deletions

View file

@ -32,76 +32,75 @@ jobs:
git status
false
fi
# deploy:
# if: "github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'Deploy')"
# runs-on: ubuntu-latest
# name: Deploy
# steps:
# - uses: actions/checkout@v2.3.4
# - name: Notify deployment starting
# run: |
# git show --no-patch | curl \
# --fail-with-body \
# -u "${{ secrets.NTFY_CREDS }}" \
# -H "X-Title: Deployment to nki-personal-do started" \
# -H "X-Priority: 1" \
# -H "X-Tags: cloud" \
# -H "Actions: view, Open Job on GitHub, ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
# -d "Commit info:
# " \
# -d @- \
# https://ntfy.nkagami.me/nki-personal-do
# - name: Add SSH key
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
# run: |
# mkdir -p /home/runner/.ssh
# echo "${{ secrets.NIX_SECRETS_SSH_KEY }}" > /home/runner/.ssh/nix_secrets_key
# echo "${{ secrets.NIX_DEPLOY_SSH_KEY }}" > /home/runner/.ssh/nix_deploy_key
# chmod 600 /home/runner/.ssh/*
# ssh-agent -a $SSH_AUTH_SOCK > /dev/null
# ssh-add /home/runner/.ssh/*
# ssh-keyscan ${{ secrets.INSTANCE_IP }} >> /home/runner/.ssh/known_hosts
# ssh-keyscan git.dtth.ch >> /home/runner/.ssh/known_hosts
# - uses: cachix/install-nix-action@v20
# with:
# extra_nix_config: |
# # Enable flakes
# experimental-features = nix-command flakes
# # Deploy tokens
# access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
# # Import my substituters
# extra-substituters = https://natsukagami.cachix.org
# extra-trusted-public-keys = natsukagami.cachix.org-1:3U6GV8i8gWEaXRUuXd2S4ASfYgdl2QFPWg4BKPbmYiQ=
# - name: Deploy with deploy-rs
# env:
# SSH_AUTH_SOCK: /tmp/ssh_agent.sock
# run: |
# nix run .#deploy-rs -- . --hostname ${{ secrets.INSTANCE_IP }} -s -- -L
# - name: Notify deployment succeeding
# run: |
# git show --no-patch | curl \
# --fail-with-body \
# -u "${{ secrets.NTFY_CREDS }}" \
# -H "X-Title: Deployment to nki-personal-do succeeded" \
# -H "X-Tags: tada,cloud" \
# -H "Actions: view, Open Job on GitHub, ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
# -d "Commit info:
# " \
# -d @- \
# https://ntfy.nkagami.me/nki-personal-do
# - name: Notify deployment failing
# if: ${{ failure() }}
# run: |
# git show --no-patch | curl \
# --fail-with-body \
# -u "${{ secrets.NTFY_CREDS }}" \
# -H "X-Title: Deployment to nki-personal-do failed" \
# -H "X-Priority: 4" \
# -H "X-Tags: warning,cloud" \
# -H "Actions: view, Open Job on GitHub, ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
# -d "Commit info:
# " \
# -d @- \
# https://ntfy.nkagami.me/nki-personal-do
deploy:
if: "github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'Deploy')"
runs-on: ubuntu-latest
name: Deploy
steps:
- uses: actions/checkout@v2.3.4
- name: Notify deployment starting
run: |
git show --no-patch | curl \
--fail-with-body \
-u "${{ secrets.NTFY_CREDS }}" \
-H "X-Title: Deployment to nki-personal-do started" \
-H "X-Priority: 1" \
-H "X-Tags: cloud" \
-H "Actions: view, Open Job on GitHub, ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
-d "Commit info:
" \
-d @- \
https://ntfy.nkagami.me/nki-personal-do
- name: Add SSH key
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: |
mkdir -p /home/runner/.ssh
echo "${{ secrets.NIX_SECRETS_SSH_KEY }}" > /home/runner/.ssh/nix_secrets_key
echo "${{ secrets.NIX_DEPLOY_SSH_KEY }}" > /home/runner/.ssh/nix_deploy_key
chmod 600 /home/runner/.ssh/*
ssh-agent -a $SSH_AUTH_SOCK > /dev/null
ssh-add /home/runner/.ssh/*
ssh-keyscan ${{ secrets.INSTANCE_IP }} >> /home/runner/.ssh/known_hosts
- uses: cachix/install-nix-action@v20
with:
extra_nix_config: |
# Enable flakes
experimental-features = nix-command flakes
# Deploy tokens
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
# Import my substituters
extra-substituters = https://natsukagami.cachix.org
extra-trusted-public-keys = natsukagami.cachix.org-1:3U6GV8i8gWEaXRUuXd2S4ASfYgdl2QFPWg4BKPbmYiQ=
- name: Deploy with deploy-rs
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: |
nix run .#deploy-rs -- . --hostname ${{ secrets.INSTANCE_IP }} -s -- -L
- name: Notify deployment succeeding
run: |
git show --no-patch | curl \
--fail-with-body \
-u "${{ secrets.NTFY_CREDS }}" \
-H "X-Title: Deployment to nki-personal-do succeeded" \
-H "X-Tags: tada,cloud" \
-H "Actions: view, Open Job on GitHub, ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
-d "Commit info:
" \
-d @- \
https://ntfy.nkagami.me/nki-personal-do
- name: Notify deployment failing
if: ${{ failure() }}
run: |
git show --no-patch | curl \
--fail-with-body \
-u "${{ secrets.NTFY_CREDS }}" \
-H "X-Title: Deployment to nki-personal-do failed" \
-H "X-Priority: 4" \
-H "X-Tags: warning,cloud" \
-H "Actions: view, Open Job on GitHub, ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
-d "Commit info:
" \
-d @- \
https://ntfy.nkagami.me/nki-personal-do

View file

@ -4,8 +4,6 @@ keys:
- &nki_pc age1tt0peqg8zdfh74m5sdgwsczcqh036nhgmwvkqnvywll88uvmm9xs433rhm
- &nkagami_main age1n8tnmmgredzltzwkspag7aufhrn6034ny8ysjeulhkwdnf7vqqaqec4mg5
- &nkagami_do age1z2h24mjt80fryqupajkh3kg5r4sjgw65uqy489xeqxhqj8u2a9fsm3ff36
- &nki_yoga age1vhjhmxura35apu5zdwg5ur5r40xay45ld9szh07dy0ph9chgsu7shfm4h9
- &nki_framework age188tgu3psvywk6shq85mk2q0jdjwd0tcswzwlwu5pa5n3pndx75dq090z59
creation_rules:
- path_regex: kagami-air-m1/secrets\.yaml$
key_groups:
@ -18,20 +16,9 @@ creation_rules:
- *nki_pc
- *nkagami_main
- *nkagami_do
- *nki_framework
- path_regex: nki-home/secrets\.yaml$
- path_regex: nki-home/secrets/secrets\.yaml$
key_groups:
- age:
- *nki_pc
- *nkagami_main
- *nkagami_do
- path_regex: nki-yoga-g8/secrets\.yaml$
key_groups:
- age:
- *nki_yoga
- age1axvjllyv2gutngwmp3pvp4xtq2gqneldaq2c4nrzmaye0uwmk9lqsealdv # The machine itself
- path_regex: nki-framework/secrets\.yaml$
key_groups:
- age:
- *nki_framework
- age1vgh6kvee8lvxylm7z86fpl3xzjyjs4u3zdfkyf064rjvxk9fpumsew7n27 # The machine itself

View file

@ -1,13 +0,0 @@
# WARN: this file will get overwritten by $ cachix use <name>
{ pkgs, lib, ... }:
let
folder = ./cachix;
toImport = name: value: folder + ("/" + name);
filterCaches = key: value: value == "regular" && lib.hasSuffix ".nix" key;
imports = lib.mapAttrsToList toImport (lib.filterAttrs filterCaches (builtins.readDir folder));
in {
inherit imports;
nix.settings.substituters = ["https://cache.nixos.org/"];
}

View file

@ -1,13 +0,0 @@
{
nix = {
settings = {
substituters = [
"https://natsukagami.cachix.org"
];
trusted-public-keys = [
"natsukagami.cachix.org-1:3U6GV8i8gWEaXRUuXd2S4ASfYgdl2QFPWg4BKPbmYiQ="
];
};
};
}

View file

@ -12,9 +12,7 @@ in
{ lib, pkgs, config, ... }:
with lib; {
imports = [
# defaultShell
./modules/services/nix-cache
./modules/services/nix-build-farm
defaultShell
];
## Packages

1458
flake.lock

File diff suppressed because it is too large Load diff

204
flake.nix
View file

@ -2,60 +2,40 @@
description = "nki's systems";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
nixpkgs.url = "github:nixos/nixpkgs/nixos-23.05";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable";
nixos-hardware.url = "github:nixos/nixos-hardware";
darwin.url = "github:lnl7/nix-darwin/master";
darwin.inputs.nixpkgs.follows = "nixpkgs-unstable";
home-manager.url = "github:nix-community/home-manager/release-24.11";
home-manager.url = "github:nix-community/home-manager/release-23.05";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
home-manager-unstable.url = "github:nix-community/home-manager";
home-manager-unstable.inputs.nixpkgs.follows = "nixpkgs-unstable";
sops-nix.url = "github:Mic92/sops-nix";
sops-nix.inputs.nixpkgs.follows = "nixpkgs-unstable";
sops-nix.inputs.nixpkgs-stable.follows = "nixpkgs";
deploy-rs.url = "github:Serokell/deploy-rs";
# --- Secure boot
lanzaboote = {
url = github:nix-community/lanzaboote/v0.4.1;
inputs.nixpkgs.follows = "nixpkgs";
};
nur.url = "github:nix-community/NUR";
# --- Build tools
flake-utils.url = github:numtide/flake-utils;
crane.url = github:ipetkov/crane;
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
arion.url = github:hercules-ci/arion;
lix-module = {
url = "https://git.lix.systems/lix-project/nixos-module/archive/2.91.1-1.tar.gz";
inputs.nixpkgs.follows = "nixpkgs";
};
# ---
# Imported apps
youmubot.url = "github:natsukagami/youmubot/osu-commands";
# swayfx = {
# url = github:WillPower3309/swayfx;
# inputs.nixpkgs.follows = "nixpkgs";
# };
mpd-mpris = {
url = github:natsukagami/mpd-mpris;
rnix-lsp.url = "github:nix-community/rnix-lsp";
youmubot.url = "github:natsukagami/youmubot";
swayfx = {
url = github:WillPower3309/swayfx;
inputs.nixpkgs.follows = "nixpkgs";
};
mpd-mpris = {
url = github:natsukagami/mpd-mpris/nix;
inputs.nixpkgs.follows = "nixpkgs";
};
dtth-phanpy.url = "git+ssh://gitea@git.dtth.ch/nki-dtth/phanpy?ref=dtth-fork";
conduit.url = "gitlab:famedly/conduit/v0.9.0";
nix-gaming.url = github:fufexan/nix-gaming;
zen-browser.url = "github:youwen5/zen-browser-flake";
niri.url = "github:sodiboo/niri-flake";
# --- Sources
kakoune.url = github:mawww/kakoune;
kakoune.flake = false;
kak-lsp.url = github:kakoune-lsp/kakoune-lsp;
kak-lsp.url = github:kak-lsp/kak-lsp;
kak-lsp.flake = false;
nixos-m1.url = github:tpwrules/nixos-apple-silicon;
nixos-m1.inputs.nixpkgs.follows = "nixpkgs";
@ -65,7 +45,7 @@
secrets.url = "git+ssh://git@github.com/natsukagami/nix-deploy-secrets";
};
outputs = { self, darwin, nixpkgs, nixpkgs-unstable, home-manager, deploy-rs, sops-nix, ... }@inputs:
outputs = { self, darwin, nixpkgs, nixpkgs-unstable, home-manager, deploy-rs, sops-nix, nur, ... }@inputs:
let
overlays = import ./overlay.nix inputs;
lib = nixpkgs.lib;
@ -77,34 +57,23 @@
nixpkgsAsRegistry_ = stable: { lib, ... }: {
imports = [ applyOverlays ];
nix.registry.current-system.flake = self;
nix.registry.nixpkgs.flake = stable;
nix.registry.nixpkgs-unstable.flake = nixpkgs-unstable;
nixpkgs.config.allowUnfree = true;
nix.nixPath = lib.mkDefault [
nix.nixPath = [
"nixpkgs=${nixpkgs}"
"nixpkgs-unstable=${nixpkgs-unstable}"
"/nix/var/nix/profiles/per-user/root/channels"
];
# Binary Cache for Haskell.nix
nix.settings.trusted-public-keys = [
"hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ="
];
nix.settings.substituters = [
"https://cache.iog.io"
];
};
osuStable = { pkgs, ... }: {
nix.settings = {
substituters = [ "https://nix-gaming.cachix.org" ];
trusted-public-keys = [ "nix-gaming.cachix.org-1:nbjlureqMbRAxR1gJ/f3hxemL9svXaZF/Ees8vCUUs4=" ];
};
environment.systemPackages = [ inputs.nix-gaming.packages.${pkgs.hostPlatform.system}.osu-stable ];
programs.gamemode = {
enable = true;
enableRenice = true;
settings = {
general = {
renice = 10;
};
custom = {
start = "${pkgs.libnotify}/bin/notify-send 'GameMode started'";
end = "${pkgs.libnotify}/bin/notify-send 'GameMode ended'";
};
};
};
};
nixpkgsAsRegistry = nixpkgsAsRegistry_ nixpkgs;
# Common Nix modules
common-nix = stable: { ... }: {
@ -112,7 +81,6 @@
(nixpkgsAsRegistry_ stable)
./common.nix
sops-nix.nixosModules.sops
inputs.lix-module.nixosModules.default
];
};
common-nixos = stable: { ... }: {
@ -121,40 +89,9 @@
./modules/common/linux
(common-nix stable)
inputs.secrets.nixosModules.common
inputs.nix-gaming.nixosModules.pipewireLowLatency
inputs.niri.nixosModules.niri
];
};
mkPersonalSystem = nixpkgs-module: system: { configuration
, homeManagerUsers ? { }
, extraModules ? [ ]
, includeCommonModules ? true
,
}:
let
home-manager-module =
if nixpkgs-module == inputs.nixpkgs then inputs.home-manager
else if nixpkgs-module == inputs.nixpkgs-unstable then inputs.home-manager-unstable
else builtins.abort "Unknown nixpkgs module, use `nixpkgs` or `nixpkgs-unstable`";
in
nixpkgs-module.lib.nixosSystem {
inherit system;
modules =
(if includeCommonModules then [
(common-nixos nixpkgs-module)
] else [ ]) ++ [
configuration
# Home Manager
home-manager-module.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users = homeManagerUsers;
}
] ++ extraModules;
};
in
{
overlays.default = lib.composeManyExtensions overlays;
@ -178,62 +115,67 @@
};
# Home configuration
nixosConfigurations."kagamiPC" = mkPersonalSystem nixpkgs-unstable "x86_64-linux" {
configuration = ./nki-home/configuration.nix;
homeManagerUsers.nki = import ./home/kagami-pc-home.nix;
extraModules = [ osuStable ];
};
# yoga g8 configuration
nixosConfigurations."nki-yoga-g8" = mkPersonalSystem nixpkgs "x86_64-linux" {
configuration = ./nki-yoga-g8/configuration.nix;
homeManagerUsers.nki = import ./home/nki-x1c1.nix;
extraModules = [
inputs.lanzaboote.nixosModules.lanzaboote
({ ... }: {
# Sets up secure boot
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/etc/secureboot";
};
})
nixosConfigurations."nki-home" = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = [
(common-nixos nixpkgs)
./nki-home/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.nki = { ... }: {
imports = [
./home/kagami-pc-home.nix
];
};
# framework configuration
nixosConfigurations."nki-framework" = mkPersonalSystem nixpkgs-unstable "x86_64-linux" {
configuration = ./nki-framework/configuration.nix;
homeManagerUsers.nki = import ./home/nki-framework.nix;
extraModules = [
inputs.lanzaboote.nixosModules.lanzaboote
inputs.nixos-hardware.nixosModules.framework-13-7040-amd
({ ... }: {
# Sets up secure boot
boot.loader.systemd-boot.enable = lib.mkForce false;
boot.lanzaboote = {
enable = true;
pkiBundle = "/etc/secureboot";
};
})
}
];
};
# x1c1 configuration
# nixosConfigurations."nki-x1c1" = nixpkgs.lib.nixosSystem rec {
# system = "x86_64-linux";
# modules = [
# (common-nixos nixpkgs)
# ./nki-x1c1/configuration.nix
# home-manager.nixosModules.home-manager
# {
# home-manager.useGlobalPkgs = true;
# home-manager.useUserPackages = true;
# home-manager.users.nki = import ./home/nki-x1c1.nix;
# }
# ];
# };
# macbook nixos
nixosConfigurations."kagami-air-m1" = mkPersonalSystem nixpkgs "aarch64-linux" {
configuration = ./kagami-air-m1/configuration.nix;
homeManagerUsers.nki = import ./home/macbook-nixos.nix;
extraModules = [ inputs.nixos-m1.nixosModules.apple-silicon-support ];
nixosConfigurations."kagami-air-m1" = inputs.nixpkgs.lib.nixosSystem rec {
system = "aarch64-linux";
modules = [
(common-nixos inputs.nixpkgs)
inputs.nixos-m1.nixosModules.apple-silicon-support
./kagami-air-m1/configuration.nix
inputs.home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.nki = import ./home/macbook-nixos.nix;
}
];
};
# DigitalOcean node
nixosConfigurations."nki-personal-do" = mkPersonalSystem nixpkgs "x86_64-linux" {
configuration = ./nki-personal-do/configuration.nix;
extraModules = [
nixosConfigurations."nki-personal-do" = nixpkgs.lib.nixosSystem rec {
system = "x86_64-linux";
modules = [
(common-nixos nixpkgs)
inputs.arion.nixosModules.arion
./modules/my-tinc
inputs.youmubot.nixosModules.default
./nki-personal-do/configuration.nix
inputs.secrets.nixosModules.nki-personal-do
];
};
deploy.nodes."nki-personal-do" = {
hostname = "nki.personal";
hostname = "nki-personal-do";
sshUser = "root";
profiles.system = {
user = "root";
@ -243,8 +185,8 @@
# This is highly advised, and will prevent many possible mistakes
checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib;
} // (inputs.flake-utils.lib.eachDefaultSystem (system: {
formatter = nixpkgs.legacyPackages.${system}.nixpkgs-fmt;
}));
}

View file

@ -1,57 +1,50 @@
{ pkgs, config, lib, ... }:
with lib;
let
kwallet = { pkgs, lib, ... }: {
home.packages = with pkgs; [ kdePackages.kwallet kdePackages.ksshaskpass ];
home.sessionVariables = {
# https://wiki.archlinux.org/title/KDE_Wallet#Using_the_KDE_Wallet_to_store_ssh_key_passphrases
SSH_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
SSH_ASKPASS_REQUIRE = "prefer";
};
# Enable this for sway
wayland.windowManager.sway.config.startup = [
{ command = "${pkgs.kdePackages.kwallet-pam}/libexec/pam_kwallet_init"; }
];
# Automatic dbus activation
xdg.dataFile."dbus-1/services/org.freedesktop.secrets.service".text = ''
[D-BUS Service]
Name=org.freedesktop.secrets
Exec=${pkgs.kdePackages.kwallet}/bin/kwalletd6
'';
};
in
{
with lib; {
imports = [
./modules/monitors.nix
./modules/linux/graphical
./modules/X11/xfce4-notifyd.nix
./modules/programs/discord.nix
kwallet
];
config = (mkIf pkgs.stdenv.isLinux {
home.packages = with pkgs; [
psmisc # killall and friends
file # Query file type
pinentry-gnome3 # until pinentry-qt introduces caching
pinentry-gnome
] ++ (
if pkgs.stdenv.isx86_64
then [
vivaldi
mpv # for anki
pkgs.unstable.anki-bin
tdesktop
whatsapp-for-linux
]
else [ ]
);
## Gnome-keyring
services.gnome-keyring = {
enable = true;
components = [ "pkcs11" "secrets" "ssh" ];
};
# services.gpg-agent.enable = true;
# services.gpg-agent.pinentryFlavor = "curses";
# services.gpg-agent.enableSshSupport = true;
# Git "safe-directory"
programs.git.extraConfig.safe.directory = [
"${config.home.homeDirectory}/.config/nixpkgs"
];
systemd.user.startServices = "sd-switch";
# Audio stuff!
# services.easyeffects.enable = true;
# Bluetooth controls
# services.mpris-proxy.enable = true;
services.mpris-proxy.enable = true;
# Owncloud
services.owncloud-client.enable = true;
services.owncloud-client.package = pkgs.owncloud-client.overrideAttrs (attrs: {
buildInputs = attrs.buildInputs ++ [ pkgs.qt6.qtwayland ];
});
# UDisks automounter
services.udiskie.enable = true;
});
}

View file

@ -1,38 +1,70 @@
{ config, pkgs, ... }:
{ config, pkgs, lib, ... }:
let
texlab = pkgs.unstable.texlab;
in
{
imports = [
./kakoune/kak.nix
./fish/fish.nix
./modules/programs/my-broot.nix
./modules/programs/my-waybar.nix
./modules/programs/my-sway
./modules/programs/my-niri.nix
./modules/programs/my-kitty
./modules/programs/openconnect-epfl.nix
./common-linux.nix
# PATH Overrides
({ config, lib, ... }: {
home.sessionPath = lib.mkBefore [
"${config.home.homeDirectory}/.bin/overrides"
];
})
];
# Let Home Manager install and manage itself.
programs.home-manager.enable = true;
# Temporarily disable the manuals
manual.html.enable = false;
# manual.manpage.enable = false;
# Enable the manual so we don't have to load it
manual.html.enable = true;
# Packages that are not in programs section
home.packages = with pkgs; [
# Build Tools
## C++
autoconf
automake
## SQL
flyway
## Go
go # to be configured later
## Rust
rust-analyzer
## JavaScript
yarn
## Nix
cachix
rnix-lsp
## Latex
tectonic
texlab
## Typst
typst
## Python
python3
# Fonts
fantasque-sans-mono
## Get the nerd font symbols
(nerdfonts.override { fonts = [ "NerdFontsSymbolsOnly" ]; })
# CLI tools
fd
sd
ripgrep
fossil
openssh
tea # gitea CLI (gh-like)
glab # gitlab CLI
fx # JSON viewer
glow # Markdown viewer
nix-output-monitor # Nice nix output formatting
unstable.scala-next
## File Manager
nnn
## PDF Processors
poppler_utils
## htop replacement
@ -44,6 +76,13 @@
unzip
zstd
atool
## To do tunneling with cloudflare
pkgs.cloudflared
# Databases
postgresql
mariadb
];
home.sessionVariables = {
@ -73,9 +112,9 @@
direnv.nix-direnv.enable = true;
direnv.config.global.load_dotenv = true;
eza = {
exa = {
enable = true;
enableFishIntegration = true;
enableAliases = true;
};
fzf = {
@ -107,8 +146,6 @@
.envrc
.kakrc
''}";
safe.directory = "*";
merge.conflictstyle = "zdiff3";
};
};

View file

@ -1,3 +1,8 @@
{
allowUnfree = true;
packageOverrides = pkgs: {
nur = import (builtins.fetchTarball "https://github.com/nix-community/NUR/archive/master.tar.gz") {
inherit pkgs;
};
};
}

View file

@ -1,42 +1,9 @@
{ config, options, pkgs, lib, ... }:
{ config, pkgs, lib, ... }:
with lib;
let
bootDesktop = pkgs.writeScript "boot-desktop" ''
#!/usr/bin/env fish
set -a PATH ${pkgs.gum}/bin
set -x GUM_CHOOSE_HEADER "Select the Desktop to boot into:"
set CHOICES
if which sway &>/dev/null
set -a CHOICES "sway"
end
if which niri-session &>/dev/null
set -a CHOICES "Niri"
end
if which startplasma-wayland &>/dev/null
set -a CHOICES "KDE Plasma"
end
set -a CHOICES "None: continue to shell"
switch (gum choose $CHOICES)
case "sway"
systemctl --user unset-environment NIXOS_OZONE_WL
exec sway
case "Niri"
exec niri-session
case "KDE Plasma"
exec ${pkgs.kdePackages.plasma-workspace}/libexec/plasma-dbus-run-session-if-needed startplasma-wayland
case '*'
exec fish -i
end
'';
in
{
imports = [
./tide
./tide/nix-shell.nix
];
options.programs.fish.everywhereAbbrs = mkOption {
@ -56,28 +23,22 @@ in
config.programs.fish = {
enable = true;
functions = {
rebuild = {
body = ''
pls nixos-rebuild --flake ~/.config/nixpkgs -L --log-format internal-json -v $argv \
&| ${pkgs.nix-output-monitor}/bin/nom --json
'';
wraps = "nixos-rebuild";
};
# Simplify nix usage!
nx = {
body = ''
argparse -s 'h/help' 'impure' 'u/unstable' 'g/git' -- $argv
if set -q _flag_help || test (count $argv) -eq 0
echo "nx [--impure] [-u/--unstable/-g/--git] {package} [args...]"
return 1
set impure
if test $argv[1] = "--impure"
set impure "--impure"
set argv $argv[2..]
end
if test (count $argv) -gt 0
nix run $impure nixpkgs#$argv[1] -- $argv[2..]
else
set -q _flag_impure && set impure "--impure"
set nixpkgs "nixpkgs"
set -q _flag_unstable && set nixpkgs "nixpkgs-unstable"
set -q _flag_git && set nixpkgs "github:nixOS/nixpkgs/nixpkgs-unstable"
nix run $impure $nixpkgs"#"$argv[1] -- $argv[2..]
echo "nx [--impure] {package} [args...]"
return 1
end
'';
wraps = "nix run";
description = "Runs an app from the nixpkgs store.";
};
@ -85,35 +46,25 @@ in
description = "Spawns a shell from the given nixpkgs packages";
wraps = "nix shell";
body = ''
function help
echo "nsh [--impure] [--impure] [-u/--unstable/-g/--git] {package}* [-c command args...]"
set impure
if test $argv[1] = "--impure"
set impure "--impure"
set argv $argv[2..]
end
argparse -s 'h/help' 'impure' 'u/unstable' 'g/git' -- $argv
if set -q _flag_help || test (count $argv) -eq 0
help
return 0
end
set packages $argv
if test (count $argv) -gt 0
set minusc (contains -i -- "-c" $argv)
if test -n "$minusc"
if test $minusc -eq 1
help
if test -z $minusc
nix shell $impure nixpkgs#$argv -c fish
else if test $minusc -eq (count $argv)
echo "nsh [--impure] {packages} [-c command args...]"
return 1
end
set packages $argv[..(math $minusc - 1)]
set argv $argv[(math $minusc + 1)..]
else
set argv "fish" "-i"
nix shell $impure nixpkgs#$argv[..(math $minusc - 1)] $argv[$minusc..]
end
if test (count $packages) -eq 0
help
else
echo "nsh [--impure] {packages} [-c command args...]"
return 1
end
set -q _flag_impure && set impure "--impure"
set nixpkgs "nixpkgs"
set -q _flag_unstable && set nixpkgs "nixpkgs-unstable"
set -q _flag_git && set nixpkgs "github:nixOS/nixpkgs/nixpkgs-unstable"
nix shell $impure $nixpkgs"#"$packages --command $argv
'';
};
# Grep stuff
@ -130,45 +81,16 @@ in
description = "Search with ripgrep and put results into the editor";
};
echo-today = "date +%F";
newfile = "mkdir -p (dirname $argv[-1]) && touch $argv";
# pls
pls = {
wraps = "sudo";
body = ''
set -l cmd "`"(string join " " -- $argv)"`"
echo "I-It's not like I'm gonna run "$cmd" for you or a-anything! Baka >:C" >&2
# Send a notification on password prompt
if command sudo -vn 2>/dev/null
# nothing to do, user already authenticated
else
# throw a notification
set notif_id (kitten notify -P \
-p ${./haruka.png} \
-a "pls" \
-u critical \
"A-a command requires your p-password" \
(printf "I-I need your p-password to r-run the following c-command:\n\n%s" $cmd))
command sudo -v -p "P-password please: "
kitten notify -i $notif_id ""
end
command sudo $argv
'';
};
};
tide = {
enable = true;
leftItems = options.programs.fish.tide.leftItems.default;
rightItems = options.programs.fish.tide.rightItems.default;
nix-shell.enable = true;
};
shellAliases = {
cat = "bat --theme=GitHub ";
catp = "bat --theme=GitHub -p ";
l = "exa -l --color=always ";
"cp+" = "rsync -avzP";
};
everywhereAbbrs = {
@ -183,17 +105,9 @@ in
if test -e /opt/homebrew/bin/brew
/opt/homebrew/bin/brew shellenv | source
end
# Override PATH
set --export --prepend PATH ~/.bin/overrides ~/.local/bin
'';
interactiveShellInit = ''
# Sway!
if status --is-login; and test -z $DISPLAY; and test (tty) = "/dev/tty1"
exec ${bootDesktop}
end
function fish_greeting
${pkgs.timg}/bin/timg ${./arona.jpg}
printf (env LANG=ja_JP.UTF-8 date +"\n%A%Y%m%d%H%M \n\n")
@ -260,8 +174,23 @@ in
set -q PERL_LOCAL_LIB_ROOT; or set -x PERL_LOCAL_LIB_ROOT ${config.home.homeDirectory}/perl5;
set -x PERL_MB_OPT --install_base\ \"${config.home.homeDirectory}/perl5\";
set -x PERL_MM_OPT INSTALL_BASE=${config.home.homeDirectory}/perl5;
# Sway!
if status --is-login; and which sway >/dev/null; and test -z $DISPLAY; and test (tty) = "/dev/tty1"
read -P "Press enter to start sway..."; and exec sway
end
'';
plugins = [
{
name = "tide";
src = pkgs.fetchFromGitHub {
owner = "IlanCosman";
repo = "tide";
rev = "447945d2cff8f70d5c791dd4eec8b322d37798dd";
# sha256 = lib.fakeSha256;
sha256 = "sha256-1c2E3UC3r9hPfijAQoZ/+4yXieFxC4+hkk7wUyr30NM=";
};
}
{
name = "fzf";
src = pkgs.fetchFromGitHub {
@ -290,8 +219,8 @@ in
target = ".config/fish/conf.d/change_cmd.fish";
};
"fish/pls.fish" = {
source = ./pls_extra.fish;
target = ".config/fish/conf.d/pls_extra.fish";
source = ./. + "/pls.fish";
target = ".config/fish/conf.d/pls.fish";
};
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

155
home/fish/pls.fish Normal file
View file

@ -0,0 +1,155 @@
alias sue="pls -e"
function pls
set -l cmd "`"(string join " " -- $argv)"`"
echo "I-It's not like I'm gonna run "$cmd" for you or a-anything! Baka >:C" >&2
# Send a notification on password prompt
if command sudo -vn 2>/dev/null
# nothing to do, user already authenticated
else
# throw a notification
# notify-send -t 3000 -u critical -i ~/Downloads/harukablush.jpg -h "STRING:command:"$cmd "A-a command requires your p-password" (printf "I-I need your p-password to r-run the following c-command: %s" $cmd)
end
command sudo $argv
end
function sudo
echo "Not polite enough."
end
function __fish_prepend_pls -d "Prepend 'pls ' to the beginning of the current commandline"
# If there is no commandline, insert the last item from history
# and *then* toggle
if not commandline | string length -q
commandline -r "$history[1]"
end
set -l cmd (commandline -po)
set -l cursor (commandline -C)
if test "$cmd[1]" = e
commandline -C 0
commandline -i "su"
commandline -C (math $cursor + 2)
else if test "$cmd[1]" = sue
commandline -r (string sub --start=3 (commandline -p))
commandline -C -- (math $cursor - 2)
else if test "$cmd[1]" != pls
commandline -C 0
commandline -i "pls "
commandline -C (math $cursor + 4)
else
commandline -r (string sub --start=5 (commandline -p))
commandline -C -- (math $cursor - 4)
end
end
bind --preset -e -M insert \es
bind -M insert \es __fish_prepend_pls
function __fish_man_page
# Get all commandline tokens not starting with "-"
set -l args (commandline -po | string match -rv '^-')
# If commandline is empty, exit.
if not set -q args[1]
printf \a
return
end
#Skip `pls` and display then manpage of following command
while set -q args[2]
and string match -qr -- '^(pls|.*=.*)$' $args[1]
set -e args[1]
end
# If there are at least two tokens not starting with "-", the second one might be a subcommand.
# Try "man first-second" and fall back to "man first" if that doesn't work out.
set -l maincmd (basename $args[1])
if set -q args[2]
# HACK: If stderr is not attached to a terminal `less` (the default pager)
# wouldn't use the alternate screen.
# But since we don't know what pager it is, and because `man` is totally underspecified,
# the best we can do is to *try* the man page, and assume that `man` will return false if it fails.
# See #7863.
if man "$maincmd-$args[2]" &>/dev/null
man "$maincmd-$args[2]"
else if man "$maincmd" &>/dev/null
man "$maincmd"
else
printf \a
end
else
if man "$maincmd" &>/dev/null
man "$maincmd"
else
printf \a
end
end
commandline -f repaint
end
#
# Completion for pls
#
function __fish_pls_print_remaining_args
set -l tokens (commandline -opc) (commandline -ct)
set -e tokens[1]
# These are all the options mentioned in the man page for Todd Miller's "pls.ws" pls (in that order).
# If any other implementation has different options, this should be harmless, since they shouldn't be used anyway.
set -l opts A/askpass b/background C/close-from= E/preserve-env='?'
# Note that "-h" is both "--host" (which takes an option) and "--help" (which doesn't).
# But `-h` as `--help` only counts when it's the only argument (`pls -h`),
# so any argument completion after that should take it as "--host".
set -a opts e/edit g/group= H/set-home h/host= 1-help
set -a opts i/login K/remove-timestamp k/reset-timestamp l/list n/non-interactive
set -a opts P/preserve-groups p/prompt= S/stdin s/shell U/other-user=
set -a opts u/user= T/command-timeout= V/version v/validate
argparse -s $opts -- $tokens 2>/dev/null
# The remaining argv is the subcommand with all its options, which is what
# we want.
if test -n "$argv"
and not string match -qr '^-' $argv[1]
string join0 -- $argv
return 0
else
return 1
end
end
function __fish_pls_no_subcommand
not __fish_pls_print_remaining_args >/dev/null
end
function __fish_complete_pls_subcommand
set -l args (__fish_pls_print_remaining_args | string split0)
set -lx -a PATH /usr/local/sbin /sbin /usr/sbin
__fish_complete_subcommand --commandline $args
end
# All these options should be valid for GNU and OSX pls
complete -c pls -n __fish_no_arguments -s h -d "Display help and exit"
complete -c pls -n __fish_no_arguments -s V -d "Display version information and exit"
complete -c pls -n __fish_pls_no_subcommand -s A -d "Ask for password via the askpass or \$SSH_ASKPASS program"
complete -c pls -n __fish_pls_no_subcommand -s C -d "Close all file descriptors greater or equal to the given number" -xa "0 1 2 255"
complete -c pls -n __fish_pls_no_subcommand -s E -d "Preserve environment"
complete -c pls -n __fish_pls_no_subcommand -s H -d "Set home"
complete -c pls -n __fish_pls_no_subcommand -s K -d "Remove the credential timestamp entirely"
complete -c pls -n __fish_pls_no_subcommand -s P -d "Preserve group vector"
complete -c pls -n __fish_pls_no_subcommand -s S -d "Read password from stdin"
complete -c pls -n __fish_pls_no_subcommand -s b -d "Run command in the background"
complete -c pls -n __fish_pls_no_subcommand -s e -rF -d Edit
complete -c pls -n __fish_pls_no_subcommand -s g -a "(__fish_complete_groups)" -x -d "Run command as group"
complete -c pls -n __fish_pls_no_subcommand -s i -d "Run a login shell"
complete -c pls -n __fish_pls_no_subcommand -s k -d "Reset or ignore the credential timestamp"
complete -c pls -n __fish_pls_no_subcommand -s l -d "List the allowed and forbidden commands for the given user"
complete -c pls -n __fish_pls_no_subcommand -s n -d "Do not prompt for a password - if one is needed, fail"
complete -c pls -n __fish_pls_no_subcommand -s p -d "Specify a custom password prompt"
complete -c pls -n __fish_pls_no_subcommand -s s -d "Run the given command in a shell"
complete -c pls -n __fish_pls_no_subcommand -s u -a "(__fish_complete_users)" -x -d "Run command as user"
complete -c pls -n __fish_pls_no_subcommand -s v -n __fish_no_arguments -d "Validate the credentials, extending timeout"
# Complete the command we are executed under pls
complete -c pls -x -n 'not __fish_seen_argument -s e' -a "(__fish_complete_pls_subcommand)"

View file

@ -1,47 +0,0 @@
alias sue="pls -e"
function sudo
echo "Not polite enough."
end
bind --preset -M visual \es 'fish_commandline_prepend pls'
bind -M insert \es 'fish_commandline_prepend pls'
function __fish_man_page
# Get all commandline tokens not starting with "-", up to and including the cursor's
set -l args (string match -rv '^-|^$' -- (commandline -cpx && commandline -t))
# If commandline is empty, exit.
if not set -q args[1]
printf \a
return
end
# Skip leading commands and display the manpage of following command
while set -q args[2]
and string match -qr -- '^(and|begin|builtin|caffeinate|command|doas|entr|env|exec|if|mosh|nice|not|or|pipenv|prime-run|setsid|sudo|pls|systemd-nspawn|time|watch|while|xargs|.*=.*)$' $args[1]
set -e args[1]
end
# If there are at least two tokens not starting with "-", the second one might be a subcommand.
# Try "man first-second" and fall back to "man first" if that doesn't work out.
set -l maincmd (path basename $args[1])
# HACK: If stderr is not attached to a terminal `less` (the default pager)
# wouldn't use the alternate screen.
# But since we don't know what pager it is, and because `man` is totally underspecified,
# the best we can do is to *try* the man page, and assume that `man` will return false if it fails.
# See #7863.
if set -q args[2]
and not string match -q -- '*/*' $args[2]
and man "$maincmd-$args[2]" &>/dev/null
man "$maincmd-$args[2]"
else
if man "$maincmd" &>/dev/null
man "$maincmd"
else
printf \a
end
end
commandline -f repaint
end

View file

@ -1,87 +0,0 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.programs.fish.tide;
in
{
options.programs.fish.tide = {
enable = mkEnableOption "Enable tide integrations for fish";
items = mkOption {
type = types.attrsOf types.str;
description = "Additional item definitions to create";
default = { };
};
rightItems = mkOption {
type = types.listOf types.str;
description = "The list of right-items, note that `time` is not included here and will always appear last";
default = [
"status"
"cmd_duration"
"jobs"
"direnv"
"node"
"python"
"rustc"
"java"
"php"
"pulumi"
"ruby"
"go"
"gcloud"
"kubectl"
"distrobox"
"toolbox"
"terraform"
"aws"
"crystal"
"elixir"
"nix_shell"
];
};
leftItems = mkOption {
type = types.listOf types.str;
description = "The list of left-items. Note that `newline` and `character` is not included here and will always appear last";
default = [ "os" "context" "pwd" "git" ];
};
};
config.programs.fish =
let
tideItems = attrsets.mapAttrs' (name: def: { name = "_tide_item_${name}"; value = def; });
in
mkIf cfg.enable {
functions = tideItems ({
nix_shell = ''
# In a Nix Shell
if set -qx DIRENV_FILE && test -f $DIRENV_FILE && rg -q "^use flake" $DIRENV_FILE
set -U tide_nix_shell_color "FFA500"
set -U tide_nix_shell_bg_color normal
_tide_print_item nix_shell ""
end
'';
} // cfg.items);
plugins = [
{
name = "tide";
src = pkgs.fetchFromGitHub {
owner = "IlanCosman";
repo = "tide";
rev = "v6.0.1";
# sha256 = lib.fakeSha256;
sha256 = "sha256-oLD7gYFCIeIzBeAW1j62z5FnzWAp3xSfxxe7kBtTLgA=";
};
}
];
};
config.xdg.configFile."fish/tide/init.fish" = {
text = ''
# Configure tide items
set -U tide_left_prompt_items ${concatMapStringsSep " " escapeShellArg cfg.leftItems} newline character
set -U tide_right_prompt_items ${concatMapStringsSep " " escapeShellArg cfg.rightItems} time
'';
onChange = "fish ~/.config/fish/tide/init.fish";
};
}

View file

@ -0,0 +1,22 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.programs.fish.tide.nix-shell;
in
{
options.programs.fish.tide.nix-shell = {
enable = mkEnableOption "An indicator of having a `nix shell` environment";
};
config.programs.fish = mkIf cfg.enable {
functions._tide_item_nix_shell = ''
# In a Nix Shell
if string match -q "/nix/store/*" $PATH
set -U tide_nix_shell_color (set -q DIRENV_DIR && echo "FFA500" || echo "blue")
set -U tide_nix_shell_bg_color normal
_tide_print_item nix_shell ""
end
'';
};
}

View file

@ -21,12 +21,9 @@
texlive.combined.scheme-full
inkscape # for TeX svg
# Gaming stuff
wineWowPackages.full
# wine-lol
winetricks
lutris
steam
# Java & sbt
openjdk11
sbt
# Manage tlmc
flacon
@ -37,24 +34,46 @@
# Enable X11 configuration
linux.graphical.type = "wayland";
linux.graphical.wallpaper = ./images/pixiv_18776904.png;
linux.graphical.defaults.webBrowser.package = pkgs.zen-browser-bin;
linux.graphical.defaults.webBrowser.desktopFile = "zen.desktop";
programs.my-niri.enable = true;
programs.my-sway.enable = true;
programs.my-sway.fontSize = 15.0;
programs.my-sway.enableLaptop = true;
programs.my-waybar.enableMpd = true;
programs.my-sway.enableLaptopBars = false;
programs.my-sway.enableMpd = true;
# Keyboard options
wayland.windowManager.sway.config.input."type:keyboard".xkb_layout = "jp";
wayland.windowManager.sway.config.input."type:pointer".accel_profile = "flat";
# 144hz adaptive refresh ON!
wayland.windowManager.sway.config.output =
let
scale = 1.5;
top_x = builtins.ceil (3840 / scale);
top_y = builtins.ceil (((2160 / scale) - 1080) / 2);
in
{
"AOC U28G2G6B PPYP2JA000013" = {
mode = "3840x2160@60Hz";
scale = toString scale;
adaptive_sync = "on";
# render_bit_depth = "10";
position = "0 0";
};
"AOC 24G2W1G4 ATNN21A005410" = {
mode = "1920x1080@144Hz";
adaptive_sync = "on";
position = "${toString top_x} ${toString top_y}";
};
"ViewSonic Corporation XG2402 SERIES V4K182501054" = {
mode = "1920x1080@144Hz";
adaptive_sync = "on";
};
};
nki.programs.kitty.enable = true;
nki.programs.kitty.fontSize = 14;
programs.my-waybar.makeBars = with config.common.monitors; barWith: [
programs.my-sway.waybar.makeBars = barWith: [
# For primary
(barWith { extraSettings = { output = [ home_4k.meta.connection ]; }; })
(barWith { extraSettings = { output = [ "AOC U28G2G6B PPYP2JA000013" ]; }; })
# For secondary, hide mpd
(barWith { showMedia = false; showConnectivity = false; extraSettings = { output = [ home_1440.meta.connection ]; }; })
(barWith { showMedia = false; showConnectivity = false; extraSettings = { output = [ "AOC 24G2W1G4 ATNN21A005410" ]; }; })
];
# Yellow light!

View file

@ -1,79 +0,0 @@
# Catppuccin theme for Kakoune
declare-option str rosewater "rgb:dc8a78"
declare-option str flamingo "rgb:dd7878"
declare-option str pink "rgb:ea76cb"
declare-option str mauve "rgb:8839ef"
declare-option str red "rgb:d20f39"
declare-option str maroon "rgb:e64553"
declare-option str peach "rgb:fe640b"
declare-option str yellow "rgb:df8e1d"
declare-option str green "rgb:40a02b"
declare-option str teal "rgb:179299"
declare-option str sky "rgb:04a5e5"
declare-option str sapphire "rgb:209fb5"
declare-option str blue "rgb:1e66f5"
declare-option str lavender "rgb:7287fd"
declare-option str text "rgb:4c4f69"
declare-option str subtext1 "rgb:5c5f77"
declare-option str subtext0 "rgb:6c6f85"
declare-option str overlay2 "rgb:7c7f93"
declare-option str overlay1 "rgb:8c8fa1"
declare-option str overlay0 "rgb:9ca0b0"
declare-option str surface2 "rgb:acb0be"
declare-option str surface1 "rgb:bcc0cc"
declare-option str surface0 "rgb:ccd0da"
declare-option str base "rgb:eff1f5"
declare-option str mantle "rgb:e6e9ef"
declare-option str crust "rgb:dce0e8"
set-face global title "%opt{text}+b"
set-face global header "%opt{subtext0}+b"
set-face global bold "%opt{maroon}+b"
set-face global italic "%opt{maroon}+i"
set-face global mono "%opt{green}"
set-face global block "%opt{sapphire}"
set-face global link "%opt{blue}"
set-face global bullet "%opt{peach}"
set-face global list "%opt{peach}"
set-face global Default "%opt{text},%opt{base}"
set-face global PrimarySelection "%opt{text},%opt{surface2}"
set-face global SecondarySelection "%opt{text},%opt{surface2}"
set-face global PrimaryCursor "%opt{crust},%opt{rosewater}"
set-face global SecondaryCursor "%opt{text},%opt{overlay0}"
set-face global PrimaryCursorEol "%opt{surface2},%opt{lavender}"
set-face global SecondaryCursorEol "%opt{surface2},%opt{overlay1}"
set-face global LineNumbers "%opt{overlay1},%opt{base}"
set-face global LineNumberCursor "%opt{rosewater},%opt{surface2}+b"
set-face global LineNumbersWrapped "%opt{rosewater},%opt{surface2}+i"
set-face global MenuForeground "%opt{text},%opt{surface1}+b"
set-face global MenuBackground "%opt{text},%opt{surface0}"
set-face global MenuInfo "%opt{crust},%opt{teal}"
set-face global Information "%opt{crust},%opt{teal}"
set-face global Error "%opt{crust},%opt{red}"
set-face global StatusLine "%opt{text},%opt{mantle}"
set-face global StatusLineMode "%opt{crust},%opt{yellow}"
set-face global StatusLineInfo "%opt{crust},%opt{teal}"
set-face global StatusLineValue "%opt{crust},%opt{yellow}"
set-face global StatusCursor "%opt{crust},%opt{rosewater}"
set-face global Prompt "%opt{teal},%opt{base}+b"
set-face global MatchingChar "%opt{maroon},%opt{base}"
set-face global Whitespace "%opt{overlay1},%opt{base}+f"
set-face global WrapMarker "Whitespace"
set-face global BufferPadding "%opt{base},%opt{base}"
set-face global value "%opt{peach}"
set-face global type "%opt{blue}"
set-face global variable "%opt{text}"
set-face global module "%opt{maroon}"
set-face global function "%opt{blue}"
set-face global string "%opt{green}"
set-face global keyword "%opt{mauve}"
set-face global operator "%opt{sky}"
set-face global attribute "%opt{green}"
set-face global comment "%opt{overlay0}"
set-face global documentation "comment"
set-face global meta "%opt{yellow}"
set-face global builtin "%opt{red}"

View file

@ -1,67 +1,46 @@
{ pkgs, lib, ... }:
{ config, pkgs, lib, ... }:
let
kak-lsp-frontend = { pkgs, lib, ... }:
let
langserver = name: {
name = "vscode-${name}-language-server";
value = {
args = [ "--stdio" ];
command = "vscode-${name}-language-server";
filetypes = [ name ];
roots = [ "package.json" ".git" ];
};
package = pkgs.nodePackages.vscode-langservers-extracted;
};
kakounePkg =
pkgs.kakoune.override {
kakoune = with lib; pkgs.stdenv.mkDerivation rec {
pname = "kakoune-unwrapped";
version = "r${builtins.substring 0 6 pkgs.sources.kakoune.rev}";
src = pkgs.sources.kakoune;
makeFlags = [ "debug=no" "PREFIX=${placeholder "out"}" ];
tailwind = {
command = "tailwindcss-language-server";
args = [ "--stdio" ];
filetypes = [ "html" "css" "javascript" "typescript" "templ" ];
roots = [ "tailwind.config.{js,cjs,mjs,ts}" "package.json" ".git" ];
settings_section = "tailwindCSS";
settings.tailwindCSS = {
validate = "warning";
userLanguages.templ = "html";
};
package = pkgs.tailwindcss-language-server;
};
preConfigure = ''
export version="v${version}";
'';
templModule = { pkgs, lib, ... }: {
programs.kak-lsp.languageServers."vscode-html-language-server".filetypes = [ "templ" ];
programs.kak-lsp.languageServers."tailwindcss-language-server".filetypes = [ "templ" ];
programs.kak-lsp.languageServers.templ = {
command = "templ";
args = [ "lsp" ];
filetypes = [ "templ" ];
roots = [ "go.mod" ".git" ];
package = pkgs.unstable.templ;
};
enableParallelBuilding = true;
};
doInstallCheck = true;
installCheckPhase = ''
$out/bin/kak -ui json -e "kill 0"
'';
in
{
imports = [ templModule ];
programs.kak-lsp.languageServers = (builtins.listToAttrs (map langserver [ "html" "css" "json" ])) // {
tailwindcss-language-server = tailwind;
postInstall = ''
# make share/kak/autoload a directory
cd "$out/share/kak"
autoload_target=$(readlink autoload)
rm autoload
mkdir autoload
ln -s --relative "$autoload_target" autoload
'';
};
};
ltexLsp = { pkgs, lib, ... }: {
programs.kak-lsp.languageServers.ltex-ls = {
command = "ltex-ls";
args = [ "--log-file=/tmp" ];
filetypes = [ "latex" "typst" ];
roots = [ "main.tex" "main.typ" ".git" ];
package = pkgs.ltex-ls;
};
kak-lsp = pkgs.libs.crane.buildPackage {
src = pkgs.sources.kak-lsp;
buildInputs = (with pkgs;
lib.optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ Security SystemConfiguration CoreServices ])
) ++ (with pkgs; [ libiconv ]);
};
in
{
imports = [ ../modules/programs/my-kakoune ./kaktex.nix kak-lsp-frontend ltexLsp ];
imports = [ ../modules/programs/my-kakoune ./kaktex.nix ];
home.packages = with pkgs; [
# ctags for peneira
@ -76,6 +55,7 @@ in
programs.my-kakoune.enable = true;
programs.my-kakoune.enable-fish-session = true;
programs.kak-lsp.enable = true;
programs.kak-lsp.package = kak-lsp;
programs.kak-lsp.semanticTokens.additionalFaces = [
# Typst
{ face = "header"; token = "heading"; }
@ -91,51 +71,26 @@ in
{ face = "ts_markup_italic"; token = "text"; modifiers = [ "emph" ]; }
];
programs.kak-lsp.languageServers.elixir-ls = {
args = [ ];
command = "elixir-ls";
filetypes = [ "elixir" ];
roots = [ "mix.exs" ];
};
programs.kak-lsp.languageServers.typescript-language-server = {
programs.kak-lsp.languages.typescript = {
args = [ "--stdio" ];
command = "typescript-language-server";
filetypes = [ "typescript" "javascript" ];
filetypes = [ "typescript" ];
roots = [ "package.json" ];
package = pkgs.nodePackages.typescript-language-server;
};
programs.kak-lsp.languageServers.fsautocomplete = {
args = [ "--adaptive-lsp-server-enabled" "--project-graph-enabled" "--source-text-factory" "RoslynSourceText" ];
command = "fsautocomplete";
filetypes = [ "fsharp" ];
roots = [ "*.fsproj" ];
settings_section = "FSharp";
settings.FSharp = {
AutomaticWorkspaceInit = true;
};
};
programs.kak-lsp.languageServers.metals = {
programs.kak-lsp.languages.scala = {
command = "metals";
filetypes = [ "scala" ];
roots = [ "build.sbt" "build.sc" "build.mill" ];
roots = [ "build.sbt" ];
settings_section = "metals";
settings.metals = {
enableSemanticHighlighting = true;
showInferredType = true;
decorationProvider = true;
inlineDecorationProvider = true;
# From kakoune-lsp's own options
icons = "unicode";
isHttpEnabled = true;
statusBarProvider = "log-message";
compilerOptions = { overrideDefFormat = "unicode"; };
};
package = pkgs.metals;
};
programs.kak-lsp.languageServers.texlab = {
programs.kak-lsp.languages.latex = {
command = "texlab";
filetypes = [ "latex" ];
roots = [ "main.tex" "all.tex" ".git" ];
roots = [ ".git" "main.tex" "all.tex" ];
settings_section = "texlab";
settings.texlab = {
build.executable = "latexmk";
@ -151,33 +106,16 @@ in
} else
{
executable = "${pkgs.zathura}/bin/zathura";
args = [ "--synctex-forward" "%l:1:%f" "%p" "-x" "${./kaktex} jump %%{input} %%{line} %%{column}" ];
args = [ "--synctex-forward" "%l:1:%f" "%p" "-x" "/home/nki/.bin/kaktex jump %{input} %{line}" ];
});
};
package = pkgs.texlab;
};
programs.kak-lsp.languageServers.tinymist = {
command = "tinymist";
programs.kak-lsp.languages.typst = {
command = "${pkgs.typst-lsp}/bin/typst-lsp";
filetypes = [ "typst" ];
roots = [ "main.typ" ".git" ];
settings_section = "tinymist";
settings.tinymist = {
exportPdf = "onSave";
formatterMode = "typstfmt";
};
};
programs.kak-lsp.languageServers.marksman = {
command = "marksman";
filetypes = [ "markdown" ];
roots = [ ".marksman.toml" ".git" ];
package = pkgs.marksman;
};
programs.kak-lsp.languageServers.rust-analyzer = {
args = [ ];
command = "rust-analyzer";
filetypes = [ "rust" ];
roots = [ "Cargo.toml" ];
package = pkgs.rust-analyzer;
roots = [ ".git" "main.typ" ];
settings_section = "typst-lsp";
settings.typst-lsp = { };
};
programs.my-kakoune.tree-sitter.extraAliases = {
@ -196,23 +134,18 @@ in
conditional = "keyword_conditional";
include = "keyword_control_import";
};
programs.my-kakoune.tree-sitter.languages =
let
tree-sitter-go = pkgs.fetchFromGitHub {
owner = "tree-sitter";
repo = "tree-sitter-go";
rev = "v0.20.0";
hash = "sha256-G7d8CHCyKDAb9j6ijRfHk/HlgPqSI+uvkuRIRRvjkHI=";
};
in
{
programs.my-kakoune.tree-sitter.languages = {
scala =
let
src = pkgs.fetchFromGitHub {
owner = "tree-sitter";
repo = "tree-sitter-scala";
rev = "70afdd5632d57dd63a960972ab25945e353a52f6";
hash = "sha256-bi0Lqo/Zs2Uaz1efuKAARpEDg5Hm59oUe7eSXgL1Wow=";
rev = "8062487fb3b7f3ce1bb7ce1fd1c84bed60c75203";
sha256 =
if pkgs.stdenv.isDarwin
then "sha256-Ff8vRw7UswvPRGQS6nlSxqz46pX4eLNckuSbqa1yvtA="
else "sha256-G83H3RJddeIdLT6JssIDD5KXVnZE6vk1chl3RpQjdCI=";
leaveDotGit = true;
};
in
{
@ -226,7 +159,11 @@ in
owner = "tree-sitter";
repo = "tree-sitter-haskell";
rev = "ba0bfb0e5d8e9e31c160d287878c6f26add3ec08";
hash = "sha256-ZSOF0CLOn82GwU3xgvFefmh/AD2j5zz8I0t5YPwfan0=";
sha256 =
if pkgs.stdenv.isDarwin
then "sha256-lW3E4gSZV/m2RfofUqeiCu8KDz06YEvXbYKs8smXFi4="
else "sha256-nocX9L8vD655nzky4PQulygWAjKGC1rh3SYDr7t4wBQ=";
leaveDotGit = true;
};
in
{
@ -235,74 +172,9 @@ in
queries.src = src;
queries.path = "queries";
};
yaml = {
grammar.src = pkgs.fetchFromGitHub {
owner = "ikatyang";
repo = "tree-sitter-yaml";
rev = "0e36bed171768908f331ff7dff9d956bae016efb";
hash = "sha256-bpiT3FraOZhJaoiFWAoVJX1O+plnIi8aXOW2LwyU23M=";
};
grammar.compile.args = [ "-c" "-fpic" "../scanner.cc" "../parser.c" "-I" ".." ];
grammar.link.args = [ "-shared" "-fpic" "scanner.o" "parser.o" ];
grammar.link.flags = [ "-O3" "-lstdc++" ];
queries.src = pkgs.fetchFromGitHub {
owner = "helix-editor";
repo = "helix";
rev = "dbd248fdfa680373d94fbc10094a160aafa0f7a7";
hash = "sha256-wk8qVUDFXhAOi1Ibc6iBMzDCXb6t+YiWZcTd0IJybqc=";
};
queries.path = "runtime/queries/yaml";
};
templ =
let
src = pkgs.fetchFromGitHub {
owner = "vrischmann";
repo = "tree-sitter-templ";
rev = "044ad200092170727650fa6d368df66a8da98f9d";
hash = "sha256-hJuB3h5pp+LLfP0/7bAYH0uLVo+OQk5jpzJb3J9BNkY=";
};
in
{
grammar.src = src;
queries.src = pkgs.runCommandLocal "templ-tree-sitter-queries" { } ''
mkdir -p $out/queries
# copy most stuff from tree-sitter-templ
install -m644 ${src}/queries/templ/* $out/queries
# override inherited files
cat ${tree-sitter-go}/queries/highlights.scm ${src}/queries/templ/highlights.scm > $out/queries/highlights.scm
'';
queries.path = "queries";
};
go = {
grammar.src = tree-sitter-go;
grammar.compile.args = [ "-c" "-fpic" "../parser.c" "-I" ".." ];
grammar.link.args = [ "-shared" "-fpic" "parser.o" ];
queries.src = tree-sitter-go;
queries.path = "queries";
};
hylo =
let
src = pkgs.fetchFromGitHub {
owner = "natsukagami";
repo = "tree-sitter-hylo";
rev = "494cbdff0d13cbc67348316af2efa0286dbddf6f";
hash = "sha256-R5UeoglCTl0do3VDJ/liCTeqbxU9slvmVKNRA/el2VY=";
};
in
{
grammar.src = src;
grammar.compile.args = [ "-c" "-fpic" "../parser.c" "-I" ".." ];
grammar.link.args = [ "-shared" "-fpic" "parser.o" ];
queries.src = src;
queries.path = "queries";
};
};
programs.my-kakoune.package = pkgs.kakoune;
programs.my-kakoune.package = kakounePkg;
programs.my-kakoune.rc =
builtins.readFile ./kakrc + ''
@ -314,11 +186,11 @@ in
'';
programs.my-kakoune.extraFaces = {
Default = "%opt{text},%opt{base}";
BufferPadding = "%opt{base},%opt{base}";
MenuForeground = "%opt{blue},white+bF";
MenuBackground = "%opt{sky},white+F";
Information = "%opt{sky},white";
Default = "%opt{white},%opt{background}";
BufferPadding = "%opt{background},%opt{background}";
MenuForeground = "blue,white+bF";
MenuBackground = "bright-blue,white+F";
Information = "bright-blue,white";
# Markdown help color scheme
InfoDefault = "Information";
InfoBlock = "@block";
@ -334,10 +206,10 @@ in
InfoDiagnosticInformation = "@Information";
InfoDiagnosticWarning = "@DiagnosticWarning";
# Extra faces
macro = "+u@function";
method = "@function";
macro = "+b@function";
method = "+i@function";
format_specifier = "+i@string";
mutable_variable = "+i@variable";
mutable_variable = "+u@variable";
class = "+b@variable";
};
programs.my-kakoune.autoload = [
@ -442,10 +314,10 @@ in
narHash = "sha256-IcxFmvG0jqpMCG/dT9crVRgPgMGKkic6xwrnW5z4+bc=";
}) + "/rc";
}
# {
# name = "kakoune-discord";
# src = (builtins.getFlake "github:natsukagami/kakoune-discord/03f95e40d6efd8fd3de7bca31653d43de2dcfc5f").packages.${pkgs.system}.kakoune-discord-rc + "/rc";
# }
{
name = "kakoune-discord";
src = (builtins.getFlake "github:natsukagami/kakoune-discord/03f95e40d6efd8fd3de7bca31653d43de2dcfc5f").packages.${pkgs.system}.kakoune-discord-rc + "/rc";
}
rec {
name = "kakoune-mirror";
src = pkgs.fetchFromGitHub
@ -510,7 +382,10 @@ in
}
];
programs.my-kakoune.themes = {
catppuccin-latte = ./catppuccin-latte.kak;
catppuccin-latte = builtins.fetchurl {
url = "https://raw.githubusercontent.com/catppuccin/kakoune/f6d43770609433c45046632f1bb68d1395305dbb/colors/catppuccin_latte.kak";
sha256 = "sha256:0ycvxs8hmsvd0zrpxiby16wzmapvmz6p34b6j343pc1girw6fi4i";
};
};
}

View file

@ -1,34 +1,31 @@
# Enable kak-tree-sitter
eval %sh{test -z "$WE_STARTED_KAK" && kak-tree-sitter --kakoune -d --server --init $kak_session}
map global normal <c-t> ": enter-user-mode tree-sitter<ret>"
# ## Set some color overrides
# set global kts_yellow "rgb:e2b75e"
# set global kts_teal "rgb:008080"
# set global kts_mauve "rgb:c264ff"
# set global kts_sky "rgb:6aa622"
eval %sh{ kak-tree-sitter --kakoune -d }
# Color scheme
colorscheme catppuccin-latte
set global base "default"
# Set indentation guides
add-highlighter global/indent-guides show-whitespaces -tab " " -spc " " -lf " " -nbsp " "
set-face global Whitespace default,default
set-face global WhitespaceIndent +d@comment
set global background default
# Assistant
set global ui_options terminal_assistant=cat
# Enable line numbers
hook global WinCreate .* %{
addhl window/number-lines number-lines
}
addhl global/ number-lines
set global grepcmd "rg --line-number --no-column --no-heading --color=never "
# Kitty-specific options
hook -group windowing global KakBegin .* %{
set global kitty_window_type os-window
}
# Floating terminal
# define-command floating-terminal -params 1 -docstring "Open a floating terminal running the given command" %{
# evaluate-commands -save-regs 'a' %{
# set-register a %arg{@}
# evaluate-commands %sh{
# alacritty \
# --class=alacritty,floating \
# -o window.dimensions.lines=24 \
# -o window.dimensions.columns=120 \
# -e sh -c "$kak_quoted_reg_a" < /dev/null > /dev/null 2>&1 &
# }
# }
# }
# map global user t -docstring "Open a side terminal on the current directory" ' :iterm-terminal-horizontal fish<ret>'
# Comment line and block
map global normal <#> ': comment-line<ret>'
@ -41,9 +38,9 @@ map global goto f -docstring "current grep-jump match" '<esc>: grep-jump<ret>'
hook global RegisterModified '"' %{ nop %sh{
printf "%s" "$kak_main_reg_dquote" | pbcopy >/dev/null 2>/dev/null &
}}
map global user P -docstring "Paste before cursor from clipboard" '! pbpaste -n | cat<ret>'
map global user p -docstring "Paste after cursor from clipboard" '<a-!> pbpaste -n | cat<ret>'
map global user R -docstring "Replace selection with text from clipboard" '<a-d>! pbpaste -n | cat<ret>'
map global user P -docstring "Paste before cursor from clipboard" '! pbpaste<ret>'
map global user p -docstring "Paste after cursor from clipboard" '<a-!> pbpaste<ret>'
map global user R -docstring "Replace selection with text from clipboard" '<a-d>! pbpaste<ret>'
define-command -params 0 -docstring "Copy line down" copyline %{
execute-keys -draft 'xy'%val{count}'P'
}
@ -61,13 +58,11 @@ map global normal D ": delete-current-brackets<ret>"
# Tab sizes
hook global InsertChar \t %{ exec -draft -itersel h@ }
set global tabstop 2
set global indentwidth 2
# Language-specific tabstop with override
hook global WinSetOption filetype=(rust) %{
set window tabstop 4
set window indentwidth 4
set global tabstop 4
set global indentwidth 4
hook global WinSetOption filetype=(c|cpp|haskell|nix|yaml) %{
set global tabstop 2
set global indentwidth 2
}
# Ctrl + a in insert mode = esc
@ -93,9 +88,8 @@ hook global InsertCompletionHide .* %{
# Enable LSP
try %{
eval %sh{test -z "$WE_STARTED_KAK" && kak-lsp --kakoune -s $kak_session}
set-option global lsp_cmd "kak-lsp -s %val{session}"
}
hook global WinSetOption filetype=(racket|rust|python|go|javascript|typescript|c|cpp|tex|latex|fsharp|ocaml|haskell|nix|scala|typst|html|css|json|markdown|templ|elixir) %{
hook global WinSetOption filetype=(racket|rust|python|go|javascript|typescript|c|cpp|tex|latex|fsharp|ocaml|haskell|nix|scala|typst) %{
lsp-enable-window
map window lsp N -docstring "Display the next message request" ": lsp-show-message-request-next<ret>"
map window normal <c-l> ": enter-user-mode lsp<ret>"
@ -103,26 +97,19 @@ hook global WinSetOption filetype=(racket|rust|python|go|javascript|typescript|c
map window normal <c-s-h> ": lsp-hover-buffer<ret>"
# lsp-auto-hover-insert-mode-enable
set window lsp_hover_anchor true
map global insert <tab> '<a-;>:try lsp-snippets-select-next-placeholders catch %{ execute-keys -with-hooks <lt>tab> }<ret>' -docstring 'Select next snippet placeholder'
map global object a '<a-semicolon>lsp-object<ret>' -docstring 'LSP any symbol'
map global object <a-a> '<a-semicolon>lsp-object<ret>' -docstring 'LSP any symbol'
map global object f '<a-semicolon>lsp-object Function Method<ret>' -docstring 'LSP function or method'
map global object t '<a-semicolon>lsp-object Class Interface Struct<ret>' -docstring 'LSP class interface or struct'
map global object d '<a-semicolon>lsp-diagnostic-object --include-warnings<ret>' -docstring 'LSP errors and warnings'
map global object D '<a-semicolon>lsp-diagnostic-object<ret>' -docstring 'LSP errors'
}
hook global WinSetOption filetype=(racket|rust|python|go|javascript|typescript|c|cpp|tex|latex|haskell|nix|fsharp|templ) %{
}
hook global WinSetOption filetype=(racket|rust|python|go|javascript|typescript|c|cpp|tex|latex|haskell|nix) %{
# Format the document if possible
hook window BufWritePre .* %{ lsp-formatting-sync }
}
hook global WinSetOption filetype=(rust|scala|fsharp) %{
hook global WinSetOption filetype=(rust) %{
# Enable inlay hints
lsp-inlay-hints-enable window
}
hook global WinSetOption filetype=(rust|go|fsharp|typst|scala) %{
hook global WinSetOption filetype=(rust|go|fsharp|typst) %{
hook window -group semantic-tokens BufReload .* lsp-semantic-tokens
hook window -group semantic-tokens NormalIdle .* lsp-semantic-tokens
hook window -group semantic-tokens InsertIdle .* lsp-semantic-tokens
@ -137,8 +124,8 @@ map global insert <a-a> '<esc>A'
hook global WinSetOption filetype=(fsharp) %{
set-option window comment_line "//"
# Set up formatting
# set-option window formatcmd "~/.dotnet/tools/fantomas --stdin --stdout"
# hook window -group fsharp-format BufWritePre .* %{ format }
set-option window formatcmd "~/.dotnet/tools/fantomas --stdin --stdout"
hook window -group fsharp-format BufWritePre .* %{ format }
}
hook global WinSetOption filetype=(ocaml) %{
@ -158,6 +145,9 @@ hook global WinSetOption filetype=(rust) %{
hook global WinSetOption filetype=(scala) %{
# Format the document if possible
hook -group scala-fmt window BufWritePre .* %{ lsp-formatting-sync }
set window tabstop 2
set window indentwidth 2
}
hook global WinSetOption filetype=(typst) %{
@ -206,7 +196,7 @@ hook global BufCreate .*[.]md %{
add-highlighter buffer/ wrap
}
hook global BufCreate .*[.](sc|sbt|mill) %{
hook global BufCreate .*[.]sc %{
set-option buffer filetype scala
}
@ -215,19 +205,12 @@ hook global BufCreate .*[.]typ %{
add-highlighter buffer/ wrap
}
hook global BufCreate .*[.]templ %{
set-option buffer filetype templ
set-option buffer comment_line "//"
}
hook global BufCreate .*[.]hylo %{
set-option buffer filetype hylo
set-option buffer comment_line "//"
}
hook global BufOpenFile .* %{
modeline-parse
}
map global normal <a-[> ':inc-dec-modify-numbers + %val{count}<ret>'
map global normal <a-]> ':inc-dec-modify-numbers - %val{count}<ret>'
# Discord integration
discord-presence-enable

View file

@ -3,11 +3,11 @@
function usage
echo "Usage: "
echo " kaktex set [client] [session]"
echo " kaktex jump [file] [line] [column]"
echo " kaktex jump [file] [line]"
exit 1
end
if test (count $argv) -lt 3
if test (count $argv) -ne 3
usage
end
@ -19,7 +19,7 @@ switch $argv[1]
echo "
evaluate-commands -client $_kaktex_client %{
evaluate-commands -try-client $_kaktex_client %{
edit -existing -- $argv[2] $(math $argv[3] + 1) $(math $argv[4] + 1)
edit -- $argv[2] $argv[3]
}
}
" | kak -p $_kaktex_session

View file

@ -1,12 +1,22 @@
{ config, pkgs, lib, ... }:
let
kaktexScript = ./kaktex;
in
{
# Create kak-tex executable
home.file.kaktex = {
source = kaktexScript;
executable = true;
target = ".bin/kaktex";
};
# Source kaktex whenever we have a tex file
programs.my-kakoune.rc = ''
hook global WinSetOption filetype=(tex|latex) %{
hook window WinDisplay '.*' %{
eval %sh{
${./kaktex} set $kak_client $kak_session
${kaktexScript} set $kak_client $kak_session
}
}
}

View file

@ -1,7 +1,18 @@
{ pkgs, config, lib, ... }:
let
discord = pkgs.armcord.override { nss = pkgs.nss_latest; };
discord =
(pkgs.armcord.override { nss = pkgs.nss_latest; }).overrideAttrs (attrs: {
postInstall = ''
# Wrap the startup command
makeWrapper $out/opt/ArmCord/armcord $out/bin/armcord \
"''${gappsWrapperArgs[@]}" \
--prefix XDG_DATA_DIRS : "${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}/" \
--add-flags "--ozone-platform=x11 --enable-features=UseOzonePlatform --enable-features=WebRTCPipeWireCapturer" \
--prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath attrs.buildInputs}" \
--suffix PATH : ${lib.makeBinPath [ pkgs.xdg-utils ]}
'';
});
in
{
imports = [
@ -19,7 +30,7 @@ in
home.homeDirectory = "/home/nki";
nki.programs.kitty.enable = true;
nki.programs.kitty.fontSize = 16;
nki.programs.kitty.fontSize = 18;
programs.fish.shellInit = lib.mkAfter ''
set -eg MESA_GL_VERSION_OVERRIDE
set -eg MESA_GLSL_VERSION_OVERRIDE
@ -42,6 +53,10 @@ in
discord
typora
# Java & sbt
openjdk11
sbt
]);
# Graphical set up
@ -49,15 +64,15 @@ in
linux.graphical.wallpaper = ./images/wallpaper-macbook.jpg;
# Enable sway
programs.my-sway.enable = true;
programs.my-sway.fontSize = 14.0;
programs.my-sway.enableLaptop = true;
programs.my-waybar.enableMpd = false;
programs.my-sway.fontSize = 12.0;
programs.my-sway.enableLaptopBars = true;
programs.my-sway.enableMpd = false;
programs.my-sway.discord = "${discord}/bin/armcord";
# Keyboard options
wayland.windowManager.sway.config.input."type:keyboard".xkb_layout = "jp";
wayland.windowManager.sway.config.output."eDP-1" = {
mode = "2560x1600@60Hz";
scale = "1.25";
scale = "1.5";
subpixel = "vrgb";
};
wayland.windowManager.sway.config.input."1452:641:Apple_Internal_Keyboard_/_Trackpad" = {
@ -97,7 +112,7 @@ in
home.file.".gnupg/gpg-agent.conf" = {
text = ''
pinentry-program ${pkgs.pinentry-gnome3}/bin/pinentry-gnome3
pinentry-program ${pkgs.pinentry-gnome}/bin/pinentry-gnome3
'';
onChange = ''
echo "Reloading gpg-agent"
@ -106,6 +121,10 @@ in
};
# Autostart
xdg.configFile."autostart/polkit.desktop".text = ''
${builtins.readFile "${pkgs.pantheon.pantheon-agent-polkit}/etc/xdg/autostart/io.elementary.desktop.agent-polkit.desktop"}
OnlyShowIn=sway;
'';
xdg.configFile."autostart/input-remapper-autoload.desktop".source =
"${pkgs.input-remapper}/share/applications/input-remapper-autoload.desktop";

View file

@ -3,38 +3,9 @@ with lib;
let
cfg = config.linux.graphical;
thunderbird = pkgs.thunderbird-128;
vscode = with pkgs; if stdenv.isAarch64 then unstable.vscode else unstable.vscode-fhs;
wifi-indicator = pkgs.writeScriptBin "wifi-indicator" ''
#!/usr/bin/env fish
set wifi_output (${lib.getExe pkgs.iw} wlan0 link | rg "SSID: (.+)" --replace '🛜 $1' | string trim)
if test -z $wifi_output
echo " not connected"
else
echo $wifi_output
end
'';
mkPackageWithDesktopOption = opts: mkOption ({
type = types.submodule {
options = {
package = mkOption {
type = types.package;
description = "The package for " + description;
};
desktopFile = mkOption {
type = types.nullOr types.str;
default = null;
description = "The desktop file name for " + description + ", defaults to [packagename].desktop";
};
};
};
} // opts);
desktopFileOf = cfg: if cfg.desktopFile == null then "${cfg.package}/share/applications/${cfg.package.pname}.desktop" else cfg.desktopFile;
alwaysStartup = with pkgs; [ ];
in
{
imports = [ ./x11.nix ./wayland.nix ./alacritty.nix ];
@ -52,24 +23,13 @@ in
startup = mkOption {
type = types.listOf types.package;
description = "List of packages to include in ~/.config/autostart";
default = with pkgs; [
cfg.defaults.webBrowser.package
thunderbird
vesktop
];
};
defaults = {
webBrowser = mkPackageWithDesktopOption { description = "default web browser"; };
terminal = mkPackageWithDesktopOption { description = "default terminal"; default.package = pkgs.kitty; };
default = [ ];
};
};
config = mkIf (cfg.type != null) {
# Packages
home.packages = (with pkgs; [
cfg.defaults.webBrowser.package
cfg.defaults.terminal.package
## GUI stuff
evince # PDF reader
gparted
@ -77,52 +37,34 @@ in
feh # For images?
deluge # Torrent client
pavucontrol # PulseAudio control panel
firefox
librewolf
cinnamon.nemo # File manager
thunderbird # Email
sublime-music # For navidrome
# cinny-desktop
gajim
vivaldi
# Audio
qpwgraph # Pipewire graph
unstable.zotero
zotero
libreoffice
mpv # for anki
anki-bin
# Chat stuff
tdesktop
whatsapp-for-linux
slack
zoom-us
## CLI stuff
dex # .desktop file management, startup
# sct # Display color temperature
xdg-utils # Open stuff
wifi-indicator
] ++ cfg.startup);
] ++ (if pkgs.stdenv.isAarch64 then [ ] else [
gnome.cheese # Webcam check, expensive
mailspring
# Chat stuff
unstable.slack
]));
nki.programs.discord.enable = pkgs.stdenv.isx86_64;
nki.programs.discord.package = pkgs.vesktop;
# OBS
programs.obs-studio = {
enable = true;
plugins = with pkgs.obs-studio-plugins; [
wlrobs
input-overlay
obs-pipewire-audio-capture
];
};
# Cursor
home.pointerCursor = {
package = pkgs.suwako-cursors;
gtk.enable = true;
name = "Suwako";
size = 32;
package = pkgs.numix-cursor-theme;
name = "Numix-Cursor";
size = 24;
};
# MIME set ups
@ -130,45 +72,22 @@ in
xdg.mimeApps.enable = true;
xdg.mimeApps.associations.added = {
"x-scheme-handler/mailto" = [ "thunderbird.desktop" "org.gnome.Evolution.desktop" ];
"x-scheme-handler/mailto" = [ "org.gnome.Evolution.desktop" ];
"application/pdf" = [ "org.gnome.Evince.desktop" ];
"text/plain" = [ "kakoune.desktop" ];
# Other Thunderbird stuff
"x-scheme-handler/mid" = [ "thunderbird.desktop" ];
"x-scheme-handler/news" = [ "thunderbird.desktop" ];
"x-scheme-handler/snews" = [ "thunderbird.desktop" ];
"x-scheme-handler/nntp" = [ "thunderbird.desktop" ];
"x-scheme-handler/feed" = [ "thunderbird.desktop" ];
"application/rss+xml" = [ "thunderbird.desktop" ];
"application/x-extension-rss" = [ "thunderbird.desktop" ];
"x-scheme-handler/tg2" = [ "org.telegram.desktop.desktop" ];
"x-scheme-handler/tonsite2" = [ "org.telegram.desktop.desktop" ];
};
xdg.mimeApps.defaultApplications = {
# Email
"x-scheme-handler/mailto" = [ "thunderbird.desktop" "org.gnome.Evolution.desktop" ];
"x-scheme-handler/webcal" = [ "thunderbird.desktop" ];
"x-scheme-handler/webcals" = [ "thunderbird.desktop" ];
# Other Thunderbird stuff
"x-scheme-handler/mid" = [ "thunderbird.desktop" ];
"x-scheme-handler/news" = [ "thunderbird.desktop" ];
"x-scheme-handler/snews" = [ "thunderbird.desktop" ];
"x-scheme-handler/nntp" = [ "thunderbird.desktop" ];
"x-scheme-handler/feed" = [ "thunderbird.desktop" ];
"application/rss+xml" = [ "thunderbird.desktop" ];
"application/x-extension-rss" = [ "thunderbird.desktop" ];
"x-scheme-handler/mailto" = [ "org.gnome.Evolution.desktop" ];
# Default web browser stuff
"text/html" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"x-scheme-handler/about" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"x-scheme-handler/unknown" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"x-scheme-handler/http" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"x-scheme-handler/https" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"x-scheme-handler/ftp" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"x-scheme-handler/ftps" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"x-scheme-handler/file" = [ (desktopFileOf cfg.defaults.webBrowser) ];
"text/html" = [ "firefox.desktop" ];
"x-scheme-handler/about" = [ "firefox.desktop" ];
"x-scheme-handler/unknown" = [ "firefox.desktop" ];
"x-scheme-handler/http" = [ "firefox.desktop" ];
"x-scheme-handler/https" = [ "firefox.desktop" ];
"x-scheme-handler/ftp" = [ "firefox.desktop" ];
"x-scheme-handler/ftps" = [ "firefox.desktop" ];
# Torrent
"application/x-bittorrent" = [ "deluge.desktop" ];
@ -176,14 +95,10 @@ in
# Text
"text/plain" = [ "kakoune.desktop" ];
"application/pdf" = [ "okularApplication_pdf.desktop" ];
"application/pdf" = [ "org.gnome.Evince.desktop" ];
# Files
"inode/directory" = [ "dolphin.desktop" ];
# Telegram
"x-scheme-handler/tg2" = "org.telegram.desktop.desktop";
"x-scheme-handler/tonsite2" = "org.telegram.desktop.desktop";
"inode/directory" = [ "nemo.desktop" ];
};
# Add one for kakoune
@ -202,36 +117,26 @@ in
## GTK
gtk.enable = true;
gtk.cursorTheme = { inherit (config.home.pointerCursor) package name size; };
gtk.font.name = "IBM Plex Sans JP";
gtk.font.name = "system-ui";
gtk.font.size = 10;
gtk.iconTheme = {
package = pkgs.kdePackages.breeze-icons;
name = "breeze";
package = pkgs.numix-icon-theme;
name = "Numix";
};
gtk.theme = {
package = pkgs.kdePackages.breeze-gtk;
name = "Breeze";
package = pkgs.numix-gtk-theme;
name = "Numix";
};
gtk.gtk2.configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc";
gtk.gtk2.extraConfig = ''
gtk-enable-animations=1
gtk-im-module="fcitx"
gtk-theme-name="Numix"
gtk-primary-button-warps-slider=1
gtk-toolbar-style=3
gtk-menu-images=1
gtk-button-images=1
gtk-sound-theme-name="ocean"
gtk-icon-theme-name="breeze"
'';
gtk.gtk3.extraConfig.gtk-im-module = "fcitx";
gtk.gtk4.extraConfig.gtk-im-module = "fcitx";
## Qt
qt.enable = true;
qt.platformTheme.name = "kde";
qt.platformTheme.package = with pkgs.kdePackages; [ plasma-integration systemsettings ];
qt.style.package = [ pkgs.kdePackages.breeze ];
qt.style.name = "Breeze";
qt.platformTheme = "gnome";
qt.style.package = pkgs.adwaita-qt;
qt.style.name = "adwaita";
home.sessionVariables = {
# Set up Java font style
_JAVA_OPTIONS = "-Dawt.useSystemAAFontSettings=lcd";
};
xdg.configFile =
let
@ -241,22 +146,17 @@ in
source =
let
srcFile = pkgs.runCommand "${pkg.name}-startup" { } ''
mkdir -p $out
cp $(ls -d ${pkg}/share/applications/*.desktop | head -n 1) $out/${pkg.name}.desktop
mkdir - p $out
cp $
(ls - d ${
pkg}/share/applications/*.desktop | head -n 1) $out/${pkg.name}.desktop
'';
in
"${srcFile}/${pkg.name}.desktop";
};
};
autoStartup = listToAttrs (map f cfg.startup);
in
autoStartup // {
## Polkit UI
"autostart/polkit.desktop".text = ''
${builtins.readFile "${pkgs.pantheon.pantheon-agent-polkit}/etc/xdg/autostart/io.elementary.desktop.agent-polkit.desktop"}
OnlyShowIn=sway;
'';
};
listToAttrs (map f (cfg.startup ++ alwaysStartup));
# IBus configuration
# dconf.settings."desktop/ibus/general" = {
# engines-order = hm.gvariant.mkArray hm.gvariant.type.string [ "xkb:jp::jpn" "mozc-jp" "Bamboo" ];
@ -265,26 +165,6 @@ in
# dconf.settings."desktop/ibus/general/hotkey" = {
# triggers = hm.gvariant.mkArray hm.gvariant.type.string [ "<Super>z" ];
# };
# Some graphical targets
systemd.user.targets = {
# For system trays, usually after graphical-session and graphical-session-pre
tray = {
Unit.Description = lib.mkDefault "System tray";
Unit.After = [ "graphical-session-pre.target" ];
Unit.Before = [ "graphical-session.target" ];
Unit.BindsTo = [ "graphical-session.target" ];
Install.WantedBy = [ "graphical-session.target" ];
};
# XWayland target
xwayland = {
Unit.Description = "XWayland support";
Unit.After = [ "graphical-session-pre.target" ];
Unit.Before = [ "graphical-session.target" ];
Unit.BindsTo = [ "graphical-session.target" ];
Install.WantedBy = [ "graphical-session.target" ];
};
};
};
}

View file

@ -2,19 +2,31 @@
let
notificationModule = { config, pkgs, lib, ... }:
let
swaync = pkgs.swaynotificationcenter;
swaync = pkgs.unstable.swaynotificationcenter;
in
with lib; mkIf (config.linux.graphical.type == "wayland") {
services.swaync = {
enable = true;
settings.widgets = [ "inhibitors" "title" "dnd" "mpris" "notifications" ];
style = ./swaync.css;
home.packages = [ swaync ];
wayland.windowManager.sway.config = {
startup = [
{ command = "swaync"; }
];
};
xdg.configFile = {
"swaync/config.json" = {
text = builtins.toJSON {
widgets = [ "inhibitors" "title" "dnd" "mpris" "notifications" ];
scripts = { };
};
onChange = "swaync-client -R";
};
"swaync/style.css" = {
source = ./swaync.css;
onChange = "swaync-client -rs";
};
};
systemd.user.services.swaync.Install.WantedBy = lib.mkForce [ "sway-session.target" ];
systemd.user.services.swaync.Unit.PartOf = lib.mkForce [ "graphical-session.target" ];
programs.my-waybar = {
extraSettings = [{
programs.my-sway.waybar = {
extraSettings = {
modules-right = mkAfter [ "custom/swaync" ];
modules."custom/swaync" = {
tooltip = false;
@ -36,81 +48,35 @@ let
on-click-right = "${swaync}/bin/swaync-client -d -sw";
escape = true;
};
}];
};
extraStyle = mkAfter ''
#custom-swaync {
padding: 0 10px;
margin: 0 5px;
background: #F0FFFF;
color: #000000;
}
'';
};
};
plasmaModule = { pkgs, ... }: {
home.packages = with pkgs.kdePackages; [
discover
kmail
kontact
akonadi
kdepim-runtime
kmail-account-wizard
akonadi-import-wizard
];
xdg.configFile."plasma-workspace/env/wayland.sh".source = pkgs.writeScript "plasma-wayland-env.sh" ''
export NIXOS_OZONE_WL=1
'';
xdg.dataFile."dbus-1/services/org.freedesktop.Notifications.service".source = "${pkgs.kdePackages.plasma-workspace}/share/dbus-1/services/org.kde.plasma.Notifications.service";
};
rofi-rbw-script = pkgs.writeShellApplication {
name = "rofi-rbw-script";
runtimeInputs = with pkgs; [ rofi wtype rofi-rbw ];
text = "rofi-rbw";
meta.mainProgram = "rofi-rbw-script";
};
in
with lib;
{
imports = [ notificationModule plasmaModule ];
imports = [ notificationModule ];
config = mkIf (config.linux.graphical.type == "wayland") {
# Additional packages
home.packages = with pkgs; [
wl-clipboard # Clipboard management
rofi-rbw-script
# Mimic the clipboard stuff in MacOS
(pkgs.writeShellScriptBin "pbcopy" ''
exec ${pkgs.wl-clipboard}/bin/wl-copy "$@"
exec ${pkgs.wl-clipboard}/bin/wl-copy
'')
(pkgs.writeShellScriptBin "pbpaste" ''
exec ${pkgs.wl-clipboard}/bin/wl-paste "$@"
exec ${pkgs.wl-clipboard}/bin/wl-paste -n
'')
];
programs.rofi = {
enable = true;
package = pkgs.rofi-wayland;
cycle = true;
font = "monospace";
terminal = "${lib.getExe config.programs.kitty.package}";
theme = "Paper";
plugins = with pkgs; [ rofi-bluetooth rofi-calc rofi-rbw rofi-power-menu ];
};
home.sessionVariables = {
ANKI_WAYLAND = "1";
};
# Yellow light!
services.wlsunset = {
enable = true;
# Lausanne
latitude = "46.31";
longitude = "6.38";
};
# Notification system
# services.dunst = {
# enable = true;
@ -130,6 +96,9 @@ with lib;
# settings.experimental.per_monitor_dpi = "true";
# };
# Forward wallpaper settings to sway
programs.my-sway.wallpaper = config.linux.graphical.wallpaper;
};
}

View file

@ -1,76 +0,0 @@
# A monitor list and common sway set up
{ config, pkgs, lib, ... }: with lib;
let
monitors = {
# Internal
"framework" = {
name = "BOE 0x0BCA Unknown";
meta.mode = { width = 2256; height = 1504; refresh = 60.0; };
scale = 1.25;
};
"yoga" = {
name = "AU Optronics 0xD291 Unknown";
meta.connection = "eDP-1";
meta.mode = { width = 1920; height = 1080; refresh = 60.026; };
scale = 1;
};
# External
## Work @ EPFL
"work" = {
name = "LG Electronics LG ULTRAFINE 301MAXSGHD10";
meta.mode = { width = 3840; height = 2160; refresh = 60.0; };
scale = 1.25;
};
"home_4k" = {
name = "AOC U28G2G6B PPYP2JA000013";
scale = 1.5;
adaptive_sync = "on";
meta = {
connection = "DP-2";
mode = { width = 3840; height = 2160; refresh = 60.0; };
fixedPosition = { x = 0; y = 0; };
niriName = "PNP(AOC) U28G2G6B PPYP2JA000013";
};
};
"home_1440" = {
name = "AOC Q27G2G3R3B VXJP6HA000442";
adaptive_sync = "on";
meta = {
connection = "DP-3";
mode = { width = 2560; height = 1440; refresh = 165.0; };
fixedPosition = { x = 2560; y = 0; };
niriName = "PNP(AOC) Q27G2G3R3B VXJP6HA000442";
};
};
"viewsonic_1080" = {
name = "ViewSonic Corporation XG2402 SERIES V4K182501054";
meta.mode = { width = 1920; height = 1080; refresh = 144.0; };
adaptive_sync = "on";
};
};
eachMonitor = _name: monitor: {
name = monitor.name;
value = builtins.removeAttrs monitor [ "scale" "name" "meta" ] // (lib.optionalAttrs (monitor ? scale) {
scale = toString monitor.scale;
}) // {
mode = with monitor.meta.mode; "${toString width}x${toString height}@${toString refresh}Hz";
} // (lib.optionalAttrs (monitor.meta ? fixedPosition) {
position = with monitor.meta.fixedPosition; "${toString x} ${toString y}";
});
};
in
{
options.common.monitors = mkOption {
type = types.attrsOf types.attrs;
description = "A list of monitors";
};
config.common.monitors = monitors;
config.home.packages = mkIf config.wayland.windowManager.sway.enable (with pkgs; [ kanshi ]);
config.wayland.windowManager.sway.config.output = mkIf config.wayland.windowManager.sway.enable (
mapAttrs' eachMonitor config.common.monitors
);
}

View file

@ -44,16 +44,14 @@ in
set -gx EDITOR "kak"
alias e="kak"
'';
};
programs.fish.tide = {
items.kakoune = ''
_tide_item_kakoune = ''
if set -q kak_session
set -U tide_kakoune_color FFA500
set -U tide_kakoune_bg_color normal
_tide_print_item kakoune " " "e[$kak_session]"
end
'';
rightItems = mkAfter [ "kakoune" ];
};
};
}

View file

@ -4,23 +4,52 @@ with lib;
let
lspConfig =
{
language_ids = {
c = "c_cpp";
cpp = "c_cpp";
javascript = "javascriptreact";
typescript = "typescriptreact";
protobuf = "proto";
sh = "shellscript";
language = {
bash = {
args = [ "start" ];
command = "bash-language-server";
filetypes = [ "sh" ];
roots = [ ".git" ".hg" ];
};
language_servers = {
ccls = {
c_cpp = {
args = [ "-v=2" "-log-file=/tmp/ccls.log" ];
command = "ccls";
filetypes = [ "c" "cpp" ];
roots = [ "compile_commands.json" ".cquery" ".git" ];
};
gopls = {
crystal = {
command = "scry";
filetypes = [ "crystal" ];
roots = [ "shard.yml" ];
};
css = {
args = [ "--stdio" ];
command = "css-languageserver";
filetypes = [ "css" ];
roots = [ "package.json" ];
};
d = {
command = "dls";
filetypes = [ "d" "di" ];
roots = [ ".git" "dub.sdl" "dub.json" ];
};
dart = {
command = "dart_language_server";
filetypes = [ "dart" ];
roots = [ "pubspec.yaml" ".git" ];
};
elm = {
args = [ "--stdio" ];
command = "elm-language-server";
filetypes = [ "elm" ];
roots = [ "elm.json" ];
};
fsharp = {
command = "FSharpLanguageServer";
filetypes = [ "fsharp" ];
roots = [ ".git" "*.fsx" ];
};
go = {
command = "gopls";
filetypes = [ "go" ];
offset_encoding = "utf-8";
@ -28,27 +57,101 @@ let
settings = { gopls = { hoverKind = "SynopsisDocumentation"; semanticTokens = true; }; };
settings_section = "gopls";
};
haskell-language-server = {
haskell = {
args = [ "--lsp" ];
command = "haskell-language-server-wrapper";
filetypes = [ "haskell" ];
roots = [ "Setup.hs" "stack.yaml" "*.cabal" "package.yaml" ];
settings_section = "haskell";
};
nil = {
command = "${pkgs.nil}/bin/nil";
html = {
args = [ "--stdio" ];
command = "html-languageserver";
filetypes = [ "html" ];
roots = [ "package.json" ];
};
javascript = {
args = [ "lsp" ];
command = "flow";
filetypes = [ "javascript" ];
roots = [ ".flowconfig" ];
};
json = {
args = [ "--stdio" ];
command = "json-languageserver";
filetypes = [ "json" ];
roots = [ "package.json" ];
};
latex = {
command = "texlab";
filetypes = [ "latex" ];
roots = [ ".git" "main.tex" "all.tex" ];
settings_section = "texlab";
settings.texlab = {
build.executable = "latexmk";
build.args = [ "-pdf" "-shell-escape" "-interaction=nonstopmode" "-synctex=1" "%f" ];
build.forwardSearchAfter = true;
build.onSave = true;
forwardSearch = {
executable = "/Applications/Skim.app/Contents/SharedSupport/displayline";
args = [ "-r" "-g" "%l" "%p" "%f" ];
};
};
};
nim = {
command = "nimlsp";
filetypes = [ "nim" ];
roots = [ "*.nimble" ".git" ];
};
nix = {
command = "rnix-lsp";
filetypes = [ "nix" ];
roots = [ "flake.nix" "shell.nix" ".git" ];
settings.nil = {
formatting.command = [ "${getExe pkgs.nixpkgs-fmt}" ];
};
ocaml = {
args = [ ];
command = "ocamllsp";
filetypes = [ "ocaml" ];
roots = [ "Makefile" "opam" "*.opam" "dune" ".merlin" ".ocamlformat" ];
};
pyls = {
php = {
args = [ "--stdio" ];
command = "intelephense";
filetypes = [ "php" ];
roots = [ ".htaccess" "composer.json" ];
};
python = {
command = "pyls";
filetypes = [ "python" ];
offset_encoding = "utf-8";
roots = [ "requirements.txt" "setup.py" ".git" ".hg" ];
};
racket = {
args = [ "-l" "racket-langserver" ];
command = "racket";
filetypes = [ "racket" ];
roots = [ ".git" ];
};
reason = {
args = [ "--stdio" ];
command = "ocaml-language-server";
filetypes = [ "reason" ];
roots = [ "package.json" "Makefile" ".git" ".hg" ];
};
ruby = {
args = [ "stdio" ];
command = "solargraph";
filetypes = [ "ruby" ];
roots = [ "Gemfile" ];
};
rust = {
args = [ ];
command = "rust-analyzer";
filetypes = [ "rust" ];
roots = [ "Cargo.toml" ];
};
};
semantic_tokens.faces = [
## Items
@ -66,8 +169,8 @@ let
{ face = "string"; token = "string"; }
{ face = "format_specifier"; token = "formatSpecifier"; }
# Variables
{ face = "variable"; token = "variable"; modifiers = [ "readonly" ]; }
{ face = "mutable_variable"; token = "variable"; }
{ face = "mutable_variable"; token = "variable"; modifiers = [ "mutable" ]; }
{ face = "variable"; token = "variable"; }
{ face = "module"; token = "namespace"; }
{ face = "variable"; token = "type_parameter"; }
{ face = "class"; token = "enum"; }
@ -85,7 +188,7 @@ let
verbosity = 255;
};
languageServerOption = types.submodule {
languageOption = types.submodule {
options = {
filetypes = mkOption {
type = types.listOf types.str;
@ -119,28 +222,10 @@ let
default = null;
description = "Additional settings to be passed to the LSP server.";
};
package = mkOption {
type = types.nullOr types.package;
default = null;
description = "The default package of the language server. Will be appended as the ending segments of the PATH to kak-lsp";
};
};
};
cfg = config.programs.kak-lsp;
serverPackages =
filter (v: v != null)
(lib.mapAttrsToList (_: serv: serv.package) cfg.languageServers);
wrappedPackage = pkgs.symlinkJoin {
name = "kak-lsp-wrapped";
nativeBuildInputs = [ pkgs.makeWrapper ];
paths = [ cfg.package ];
postBuild = ''
wrapProgram $out/bin/kak-lsp --suffix PATH ":" ${lib.makeBinPath serverPackages}
'';
};
in
{
options.programs.kak-lsp = {
@ -174,40 +259,35 @@ in
description = "Server timeout";
};
languageServers = mkOption {
type = types.attrsOf languageServerOption;
languages = mkOption {
type = types.attrsOf languageOption;
default = { };
description = "The language options";
};
languageIds = mkOption {
type = types.attrsOf types.str;
default = { };
description = "Language IDs to be sent to the LSP";
};
};
config = mkIf cfg.enable
{
home.packages = [ wrappedPackage ];
config = mkIf cfg.enable {
home.packages = [ cfg.package ];
# Configurations
xdg.configFile."kak-lsp/kak-lsp.toml" =
let
toml = pkgs.formats.toml { };
toLspConfig = lib.filterAttrsRecursive (n: v: n != "package" && v != null);
in
{
source = toml.generate "config.toml"
xdg.configFile."kak-lsp/kak-lsp.toml" = {
source = pkgs.runCommand "config.toml"
{
buildInputs = [ pkgs.yj ];
preferLocalBuild = true;
} ''
yj -jt -i \
< ${
pkgs.writeText "config.json" (builtins.toJSON {
semantic_tokens.faces = cfg.semanticTokens.faces ++ cfg.semanticTokens.additionalFaces;
server.timeout = cfg.serverTimeout;
snippet_support = cfg.enableSnippets;
verbosity = 255;
language_server = toLspConfig (lspConfig.language_servers // cfg.languageServers);
language_ids = lspConfig.language_ids // cfg.languageIds;
};
language = lspConfig.language // cfg.languages;
})
} \
> $out
'';
};
};
}

View file

@ -43,19 +43,6 @@ in
};
package = mkPackageOption pkgs "kak-tree-sitter" { };
features = {
highlighting = mkOption {
type = types.bool;
description = "Enable highlighting";
default = true;
};
text_objects = mkOption {
type = types.bool;
description = "Enable text objects";
default = true;
};
};
highlighterGroups = mkOption {
type = types.attrsOf types.str;
default = {
@ -103,11 +90,11 @@ in
markup_link_url = "%opt{teal}+u";
markup_link_uri = "%opt{teal}+u";
markup_link_text = "%opt{blue}";
markup_quote = "%opt{crust}";
markup_quote = "%opt{gray1}";
markup_raw = "%opt{sky}";
markup_raw_block = "%opt{sky}";
markup_raw_inline = "%opt{green}";
markup_strikethrough = "%opt{crust}+s";
markup_strikethrough = "%opt{gray1}+s";
namespace = "@module";
operator = "@operator";
property = "%opt{sky}";
@ -126,12 +113,12 @@ in
tag = "%opt{teal}";
tag_error = "%opt{red}";
text_title = "%opt{mauve}";
type = "@type";
type_enum_variant = "+i@ts_type";
type = "%opt{yellow}";
type_enum_variant = "%opt{flamingo}";
variable = "@variable";
variable_builtin = "@builtin";
variable_other_member = "%opt{teal}";
variable_parameter = "+i@variable";
variable_parameter = "%opt{maroon}+i";
warning = "%opt{peach}+b";
};
};
@ -187,6 +174,8 @@ in
config =
let
aliasedOnce = name: values: if asserts.assertMsg (builtins.length values 1) "face ${name} was aliased more than once: ${toString values}" then (builtins.head values) else [ ];
allGroups = attrsets.recursiveUpdate cfg.highlighterGroups cfg.extraHighlighterGroups;
aliases = attrsets.recursiveUpdate cfg.aliases cfg.extraAliases;
@ -200,24 +189,10 @@ in
toml = pkgs.formats.toml { };
srcName = src: lib.removePrefix "/nix/store/" src.outPath;
mkGitRepo = src: pkgs.runCommandLocal "${src.name}-git" { } ''
cp -r --no-preserve=all ${src} $out
cd $out
if ! test -d $out/.git; then
${lib.getExe pkgs.git} init -b ${srcName src}
${lib.getExe pkgs.git} config user.email "a@b.com"
${lib.getExe pkgs.git} config user.name "a"
${lib.getExe pkgs.git} add .
${lib.getExe pkgs.git} commit -m "Just making a git commit"
fi
'';
toLanguageConf = name: lang: with lang; {
grammar = {
inherit (grammar) path;
source.git.url = "${mkGitRepo grammar.src}";
source.git.pin = "${srcName grammar.src}";
url = "${grammar.src}";
compile = grammar.compile.command;
compile_args = grammar.compile.args;
compile_flags = grammar.compile.flags;
@ -226,8 +201,7 @@ in
link_flags = grammar.link.flags;
};
queries = {
source.git.url = "${mkGitRepo queries.src}";
source.git.pin = "${srcName queries.src}";
url = "${queries.src}";
path = if queries.path == null then "runtime/queries/${name}" else queries.path;
};
};
@ -246,13 +220,18 @@ in
xdg.configFile."kak-tree-sitter/config.toml" = {
source = toml.generate "config.toml" {
highlight.groups = builtins.map toScm (builtins.attrNames allGroups ++ builtins.attrNames aliases);
features = cfg.features;
language = builtins.mapAttrs toLanguageConf cfg.languages;
};
onChange = ''
export PATH=$PATH:${lib.getBin pkgs.gcc}
${cfg.package}/bin/ktsctl sync -a
onChange =
let
buildCmd = lang: "ktsctl -fci ${lang}";
buildAll = strings.concatMapStringsSep "\n" buildCmd (builtins.attrNames cfg.languages);
in
''
# Rebuild languages
${buildAll}
'';
};

View file

@ -2,28 +2,14 @@
let
cfg = config.nki.programs.kitty;
theme = { lib, options, config, ... }: {
programs.kitty = lib.mkIf config.nki.programs.kitty.enable (
if builtins.hasAttr "themeFile" options.programs.kitty then {
themeFile = "ayu_light";
} else {
theme = "Ayu Light";
}
);
};
cmd = if pkgs.stdenv.isDarwin then "cmd" else "ctrl";
in
with lib;
{
imports = [ theme ./darwin.nix ./linux.nix ./tabs.nix ];
imports = [ ./darwin.nix ./linux.nix ./tabs.nix ];
options.nki.programs.kitty = {
enable = mkEnableOption "Enable kitty";
setDefault = mkOption {
type = types.bool;
description = "Set kitty as default terminal";
default = true;
};
package = mkOption {
type = types.package;
@ -56,10 +42,6 @@ with lib;
};
};
config.linux.graphical = mkIf (cfg.enable && cfg.setDefault) {
defaults.terminal.package = cfg.package;
};
config.programs.kitty = mkIf cfg.enable {
enable = true;
@ -69,12 +51,14 @@ with lib;
font.name = "Fantasque Sans Mono";
font.size = cfg.fontSize;
theme = "Ayu Light";
settings =
let
# Background color and transparency
background =
if isNull cfg.background then {
background_opacity = "0.93";
background_opacity = "0.85";
dynamic_background_opacity = true;
} else {
background_image = "${cfg.background}";
@ -109,18 +93,6 @@ with lib;
## Hints
"${cfg.cmd}+shift+p>n" = "kitten hints --type=linenum --linenum-action=tab kak {path} +{line}";
};
extraConfig =
let
# Nerd Fonts glyph map
glyphMap = pkgs.fetchurl {
url = "https://raw.githubusercontent.com/Sharparam/dotfiles/main/kitty/.config/kitty/font-nerd-symbols.conf";
hash = "sha256-1OaDWLC3y8ASD2ttRWWgPEpRnfKXu6H6vS3cFVpzT0o=";
};
in
''
include ${glyphMap}
'';
};
}

View file

@ -1,388 +0,0 @@
{ config, osConfig, lib, pkgs, ... }:
let
cfg = config.programs.my-niri;
sh = config.lib.niri.actions.spawn "sh" "-c";
playerctl = lib.getExe pkgs.playerctl;
amixer = lib.getExe' pkgs.alsa-utils "amixer";
brightnessctl = lib.getExe pkgs.brightnessctl;
app-menu = "${pkgs.dmenu}/bin/dmenu_path | ${pkgs.bemenu}/bin/bemenu | ${pkgs.findutils}/bin/xargs swaymsg exec --";
wallpaper = config.linux.graphical.wallpaper;
xwayland-display = ":0";
in
{
options.programs.my-niri = {
enable = lib.mkEnableOption "My own niri configuration";
enableLaptop = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Enable laptop options";
};
lock-command = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "The command to lock the screen";
default = [ "${pkgs.swaylock}/bin/swaylock" ]
++ (if wallpaper == "" then [ "" ] else [ "-i" "${wallpaper}" "-s" "fill" ])
++ [ "-l" "-k" ];
};
workspaces = lib.mkOption {
type = lib.types.attrsOf
(lib.types.submodule {
options = {
name = lib.mkOption { type = lib.types.str; description = "workspace name"; };
fixed = lib.mkOption {
type = lib.types.bool;
default = true;
description = "whether workspace always exists";
};
monitor = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Default monitor to spawn workspace in";
};
};
});
description = "A mapping of ordering to workspace names, for fixed workspaces";
};
};
config = lib.mkIf cfg.enable {
programs.my-niri.workspaces = {
# Default workspaces, always there
"01" = { name = "🌏 web"; };
"02" = { name = "💬 chat"; };
"03" = { name = " code"; };
"04" = { name = "🎶 music"; };
"05" = { name = "🔧 extra"; };
"06" = { name = "🧰 6"; };
"07" = { name = "🔩 7"; };
"08" = { name = "🛠 8"; };
"09" = { name = "🔨 9"; };
"10" = { name = "🎲 misc"; };
"99" = { name = "📧 Email"; };
};
systemd.user.services.swaync.Install.WantedBy = [ "niri.service" ];
systemd.user.services.swaync.Unit.After = [ "niri.service" ];
systemd.user.targets.tray.Unit.After = [ "niri.service" ];
systemd.user.services.waybar.Unit.After = [ "niri.service" ];
systemd.user.services.waybar.Install.WantedBy = [ "niri.service" ];
systemd.user.targets.xwayland.Unit.After = [ "niri.service" ];
# xwayland-satellite
systemd.user.services.niri-xwayland-satellite = lib.mkIf cfg.enable {
Unit = {
Description = "XWayland Client for niri";
PartOf = [ "xwayland.target" ];
Before = [ "xwayland.target" "xdg-desktop-autostart.target" ];
After = [ "niri.service" ];
};
Install.UpheldBy = [ "niri.service" ];
Service.Slice = "session.slice";
Service.Type = "notify";
Service.ExecStart = "${lib.getExe pkgs.xwayland-satellite} ${xwayland-display}";
};
programs.niri.settings = {
environment = {
QT_QPA_PLATFORM = "wayland";
QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
QT_IM_MODULE = "fcitx";
GTK_IM_MODULE = "fcitx"; # Til text-input is merged
# export NIXOS_OZONE_WL=1 # Until text-input is merged
DISPLAY = xwayland-display;
} // lib.optionalAttrs osConfig.services.desktopManager.plasma6.enable {
XDG_MENU_PREFIX = "plasma-";
};
input.keyboard.xkb = { layout = "jp"; };
input.touchpad = lib.mkIf cfg.enableLaptop {
tap = true;
dwt = true;
natural-scroll = true;
middle-emulation = true;
};
input.mouse = {
accel-profile = "flat";
};
input.warp-mouse-to-focus = true;
input.focus-follows-mouse = {
enable = true;
max-scroll-amount = "0%";
};
outputs =
let
eachMonitor = _: monitor: {
name = monitor.meta.niriName or monitor.name; # Niri might not find the monitor by name
value = {
mode = monitor.meta.mode;
position = monitor.meta.fixedPosition or null;
scale = monitor.scale or 1;
variable-refresh-rate = (monitor.adaptive_sync or "off") == "on";
};
};
in
lib.mapAttrs' eachMonitor config.common.monitors;
spawn-at-startup = [
# Wallpaper
{ command = [ (lib.getExe pkgs.swaybg) "-i" "${wallpaper}" "-m" "fill" ]; }
# Waybar
{ command = [ "systemctl" "--user" "start" "xdg-desktop-portal-gtk.service" "xdg-desktop-portal.service" ]; }
];
layout = {
gaps = 16;
preset-column-widths = [
{ proportion = 1. / 3.; }
{ proportion = 1. / 2.; }
{ proportion = 2. / 3.; }
];
default-column-width.proportion = 1. / 2.;
focus-ring = {
width = 4;
active.gradient = { from = "#00447AFF"; to = "#71C4FFAA"; angle = 45; };
inactive.color = "#505050";
};
border.enable = false;
struts = let v = 8; in { left = v; right = v; bottom = v; top = v; };
};
prefer-no-csd = true;
workspaces =
let
fixedWorkspaces = lib.filterAttrs (_: w: w.fixed) cfg.workspaces;
workspaceConfig = lib.mapAttrs
(_: w: { inherit (w) name; } // (lib.optionalAttrs (w.monitor != null) {
open-on-output = w.monitor;
}))
fixedWorkspaces;
in
workspaceConfig;
window-rules = [
# Rounded Corners
{
geometry-corner-radius = let v = 8.0; in { bottom-left = v; bottom-right = v; top-left = v; top-right = v; };
clip-to-geometry = true;
}
# Workspace assignments
{
open-on-workspace = cfg.workspaces."01".name;
open-maximized = true;
matches = [
{ at-startup = true; app-id = "^firefox$"; }
{ at-startup = true; app-id = "^librewolf$"; }
{ at-startup = true; app-id = "^zen$"; }
];
}
{
open-on-workspace = cfg.workspaces."02".name;
open-maximized = true;
matches = [
{ title = "^((d|D)iscord|((A|a)rm(c|C)ord))$"; }
{ title = "VencordDesktop"; }
{ app-id = "VencordDesktop"; }
{ title = "vesktop"; }
{ app-id = "vesktop"; }
{ title = "Slack"; }
];
}
{
open-on-workspace = cfg.workspaces."99".name;
open-maximized = true;
matches = [
{ app-id = "thunderbird"; }
{ app-id = "evolution"; }
];
}
# Floating
{
open-floating = true;
matches = [
{ app-id = ".*float.*"; }
{ app-id = "org\\.freedesktop\\.impl\\.portal\\.desktop\\..*"; }
{ title = ".*float.*"; }
{ title = "Extension: .*Bitwarden.*"; }
{ app-id = "Rofi"; }
];
}
# xwaylandvideobridge
{
matches = [{ app-id = "^xwaylandvideobridge$"; }];
open-floating = true;
focus-ring.enable = false;
opacity = 0.0;
default-floating-position = {
x = 0;
y = 0;
relative-to = "bottom-right";
};
min-width = 1;
max-width = 1;
min-height = 1;
max-height = 1;
}
# Kitty dimming
{
matches = [{ app-id = "kitty"; }];
excludes = [{ is-focused = true; }];
opacity = 0.95;
}
];
layer-rules = [
{
matches = [{ namespace = "^swaync-.*"; }];
block-out-from = "screen-capture";
}
];
binds = with config.lib.niri.actions; {
# Mod-Shift-/, which is usually the same as Mod-?,
# shows a list of important hotkeys.
"Mod+Shift+Slash".action = show-hotkey-overlay;
# Some basic spawns
"Mod+Return".action = spawn (lib.getExe config.linux.graphical.defaults.terminal.package);
"Mod+Space".action = spawn (lib.getExe pkgs.rofi) "-show" "drun";
"Mod+R".action = sh app-menu;
"Mod+Semicolon".action = spawn cfg.lock-command;
"Mod+Shift+P".action = spawn "rofi-rbw-script";
# Audio and Volume
"XF86AudioPrev" = { action = spawn playerctl "previous"; allow-when-locked = true; };
"XF86AudioPlay" = { action = spawn playerctl "play-pause"; allow-when-locked = true; };
"Shift+XF86AudioPlay" = { action = spawn playerctl "stop"; allow-when-locked = true; };
"XF86AudioNext" = { action = spawn playerctl "next"; allow-when-locked = true; };
"XF86AudioRecord" = { action = spawn amixer "-q" "set" "Capture" "toggle"; allow-when-locked = true; };
"XF86AudioMute" = { action = spawn amixer "-q" "set" "Master" "toggle"; allow-when-locked = true; };
"XF86AudioLowerVolume" = { action = spawn amixer "-q" "set" "Master" "3%-"; allow-when-locked = true; };
"XF86AudioRaiseVolume" = { action = spawn amixer "-q" "set" "Master" "3%+"; allow-when-locked = true; };
# Backlight
"XF86MonBrightnessDown".action = spawn brightnessctl "s" "10%-";
"XF86MonBrightnessUp".action = spawn brightnessctl "s" "10%+";
"Shift+XF86MonBrightnessDown".action = spawn brightnessctl "-d" "kbd_backlight" "s" "25%-";
"Shift+XF86MonBrightnessUp".action = spawn brightnessctl "-d" "kbd_backlight" "s" "25%+";
"Mod+Shift+Q".action = close-window;
"Mod+Left".action = focus-column-or-monitor-left;
"Mod+Right".action = focus-column-or-monitor-right;
"Mod+Up".action = focus-window-or-workspace-up;
"Mod+Down".action = focus-window-or-workspace-down;
"Mod+H".action = focus-column-or-monitor-left;
"Mod+L".action = focus-column-or-monitor-right;
"Mod+K".action = focus-window-or-workspace-up;
"Mod+J".action = focus-window-or-workspace-down;
"Mod+Shift+Left".action = move-column-left-or-to-monitor-left;
"Mod+Shift+Right".action = move-column-right-or-to-monitor-right;
"Mod+Shift+Up".action = move-window-up-or-to-workspace-up;
"Mod+Shift+Down".action = move-window-down-or-to-workspace-down;
"Mod+Shift+H".action = move-column-left-or-to-monitor-left;
"Mod+Shift+L".action = move-column-right-or-to-monitor-right;
"Mod+Shift+K".action = move-window-up-or-to-workspace-up;
"Mod+Shift+J".action = move-window-down-or-to-workspace-down;
"Mod+Bracketleft".action = focus-column-first;
"Mod+Bracketright".action = focus-column-last;
"Mod+Shift+Bracketleft".action = move-column-to-first;
"Mod+Shift+Bracketright".action = move-column-to-last;
# For compat with my current sway
"Mod+Ctrl+H".action = move-workspace-to-monitor-left;
"Mod+Ctrl+L".action = move-workspace-to-monitor-right;
"Mod+I".action = focus-workspace-down;
"Mod+O".action = focus-workspace-up;
"Mod+Shift+I".action = move-column-to-workspace-down;
"Mod+Shift+O".action = move-column-to-workspace-up;
"Mod+Ctrl+I".action = move-workspace-down;
"Mod+Ctrl+O".action = move-workspace-up;
# Mouse bindings
"Mod+WheelScrollDown" = { action = focus-workspace-down; cooldown-ms = 150; };
"Mod+WheelScrollUp" = { action = focus-workspace-up; cooldown-ms = 150; };
"Mod+Ctrl+WheelScrollDown" = { action = move-column-to-workspace-down; cooldown-ms = 150; };
"Mod+Ctrl+WheelScrollUp" = { action = move-column-to-workspace-up; cooldown-ms = 150; };
"Mod+WheelScrollRight".action = focus-column-right;
"Mod+WheelScrollLeft".action = focus-column-left;
"Mod+Ctrl+WheelScrollRight".action = move-column-right;
"Mod+Ctrl+WheelScrollLeft".action = move-column-left;
# You can refer to workspaces by index. However, keep in mind that
# niri is a dynamic workspace system, so these commands are kind of
# "best effort". Trying to refer to a workspace index bigger than
# the current workspace count will instead refer to the bottommost
# (empty) workspace.
#
# For example, with 2 workspaces + 1 empty, indices 3, 4, 5 and so on
# will all refer to the 3rd workspace.
"Mod+1".action = focus-workspace (cfg.workspaces."01".name);
"Mod+2".action = focus-workspace (cfg.workspaces."02".name);
"Mod+3".action = focus-workspace (cfg.workspaces."03".name);
"Mod+4".action = focus-workspace (cfg.workspaces."04".name);
"Mod+5".action = focus-workspace (cfg.workspaces."05".name);
"Mod+6".action = focus-workspace (cfg.workspaces."06".name);
"Mod+7".action = focus-workspace (cfg.workspaces."07".name);
"Mod+8".action = focus-workspace (cfg.workspaces."08".name);
"Mod+9".action = focus-workspace (cfg.workspaces."09".name);
"Mod+0".action = focus-workspace (cfg.workspaces."10".name);
"Mod+Shift+1".action = move-column-to-workspace (cfg.workspaces."01".name);
"Mod+Shift+2".action = move-column-to-workspace (cfg.workspaces."02".name);
"Mod+Shift+3".action = move-column-to-workspace (cfg.workspaces."03".name);
"Mod+Shift+4".action = move-column-to-workspace (cfg.workspaces."04".name);
"Mod+Shift+5".action = move-column-to-workspace (cfg.workspaces."05".name);
"Mod+Shift+6".action = move-column-to-workspace (cfg.workspaces."06".name);
"Mod+Shift+7".action = move-column-to-workspace (cfg.workspaces."07".name);
"Mod+Shift+8".action = move-column-to-workspace (cfg.workspaces."08".name);
"Mod+Shift+9".action = move-column-to-workspace (cfg.workspaces."09".name);
"Mod+Shift+0".action = move-column-to-workspace (cfg.workspaces."10".name);
"Mod+asciicircum".action = focus-workspace (cfg.workspaces."99".name);
"Mod+Shift+asciicircum".action = move-column-to-workspace (cfg.workspaces."99".name);
"Mod+Tab".action = focus-workspace-previous;
"Mod+Comma".action = consume-or-expel-window-left;
"Mod+Period".action = consume-or-expel-window-right;
"Mod+W".action = switch-preset-column-width;
"Mod+Shift+W".action = switch-preset-window-height;
"Mod+Ctrl+W".action = reset-window-height;
"Mod+F".action = maximize-column;
"Mod+Shift+F".action = fullscreen-window;
"Mod+E".action = center-column;
"Mod+Minus".action = set-column-width "-10%";
"Mod+At".action = set-column-width "+10%";
"Mod+Shift+Minus".action = set-window-height "-10%";
"Mod+Shift+At".action = set-window-height "+10%";
"Mod+V".action = switch-focus-between-floating-and-tiling;
"Mod+Shift+V".action = toggle-window-floating;
"Mod+Shift+Space".action = toggle-window-floating; # Sway compat
"Print".action = screenshot;
"Ctrl+Print".action = screenshot-screen;
"Shift+Print".action = screenshot-window;
"Mod+Shift+E".action = quit;
};
};
};
}

View file

@ -1,4 +1,4 @@
{ pkgs, lib, options, config, osConfig, ... }:
{ pkgs, lib, config, ... }:
with lib;
let
cfg = config.programs.my-sway;
@ -18,9 +18,6 @@ let
"9:🔨 9"
"10:🎲 misc"
];
extraWorkspaces = {
mail = "📧 Email";
};
wsAttrs = builtins.listToAttrs (
map
(i: { name = toString (remainder i 10); value = builtins.elemAt workspaces (i - 1); })
@ -39,12 +36,9 @@ let
${pkgs.grim}/bin/grim -g (${pkgs.slurp}/bin/slurp) - | ${pkgs.wl-clipboard}/bin/wl-copy
'';
screenshotEditScript = pkgs.writeScriptBin "screenshot" ''
#! ${pkgs.fish}/bin/fish
ignored-devices = [ "Surface_Headphones" ];
playerctl = "${pkgs.playerctl}/bin/playerctl --ignore-player=${strings.concatStringsSep "," ignored-devices}";
${pkgs.grim}/bin/grim -g (${pkgs.slurp}/bin/slurp) - | ${pkgs.swappy}/bin/swappy -f -
'';
playerctl = "${pkgs.playerctl}/bin/playerctl";
in
{
# imports = [ ./ibus.nix ];
@ -63,23 +57,22 @@ in
wallpaper = mkOption {
type = types.oneOf [ types.path types.str ];
description = "Path to the wallpaper to be used";
default = config.linux.graphical.wallpaper;
default = "";
};
terminal = mkOption {
type = types.str;
description = "The command to the terminal emulator to be used";
default = lib.getExe config.linux.graphical.defaults.terminal.package;
default = "${config.programs.kitty.package}/bin/kitty";
};
browser = mkOption {
type = types.str;
description = "The command for the browser";
default = lib.getExe config.linux.graphical.defaults.webBrowser.package;
default = "${pkgs.firefox-wayland}/bin/firefox";
};
enableLaptop = lib.mkOption {
type = lib.types.bool;
description = "Whether to enable laptop-specific stuff";
default = true;
discord = mkOption {
type = types.nullOr types.str;
description = "The command for discord";
default = "${config.nki.programs.discord.package}/bin/discord";
};
lockCmd = mkOption {
@ -89,38 +82,40 @@ in
+ (if cfg.wallpaper == "" then "" else " -i ${cfg.wallpaper} -s fill")
+ " -l -k";
};
enableLaptopBars = mkOption {
type = types.bool;
description = "Whether to enable laptop-specific bars (battery)";
default = true;
};
enableMpd = mkOption {
type = types.bool;
description = "Whether to enable mpd on waybar";
default = false;
};
config.systemd.user.targets.sway-session = mkIf cfg.enable {
Unit.Before = [ "tray.target" "xwayland.target" "xdg-desktop-portal.service" "xdg-desktop-autostart.target" ];
Unit.Upholds = [ "waybar.service" ];
Unit.Wants = [ "xdg-desktop-autostart.target" ];
waybar = {
makeBars = mkOption {
type = types.raw;
description = "Create bars with the barWith function, return a list of bars";
default = barWith: [ (barWith { }) ];
};
extraSettings = mkOption {
type = types.raw;
description = "Extra settings to be included with every default bar";
default = { };
};
extraStyle = mkOption {
type = types.str;
description = "Additional style for the default waybar";
default = "";
};
};
# Enable waybar
config.programs.my-waybar = mkIf cfg.enable {
enable = true;
fontSize = mkDefault cfg.fontSize;
enableLaptopBars = mkDefault cfg.enableLaptop;
terminal = mkDefault cfg.terminal;
};
config.wayland.windowManager.sway = mkIf cfg.enable {
enable = true;
systemd.enable = true;
systemd.variables = options.wayland.windowManager.sway.systemd.variables.default ++ [
"PATH" # for portals
"XDG_DATA_DIRS" # For extra icons
"XDG_DATA_HOME" # For extra icons
] ++ lib.optionals osConfig.services.desktopManager.plasma6.enable [
"XDG_MENU_PREFIX"
];
# systemd.extraCommands = options.wayland.windowManager.sway.systemd.extraCommands.default
# ++ [
# "systemctl --user restart xdg-desktop-portal.service"
# ];
systemdIntegration = true;
checkConfig = false; # Not working atm
config = {
### Inputs
#
@ -147,13 +142,20 @@ in
menu = "${pkgs.dmenu}/bin/dmenu_path | ${pkgs.bemenu}/bin/bemenu | ${pkgs.findutils}/bin/xargs swaymsg exec --";
# Startup
startup = [
# # Dex for autostart
# { command = "${pkgs.dex}/bin/dex -ae sway"; }
# # Waybar
# { command = "systemctl --user restart waybar"; always = true; }
# IME
{ command = "fcitx5"; }
];
# Dex for autostart
{ command = "${pkgs.dex}/bin/dex -ae sway"; }
# Waybar
{ command = "systemctl --user restart waybar"; always = true; }
# Startup programs
{ command = "${cfg.browser}"; }
{ command = "thunderbird"; } # Rely on system package with plugins
] ++ (if cfg.discord != null then [
{ command = "${cfg.discord}"; }
] ++ lib.lists.optional
(!pkgs.stdenv.isAarch64)
(
{ command = "${pkgs.premid}/bin/premid"; }
) else [ ]);
### Keybindings
#
@ -206,7 +208,6 @@ in
# Launcher
"${mod}+space" = "exec rofi -show drun";
"${mod}+tab" = "exec ${./rofi-window.py}";
"${mod}+shift+p" = "exec rofi-rbw-script";
} // {
## Splits
"${mod}+v" = "split v";
@ -216,7 +217,6 @@ in
"${mod}+Shift+r" = "mode resize";
## Screenshot
"Print" = "exec ${screenshotScript}/bin/screenshot";
"Shift+Print" = "exec ${screenshotEditScript}/bin/screenshot";
## Locking
"${mod}+semicolon" = "exec ${cfg.lockCmd}";
## Multimedia
@ -248,11 +248,6 @@ in
])
(builtins.attrNames wsAttrs))
)) //
{
# Extra workspaces
"${mod}+asciicircum" = "workspace ${extraWorkspaces.mail}";
"${mod}+shift+asciicircum" = "move to workspace ${extraWorkspaces.mail}";
} //
# Move workspaces between outputs
{
"${mod}+ctrl+h" = "move workspace to output left";
@ -280,40 +275,21 @@ in
# Assigning windows to workspaces
assigns = {
"${builtins.elemAt workspaces 0}" = [
{ app_id = "^firefox$"; }
{ app_id = "^librewolf$"; }
{ app_id = "^zen$"; }
{ class = "^firefox$"; }
];
"${builtins.elemAt workspaces 1}" = [
{ class = "^((d|D)iscord|((A|a)rm(c|C)ord))$"; }
{ class = "VencordDesktop"; }
{ app_id = "VencordDesktop"; }
{ class = "vesktop"; }
{ app_id = "vesktop"; }
{ class = "Slack"; }
];
${extraWorkspaces.mail} = [
"📧 Email" = [
{ app_id = "thunderbird"; }
{ app_id = "evolution"; }
];
};
# Commands
window.commands = [
{ criteria = { title = ".*"; }; command = "inhibit_idle fullscreen"; }
] ++ (
# Floating assignments
let
criterias = [
{ app_id = ".*float.*"; }
{ app_id = "org\\.freedesktop\\.impl\\.portal\\.desktop\\..*"; }
{ class = ".*float.*"; }
{ title = "Extension: .*Bitwarden.*"; }
{ criteria = { app_id = ".*float.*"; }; command = "floating enable"; }
{ criteria = { class = ".*float.*"; }; command = "floating enable"; }
];
toCommand = criteria: { inherit criteria; command = "floating enable"; };
in
map toCommand criterias
);
# Focus
focus.followMouse = true;
focus.mouseWarping = true;
@ -322,7 +298,7 @@ in
gaps.outer = 4;
gaps.inner = 4;
gaps.smartBorders = "on";
gaps.smartGaps = false;
gaps.smartGaps = true;
### Bars
# Let systemd manage it
@ -336,27 +312,22 @@ in
swaynag.enable = true;
# Environment Variables
extraSessionCommands = ''
export MOZ_ENABLE_WAYLAND=1
export SDL_VIDEODRIVER=wayland
export QT_QPA_PLATFORM=wayland
export QT_WAYLAND_DISABLE_WINDOWDECORATION="1"
export QT_IM_MODULE=fcitx
export GTK_IM_MODULE=fcitx # Til text-input is merged
# export NIXOS_OZONE_WL=1 # Until text-input is merged
'' + (if config.services.gnome-keyring.enable then ''
# gnome-keyring
if type gnome-keyring-daemon >/dev/null; then
eval `gnome-keyring-daemon`
export SSH_AUTH_SOCK
fi
'' else "") + lib.optionalString osConfig.services.desktopManager.plasma6.enable ''
export XDG_MENU_PREFIX=plasma-
'';
'' else "");
# Extra
wrapperFeatures.base = true;
wrapperFeatures.gtk = true;
extraConfig =
(if cfg.enableLaptop then ''
(if cfg.enableLaptopBars then ''
# Lock screen on lid close
bindswitch lid:off exec ${cfg.lockCmd}
@ -382,32 +353,365 @@ in
'' + ''
# Enable portal stuff
exec ${pkgs.writeShellScript "start-portals.sh" ''
# Import the WAYLAND_DISPLAY env var from sway into the systemd user session.
dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=sway
# Stop any services that are running, so that they receive the new env var when they restart.
systemctl --user stop pipewire pipewire-media-session xdg-desktop-portal xdg-desktop-portal-wlr
systemctl --user start pipewire-media-session
''}
'';
};
config.services.swayidle = mkIf cfg.enable {
enable = true;
systemdTarget = "sway-session.target";
timeouts = [
# Lock after 15 minutes of idle
# { timeout = 15 * 60; command = cfg.lockCmd; }
];
events = [
{ event = "lock"; command = cfg.lockCmd; }
{ event = "before-sleep"; command = cfg.lockCmd; }
{ timeout = 15 * 60; command = cfg.lockCmd; }
];
};
config.programs.waybar =
let
barWith = { showMedia ? true, showConnectivity ? true, extraSettings ? { }, ... }: (mkMerge [{
position = "top";
modules-left = [
"sway/workspaces"
"sway/mode"
"sway/window"
];
modules-center = [
];
modules-right =
lib.optional showMedia (if cfg.enableMpd then "mpd" else "custom/media")
++ [
"tray"
"pulseaudio"
] ++ lib.optionals showConnectivity [
"bluetooth"
"network"
] ++ [
"cpu"
"memory"
"temperature"
] ++ lib.optionals cfg.enableLaptopBars [ "battery" "battery#bat2" ]
++ [
"clock"
];
modules = {
"sway/workspaces" = {
format = "{name}";
};
"sway/mode" = {
format = "<span style=\"italic\">{}</span>";
};
"sway/window" = {
max-length = 70;
format = "{title}";
"rewrite" = {
"(.*) Mozilla Firefox" = "[🌎] $1";
"(.*) - Mozilla Thunderbird" = "[📧] $1";
"(.*) - Kakoune" = "[] $1";
"(.*) - fish" = "[>_] $1";
"(.*) - Discord" = "[🗨] $1";
# ArmCord thing
" Discord \\| (.*)" = "[🗨] $1";
"\\((\\d+)\\) Discord \\| (.*)" = "[🗨] {$1} $2";
};
};
"tray" = {
icon-size = 21;
spacing = 10;
};
"clock" = {
tooltip-format = "{:%Y-%m-%d | %H:%M}";
format-alt = "{:%Y-%m-%d}";
};
"cpu" = {
format = "{usage}% ";
};
"memory" = {
format = "{}% ";
};
"temperature" = {
# thermal-zone = 2;
# hwmon-path" = "/sys/class/hwmon/hwmon2/temp1_input";
critical-threshold = 80;
# format-critical = "{temperatureC}°C ";
format = "{temperatureC}°C ";
};
"backlight" = {
# device = "acpi_video1";
format = "{percent}% {icon}";
states = [ 0 50 ];
format-icons = [ "" "" ];
};
"battery" = mkIf cfg.enableLaptopBars {
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "{capacity}% {icon}";
# format-good = ""; # An empty format will hide the module
# format-full = "";
format-icons = [ "" "" "" "" "" ];
};
"battery#bat2" = mkIf cfg.enableLaptopBars {
bat = "BAT2";
};
"network" = {
# interface = wlp2s0 # (Optional) To force the use of this interface
format-wifi = "{essid} ({signalStrength}%) ";
format-ethernet = "{ifname}: {ipaddr}/{cidr} ";
format-disconnected = "Disconnected ";
interval = 7;
};
"bluetooth" = {
format = " {status}";
format-connected = " {device_alias}";
format-connected-battery = " {device_alias} {device_battery_percentage}%";
# format-device-preference= [ "device1", "device2" ], // preference list deciding the displayed devic;
tooltip-format = "{controller_alias}\t{controller_address}\n\n{num_connections} connected";
tooltip-format-connected = "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}";
tooltip-format-enumerate-connected = "{device_alias}\t{device_address}";
tooltip-format-enumerate-connected-battery = "{device_alias}\t{device_address}\t{device_battery_percentage}%";
on-click = "${pkgs.blueman}/bin/blueman-manager";
};
"pulseaudio" = {
# scroll-step = 1;
format = "{volume}% {icon}";
format-bluetooth = "{volume}% {icon}";
format-muted = "";
format-icons = {
headphones = "";
handsfree = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [ "" "" ];
};
on-click = "${pkgs.pavucontrol}/bin/pavucontrol";
};
"mpd" = {
"format" = "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) 🎧";
"format-disconnected" = "Disconnected 🎧";
"format-stopped" = "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped 🎧";
"interval" = 2;
"consume-icons" = {
"on" = " "; # Icon shows only when "consume" is on
};
"random-icons" = {
"off" = "<span color=\"#f53c3c\"></span> "; # Icon grayed out when "random" is off;
"on" = " ";
};
"repeat-icons" = {
"on" = " ";
};
"single-icons" = {
"on" = "1 ";
};
"state-icons" = {
"paused" = "";
"playing" = "";
};
"tooltip-format" = "MPD (connected)";
"tooltip-format-disconnected" = "MPD (disconnected)";
"on-click" = "${pkgs.mpc_cli}/bin/mpc toggle";
"on-click-right" = "${pkgs.mpc_cli}/bin/mpc stop";
"on-click-middle" = "${cfg.terminal} --class=kitty_ncmpcpp ${pkgs.ncmpcpp}/bin/ncmpcpp";
};
"custom/media" = {
"format" = "{icon}{}";
"return-type" = "json";
"format-icons" = {
"Playing" = " ";
"Paused" = " ";
};
"max-length" = 80;
"exec" = "${playerctl} -a metadata --format '{\"text\": \"{{artist}} - {{markup_escape(title)}}\", \"tooltip\": \"{{playerName}} : {{markup_escape(title)}}\", \"alt\": \"{{status}}\", \"class\": \"{{status}}\"}' -F";
"on-click" = "${playerctl} play-pause";
};
};
}
cfg.waybar.extraSettings
extraSettings]);
in
mkIf cfg.enable {
enable = true;
systemd.enable = true;
settings = cfg.waybar.makeBars barWith;
style = ''
* {
border: none;
border-radius: 0;
font-family: monospace, 'Font Awesome 5', 'Symbols Nerd Font Mono', 'SFNS Display', Helvetica, Arial, sans-serif;
font-size: ${toString (cfg.fontSize * 1.1)}px;
min-height: 0;
}
window#waybar {
background: rgba(43, 48, 59, 0.8);
border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: #ffffff;
}
window#waybar.hidden {
opacity: 0.0;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button {
padding: 0 5px;
background: transparent;
color: #ffffff;
border-bottom: 3px solid transparent;
}
#workspaces button.focused {
background: #64727D;
border-bottom: 3px solid #ffffff;
}
#workspaces button.urgent {
background-color: #eb4d4b;
}
#window, #sway, #sway-window {
padding: 0 10px;
}
#mode {
background: #64727D;
border-bottom: 3px solid #ffffff;
}
#clock, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #bluetooth, #custom-media, #tray, #mode, #idle_inhibitor, #mpd {
padding: 0 10px;
margin: 0 5px;
}
#clock {
background-color: #64727D;
}
#battery {
background-color: #ffffff;
color: #000000;
}
#battery.charging {
color: #ffffff;
background-color: #26A65B;
}
@keyframes blink {
to {
background-color: #ffffff;
color: #000000;
}
}
#battery.critical:not(.charging) {
background: #f53c3c;
color: #ffffff;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
#cpu {
background: #2ecc71;
color: #000000;
}
#memory {
background: #9b59b6;
}
#backlight {
background: #90b1b1;
}
#network {
background: #2980b9;
}
#network.disconnected {
background: #f53c3c;
}
#pulseaudio {
background: #f1c40f;
color: #000000;
}
#pulseaudio.muted {
background: #90b1b1;
}
#bluetooth {
background: DarkSlateBlue;
color: white;
}
#custom-media {
background: #66cc99;
color: #2a5c45;
}
.custom-spotify {
background: #66cc99;
}
.custom-vlc {
background: #ffa000;
}
#temperature {
background: #f0932b;
}
#temperature.critical {
background: #eb4d4b;
}
#tray {
background-color: #2980b9;
}
#idle_inhibitor {
background-color: #2d3436;
}
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}
#mpd {
background-color: teal;
color: white;
}
'' + cfg.waybar.extraStyle;
};
config.home.packages = mkIf cfg.enable (with pkgs; [
# Needed for QT_QPA_PLATFORM
kdePackages.qtwayland
qt5.qtwayland
# For waybar
font-awesome
]);
config.programs.rofi = mkIf cfg.enable {
font = lib.mkForce "monospace ${toString cfg.fontSize}";
enable = true;
package = pkgs.rofi-wayland;
cycle = true;
font = "monospace ${toString cfg.fontSize}";
terminal = cfg.terminal;
theme = "Paper";
plugins = with pkgs; [ rofi-bluetooth rofi-calc rofi-rbw rofi-power-menu ];
};
}

View file

@ -1,427 +0,0 @@
{ lib, config, pkgs, ... }:
let
cfg = config.programs.my-waybar;
in
{
options.programs.my-waybar = {
enable = lib.mkEnableOption "custom configuration for waybar";
fontSize = lib.mkOption {
type = lib.types.float;
description = "The default font size";
};
terminal = lib.mkOption {
type = lib.types.str;
description = "The command to the terminal emulator to be used";
default = "${config.programs.kitty.package}/bin/kitty";
};
enableLaptopBars = lib.mkOption {
type = lib.types.bool;
description = "Whether to enable laptop-specific bars (battery)";
default = true;
};
enableMpd = lib.mkOption {
type = lib.types.bool;
description = "Whether to enable mpd on waybar";
default = false;
};
makeBars = lib.mkOption {
type = lib.types.raw;
description = "Create bars with the barWith function, return a list of bars";
default = barWith: [ (barWith { }) ];
};
extraSettings = lib.mkOption {
type = lib.types.listOf lib.types.raw;
description = "Extra settings to be included with every default bar";
default = [ ];
};
extraStyle = lib.mkOption {
type = lib.types.lines;
description = "Additional style for the default waybar";
default = "";
};
};
config.systemd.user.services.waybar = lib.mkIf cfg.enable {
Unit.Before = [ "tray.target" ];
};
config.programs.waybar =
let
barWith = { showMedia ? true, showConnectivity ? true, extraSettings ? { }, ... }: lib.mkMerge ([{
layer = "top";
position = "top";
modules-left = [
"sway/workspaces"
"sway/mode"
"sway/window"
"niri/workspaces"
"niri/window"
];
modules-center = [
];
modules-right =
lib.optional showMedia (if cfg.enableMpd then "mpd" else "custom/media")
++ [
"tray"
"pulseaudio"
] ++ lib.optionals showConnectivity [
"bluetooth"
"network"
] ++ [
"cpu"
"memory"
"temperature"
] ++ lib.optionals cfg.enableLaptopBars [ "battery" "battery#bat2" ]
++ [
"clock"
];
modules = {
"sway/workspaces" = {
format = "{name}";
};
"sway/mode" = {
format = "<span style=\"italic\">{}</span>";
};
"sway/window" = {
max-length = 70;
format = "{title}";
"rewrite" = {
"(.*) Mozilla Firefox" = "[🌎] $1";
"(.*) - Mozilla Thunderbird" = "[📧] $1";
"(.*) - Kakoune" = "[] $1";
"(.*) - fish" = "[>_] $1";
"(.*) - Discord" = "[🗨] $1";
# ArmCord thing
" Discord \\| (.*)" = "[🗨] $1";
"\\((\\d+)\\) Discord \\| (.*)" = "[🗨] {$1} $2";
};
};
"niri/window" = {
format = "{title}";
"rewrite" = {
"(.*) Mozilla Firefox" = "[🌎] $1";
"(.*) - Mozilla Thunderbird" = "[📧] $1";
"(.*) - Kakoune" = "[] $1";
"(.*) - fish" = "[>_] $1";
"(.*) - Discord" = "[🗨] $1";
# ArmCord thing
" Discord \\| (.*)" = "[🗨] $1";
"\\((\\d+)\\) Discord \\| (.*)" = "[🗨] {$1} $2";
};
};
"tray" = {
icon-size = 21;
spacing = 10;
};
"clock" = {
# format = "{:📅 %Y-%m-%d | 🕰️ %H:%M [%Z]}";
format = "📅 {0:%Y-%m-%d} | 🕰 {0:%H:%M [%Z]}";
tooltip-format = "\n<span size='9pt' font_family='Noto Sans Mono CJK JP'>{calendar}</span>";
timezones = [
"Europe/Zurich"
"America/Toronto"
"Asia/Tokyo"
"Asia/Ho_Chi_Minh"
];
calendar = {
mode = "year";
mode-mon-col = 3;
weeks-pos = "right";
on-scroll = 1;
on-click-right = "mode";
format = {
months = "<span color='#ffead3'><b>{}</b></span>";
days = "<span color='#ecc6d9'><b>{}</b></span>";
weeks = "<span color='#99ffdd'><b>W{}</b></span>";
weekdays = "<span color='#ffcc66'><b> </b></span>"; # See https://github.com/Alexays/Waybar/issues/3132
today = "<span color='#ff6699'><b><u>{}</u></b></span>";
};
};
actions = {
on-click-middle = "mode";
on-click-right = "tz_up";
on-scroll-up = "shift_up";
on-scroll-down = "shift_down";
};
};
"cpu" = {
format = "{usage}% ";
};
"memory" = {
format = "{}% ";
};
"temperature" = {
# thermal-zone = 2;
# hwmon-path" = "/sys/class/hwmon/hwmon2/temp1_input";
critical-threshold = 80;
# format-critical = "{temperatureC}°C ";
format = "{temperatureC}°C ";
};
"backlight" = {
# device = "acpi_video1";
format = "{percent}% {icon}";
states = [ 0 50 ];
format-icons = [ "" "" ];
};
"battery" = lib.mkIf cfg.enableLaptopBars {
states = {
good = 95;
warning = 30;
critical = 15;
};
format = "{capacity}% {icon}";
# format-good = ""; # An empty format will hide the module
# format-full = "";
format-icons = [ "" "" "" "" "" ];
};
"battery#bat2" = lib.mkIf cfg.enableLaptopBars {
bat = "BAT2";
};
"network" = {
# interface = wlp2s0 # (Optional) To force the use of this interface
format-wifi = "{essid} ({signalStrength}%) ";
format-ethernet = "{ifname}: {ipaddr}/{cidr} ";
format-disconnected = "Disconnected ";
interval = 7;
};
"bluetooth" = {
format = " {status}";
format-connected = " {device_alias}";
format-connected-battery = " {device_alias} {device_battery_percentage}%";
# format-device-preference= [ "device1", "device2" ], // preference list deciding the displayed devic;
tooltip-format = "{controller_alias}\t{controller_address}\n\n{num_connections} connected";
tooltip-format-connected = "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}";
tooltip-format-enumerate-connected = "{device_alias}\t{device_address}";
tooltip-format-enumerate-connected-battery = "{device_alias}\t{device_address}\t{device_battery_percentage}%";
on-click = "${pkgs.blueman}/bin/blueman-manager";
};
"pulseaudio" = {
# scroll-step = 1;
format = "{volume}% {icon}";
format-bluetooth = "{volume}% {icon}";
format-muted = "";
format-icons = {
headphones = "";
handsfree = "";
headset = "";
phone = "";
portable = "";
car = "";
default = [ "" "" ];
};
on-click = "${pkgs.pavucontrol}/bin/pavucontrol";
};
"mpd" = {
"format" = "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) 🎧";
"format-disconnected" = "Disconnected 🎧";
"format-stopped" = "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped 🎧";
"interval" = 2;
"consume-icons" = {
"on" = " "; # Icon shows only when "consume" is on
};
"random-icons" = {
"off" = "<span color=\"#f53c3c\"></span> "; # Icon grayed out when "random" is off;
"on" = " ";
};
"repeat-icons" = {
"on" = " ";
};
"single-icons" = {
"on" = "1 ";
};
"state-icons" = {
"paused" = "";
"playing" = "";
};
"tooltip-format" = "MPD (connected)";
"tooltip-format-disconnected" = "MPD (disconnected)";
"on-click" = "${pkgs.mpc_cli}/bin/mpc toggle";
"on-click-right" = "${pkgs.mpc_cli}/bin/mpc stop";
"on-click-middle" = "${cfg.terminal} --class=kitty_ncmpcpp ${pkgs.ncmpcpp}/bin/ncmpcpp";
};
"custom/media" = {
"format" = "{icon}{}";
"return-type" = "json";
"format-icons" = {
"Playing" = " ";
"Paused" = " ";
};
"max-length" = 80;
"exec" = "${lib.getExe pkgs.playerctl} -a metadata --format '{\"text\": \"{{artist}} - {{markup_escape(title)}}\", \"tooltip\": \"{{playerName}} : {{markup_escape(title)}}\", \"alt\": \"{{status}}\", \"class\": \"{{status}}\"}' -F";
"on-click" = "${lib.getExe pkgs.playerctl} play-pause";
};
};
}] ++
cfg.extraSettings
++ [ extraSettings ]);
in
lib.mkIf cfg.enable {
enable = true;
systemd.enable = true;
systemd.target = "sway-session.target";
settings = cfg.makeBars barWith;
style = ''
* {
border: none;
border-radius: 0;
font-family: monospace, 'Font Awesome 5', 'Symbols Nerd Font Mono', 'SFNS Display', Helvetica, Arial, sans-serif;
font-size: ${toString (cfg.fontSize * 1.1)}px;
min-height: 0;
}
window#waybar {
background: rgba(43, 48, 59, 0.8);
border-bottom: 3px solid rgba(100, 114, 125, 0.5);
color: #ffffff;
}
window#waybar.hidden {
opacity: 0.0;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button {
padding: 0 5px;
background: transparent;
color: #ffffff;
border-bottom: 3px solid transparent;
}
#workspaces button.focused {
background: #64727D;
border-bottom: 3px solid #ffffff;
}
#workspaces button.urgent {
background-color: #eb4d4b;
}
#window, #sway, #sway-window {
padding-left: 1em;
margin-bottom: 0.4em;
}
#mode {
background: #64727D;
border-bottom: 3px solid #ffffff;
}
/* #clock, #battery, #cpu, #memory, #temperature, #backlight, #network, #pulseaudio, #bluetooth, #custom-media, #tray, #mode, #idle_inhibitor, #mpd { */
.modules-right > * > * {
margin: 0.2em 0 0.4em 0;
padding: 0.2em 0.5em;
border: 1px solid rgba(0, 0, 0, 0.25);
border-radius: 0.3em;
}
.modules-right > *:not(:last-child) > * {
margin-right: 0.4em;
}
#clock {
background-color: #64727D;
}
#battery {
background-color: #ffffff;
color: #000000;
}
#battery.charging {
color: #ffffff;
background-color: #26A65B;
}
@keyframes blink {
to {
background-color: #ffffff;
color: #000000;
}
}
#battery.critical:not(.charging) {
background: #f53c3c;
color: #ffffff;
animation-name: blink;
animation-duration: 0.5s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
#cpu {
background: #2ecc71;
color: #000000;
}
#memory {
background: #9b59b6;
}
#backlight {
background: #90b1b1;
}
#network {
background: #2980b9;
}
#network.disconnected {
background: #f53c3c;
}
#pulseaudio {
background: #f1c40f;
color: #000000;
}
#pulseaudio.muted {
background: #90b1b1;
}
#bluetooth {
background: DarkSlateBlue;
color: white;
}
#custom-media {
background: #66cc99;
color: #2a5c45;
}
.custom-spotify {
background: #66cc99;
}
.custom-vlc {
background: #ffa000;
}
#temperature {
background: #f0932b;
}
#temperature.critical {
background: #eb4d4b;
}
#tray {
background-color: #2980b9;
}
#idle_inhibitor {
background-color: #2d3436;
}
#idle_inhibitor.activated {
background-color: #ecf0f1;
color: #2d3436;
}
#mpd {
background-color: teal;
color: white;
}
'' + cfg.extraStyle;
};
}

View file

@ -4,15 +4,12 @@ let
name = "openconnect-epfl";
runtimeInputs = with pkgs; [ openconnect rbw ];
text = ''
METHOD="Microsoft Entra ID"
RBW_ENTRY="EPFL Microsoft Auth"
GASPAR_PASSWORD=$(rbw get "$RBW_ENTRY")
GASPAR_TOKEN=$(rbw code "$RBW_ENTRY")
GASPAR_PASSWORD=$(rbw get gaspar)
GASPAR_TOKEN=$(rbw code gaspar)
printf "\n%s\n%s\n%s\n" "$METHOD" "$GASPAR_PASSWORD" "$GASPAR_TOKEN" | command sudo openconnect \
printf "%s\n%s" "$GASPAR_PASSWORD" "$GASPAR_TOKEN" | sudo openconnect \
--passwd-on-stdin \
-u "pham" \
--useragent='AnyConnect' \
-u pham \
"https://vpn.epfl.ch"
'';
};

View file

@ -1,120 +0,0 @@
{ pkgs, config, lib, ... }:
{
imports = [
# Common configuration
./common.nix
# We use our own firefox
# ./firefox.nix
# osu!
./osu.nix
];
# Home Manager needs a bit of information about you and the
# paths it should manage.
home.username = "nki";
home.homeDirectory = "/home/nki";
# More packages
home.packages = (with pkgs; [
# CLI stuff
python3
zip
# TeX
texlive.combined.scheme-full
# Note-taking
rnote
]);
# Graphical set up
linux.graphical.type = "wayland";
linux.graphical.wallpaper = ./images/wallpaper_0.png;
linux.graphical.defaults.webBrowser.package = pkgs.librewolf;
# Enable sway
programs.my-sway.enable = true;
programs.my-sway.fontSize = 14.0;
wayland.windowManager.sway.config = {
# Keyboard support
input."*".xkb_layout = "jp";
input."1278:34:HHKB-Hybrid_3_Keyboard".xkb_layout = "jp";
input."1:1:AT_Translated_Set_2_keyboard" = {
xkb_options = "ctrl:swapcaps";
};
input."2362:628:PIXA3854:00_093A:0274_Touchpad" = {
drag = "enabled";
natural_scroll = "enabled";
tap = "enabled";
};
};
programs.my-niri.enable = true;
programs.niri.settings = {
input.keyboard.xkb.options = "ctrl:swapcaps";
};
programs.my-waybar.extraSettings =
let
change-mode = pkgs.writeScript "change-mode" ''
#!/usr/bin/env ${lib.getExe pkgs.fish}
set -ax PATH ${lib.getBin pkgs.power-profiles-daemon} ${lib.getBin pkgs.rofi} ${lib.getBin pkgs.ripgrep}
set profiles (powerprofilesctl list | rg "^[ *] (\S+):" -r '$1')
set selected_index (math (contains -i (powerprofilesctl get) $profiles) - 1)
set new_profile (printf "%s\n" $profiles | rofi -dmenu -p "Switch to power profile" -a $selected_index)
powerprofilesctl set $new_profile
'';
in
[{
modules."battery"."on-click" = change-mode;
}];
# input-remapping
xdg.configFile."autostart/input-remapper-autoload.desktop".source =
"${pkgs.input-remapper}/share/applications/input-remapper-autoload.desktop";
# Kitty
nki.programs.kitty = {
enable = true;
fontSize = 16;
};
# Multiple screen setup
services.kanshi = with config.common.monitors; {
enable = true;
settings = [
{
profile.name = "undocked";
profile.outputs = [{ criteria = "eDP-1"; }];
}
{
profile.name = "work-both";
profile.outputs = [
{
criteria = "eDP-1";
position = "0,${toString (builtins.floor ((2160 / work.scale - 1200) + 1200 / 3))}";
status = "enable";
}
{ criteria = work.name; position = "1920,0"; }
];
}
{
profile.name = "work-one";
profile.outputs = [
{
criteria = "eDP-1";
status = "disable";
}
];
}
{ output.criteria = config.common.monitors.work.name; }
];
};
# This value determines the Home Manager release that your
# configuration is compatible with. This helps avoid breakage
# when a new Home Manager release introduces backwards
# incompatible changes.
#
# You can update Home Manager without changing this value. See
# the Home Manager release notes for a list of state version
# changes in each release.
home.stateVersion = "21.05";
}

View file

@ -1,20 +1,5 @@
{ pkgs, config, lib, ... }:
let
iio-sway = pkgs.stdenv.mkDerivation {
name = "iio-sway";
version = "0.0.1";
src = pkgs.fetchFromGitHub {
owner = "okeri";
repo = "iio-sway";
rev = "e07477d1b2478fede1446e97424a94c80767819d";
hash = "sha256-JGacKajslCOvd/BFfFSf7s1/hgF6rJqJ6H6xNnsuMb4=";
};
buildInputs = with pkgs; [ dbus ];
nativeBuildInputs = with pkgs; [ meson ninja pkg-config ];
meta.mainProgram = "iio-sway";
};
in
{
imports = [
# Common configuration
@ -22,7 +7,7 @@ in
# We use our own firefox
# ./firefox.nix
# osu!
./osu.nix
# ./osu.nix
];
# Home Manager needs a bit of information about you and the
@ -37,71 +22,27 @@ in
zip
# TeX
texlive.combined.scheme-full
# Note-taking
rnote
# Java & sbt
openjdk11
sbt
]);
# Graphical set up
linux.graphical.type = "wayland";
linux.graphical.wallpaper = ./images/wallpaper_0.png;
linux.graphical.startup = with pkgs; [ zen-browser-bin thunderbird vesktop slack ];
linux.graphical.defaults.webBrowser.package = pkgs.zen-browser-bin;
linux.graphical.defaults.webBrowser.desktopFile = "zen.desktop";
# Enable sway
programs.my-sway.enable = true;
programs.my-sway.fontSize = 14.0;
wayland.windowManager.sway.config = {
programs.my-sway.terminal = "${config.programs.kitty.package}/bin/kitty";
# Keyboard support
input."*".xkb_layout = "jp";
wayland.windowManager.sway.config = {
input."1278:34:HHKB-Hybrid_3_Keyboard".xkb_layout = "jp";
input."1:1:AT_Translated_Set_2_keyboard" = {
xkb_options = "ctrl:swapcaps";
};
# Touch and Pen
input."1386:21338:Wacom_HID_535A_Finger".map_to_output = "eDP-1";
input."1386:21338:Wacom_HID_535A_Pen".map_to_output = "eDP-1";
startup = [
# rotation
{ command = "${lib.getExe iio-sway}"; }
];
};
programs.my-niri.enable = true;
# Assign some of the workspaces to big screen
programs.my-niri.workspaces = lib.genAttrs [ "06" "07" "08" "09" "10" ] (_: { monitor = config.common.monitors.work.name; });
programs.niri.settings = {
# input.keyboard.xkb.options = "ctrl:swapcaps";
input.mouse = lib.mkForce {
# Make M575 fast for now
accel-profile = "adaptive";
accel-speed = 0.4;
};
input.touch.map-to-output = "eDP-1";
switch-events = with config.lib.niri.actions; {
tablet-mode-on.action = spawn "systemctl" "--user" "kill" "--signal" "SIGUSR2" "wvkbd";
tablet-mode-off.action = spawn "systemctl" "--user" "kill" "--signal" "SIGUSR1" "wvkbd";
xkb_layout = "us";
};
};
## Virtual keyboard
systemd.user.services.wvkbd = {
Unit = {
Description = "Wayland virtual keyboard";
};
Install.WantedBy = [ "waybar.service" ];
Service = {
Type = "simple";
ExecStart = "${pkgs.wvkbd}/bin/wvkbd-mobintl -l simple,special,emoji --landscape-layers simple,special,emoji --hidden";
};
};
wayland.windowManager.sway.extraConfig = ''
bindswitch tablet:on exec systemctl --user kill --signal SIGUSR2 wvkbd
bindswitch tablet:off exec systemctl --user kill --signal SIGUSR1 wvkbd
'';
# input-remapping
xdg.configFile."autostart/input-remapper-autoload.desktop".source =
"${pkgs.input-remapper}/share/applications/input-remapper-autoload.desktop";
# Kitty
nki.programs.kitty = {
enable = true;
@ -109,27 +50,12 @@ in
};
# Multiple screen setup
services.kanshi = with config.common.monitors; {
services.kanshi = {
enable = true;
settings = [
{
profile.name = "undocked";
profile.outputs = [{ criteria = "eDP-1"; }];
}
{
profile.name = "work-both";
profile.outputs = [
{ criteria = "eDP-1"; position = "0,${toString (builtins.floor ((2160 / work.scale - 1200) + 1200 / 3))}"; status = "enable"; }
{ criteria = work.name; position = "1920,0"; }
];
}
{
profile.name = "work-one";
profile.outputs = [
{ criteria = "eDP-1"; status = "disable"; }
{ criteria = work.name; }
];
}
profiles.undocked.outputs = [{ criteria = "LVDS-1"; }];
profiles.docked-hdmi.outputs = [
{ criteria = "LVDS-1"; status = "disable"; }
{ criteria = "HDMI-A-1"; }
];
};

View file

@ -1,32 +1,31 @@
{ pkgs, lib, ... }:
{ pkgs, config, lib, ... }:
let
# osu-pkg = pkgs.unstable.osu-lazer-bin;
osu-pkg = with pkgs; with lib;
appimageTools.wrapType2 rec {
pname = "osu-lazer-bin";
version = "2025.101.0";
version = "2023.123.0";
src = fetchurl {
url = "https://github.com/ppy/osu/releases/download/${version}/osu.AppImage";
hash = "sha256-GsnTxVpNk2RXHLET6Ugv0/ZOlq8RUkw2ZXqRjkU+dzw=";
sha256 = "sha256-edu93pvTEM5/s0kW55U1xfYGDl0eUpGXypvuYIwsM3w=";
};
extraPkgs = pkgs: with pkgs; [ icu ];
extraInstallCommands =
let contents = appimageTools.extract { inherit pname version src; };
in
''
mv -v $out/bin/${pname} $out/bin/osu\!
mv -v $out/bin/${pname}-${version} $out/bin/osu\!
install -m 444 -D ${contents}/osu\!.desktop -t $out/share/applications
for i in 16 32 48 64 96 128 256 512 1024; do
install -D ${contents}/osu.png $out/share/icons/hicolor/''${i}x$i/apps/osu.png
install -D ${contents}/osu\!.png $out/share/icons/hicolor/''${i}x$i/apps/osu\!.png
done
'';
};
in
{
home.packages = [ osu-pkg ];
xdg.mimeApps.defaultApplications."x-scheme-handler/osu" = "osu!.desktop";
# home.packages = [ pkgs.osu-lazer ];
}

View file

@ -13,14 +13,8 @@
../modules/personal/fonts
# Encrypted DNS
../modules/services/edns
# Override base mesa
({ ... }: { nixpkgs.overlays = lib.mkBefore [ (final: prev: { mesa = prev.mesa.override { enableOpenCL = true; meson = final.unstable.meson; }; }) ]; })
];
# time.timeZone = lib.mkForce "Asia/Ho_Chi_Minh";
services.xserver.desktopManager.plasma5.enable = true;
# Asahi kernel configuration
hardware.asahi = {
peripheralFirmwareDirectory = ./firmware;
@ -30,30 +24,6 @@
useExperimentalGPUDriver = true;
experimentalGPUInstallMode = "overlay";
};
# Override mesa
nixpkgs.overlays = lib.mkAfter [
(final: prev: {
mesa-asahi-edge = prev.mesa-asahi-edge.overrideAttrs (attrs: {
version = "24.0.0";
# buildInputs = attrs.buildInputs ++ (with pkgslw; [ libclc cmake (spirv-llvm-translator.override { inherit (llvmPackages_15) llvm; }) ]);
# nativeBuildInputs = attrs.nativeBuildInputs ++ (with pkgs; [ pkgs.unstable.spirv-llvm-translator ]);
src = final.fetchFromGitLab {
# latest release
domain = "gitlab.freedesktop.org";
owner = "asahi";
repo = "mesa";
rev = "asahi-20231121";
hash = "sha256-IcKKe1RA8sCaUfWK71ELzF15YaBS3DjoYhNMIWiQ5Jw=";
};
patches = lib.forEach attrs.patches (p:
if lib.hasSuffix "opencl.patch" p
then ./mesa-asahi-edge/opencl.patch else p);
});
})
];
## Additional mesa-related packages
environment.systemPackages = with pkgs; [ SDL2 ];
# Power Management
services.upower = {
@ -70,7 +40,7 @@
services.printing.drivers = with pkgs; [ epfl-cups-drivers ];
# Enable touchpad support (enabled default in most desktopManager).
services.libinput.enable = true;
services.xserver.libinput.enable = true;
# Keyboard
services.input-remapper.enable = true;
services.input-remapper.serviceWantedBy = [ "multi-user.target" ];

View file

@ -1,66 +0,0 @@
diff --git a/meson.build b/meson.build
index 04d89987311..babfe440973 100644
--- a/meson.build
+++ b/meson.build
@@ -1812,7 +1812,7 @@ endif
dep_clang = null_dep
if with_clc
- llvm_libdir = dep_llvm.get_variable(cmake : 'LLVM_LIBRARY_DIR', configtool: 'libdir')
+ llvm_libdir = get_option('clang-libdir')
dep_clang = cpp.find_library('clang-cpp', dirs : llvm_libdir, required : false)
diff --git a/meson_options.txt b/meson_options.txt
index e885ba61a8a..29ce0270479 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,6 +1,12 @@
# Copyright © 2017-2019 Intel Corporation
# SPDX-License-Identifier: MIT
+option(
+ 'clang-libdir',
+ type : 'string',
+ value : '',
+ description : 'Locations to search for clang libraries.'
+)
option(
'platforms',
type : 'array',
diff --git a/src/gallium/targets/opencl/meson.build b/src/gallium/targets/opencl/meson.build
index 7c14135898e..74dc6850603 100644
--- a/src/gallium/targets/opencl/meson.build
+++ b/src/gallium/targets/opencl/meson.build
@@ -39,7 +39,8 @@ if dep_llvm.version().version_compare('>=10.0.0')
polly_isl_dep = cpp.find_library('PollyISL', dirs : llvm_libdir, required : false)
endif
-dep_clang = cpp.find_library('clang-cpp', dirs : llvm_libdir, required : false)
+clang_libdir = get_option('clang-libdir')
+dep_clang = cpp.find_library('clang-cpp', dirs : clang_libdir, required : false)
# meson will return clang-cpp from system dirs if it's not found in llvm_libdir
linker_rpath_arg = '-Wl,--rpath=@0@'.format(llvm_libdir)
@@ -123,7 +124,7 @@ if with_opencl_icd
configuration : _config,
input : 'mesa.icd.in',
output : 'mesa.icd',
- install : true,
+ install : false,
install_tag : 'runtime',
install_dir : join_paths(get_option('sysconfdir'), 'OpenCL', 'vendors'),
)
diff --git a/src/gallium/targets/rusticl/meson.build b/src/gallium/targets/rusticl/meson.build
index b2963fe6dfa..99d6d801b94 100644
--- a/src/gallium/targets/rusticl/meson.build
+++ b/src/gallium/targets/rusticl/meson.build
@@ -76,7 +76,7 @@ configure_file(
configuration : _config,
input : 'rusticl.icd.in',
output : 'rusticl.icd',
- install : true,
+ install : false,
install_tag : 'runtime',
install_dir : join_paths(get_option('sysconfdir'), 'OpenCL', 'vendors'),
)

View file

@ -12,8 +12,8 @@ let
images = {
postgresql = mkImage {
imageName = "postgres";
finalImageTag = "16-alpine";
imageDigest = "sha256:b40547ea0c7bcb401d8f11c6a233ebe65e2067e5966e54ccf9b03c5f01c2957c";
finalImageTag = "12-alpine";
imageDigest = "sha256:f52ffee699232c84d820c35c28656363f4fda6a3e3e934b83f4e5e1898e2bdfa";
};
redis = mkImage {
imageName = "redis";
@ -22,8 +22,8 @@ let
};
authentik = mkImage {
imageName = "ghcr.io/goauthentik/server";
finalImageTag = "2024.1.0.4";
imageDigest = "sha256:5843c56a56be58dcee8927c705f8c9def0af7189511c0e8e687d430f543b0f6d";
finalImageTag = "2023.5.5";
imageDigest = "sha256:7a82b9dcd0236e06079b3c78e7b3f1b8f3c597a7d2026c60244d685d4908a23b";
};
};
authentikEnv = pkgs.writeText "authentik.env" ''
@ -48,14 +48,7 @@ in
};
config = mkIf cfg.enable {
systemd.services.arion-authentik = {
serviceConfig.EnvironmentFile = cfg.envFile;
serviceConfig.Type = "notify";
serviceConfig.NotifyAccess = "all";
script = lib.mkBefore ''
${lib.getExe pkgs.wait4x} http http://127.0.0.1:${toString cfg.port} --expect-status-code 200 -t 0 -q -- systemd-notify --ready &
'';
};
systemd.services.arion-authentik.serviceConfig.EnvironmentFile = cfg.envFile;
virtualisation.arion.projects.authentik.settings = {
services.postgresql.service = {
image = images.postgresql;
@ -105,7 +98,6 @@ in
ports = [
"127.0.0.1:${toString cfg.port}:9000"
];
};
services.worker.service = {
image = images.authentik;
@ -124,7 +116,6 @@ in
AUTHENTIK_POSTGRESQL__NAME = "authentik";
};
env_file = [ cfg.envFile "${authentikEnv}" ];
user = "root";
};
docker-compose.volumes = {
database.driver = "local";

View file

@ -10,19 +10,12 @@ let
user = "bitwarden";
port = 8001;
notificationsPort = 8002;
host = "bw.nkagami.me";
package = pkgs.vaultwarden-postgresql;
in
{
options.cloud.bitwarden = {
envFile = mkOption {
type = types.nullOr types.path;
description = "Path to the env file containing stuff";
default = null;
};
};
options.cloud.bitwarden = { };
config = {
# users
@ -36,7 +29,11 @@ in
# traefik
cloud.traefik.hosts.bitwarden = {
inherit port host;
noCloudflare = true;
};
cloud.traefik.hosts.bitwarden-notifications = {
inherit host;
port = notificationsPort;
path = "/notifications/hub";
};
# systemd unit
systemd.services.bitwarden-server = {
@ -47,20 +44,19 @@ in
DATABASE_URL = databaseUrl;
DATA_FOLDER = "/var/lib/bitwarden-server";
WEB_VAULT_FOLDER = "${pkgs.vaultwarden-vault}/share/vaultwarden/vault";
WEB_VAULT_FOLDER = "${pkgs.unstable.vaultwarden-vault}/share/vaultwarden/vault";
ROCKET_PORT = toString port;
PUSH_ENABLED = "true";
WEBSOCKET_ENABLED = "true";
WEBSOCKET_PORT = toString notificationsPort;
DOMAIN = "https://${host}";
};
serviceConfig = {
User = user;
Group = user;
ExecStart = "${package}/bin/vaultwarden";
EnvironmentFile = lists.optional (cfg.envFile != null) cfg.envFile;
ExecStart = "${pkgs.unstable.vaultwarden-postgresql}/bin/vaultwarden";
LimitNOFILE = "1048576";
PrivateTmp = "true";
PrivateDevices = "true";
@ -69,7 +65,6 @@ in
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
StateDirectory = "bitwarden-server";
};
requires = [ "postgresql.service" ];
wantedBy = [ "multi-user.target" ];
};
};

View file

@ -45,10 +45,6 @@ with lib;
port = mkOption {
type = types.int;
};
noCloudflare = mkOption {
type = types.bool;
default = false;
};
allow_registration = mkOption {
type = types.bool;
default = false;
@ -73,9 +69,7 @@ with lib;
global.server_name = server_name;
global.port = instance.port;
global.allow_registration = instance.allow_registration;
global.database_path = "/mnt/data/${srvName}/";
global.well_known_client = "https://${instance.host}";
global.well_known_server = "${instance.host}:443";
global.database_path = "/var/lib/${srvName}/";
});
in
{
@ -105,8 +99,7 @@ with lib;
"@system-service"
"~@privileged"
];
# StateDirectory = "/mnt/data/${srvName}";
BindPaths = [ "/mnt/data/${srvName}" ];
StateDirectory = "${srvName}";
ExecStart = "${cfg.package}/bin/conduit";
Restart = "on-failure";
RestartSec = 10;
@ -116,12 +109,55 @@ with lib;
))
cfg.instances);
# Serving .well-known files
# This is a single .well-known/matrix/server file that points to the server,
# which is NOT on port 8448 since Cloudflare doesn't allow us to route HTTPS
# through that port.
config.services.nginx = mkIf cfg.enable
{
enable = true;
virtualHosts = lib.attrsets.mapAttrs'
(name: instance: lib.attrsets.nameValuePair "conduit-${name}-well-known" {
listen = [{ addr = "127.0.0.1"; port = instance.well-known_port; }];
# Check https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-well-known.md
# for the file structure.
root = pkgs.symlinkJoin
{
name = "well-known-files-for-conduit-${name}";
paths = [
(pkgs.writeTextDir ".well-known/matrix/client" (builtins.toJSON {
"m.homeserver".base_url = "https://${instance.host}";
}))
(pkgs.writeTextDir ".well-known/matrix/server" (builtins.toJSON {
"m.server" = "${instance.host}:443";
}))
];
};
# Enable CORS from anywhere since we want all clients to find us out
extraConfig = ''
add_header 'Access-Control-Allow-Origin' "*";
'';
})
cfg.instances;
};
config.cloud.traefik.hosts = mkIf cfg.enable (
(lib.attrsets.mapAttrs'
(name: instance: lib.attrsets.nameValuePair "conduit-${name}" ({
inherit (instance) host port noCloudflare;
inherit (instance) host port;
}))
cfg.instances)
// (lib.attrsets.mapAttrs'
(name: instance: lib.attrsets.nameValuePair "conduit-${name}-well-known" (
let
server_name = if instance.server_name == "" then instance.host else instance.server_name;
in
{
port = instance.well-known_port;
filter = "Host(`${server_name}`) && PathPrefix(`/.well-known`)";
}
))
cfg.instances)
);
}

View file

@ -33,7 +33,7 @@ with lib; {
{
systemd.services.heisenbridge = {
description = "Matrix<->IRC bridge";
requires = [ "matrix-conduit-nkagami.service" "matrix-synapse.service" ]; # So the registration file can be used by Synapse
before = [ "matrix-synapse.service" ]; # So the registration file can be used by Synapse
wantedBy = [ "multi-user.target" ];
serviceConfig = rec {

View file

@ -4,12 +4,34 @@ let
cfg = config.cloud.gotosocial;
dbUser = "gotosocial";
storageLocation = "/mnt/data/gotosocial";
configFile = pkgs.writeText "config.yml" (generators.toYAML { } {
# General
host = cfg.host;
account-domain = cfg.accountDomain;
bind-address = "localhost";
port = cfg.port;
# Database
db-port = 0; # Use socket
db-user = dbUser;
db-database = dbUser;
# Web
web-template-base-dir = "${cfg.package}/share/web/template";
web-asset-base-dir = "${cfg.package}/share/web/assets";
# OIDC
oidc-enabled = true;
oidc-idp-name = "DTTH";
oidc-scopes = [ "openid" "email" "profile" ];
# HTTP Client
http-client.block-ips = [ "11.0.0.0/24" ];
# Advanced
advanced-rate-limit-requests = 0;
});
in
{
options.cloud.gotosocial = {
enable = mkEnableOption "Enable our local GtS server";
package = mkPackageOption pkgs "gotosocial" { };
package = mkPackageOption pkgs "gotosocial-bin" { };
host = mkOption {
type = types.str;
description = "The GtS host";
@ -49,59 +71,48 @@ in
};
} else { });
# The service itself
services.gotosocial = {
enable = true;
package = cfg.package;
environmentFile = cfg.envFile;
settings = {
# General
host = cfg.host;
account-domain = cfg.accountDomain;
bind-address = "localhost";
port = cfg.port;
# Instance
instance-languages = [ "en-ca" "vi" ];
# Accounts
accounts-registration-open = false;
accounts-allow-custom-css = true;
# Database
db-type = "postgres";
db-address = "/run/postgresql"; # Use socket
db-user = dbUser;
db-database = dbUser;
# Web
web-template-base-dir = "${cfg.package}/share/gotosocial/web/template";
web-asset-base-dir = "${cfg.package}/share/gotosocial/web/assets";
# Media
media-emoji-remote-max-size = 256 * 1024 /* bytes */;
media-emoji-local-max-size = 256 * 1024 /* bytes */;
media-remote-cache-days = 7;
media-cleanup-from = "00:00";
media-cleanup-every = "24h";
# OIDC
oidc-enabled = true;
oidc-idp-name = "DTTH";
oidc-scopes = [ "openid" "email" "profile" ];
# HTTP Client
http-client.block-ips = [ "11.0.0.0/24" ];
# Advanced
advanced-rate-limit-requests = 0;
# Storage
storage-backend = "local";
storage-local-base-path = "${storageLocation}/storage";
# instance-inject-mastodon-version = true;
systemd.services.gotosocial = {
after = [ "network.target" ];
serviceConfig = {
User = dbUser;
Group = dbUser;
ExecStart = "${cfg.package}/bin/gotosocial --config-path ${configFile} server start";
EnvironmentFile = cfg.envFile;
# Sandboxing options to harden security
# Details for these options: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
NoNewPrivileges = "yes";
PrivateTmp = "yes";
PrivateDevices = "yes";
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
RestrictNamespaces = "yes";
RestrictRealtime = "yes";
DevicePolicy = "closed";
ProtectSystem = "full";
ProtectControlGroups = "yes";
ProtectKernelModules = "yes";
ProtectKernelTunables = "yes";
LockPersonality = "yes";
SystemCallFilter = "~@clock @debug @module @mount @obsolete @reboot @setuid @swap";
# Denying access to capabilities that should not be relevant
# Doc: https://man7.org/linux/man-pages/man7/capabilities.7.html
CapabilityBoundingSet = strings.concatStringsSep " " [
"CAP_RAWIO CAP_MKNOD"
"CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE"
"CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT"
"CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK"
"CAP_BLOCK_SUSPEND CAP_WAKE_ALARM"
"CAP_SYS_TTY_CONFIG"
"CAP_MAC_ADMIN CAP_MAC_OVERRIDE"
"CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW"
"CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG "
];
# You might need this if you are running as non-root on a privileged port (below 1024)
#AmbientCapabilities=CAP_NET_BIND_SERVICE
StateDirectory = "gotosocial";
WorkingDirectory = "/var/lib/gotosocial";
};
};
systemd.services.gotosocial.requires = mkAfter [ "postgresql.service" "arion-authentik.service" ];
systemd.services.gotosocial.after = mkAfter [ "postgresql.service" "arion-authentik.service" ];
systemd.services.gotosocial.unitConfig = {
RequiresMountsFor = [ storageLocation ];
ReadWritePaths = [ storageLocation ];
};
systemd.tmpfiles.settings."10-gotosocial".${storageLocation}.d = {
user = dbUser;
group = dbUser;
mode = "0700";
wantedBy = [ "multi-user.target" ];
};
};
}

View file

@ -268,14 +268,12 @@ in
cloud.traefik.hosts.maddy-mta-sts = {
port = mtaStsPort;
host = "mta-sts.nkagami.me";
noCloudflare = true;
};
# maddy itself
systemd.services."${name}" = {
after = [ "network.target" "traefik-certs-dumper.service" ];
wantedBy = [ "multi-user.target" ];
requires = [ "postgresql.service" ];
description = "maddy mail server";
documentation = [

788
modules/cloud/outline.nix Normal file
View file

@ -0,0 +1,788 @@
# Stub until 0.69.2 lands in stable
{ config, lib, pkgs, ... }:
let
defaultUser = "outline";
cfg = config.cloud.services.outline;
in
{
# See here for a reference of all the options:
# https://github.com/outline/outline/blob/v0.67.0/.env.sample
# https://github.com/outline/outline/blob/v0.67.0/app.json
# https://github.com/outline/outline/blob/v0.67.0/server/env.ts
# https://github.com/outline/outline/blob/v0.67.0/shared/types.ts
# The order is kept the same here to make updating easier.
options.cloud.services.outline = {
enable = lib.mkEnableOption (lib.mdDoc "outline");
package = lib.mkOption {
default = pkgs.unstable.outline;
defaultText = lib.literalExpression "pkgs.unstable.outline";
type = lib.types.package;
example = lib.literalExpression ''
pkgs.outline.overrideAttrs (super: {
# Ignore the domain part in emails that come from OIDC. This is might
# be helpful if you want multiple users with different email providers
# to still land in the same team. Note that this effectively makes
# Outline a single-team instance.
patchPhase = ${"''"}
sed -i 's/const domain = parts\.length && parts\[1\];/const domain = "example.com";/g' server/routes/auth/providers/oidc.ts
${"''"};
})
'';
description = lib.mdDoc "Outline package to use.";
};
user = lib.mkOption {
type = lib.types.str;
default = defaultUser;
description = lib.mdDoc ''
User under which the service should run. If this is the default value,
the user will be created, with the specified group as the primary
group.
'';
};
group = lib.mkOption {
type = lib.types.str;
default = defaultUser;
description = lib.mdDoc ''
Group under which the service should run. If this is the default value,
the group will be created.
'';
};
sequelizeArguments = lib.mkOption {
type = lib.types.str;
default = "";
example = "--env=production-ssl-disabled";
description = lib.mdDoc ''
Optional arguments to pass to `sequelize` calls.
'';
};
#
# Required options
#
secretKeyFile = lib.mkOption {
type = lib.types.str;
default = "/var/lib/outline/secret_key";
description = lib.mdDoc ''
File path that contains the application secret key. It must be 32
bytes long and hex-encoded. If the file does not exist, a new key will
be generated and saved here.
'';
};
utilsSecretFile = lib.mkOption {
type = lib.types.str;
default = "/var/lib/outline/utils_secret";
description = lib.mdDoc ''
File path that contains the utility secret key. If the file does not
exist, a new key will be generated and saved here.
'';
};
databaseUrl = lib.mkOption {
type = lib.types.str;
default = "local";
description = lib.mdDoc ''
URI to use for the main PostgreSQL database. If this needs to include
credentials that shouldn't be world-readable in the Nix store, set an
environment file on the systemd service and override the
`DATABASE_URL` entry. Pass the string
`local` to setup a database on the local server.
'';
};
redisUrl = lib.mkOption {
type = lib.types.str;
default = "local";
description = lib.mdDoc ''
Connection to a redis server. If this needs to include credentials
that shouldn't be world-readable in the Nix store, set an environment
file on the systemd service and override the
`REDIS_URL` entry. Pass the string
`local` to setup a local Redis database.
'';
};
publicUrl = lib.mkOption {
type = lib.types.str;
default = "http://localhost:3000";
description = lib.mdDoc "The fully qualified, publicly accessible URL";
};
port = lib.mkOption {
type = lib.types.port;
default = 3000;
description = lib.mdDoc "Listening port.";
};
storage = lib.mkOption {
description = lib.mdDoc ''
To support uploading of images for avatars and document attachments an
s3-compatible storage must be provided. AWS S3 is recommended for
redundancy however if you want to keep all file storage local an
alternative such as [minio](https://github.com/minio/minio)
can be used.
A more detailed guide on setting up S3 is available
[here](https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f).
'';
example = lib.literalExpression ''
{
accessKey = "...";
secretKeyFile = "/somewhere";
uploadBucketUrl = "https://minio.example.com";
uploadBucketName = "outline";
region = "us-east-1";
}
'';
type = lib.types.submodule {
options = {
accessKey = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "S3 access key.";
};
secretKeyFile = lib.mkOption {
type = lib.types.path;
description = lib.mdDoc "File path that contains the S3 secret key.";
};
region = lib.mkOption {
type = lib.types.str;
default = "xx-xxxx-x";
description = lib.mdDoc "AWS S3 region name.";
};
uploadBucketUrl = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
URL endpoint of an S3-compatible API where uploads should be
stored.
'';
};
uploadBucketName = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Name of the bucket where uploads should be stored.";
};
uploadMaxSize = lib.mkOption {
type = lib.types.int;
default = 26214400;
description = lib.mdDoc "Maxmium file size for uploads.";
};
forcePathStyle = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc "Force S3 path style.";
};
acl = lib.mkOption {
type = lib.types.str;
default = "private";
description = lib.mdDoc "ACL setting.";
};
};
};
};
#
# Authentication
#
slackAuthentication = lib.mkOption {
description = lib.mdDoc ''
To configure Slack auth, you'll need to create an Application at
https://api.slack.com/apps
When configuring the Client ID, add a redirect URL under "OAuth & Permissions"
to `https://[publicUrl]/auth/slack.callback`.
'';
default = null;
type = lib.types.nullOr (lib.types.submodule {
options = {
clientId = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Authentication key.";
};
secretFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "File path containing the authentication secret.";
};
};
});
};
googleAuthentication = lib.mkOption {
description = lib.mdDoc ''
To configure Google auth, you'll need to create an OAuth Client ID at
https://console.cloud.google.com/apis/credentials
When configuring the Client ID, add an Authorized redirect URI to
`https://[publicUrl]/auth/google.callback`.
'';
default = null;
type = lib.types.nullOr (lib.types.submodule {
options = {
clientId = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Authentication client identifier.";
};
clientSecretFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "File path containing the authentication secret.";
};
};
});
};
azureAuthentication = lib.mkOption {
description = lib.mdDoc ''
To configure Microsoft/Azure auth, you'll need to create an OAuth
Client. See
[the guide](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4)
for details on setting up your Azure App.
'';
default = null;
type = lib.types.nullOr (lib.types.submodule {
options = {
clientId = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Authentication client identifier.";
};
clientSecretFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "File path containing the authentication secret.";
};
resourceAppId = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Authentication application resource ID.";
};
};
});
};
oidcAuthentication = lib.mkOption {
description = lib.mdDoc ''
To configure generic OIDC auth, you'll need some kind of identity
provider. See the documentation for whichever IdP you use to fill out
all the fields. The redirect URL is
`https://[publicUrl]/auth/oidc.callback`.
'';
default = null;
type = lib.types.nullOr (lib.types.submodule {
options = {
clientId = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Authentication client identifier.";
};
clientSecretFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "File path containing the authentication secret.";
};
authUrl = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "OIDC authentication URL endpoint.";
};
tokenUrl = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "OIDC token URL endpoint.";
};
userinfoUrl = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "OIDC userinfo URL endpoint.";
};
usernameClaim = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
Specify which claims to derive user information from. Supports any
valid JSON path with the JWT payload
'';
default = "preferred_username";
};
displayName = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Display name for OIDC authentication.";
default = "OpenID";
};
scopes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = lib.mdDoc "OpenID authentication scopes.";
default = [ "openid" "profile" "email" ];
};
};
});
};
#
# Optional configuration
#
sslKeyFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
File path that contains the Base64-encoded private key for HTTPS
termination. This is only required if you do not use an external reverse
proxy. See
[the documentation](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4).
'';
};
sslCertFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
File path that contains the Base64-encoded certificate for HTTPS
termination. This is only required if you do not use an external reverse
proxy. See
[the documentation](https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4).
'';
};
cdnUrl = lib.mkOption {
type = lib.types.str;
default = "";
description = lib.mdDoc ''
If using a Cloudfront/Cloudflare distribution or similar it can be set
using this option. This will cause paths to JavaScript files,
stylesheets and images to be updated to the hostname defined here. In
your CDN configuration the origin server should be set to public URL.
'';
};
forceHttps = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc ''
Auto-redirect to HTTPS in production. The default is
`true` but you may set this to `false`
if you can be sure that SSL is terminated at an external loadbalancer.
'';
};
enableUpdateCheck = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Have the installation check for updates by sending anonymized statistics
to the maintainers.
'';
};
concurrency = lib.mkOption {
type = lib.types.int;
default = 1;
description = lib.mdDoc ''
How many processes should be spawned. For a rough estimate, divide your
server's available memory by 512.
'';
};
maximumImportSize = lib.mkOption {
type = lib.types.int;
default = 5120000;
description = lib.mdDoc ''
The maximum size of document imports. Overriding this could be required
if you have especially large Word documents with embedded imagery.
'';
};
debugOutput = lib.mkOption {
type = lib.types.nullOr (lib.types.enum [ "http" ]);
default = null;
description = lib.mdDoc "Set this to `http` log HTTP requests.";
};
slackIntegration = lib.mkOption {
description = lib.mdDoc ''
For a complete Slack integration with search and posting to channels
this configuration is also needed. See here for details:
https://wiki.generaloutline.com/share/be25efd1-b3ef-4450-b8e5-c4a4fc11e02a
'';
default = null;
type = lib.types.nullOr (lib.types.submodule {
options = {
verificationTokenFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "File path containing the verification token.";
};
appId = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Application ID.";
};
messageActions = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc "Whether to enable message actions.";
};
};
});
};
googleAnalyticsId = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Optionally enable Google Analytics to track page views in the knowledge
base.
'';
};
sentryDsn = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Optionally enable [Sentry](https://sentry.io/) to
track errors and performance.
'';
};
sentryTunnel = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Optionally add a
[Sentry proxy tunnel](https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option)
for bypassing ad blockers in the UI.
'';
};
logo = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = lib.mdDoc ''
Custom logo displayed on the authentication screen. This will be scaled
to a height of 60px.
'';
};
smtp = lib.mkOption {
description = lib.mdDoc ''
To support sending outgoing transactional emails such as
"document updated" or "you've been invited" you'll need to provide
authentication for an SMTP server.
'';
default = null;
type = lib.types.nullOr (lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Host name or IP address of the SMTP server.";
};
port = lib.mkOption {
type = lib.types.port;
description = lib.mdDoc "TCP port of the SMTP server.";
};
username = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Username to authenticate with.";
};
passwordFile = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
File path containing the password to authenticate with.
'';
};
fromEmail = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Sender email in outgoing mail.";
};
replyEmail = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "Reply address in outgoing mail.";
};
tlsCiphers = lib.mkOption {
type = lib.types.str;
default = "";
description = lib.mdDoc "Override SMTP cipher configuration.";
};
secure = lib.mkOption {
type = lib.types.bool;
default = true;
description = lib.mdDoc "Use a secure SMTP connection.";
};
};
});
};
defaultLanguage = lib.mkOption {
type = lib.types.enum [
"da_DK"
"de_DE"
"en_US"
"es_ES"
"fa_IR"
"fr_FR"
"it_IT"
"ja_JP"
"ko_KR"
"nl_NL"
"pl_PL"
"pt_BR"
"pt_PT"
"ru_RU"
"sv_SE"
"th_TH"
"vi_VN"
"zh_CN"
"zh_TW"
];
default = "en_US";
description = lib.mdDoc ''
The default interface language. See
[translate.getoutline.com](https://translate.getoutline.com/)
for a list of available language codes and their rough percentage
translated.
'';
};
rateLimiter.enable = lib.mkEnableOption (lib.mdDoc "rate limiter for the application web server");
rateLimiter.requests = lib.mkOption {
type = lib.types.int;
default = 5000;
description = lib.mdDoc "Maximum number of requests in a throttling window.";
};
rateLimiter.durationWindow = lib.mkOption {
type = lib.types.int;
default = 60;
description = lib.mdDoc "Length of a throttling window.";
};
};
config = lib.mkIf cfg.enable {
assertions = [
{ assertion = pkgs.outline.version == "0.68.1"; message = "Please use the nixpkgs module for outline > 0.68.1"; }
];
users.users = lib.optionalAttrs (cfg.user == defaultUser) {
${defaultUser} = {
isSystemUser = true;
group = cfg.group;
};
};
users.groups = lib.optionalAttrs (cfg.group == defaultUser) {
${defaultUser} = { };
};
systemd.tmpfiles.rules = [
"f ${cfg.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
"f ${cfg.utilsSecretFile} 0600 ${cfg.user} ${cfg.group} -"
"f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
];
services.postgresql = lib.mkIf (cfg.databaseUrl == "local") {
enable = true;
ensureUsers = [{
name = "outline";
ensurePermissions."DATABASE outline" = "ALL PRIVILEGES";
}];
ensureDatabases = [ "outline" ];
};
services.redis.servers.outline = lib.mkIf (cfg.redisUrl == "local") {
enable = true;
user = config.services.outline.user;
port = 0; # Disable the TCP listener
};
systemd.services.outline =
let
localRedisUrl = "redis+unix:///run/redis-outline/redis.sock";
localPostgresqlUrl = "postgres://localhost/outline?host=/run/postgresql";
# Create an outline-sequalize wrapper (a wrapper around the wrapper) that
# has the config file's path baked in. This is necessary because there is
# at least two occurrences of outline calling this from its own code.
sequelize = pkgs.writeShellScriptBin "outline-sequelize" ''
exec ${cfg.package}/bin/outline-sequelize \
--config $RUNTIME_DIRECTORY/database.json \
${cfg.sequelizeArguments} \
"$@"
'';
in
{
description = "Outline wiki and knowledge base";
wantedBy = [ "multi-user.target" ];
after = [ "networking.target" ]
++ lib.optional (cfg.databaseUrl == "local") "postgresql.service"
++ lib.optional (cfg.redisUrl == "local") "redis-outline.service";
requires = lib.optional (cfg.databaseUrl == "local") "postgresql.service"
++ lib.optional (cfg.redisUrl == "local") "redis-outline.service";
path = [
pkgs.openssl # Required by the preStart script
sequelize
];
environment = lib.mkMerge [
{
NODE_ENV = "production";
REDIS_URL = if cfg.redisUrl == "local" then localRedisUrl else cfg.redisUrl;
URL = cfg.publicUrl;
PORT = builtins.toString cfg.port;
AWS_ACCESS_KEY_ID = cfg.storage.accessKey;
AWS_REGION = cfg.storage.region;
AWS_S3_UPLOAD_BUCKET_URL = cfg.storage.uploadBucketUrl;
AWS_S3_UPLOAD_BUCKET_NAME = cfg.storage.uploadBucketName;
AWS_S3_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize;
AWS_S3_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle;
AWS_S3_ACL = cfg.storage.acl;
CDN_URL = cfg.cdnUrl;
FORCE_HTTPS = builtins.toString cfg.forceHttps;
ENABLE_UPDATES = builtins.toString cfg.enableUpdateCheck;
WEB_CONCURRENCY = builtins.toString cfg.concurrency;
MAXIMUM_IMPORT_SIZE = builtins.toString cfg.maximumImportSize;
DEBUG = cfg.debugOutput;
GOOGLE_ANALYTICS_ID = lib.optionalString (cfg.googleAnalyticsId != null) cfg.googleAnalyticsId;
SENTRY_DSN = lib.optionalString (cfg.sentryDsn != null) cfg.sentryDsn;
SENTRY_TUNNEL = lib.optionalString (cfg.sentryTunnel != null) cfg.sentryTunnel;
TEAM_LOGO = lib.optionalString (cfg.logo != null) cfg.logo;
DEFAULT_LANGUAGE = cfg.defaultLanguage;
RATE_LIMITER_ENABLED = builtins.toString cfg.rateLimiter.enable;
RATE_LIMITER_REQUESTS = builtins.toString cfg.rateLimiter.requests;
RATE_LIMITER_DURATION_WINDOW = builtins.toString cfg.rateLimiter.durationWindow;
}
(lib.mkIf (cfg.slackAuthentication != null) {
SLACK_CLIENT_ID = cfg.slackAuthentication.clientId;
})
(lib.mkIf (cfg.googleAuthentication != null) {
GOOGLE_CLIENT_ID = cfg.googleAuthentication.clientId;
})
(lib.mkIf (cfg.azureAuthentication != null) {
AZURE_CLIENT_ID = cfg.azureAuthentication.clientId;
AZURE_RESOURCE_APP_ID = cfg.azureAuthentication.resourceAppId;
})
(lib.mkIf (cfg.oidcAuthentication != null) {
OIDC_CLIENT_ID = cfg.oidcAuthentication.clientId;
OIDC_AUTH_URI = cfg.oidcAuthentication.authUrl;
OIDC_TOKEN_URI = cfg.oidcAuthentication.tokenUrl;
OIDC_USERINFO_URI = cfg.oidcAuthentication.userinfoUrl;
OIDC_USERNAME_CLAIM = cfg.oidcAuthentication.usernameClaim;
OIDC_DISPLAY_NAME = cfg.oidcAuthentication.displayName;
OIDC_SCOPES = lib.concatStringsSep " " cfg.oidcAuthentication.scopes;
})
(lib.mkIf (cfg.slackIntegration != null) {
SLACK_APP_ID = cfg.slackIntegration.appId;
SLACK_MESSAGE_ACTIONS = builtins.toString cfg.slackIntegration.messageActions;
})
(lib.mkIf (cfg.smtp != null) {
SMTP_HOST = cfg.smtp.host;
SMTP_PORT = builtins.toString cfg.smtp.port;
SMTP_USERNAME = cfg.smtp.username;
SMTP_FROM_EMAIL = cfg.smtp.fromEmail;
SMTP_REPLY_EMAIL = cfg.smtp.replyEmail;
SMTP_TLS_CIPHERS = cfg.smtp.tlsCiphers;
SMTP_SECURE = builtins.toString cfg.smtp.secure;
})
];
preStart = ''
if [ ! -s ${lib.escapeShellArg cfg.secretKeyFile} ]; then
openssl rand -hex 32 > ${lib.escapeShellArg cfg.secretKeyFile}
fi
if [ ! -s ${lib.escapeShellArg cfg.utilsSecretFile} ]; then
openssl rand -hex 32 > ${lib.escapeShellArg cfg.utilsSecretFile}
fi
# The config file is required for the sequelize CLI.
${if (cfg.databaseUrl == "local") then ''
cat <<EOF > $RUNTIME_DIRECTORY/database.json
{
"production-ssl-disabled": {
"host": "/run/postgresql",
"username": null,
"password": null,
"dialect": "postgres"
}
}
EOF
'' else ''
cat <<EOF > $RUNTIME_DIRECTORY/database.json
{
"production": {
"use_env_variable": "DATABASE_URL",
"dialect": "postgres",
"dialectOptions": {
"ssl": {
"rejectUnauthorized": false
}
}
},
"production-ssl-disabled": {
"use_env_variable": "DATABASE_URL",
"dialect": "postgres"
}
}
EOF
''}
'';
script = ''
export SECRET_KEY="$(head -n1 ${lib.escapeShellArg cfg.secretKeyFile})"
export UTILS_SECRET="$(head -n1 ${lib.escapeShellArg cfg.utilsSecretFile})"
export AWS_SECRET_ACCESS_KEY="$(head -n1 ${lib.escapeShellArg cfg.storage.secretKeyFile})"
${lib.optionalString (cfg.slackAuthentication != null) ''
export SLACK_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.slackAuthentication.secretFile})"
''}
${lib.optionalString (cfg.googleAuthentication != null) ''
export GOOGLE_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.googleAuthentication.clientSecretFile})"
''}
${lib.optionalString (cfg.azureAuthentication != null) ''
export AZURE_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.azureAuthentication.clientSecretFile})"
''}
${lib.optionalString (cfg.oidcAuthentication != null) ''
export OIDC_CLIENT_SECRET="$(head -n1 ${lib.escapeShellArg cfg.oidcAuthentication.clientSecretFile})"
''}
${lib.optionalString (cfg.sslKeyFile != null) ''
export SSL_KEY="$(head -n1 ${lib.escapeShellArg cfg.sslKeyFile})"
''}
${lib.optionalString (cfg.sslCertFile != null) ''
export SSL_CERT="$(head -n1 ${lib.escapeShellArg cfg.sslCertFile})"
''}
${lib.optionalString (cfg.slackIntegration != null) ''
export SLACK_VERIFICATION_TOKEN="$(head -n1 ${lib.escapeShellArg cfg.slackIntegration.verificationTokenFile})"
''}
${lib.optionalString (cfg.smtp != null) ''
export SMTP_PASSWORD="$(head -n1 ${lib.escapeShellArg cfg.smtp.passwordFile})"
''}
${if (cfg.databaseUrl == "local") then ''
export DATABASE_URL=${lib.escapeShellArg localPostgresqlUrl}
export PGSSLMODE=disable
'' else ''
export DATABASE_URL=${lib.escapeShellArg cfg.databaseUrl}
''}
${cfg.package}/bin/outline-server
'';
serviceConfig = {
User = cfg.user;
Group = cfg.group;
Restart = "always";
ProtectSystem = "strict";
PrivateHome = true;
PrivateTmp = true;
UMask = "0007";
StateDirectory = "outline";
StateDirectoryMode = "0750";
RuntimeDirectory = "outline";
RuntimeDirectoryMode = "0750";
# This working directory is required to find stuff like the set of
# onboarding files:
WorkingDirectory = "${cfg.package}/share/outline";
};
};
};
}

View file

@ -17,7 +17,6 @@ index d218dbc1f..24c0505e3 100644
/**
* The product name
*/
@Public
- public APP_NAME = "Outline";
+ public APP_NAME = "DTTH Wiki";

View file

@ -1,183 +0,0 @@
commit 8c7f8c28fabc174a71499a4737579b24b5c4b244
Author: Natsu Kagami <nki@nkagami.me>
Date: Mon Oct 21 02:17:36 2024 +0200
Support R2
diff --git a/.env.sample b/.env.sample
index eb57ad85c..94ffcee07 100644
--- a/.env.sample
+++ b/.env.sample
@@ -66,6 +66,8 @@ AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private
+AWS_S3_R2=true
+AWS_S3_R2_PUBLIC_URL=http://s3:4569
# AUTHENTICATION
diff --git a/app/utils/files.ts b/app/utils/files.ts
index 6607a6b12..5138f68ad 100644
--- a/app/utils/files.ts
+++ b/app/utils/files.ts
@@ -63,8 +63,13 @@ export const uploadFile = async (
xhr.addEventListener("loadend", () => {
resolve(xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 400);
});
- xhr.open("POST", data.uploadUrl, true);
- xhr.send(formData);
+ xhr.open(data.method, data.uploadUrl, true);
+ xhr.setRequestHeader("Content-Type", file.type);
+ if (data.method === "POST") {
+ xhr.send(formData);
+ } else {
+ xhr.send(file);
+ }
});
if (!success) {
diff --git a/server/env.ts b/server/env.ts
index 5b420f2e1..4ea1e8d3c 100644
--- a/server/env.ts
+++ b/server/env.ts
@@ -519,6 +519,14 @@ export class Environment {
environment.AWS_S3_UPLOAD_BUCKET_NAME
);
+ @IsOptional()
+ public AWS_S3_R2 = this.toBoolean(environment.AWS_S3_R2 ?? "false");
+
+ @IsOptional()
+ public AWS_S3_R2_PUBLIC_URL = this.toOptionalString(
+ environment.AWS_S3_R2_PUBLIC_URL
+ );
+
/**
* Whether to force path style URLs for S3 objects, this is required for some
* S3-compatible storage providers.
diff --git a/server/routes/api/attachments/attachments.ts b/server/routes/api/attachments/attachments.ts
index 5e6c27594..b7620f440 100644
--- a/server/routes/api/attachments/attachments.ts
+++ b/server/routes/api/attachments/attachments.ts
@@ -3,6 +3,7 @@ import { v4 as uuidv4 } from "uuid";
import { AttachmentPreset } from "@shared/types";
import { bytesToHumanReadable } from "@shared/utils/files";
import { AttachmentValidation } from "@shared/validations";
+import env from "@server/env";
import { AuthorizationError, ValidationError } from "@server/errors";
import auth from "@server/middlewares/authentication";
import { rateLimiter } from "@server/middlewares/rateLimiter";
@@ -90,16 +91,30 @@ router.post(
{ transaction }
);
- const presignedPost = await FileStorage.getPresignedPost(
- key,
- acl,
- maxUploadSize,
- contentType
- );
+ let uploadUrl;
+ let method;
+ let presignedPost = {
+ fields: {},
+ };
+ if (env.AWS_S3_R2) {
+ uploadUrl = await FileStorage.getPresignedPut(key);
+ method = "PUT";
+ } else {
+ uploadUrl = FileStorage.getUploadUrl();
+ method = "POST";
+
+ presignedPost = await FileStorage.getPresignedPost(
+ key,
+ acl,
+ maxUploadSize,
+ contentType
+ );
+ }
ctx.body = {
data: {
- uploadUrl: FileStorage.getUploadUrl(),
+ uploadUrl,
+ method,
form: {
"Cache-Control": "max-age=31557600",
"Content-Type": contentType,
diff --git a/server/storage/files/BaseStorage.ts b/server/storage/files/BaseStorage.ts
index ce0287ebc..a1931c83d 100644
--- a/server/storage/files/BaseStorage.ts
+++ b/server/storage/files/BaseStorage.ts
@@ -26,6 +26,8 @@ export default abstract class BaseStorage {
contentType: string
): Promise<Partial<PresignedPost>>;
+ public abstract getPresignedPut(key: string): Promise<string>;
+
/**
* Returns a promise that resolves with a stream for reading a file from the storage provider.
*
diff --git a/server/storage/files/LocalStorage.ts b/server/storage/files/LocalStorage.ts
index 83cf98c50..324e60dd9 100644
--- a/server/storage/files/LocalStorage.ts
+++ b/server/storage/files/LocalStorage.ts
@@ -30,6 +30,10 @@ export default class LocalStorage extends BaseStorage {
});
}
+ public async getPresignedPut(key: string) {
+ return this.getUrlForKey(key);
+ }
+
public getUploadUrl() {
return "/api/files.create";
}
diff --git a/server/storage/files/S3Storage.ts b/server/storage/files/S3Storage.ts
index a42442e0c..d55ef5472 100644
--- a/server/storage/files/S3Storage.ts
+++ b/server/storage/files/S3Storage.ts
@@ -4,6 +4,7 @@ import {
S3Client,
DeleteObjectCommand,
GetObjectCommand,
+ PutObjectCommand,
ObjectCannedACL,
} from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
@@ -58,6 +59,16 @@ export default class S3Storage extends BaseStorage {
return createPresignedPost(this.client, params);
}
+ public async getPresignedPut(key: string) {
+ const params = {
+ Bucket: env.AWS_S3_UPLOAD_BUCKET_NAME,
+ Key: key,
+ };
+
+ const command = new PutObjectCommand(params);
+ return await getSignedUrl(this.client, command, { expiresIn: 3600 });
+ }
+
private getPublicEndpoint(isServerUpload?: boolean) {
if (env.AWS_S3_ACCELERATE_URL) {
return env.AWS_S3_ACCELERATE_URL;
@@ -137,10 +148,17 @@ export default class S3Storage extends BaseStorage {
);
}
+ public getR2ObjectUrl = async (key: string) =>
+ env.AWS_S3_R2_PUBLIC_URL + "/" + key;
+
public getSignedUrl = async (
key: string,
expiresIn = S3Storage.defaultSignedUrlExpires
) => {
+ if (env.AWS_S3_R2) {
+ return this.getR2ObjectUrl(key);
+ }
+
const isDocker = env.AWS_S3_UPLOAD_BUCKET_URL.match(/http:\/\/s3:/);
const params = {
Bucket: this.getBucket(),

View file

@ -9,7 +9,9 @@ let
# to that database.
userFromDatabase = databaseName: {
name = databaseName;
ensureDBOwnership = true;
ensurePermissions = {
"DATABASE \"${databaseName}\"" = "ALL PRIVILEGES";
};
};
in
{
@ -30,14 +32,10 @@ in
ensureDatabases = cfg.databases;
ensureUsers = (map userFromDatabase cfg.databases);
dataDir = "/mnt/data/postgresql/${config.services.postgresql.package.psqlSchema}";
};
config.systemd.services.postgresql.serviceConfig = {
StateDirectory = "postgresql postgresql ${config.services.postgresql.dataDir}";
StateDirectoryMode = "0750";
ensureUsers = (map userFromDatabase cfg.databases) ++ [{
name = "root";
ensurePermissions = { "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; };
}];
};
# Backup settings

View file

@ -20,24 +20,6 @@ let
in
valueType;
# https://www.cloudflare.com/ips/
trustedIPs =
let
files = [
(pkgs.fetchurl {
url = "https://www.cloudflare.com/ips-v4";
hash = "sha256-8Cxtg7wBqwroV3Fg4DbXAMdFU1m84FTfiE5dfZ5Onns=";
})
(pkgs.fetchurl {
url = "https://www.cloudflare.com/ips-v6";
hash = "sha256-np054+g7rQDE3sr9U8Y/piAp89ldto3pN9K+KCNMoKk=";
})
];
readLines = path: lib.splitString "\n" (builtins.readFile path);
in
lib.concatMap readLines files;
cfg = config.cloud.traefik;
in
{
@ -75,7 +57,6 @@ in
};
## HTTPS entrypoint: ok!
entrypoints.https.address = ":443";
entrypoints.https.forwardedHeaders.trustedIPs = trustedIPs;
## IMAP and SMTP
entrypoints.imap.address = ":993";
entrypoints.smtp-submission.address = ":587";

View file

@ -13,6 +13,7 @@ in
};
host = mkOption {
type = types.str;
default = "write.nkagami.me";
description = "The hostname for the instance";
};
site.title = mkOption {

View file

@ -11,53 +11,21 @@ let
programs.adb.enable = true;
users.users.${config.common.linux.username}.extraGroups = [ "adbusers" ];
};
ios = { config, pkgs, ... }: mkIf config.common.linux.enable {
ios = { config, ... }: mkIf config.common.linux.enable {
services.usbmuxd.enable = true;
services.usbmuxd.package = pkgs.usbmuxd2;
environment.systemPackages = with pkgs; [
libimobiledevice
ifuse
];
users.users.${config.common.linux.username}.extraGroups = [ config.services.usbmuxd.group ];
systemd.network.networks."05-ios-tethering" = {
matchConfig.Driver = "ipheth";
networkConfig.DHCP = "yes";
linkConfig.RequiredForOnline = "no";
};
};
graphics = { config, pkgs, ... }: {
hardware.graphics.enable = true;
hardware.graphics.enable32Bit = true;
# Monitor backlight
hardware.i2c.enable = true;
services.ddccontrol.enable = true;
environment.systemPackages = [ pkgs.luminance pkgs.ddcutil ];
};
accounts = { pkgs, ... }: mkIf (config.common.linux.enable && !pkgs.stdenv.isAarch64) {
environment.systemPackages = [ pkgs.glib (pkgs.gnome-control-center or pkgs.gnome.gnome-control-center) ];
services.accounts-daemon.enable = true;
services.gnome.gnome-online-accounts.enable = true;
# programs.evolution.enable = true;
# programs.evolution.plugins = with pkgs; [ evolution-ews ];
# services.gnome.evolution-data-server.enable = true;
# services.gnome.evolution-data-server.plugins = with pkgs; [ evolution-ews ];
};
wlr = { lib, config, ... }: mkIf config.common.linux.enable {
wlr = { ... }: mkIf config.common.linux.enable {
# swaync disable notifications on screencast
xdg.portal.wlr.settings.screencast = {
exec_before = ''which swaync-client && swaync-client --inhibitor-add "xdg-desktop-portal-wlr" || true'';
exec_after = ''which swaync-client && swaync-client --inhibitor-remove "xdg-desktop-portal-wlr" || true'';
};
# Niri stuff
# https://github.com/sodiboo/niri-flake/blob/main/docs.md
programs.niri.enable = true;
programs.niri.package = pkgs.niri-stable;
# Override gnome-keyring disabling
services.gnome.gnome-keyring.enable = lib.mkForce false;
};
logitech = { pkgs, ... }: mkIf cfg.enable {
@ -65,73 +33,19 @@ let
environment.systemPackages = with pkgs; [ piper ];
};
kwallet = { pkgs, lib, ... }: mkIf cfg.enable {
environment.systemPackages = [ pkgs.kdePackages.kwallet ];
services.dbus.packages = [ pkgs.kdePackages.kwallet ];
xdg.portal = {
extraPortals = [ pkgs.kdePackages.kwallet ];
};
};
virtualisation = { pkgs, ... }: mkIf cfg.enable {
virtualisation.podman = {
enable = true;
dockerCompat = true;
defaultNetwork.settings.dns_enabled = true;
# defaultNetwork.settings.dns_enabled = true;
};
virtualisation.oci-containers.backend = "podman";
virtualisation.virtualbox.host.enable = false;
users.extraGroups.vboxusers.members = [ cfg.username ];
};
};
rt-audio = { pkgs, ... }: mkIf cfg.enable {
services.pipewire.lowLatency = {
# enable this module
enable = true;
# defaults (no need to be set unless modified)
quantum = 32;
rate = 44100;
};
security.rtkit.enable = true;
# Real time configurations
boot.kernel.sysctl = {
"vm.swappiness" = 10;
"fs.inotify.max_user_watches" = 524288;
};
security.pam.loginLimits = [
{
domain = "@audio";
item = "rtprio";
type = "-";
value = "90";
}
{
domain = "@audio";
item = "memlock";
type = "-";
value = "unlimited";
}
];
};
in
{
imports = with modules; [
./sops.nix
adb
ios
graphics
wlr
logitech
kwallet
virtualisation
accounts
rt-audio
];
imports = with modules; [ adb ios wlr logitech virtualisation ] ++ [ ./networking.nix ];
options.common.linux = {
enable = mkOption {
@ -154,7 +68,7 @@ in
dnsServers = mkOption {
type = types.listOf types.str;
description = "DNS server list";
default = [ "1.1.1.1" "2606:4700:4700:1111" ];
default = [ "8.8.8.8" "8.8.4.4" ];
};
networks = mkOption {
type = types.attrsOf (types.submodule {
@ -188,10 +102,11 @@ in
boot.kernelPackages = mkDefault pkgs.linuxPackages_latest;
# Use the systemd-boot EFI boot loader.
boot = {
plymouth.enable = true;
loader.timeout = 60;
loader.systemd-boot.enable = true;
loader.efi.canTouchEfiVariables = true;
supportedFilesystems.ntfs = true;
supportedFilesystems = [ "ntfs" ];
};
boot.initrd.systemd.enable = builtins.length (builtins.attrNames (cfg.luksDevices)) > 0;
# LUKS devices
@ -209,11 +124,8 @@ in
cfg.luksDevices;
## Hardware-related
# Firmware stuff
services.fwupd.enable = true;
# Enable sound.
sound.enable = true;
services.pipewire = {
enable = true;
# alsa is optional
@ -222,20 +134,15 @@ in
pulse.enable = true;
};
# udev configurations
services.udev.packages = with pkgs; [
qmk-udev-rules # For keyboards
];
# udisks
services.udisks2.enable = true;
# Bluetooth: just enable
hardware.bluetooth.enable = true;
hardware.bluetooth.package = pkgs.bluez5-experimental; # Why do we need experimental...?
hardware.bluetooth.settings.General.Experimental = true;
services.blueman.enable = true; # For a GUI
# ZRAM
zramSwap.enable = true;
## Users
users.users.${cfg.username} = {
@ -244,23 +151,13 @@ in
extraGroups = [
"wheel" # Enable sudo for the user.
"plugdev" # Enable openrazer-daemon privileges
"audio"
"video"
"input"
];
shell = pkgs.fish;
};
nix.settings.trusted-users = [ "root" cfg.username ];
## Network configuration
systemd.network.enable = true;
networking.dhcpcd.enable = lib.mkForce false;
networking.useDHCP = false;
networking.useNetworkd = true;
systemd.network.wait-online.enable = false;
networking.hostName = cfg.networking.hostname;
networking.wireless.iwd.enable = true;
networking.wireless.iwd.settings.General.EnableNetworkConfiguration = true;
systemd.network.networks = builtins.mapAttrs
(name: cfg: {
matchConfig.Name = cfg.match;
@ -273,71 +170,68 @@ in
services.resolved.domains = cfg.networking.dnsServers;
services.resolved.fallbackDns = cfg.networking.dnsServers;
# Firewall: only open to SSH now
networking.nftables.enable = true;
networking.firewall.allowedTCPPorts = [ 22 ];
networking.firewall.allowedUDPPorts = [ 22 ];
# Enable tailscale
services.tailscale.enable = true;
## Time and Region
time.timeZone = lib.mkDefault "Europe/Zurich";
time.timeZone = "Europe/Zurich";
# Select internationalisation properties.
console.keyMap = "jp106"; # Console key layout
i18n.defaultLocale = "ja_JP.UTF-8";
# Input methods (only fcitx5 works reliably on Wayland)
i18n.inputMethod = {
fcitx5.waylandFrontend = true;
enabled = "fcitx5";
fcitx5.addons = with pkgs; [
fcitx5-mozc
fcitx5-unikey
fcitx5-gtk
];
} // (if config.system.nixos.release == "24.05" then {
enabled = "fcitx5";
} else {
enable = true;
type = "fcitx5";
});
};
# Default packages
environment.systemPackages = with pkgs; [
kakoune # An editor
wget # A simple fetcher
fish # Good shell
## System monitoring tools
usbutils # lsusb and friends
pciutils # lspci and friends
psmisc # killall, pstree, ...
lm_sensors # sensors
## Security stuff
libsForQt5.qtkeychain
## Wayland
kdePackages.qtwayland
qt5.qtwayland
];
# Add a reliable terminal
programs.fish.enable = true;
# programs.gnome-terminal.enable = true;
# KDEConnect is just based
programs.kdeconnect.enable = true;
# Flatpaks are useful... sometimes...
services.flatpak.enable = true;
# AppImages should run
programs.appimage = {
enable = true;
binfmt = true;
};
# DConf for GNOME configurations
programs.dconf.enable = true;
# Gaming! (not for ARM64)
programs.steam.enable = !pkgs.stdenv.isAarch64;
hardware.opengl.enable = true;
hardware.opengl.driSupport = true;
hardware.opengl.driSupport32Bit = !pkgs.stdenv.isAarch64; # For 32 bit applications
## Services
# gnome-keyring for storing keys
services.gnome.gnome-keyring.enable = true;
# OpenSSH so you can SSH to me
services.openssh.enable = true;
# PAM
security.pam.services.login.enableKwallet = true;
security.pam.services.login.enableGnomeKeyring = true;
security.pam.services.lightdm.enableKwallet = true;
security.pam.services.lightdm.enableGnomeKeyring = true;
security.pam.services.swaylock = { };
# Printers
services.printing.enable = true;
@ -345,18 +239,8 @@ in
xdg.portal = {
enable = true;
wlr.enable = true;
xdgOpenUsePortal = true;
# gtk portal needed to make gtk apps happy
extraPortals = [ pkgs.kdePackages.xdg-desktop-portal-kde pkgs.xdg-desktop-portal-gtk ];
config.sway.default = [ "wlr" "kde" "kwallet" ];
config.niri = {
default = [ "gnome" "gtk" ];
"org.freedesktop.impl.portal.Access" = "gtk";
"org.freedesktop.impl.portal.Notification" = "gtk";
"org.freedesktop.impl.portal.Secret" = "kwallet";
"org.freedesktop.impl.portal.FileChooser" = "kde";
};
extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
};
# D-Bus
services.dbus.packages = with pkgs; [ gcr ];

View file

@ -0,0 +1,47 @@
let
localNetworks = { config, lib, pkgs, ... }: with lib; {
# Default local networks
options.nki.networking = {
localNetworks = mkOption {
type = types.listOf types.str;
description = "A list of known IPv4 local networks";
};
allowLocalPorts = mkOption {
type = types.listOf types.port;
default = [ ];
description = "Open the following ports in all local networks";
};
};
options.nki.networking.ipv6.localNetworks = mkOption {
type = types.listOf types.str;
description = "A list of known IPv6 local networks";
};
config.nki.networking.localNetworks = [
"11.0.0.0/24" # tinc
"100.64.0.0/10" # Headscale
];
config.nki.networking.ipv6.localNetworks = [
"fd7a:115c:a1e0::/48" # Headscale
];
config.networking = mkIf (config.nki.networking.allowLocalPorts != [ ]) {
nftables.enable = true;
firewall.extraInputRules =
let
portsStr = concatMapStringsSep ", " toString config.nki.networking.allowLocalPorts;
ip4Str = concatStringsSep ", " config.nki.networking.localNetworks;
ip6Str = concatStringsSep ", " config.nki.networking.ipv6.localNetworks;
in
''
${if ip4Str == "" then "" else "ip saddr { ${ip4Str} } dport { ${portsStr} } accept"}
${if ip6Str == "" then "" else "ip6 saddr { ${ip6Str} } dport { ${portsStr} } accept"}
'';
};
};
in
{ ... }: {
imports = [ localNetworks ];
}

View file

@ -1,18 +0,0 @@
{ config, lib, ... }:
with { inherit (lib) types mkOption mkEnableOption; };
let
cfg = config.common.linux.sops;
in
{
options.common.linux.sops = {
enable = mkEnableOption "Enable sops configuration";
file = mkOption {
type = types.path;
description = "Path to the default sops file";
};
};
config = lib.mkIf cfg.enable {
sops.defaultSopsFile = cfg.file;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
};
}

View file

@ -34,6 +34,11 @@ in
default = 655;
description = "The port to listen on";
};
meshIp = mkOption {
type = types.str;
description = "The mesh ip to be assigned by hostname";
};
};
config = mkIf cfg.enable (builtins.seq
@ -46,6 +51,7 @@ in
myMeshIp = myHost.subnetAddr;
in
{
services.my-tinc.meshIp = myMeshIp;
# Scripts that set up the tinc services
environment.etc = {
"tinc/${networkName}/tinc-up".source = pkgs.writeScript "tinc-up-${networkName}" ''
@ -78,11 +84,6 @@ in
# firewall
networking.firewall.allowedUDPPorts = [ 655 ];
networking.firewall.allowedTCPPorts = [ 655 ];
networking.firewall.interfaces."tinc.${networkName}" = {
allowedUDPPortRanges = [{ from = 0; to = 65535; }];
allowedTCPPortRanges = [{ from = 0; to = 65535; }];
};
# configure tinc service
# ----------------------

View file

@ -23,13 +23,10 @@ in
hosts;
# Add all of them to host
nki.services.edns = {
enable = true;
cloaking-rules =
(lib.attrsets.mapAttrs'
(name: host: { name = "${name}.tinc"; value = host.subnetAddr; })
hosts)
;
};
networking.extraHosts = lib.strings.concatStringsSep
"\n"
(lib.attrsets.mapAttrsToList
(name: host: "${host.subnetAddr} ${name}.tinc")
hosts);
};
}

View file

@ -22,14 +22,4 @@
subnetAddr = "11.0.0.4";
ed25519PublicKey = "6MN5LVE4juavv8qJW2dTN4t/haKCADWquAQj/ADF7iN";
};
yoga = {
subnetAddr = "11.0.0.5";
ed25519PublicKey = "n+gIZjuuTPxi0OBqw2oOcmXd3loOHG+GQHBMXNlgyqI";
};
framework = {
subnetAddr = "11.0.0.6";
ed25519PublicKey = "YL7NA6Ydv/3FBfSzOPvyHlGweAViPvsG3b0Zh8L0NzF";
};
}

View file

@ -2,35 +2,42 @@
with lib;
let
nerd-fonts =
if builtins.hasAttr "nerd-fonts" pkgs then
pkgs.nerd-fonts.symbols-only
else
pkgs.nerdfonts.override { fonts = [ "NerdFontsSymbolsOnly" ]; };
noto-fonts-emoji-blob-bin =
let
pname = "noto-fonts-emoji-blob-bin";
version = "15.0";
in
pkgs.fetchurl {
name = "${pname}-${version}";
url = "https://github.com/C1710/blobmoji/releases/download/v${version}/Blobmoji.ttf";
sha256 = "sha256-n5yVk2w9x7UVrMe0Ho6nwu1Z9E/ktjo1UHdHKStoJWc=";
downloadToTemp = true;
recursiveHash = true;
postFetch = ''
install -Dm 444 $downloadedFile $out/share/fonts/blobmoji/Blobmoji.ttf
'';
};
in
{
imports = [ ./mounting.nix ];
# Fonts
config.fonts = {
packages = with pkgs; mkForce [
fonts = with pkgs; [
noto-fonts-emoji-blob-bin
ibm-plex
nerd-fonts
(nerdfonts.override { fonts = [ "NerdFontsSymbolsOnly" ]; })
noto-fonts
(pkgs.noto-fonts-cjk-sans or pkgs.noto-fonts-cjk)
noto-fonts-cjk
merriweather
corefonts
font-awesome
hack-font # for Plasma
];
} // (if pkgs.stdenv.isLinux then {
enableDefaultPackages = false;
enableDefaultFonts = false;
fontconfig = {
defaultFonts = {
emoji = lib.mkBefore [ "Blobmoji" ];
serif = lib.mkBefore [ "IBM Plex Serif" "IBM Plex Sans JP" "IBM Plex Sans KR" "Blobmoji" ];
sansSerif = lib.mkBefore [ "IBM Plex Sans" "IBM Plex Sans JP" "IBM Plex Sans KR" "Blobmoji" ];
monospace = lib.mkBefore [ "IBM Plex Mono" "Font Awesome 6 Free" "Symbols Nerd Font" "Blobmoji" "IBM Plex Sans JP" ];
serif = lib.mkBefore [ "IBM Plex Serif" "IBM Plex Sans JP" "IBM Plex Sans KR" ];
sansSerif = lib.mkBefore [ "IBM Plex Sans" "IBM Plex Sans JP" "IBM Plex Sans KR" ];
monospace = lib.mkBefore [ "IBM Plex Mono" ];
};
localConf = ''
<?xml version="1.0"?>
@ -42,7 +49,6 @@ in
<family>IBM Plex Sans</family>
<family>IBM Plex Sans JP</family>
<family>IBM Plex Sans KR</family>
<family>Blobmoji</family>
</prefer>
</alias>
</fontconfig>

View file

@ -1,29 +0,0 @@
{ pkgs, config, lib, ... }:
lib.mkIf pkgs.stdenv.isLinux {
system.fsPackages = [ pkgs.bindfs ];
fileSystems =
let
mkRoSymBind = path: {
device = path;
fsType = "fuse.bindfs";
options = [ "ro" "resolve-symlinks" "x-gvfs-hide" ];
};
aggregatedIcons = pkgs.buildEnv {
name = "system-icons";
paths = with pkgs; [
#libsForQt5.breeze-qt5 # for plasma
(pkgs.gnome-themes-extra or gnome.gnome-themes-extra) # Until 24.11
];
pathsToLink = [ "/share/icons" ];
};
aggregatedFonts = pkgs.buildEnv {
name = "system-fonts";
paths = config.fonts.packages;
pathsToLink = [ "/share/fonts" ];
};
in
{
"/usr/share/icons" = mkRoSymBind "${aggregatedIcons}/share/icons";
"/usr/local/share/fonts" = mkRoSymBind "${aggregatedFonts}/share/fonts";
};
}

View file

@ -16,7 +16,7 @@ in
security.pam = mkIf pkgs.stdenv.isLinux {
u2f = {
enable = true;
settings.cue = true;
cue = true;
};
# Services

View file

@ -8,11 +8,6 @@ in
options.nki.services.edns = {
enable = mkEnableOption "Enable encrypted DNS";
ipv6 = mkEnableOption "Enable ipv6";
cloaking-rules = mkOption {
type = types.attrsOf types.str;
default = { };
description = "A set of domain -> ip mapping for cloaking_rules";
};
};
config = mkIf cfg.enable {
@ -47,11 +42,6 @@ in
{ server_name = "*"; via = [ "anon-plan9-dns" "anon-v.dnscrypt.up-ipv4" ]; }
];
anonymized_dns.skip_incompatible = true;
# Cloaking rules
cloaking_rules = pkgs.writeText "cloaking_rules.txt" (lib.strings.concatStringsSep
"\n"
(lib.attrsets.mapAttrsToList (name: ip: "${name} ${ip}") cfg.cloaking-rules));
};
};
};

View file

@ -1,66 +0,0 @@
{ config, lib, ... }:
with { inherit (lib) mkOption types mkIf; };
let
cfg = config.services.nix-build-farm;
hosts = import ./hosts.nix;
build-user = "nix-builder";
isBuilder = host: host ? "builder";
allBuilders = lib.filterAttrs (_: isBuilder) hosts;
in
{
options.services.nix-build-farm = {
enable = mkOption {
type = types.bool;
default = true;
description = "Whether to enable nix-build-farm as a client";
};
hostname = mkOption {
type = types.enum (builtins.attrNames hosts);
description = "The hostname as listed in ./hosts.nix file";
};
privateKeyFile = mkOption {
type = types.path;
description = "The path to the private SSH key file";
};
ipAddrs = mkOption {
type = types.str;
description = "The ip addresses to limit access to";
default = "11.0.0.*";
};
};
config = mkIf cfg.enable (
let
host = hosts.${cfg.hostname};
otherHosts = lib.filterAttrs (name: _: name != cfg.hostname) hosts;
otherBuilders = lib.filterAttrs (name: _: name != cfg.hostname) allBuilders;
in
{
nix.distributedBuilds = true;
nix.buildMachines = lib.mapAttrsToList
(name: host: {
hostName = host.host;
sshUser = build-user;
sshKey = cfg.privateKeyFile;
} // host.builder)
otherBuilders;
users = mkIf (isBuilder host) {
users.${build-user} = {
description = "Nix build farm user";
group = build-user;
isNormalUser = true;
openssh.authorizedKeys.keys = lib.mapAttrsToList (_: host: ''from="${cfg.ipAddrs}" ${host.pubKey}'') otherHosts;
};
groups.${build-user} = { };
};
nix.settings.trusted-users = mkIf (isBuilder host) [ build-user ];
}
);
}

View file

@ -1,37 +0,0 @@
{
cloud = {
host = "cloud.tinc";
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE87ddj0fTH0NuvJz0dT5ln7v7zbafXqDVdM2A4ddOb0 root@nki-personal-do";
};
home = {
host = "home.tinc";
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK6N1uTxnbo73tyzD9X7d7OgPeoOpY7JmQaHASjSWFPI nki@kagamiPC";
builder = {
publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSUhiVTh2NlNBa0kyOTBCc1QzVG1IRVVJQWdXcVFyNm9jRmpjakRRczRoT2ggcm9vdEBrYWdhbWlQQwo=";
systems = [ "x86_64-linux" "aarch64-linux" ];
maxJobs = 16;
speedFactor = 2;
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
};
};
yoga = {
host = "yoga.tinc";
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE6ZrO/xIdmwBCUx80cscBSpJBBTp55OHGrXYBGRXKAw nki@nki-yoga-g8";
};
framework = {
host = "framework.tinc";
pubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH/g472MaT7YySUhBjxClfmMjpn98qYnKXDKlzWHYwuO nki@nki-framework";
builder = {
publicHostKey = "c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSUdOUlBCVFRkNTVVMXY1U1Jac0FjYVdhS3JGZTY0ZjIxOVViODVTQ2NWd28gcm9vdEBua2ktZnJhbWV3b3JrCg==";
systems = [ "x86_64-linux" "aarch64-linux" ];
maxJobs = 16;
speedFactor = 3;
supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
};
};
}

View file

@ -1 +0,0 @@
nix.home.tinc:zG2uDy0MbLY0wLuoVH/qKzTD6hTfKZufA2cWDSTCZMA=

View file

@ -1,60 +0,0 @@
{ config, pkgs, lib, ... }:
with { inherit (lib) mkEnableOption mkOption types mkIf; };
let
cfg = config.nki.services.nix-cache;
bindAddr = "127.0.0.1:5000";
in
{
options.nki.services.nix-cache = {
enableClient = mkOption {
type = types.bool;
default = !cfg.enableServer;
description = "Enable nix-cache client";
};
enableServer = mkEnableOption "Enable nix-cache server";
host = mkOption {
type = types.str;
default = "nix.home.tinc";
};
publicKey = mkOption {
type = types.str;
default = builtins.readFile ./cache-pub-key.pem;
};
privateKeyFile = mkOption {
type = types.path;
description = "Path to the private key .pem file";
};
};
config = {
nix.settings = mkIf cfg.enableClient {
substituters = lib.mkAfter [ "http://${cfg.host}" ];
trusted-public-keys = [ cfg.publicKey ];
};
services.harmonia = mkIf cfg.enableServer {
enable = true;
signKeyPaths = [ cfg.privateKeyFile ];
settings = {
bind = bindAddr;
priority = 45;
};
};
services.nginx = mkIf cfg.enableServer {
enable = true;
recommendedProxySettings = true;
virtualHosts = {
# ... existing hosts config etc. ...
"${cfg.host}" = {
locations."/".proxyPass = "http://${bindAddr}";
};
};
};
};
}

View file

@ -0,0 +1,12 @@
{ pkgs, config, lib, ... }:
with lib;
let
cfg = config.nki.services.pam;
in
{
options.nki.services.pam.enableGnomeKeyring = mkEnableOption "Enable gnome-keyring on login";
config = mkIf cfg.enableGnomeKeyring {
security.pam.services.login.enableGnomeKeyring = true;
# security.pam.services.login.gnupg.enable = true;
};
}

View file

@ -1,132 +0,0 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running nixos-help).
{ config, pkgs, lib, ... }:
{
imports =
[
# Include the results of the hardware scan.
./hardware-configuration.nix
# Fonts
../modules/personal/fonts
# Encrypted DNS
../modules/services/edns
# Wireless card
./wireless.nix
];
# Sops
common.linux.sops.enable = true;
common.linux.sops.file = ./secrets.yaml;
sops.secrets."nix-build-farm/private-key" = { mode = "0400"; };
services.nix-build-farm.hostname = "framework";
services.nix-build-farm.privateKeyFile = config.sops.secrets."nix-build-farm/private-key".path;
# services.xserver.enable = true;
# services.xserver.displayManager.sddm.enable = true;
# services.xserver.displayManager.sddm.wayland.enable = true;
services.desktopManager.plasma6.enable = true;
# Power Management
services.upower = {
enable = true;
criticalPowerAction = "Hibernate";
usePercentageForPolicy = true;
percentageCritical = 3;
percentageLow = 10;
};
services.power-profiles-daemon.enable = true;
# powerManagement.enable = true;
# powerManagement.powertop.enable = true;
services.logind.lidSwitch = "suspend-then-hibernate";
# Printing
services.printing.drivers = with pkgs; [ epfl-cups-drivers ];
# Enable touchpad support (enabled default in most desktopManager).
services.libinput.enable = true;
# Keyboard
services.input-remapper.enable = true;
services.input-remapper.serviceWantedBy = [ "multi-user.target" ];
hardware.uinput.enable = true;
# Define a user account. Don't forget to set a password with passwd.
common.linux.username = "nki";
# Networking
common.linux.networking = {
hostname = "nki-framework";
networks."10-wired".match = "enp*";
networks."20-wireless".match = "wlan*";
dnsServers = [ "127.0.0.1" ];
};
nki.services.edns.enable = true;
nki.services.edns.ipv6 = true;
# Backup home
services.btrbk.extraPackages = with pkgs; [ sudo ];
services.btrbk.instances.home = {
onCalendar = "hourly";
settings = {
snapshot_preserve = "24h 30d 6m 1y";
snapshot_preserve_min = "7d";
volume."/" = {
subvolume.home.snapshot_name = ".backups-home";
};
};
};
# Enable fingerprint auth for some stuff
security.pam.services.sudo.fprintAuth = true;
security.pam.services.swaylock.fprintAuth = true;
security.pam.services.login.fprintAuth = true;
# tinc network
sops.secrets."tinc-private-key" = { };
services.my-tinc = {
enable = true;
hostName = "framework";
ed25519PrivateKey = config.sops.secrets."tinc-private-key".path;
bindPort = 6565;
};
# Secrets
# sops.defaultSopsFile = ./secrets.yaml;
# sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
## tinc
# sops.secrets."tinc/ed25519-private-key" = { };
# services.my-tinc = {
# enable = true;
# hostName = "macbooknix";
# ed25519PrivateKey = config.sops.secrets."tinc/ed25519-private-key".path;
# bindPort = 6565;
# };
services.dbus.packages = with pkgs; [ gcr ];
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in case you
# accidentally delete configuration.nix.
# system.copySystemConfiguration = true;
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "22.05"; # Did you read the comment?
}

View file

@ -1,73 +0,0 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "thunderbolt" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.kernelParams = [
# Hibernation
"resume=UUID=fa8aaf51-b99f-4fb4-9230-8c0957d8af3f"
"resume_offset=5776640" # btrfs inspect-internal map-swapfile -r /var/swapfile
];
fileSystems."/" =
{
device = "/dev/disk/by-uuid/fa8aaf51-b99f-4fb4-9230-8c0957d8af3f";
fsType = "btrfs";
options = [ "subvol=root" "compress=zstd" ];
};
services.btrfs.autoScrub = {
enable = true;
interval = "monthly";
};
common.linux.luksDevices."cryptroot" = "/dev/disk/by-uuid/94226aae-6d1c-401a-bfad-3aa5f371a365";
fileSystems."/home" =
{
device = "/dev/disk/by-uuid/fa8aaf51-b99f-4fb4-9230-8c0957d8af3f";
fsType = "btrfs";
options = [ "subvol=home" "compress=zstd" ];
};
fileSystems."/nix" =
{
device = "/dev/disk/by-uuid/fa8aaf51-b99f-4fb4-9230-8c0957d8af3f";
fsType = "btrfs";
options = [ "subvol=nix" "compress=zstd" ];
};
fileSystems."/boot" =
{
device = "/dev/disk/by-uuid/6A0E-4D23";
fsType = "vfat";
};
swapDevices = [
{ device = "/var/swapfile"; size = 32 * 1024; priority = 10; }
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp1s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
environment.systemPackages = with pkgs; [ vulkan-validation-layers ];
# Fingerprint
services.fprintd.enable = true;
hardware.sensor.iio.enable = true; # Orientaion and ambient light sensors
}

View file

@ -1,32 +0,0 @@
tinc-private-key: ENC[AES256_GCM,data:cKtOFrF5FRSHWxe/QxH5O9GAba1WcWeCwW1IOzmbgdtFufRoWbCtYeaLP+WQhQ70z6xobiY9DN8Jrh7mDptKSsfKrrx2SH5JrdpsoINhLMbetXq7E29+q6CkS8NlLgE/KyV8eFjQySNsYiA/+Efq9xj9e1wOmHBDsND/jgiJDkA1qsEIFZg/vuv8LdoRY3TV/oKJ4pao9+70G4H+8Ef1sMZHGNe9qJ94Wa71nNX2fTSjKH5YBbRijMAePWr/IeCpZ9Phs7RqjBs=,iv:l0iB136X7nLVblQjFi7K4f42JKSxdsiLIRy5GPzK1nc=,tag:HAgkvWkl0Rx62ejGZckdKA==,type:str]
nix-build-farm:
private-key: ENC[AES256_GCM,data:w56sobHrhvVcKbuK15Yj20EqgFrR/5pNy/rcQjlZCiFEfgPC6ZrbsxziIdMTOX+Q4DyllKCKo+g/MIKm5S+s+nIff509ODlILhTtXlZvZlnT9+wvm5oN3WQCdkkqOr1gNnErPupfMxA6V35tNvNBCeomYuM8Xb7TuH4I1fXa5GFeT0Tnp5A2WqWoS1MzXz34CxlKXvoL6i4w7nUngt5zaGr9oMZbSa0pHxEzIhXk6h/lE864QlJid7q/mDok9gh2R80WvArGtNNj9PrT6cNKYIFuXo7vITkSkT3vo9BLflg5sgRxy4+7rBaRla0ziihQvZLtworwo6aNDvEqPqYfiX0TMYPpEWvJpp/HGDJy+Po7b0ZZkwSza+tB0J79vFH2h11Px1XSwXdq757Bn9G4OTb5oKWS5ycBU4xTnnHQBjYTSooFxYyTAvfKNEsB8AHKSuQRZgtVhWoOSPpJ6YL94ClUIT+DTaNmQjwouqNatf/nif+N/FCar8JWZDE+FX1TBu0yLmJie/f0LCdCQx+6IVdyXg7oVCoNiGg0,iv:TeSxlVc0WlOMMUtv/uq3f7JvW/kNCM7LjguhZxL51a4=,tag:m1iuk4pAX/yugM0ObzkJHQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age188tgu3psvywk6shq85mk2q0jdjwd0tcswzwlwu5pa5n3pndx75dq090z59
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmL2Z1RzBWaTI1TDl6WDNa
NTNVdEhTSFU5enNlTGVNWTI5anBZb1BtaVhjCm1BRnJDSXl1cWdBRUs1VnREVjBU
QWZxdkgzdm9JL0k5WmhDL1RCNTltdm8KLS0tIFhvQTlKMDZiVklTRWd4TzVmc2ll
bmpjcWdBV1doZml2NjlzQzdQczJ3alEKBMRP3POxtPIqBWnrvxY/++5jtVE70Uxa
EVfhsUO76A/hzyxfzpLEy1QGFE+DB/zlU0CK7HkNGPD2TrBHbzkPJA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1vgh6kvee8lvxylm7z86fpl3xzjyjs4u3zdfkyf064rjvxk9fpumsew7n27
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2MUxQU0dZOGRaekF4MWdo
T0krcERtRTJndFR1RHZmL0t6MjBxMW5PSENNCkR6SUhxQ0FoaEhuaWpiUzJ0MnJE
RXRERzVhL0lRVW1iRUlac0c5OHZsckEKLS0tIC9VM1dNZTNzdkFnMWk2YUwvcDNB
TDZnVjBaVzZBem5lZDB1MW4xQ0RmZ28K6d7mF+f3ZyilXlSIQGT2pBrTWuYLccE1
rYIJjHjFft/2wPX2gAW9VTiwfMT3lKJhJRqNdoie5phV5BZhkb3D9w==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-08-17T14:58:10Z"
mac: ENC[AES256_GCM,data:ZCrzXDttLxYUvdLiqM5I17Ys6O3zoOVKq8xP78VaLb3AAoV4RGGQxixKVQ6K9h84e8bFymh512BR7xKa9fqebxTyL1XCqPkRaSZy0aWjbc6QCaK+JD4yqivgO/x5x2xgMpX/ZhPFzKNLpMga61bnm6plvF8ocG+wOqYvj3vL0Ss=,iv:QZ8YJD7h2QD2jqVKo4bg0rwpZSTyyNw6zZDcBfClKPo=,tag:PH2XnTqxV2irymg2+Z+Egg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.0

View file

@ -1,13 +0,0 @@
{ pkgs, lib, ... }: {
environment.systemPackages = with pkgs; [ iw ];
# Disable power_save on boot
services.udev.packages = [
(pkgs.writeTextFile {
name = "udev_disable_wifi_power_save";
destination = "/etc/udev/rules.d/10-wifi-power_save.rules";
text = ''
ACTION=="add", SUBSYSTEM=="net", KERNEL=="wl*", RUN+="${lib.getExe pkgs.iw} dev $name set power_save off"
'';
})
];
}

View file

@ -1,5 +0,0 @@
{ config, pkgs, lib, ... }: {
environment.etc = {
"wireplumber/wireplumber.conf.d/51-sdac.conf".source = ./sdac.conf.json;
};
}

View file

@ -1,19 +0,0 @@
monitor.alsa.rules = [
{
matches = [
{
device.name = "alsa_output.usb-Grace_Design_SDAC-00.*"
}
]
actions = {
update-props = {
# audio.format = "S24_3LE"
audio.rate = 88200
api.alsa.period-size = 2
api.alsa.headroom = 0
api.alsa.disable-batch = true
}
}
}
]

View file

@ -1,16 +0,0 @@
rule = {
matches = {
{
{ "node.name", "matches", "alsa_output.usb-Grace_Design_SDAC-00.*" },
},
},
apply_properties = {
["audio.format"] = "S24_3LE",
["audio.rate"] = 44100,
["api.alsa.period-size"] = 2,
["api.alsa.headroom"] = 0,
["api.alsa.disable-batch"] = true
},
}
table.insert(alsa_monitor.rules, rule)

View file

@ -5,53 +5,24 @@
{ lib, config, pkgs, ... }:
with lib;
let
openrazer = { pkgs, ... }: {
# Razer stuff
hardware.openrazer = {
enable = true;
users = [ "nki" ];
};
environment.systemPackages = with pkgs; [ polychromatic ];
};
in
{
imports =
[
# Include the results of the hardware scan.
./hardware-configuration.nix
# secret management
./secrets
# Fonts
../modules/personal/fonts
# Encrypted DNS
../modules/services/edns
# Other services
../modules/personal/u2f.nix
./peertube-runner.nix
openrazer
];
# Kernel
boot.kernelPackages = pkgs.linuxKernel.packages.linux_xanmod_stable;
# Plasma!
services.desktopManager.plasma6.enable = true;
## Encryption
# Kernel modules needed for mounting USB VFAT devices in initrd stage
common.linux.luksDevices.root = "/dev/disk/by-uuid/7c6e40a8-900b-4f85-9712-2b872caf1892";
common.linux.sops.enable = true;
common.linux.sops.file = ./secrets.yaml;
# Nix cache server
sops.secrets."nix-cache/private-key" = { owner = "harmonia"; group = "harmonia"; mode = "0600"; };
nki.services.nix-cache = {
enableServer = true;
privateKeyFile = config.sops.secrets."nix-cache/private-key".path;
};
sops.secrets."nix-build-farm/private-key" = { mode = "0400"; };
services.nix-build-farm.hostname = "home";
services.nix-build-farm.privateKeyFile = config.sops.secrets."nix-build-farm/private-key".path;
# Networking
common.linux.networking =
@ -82,11 +53,13 @@ in
PrivateKeyFile = config.sops.secrets."dtth-wg/private-key".path;
};
wireguardPeers = [{
wireguardPeerConfig = {
PublicKey = "+7iI4jwmM1Qr+/DKB1Hv8JgFkGu7lSV0PAoo+O5d3yQ=";
PresharedKeyFile = config.sops.secrets."dtth-wg/preshared-key".path;
AllowedIPs = [ "100.64.0.0/10" "fd00::/106" ];
Endpoint = "vpn.dtth.ch:51820";
PersistentKeepalive = 25;
};
}];
};
systemd.network.networks."dtth-wg" = {
@ -94,8 +67,8 @@ in
address = [ "100.73.146.80/32" "fd00::33:105b/128" ];
DHCP = "no";
routes = [
{ Destination = "100.64.0.0/10"; Scope = "link"; }
{ Destination = "fd00::/106"; }
{ routeConfig = { Destination = "100.64.0.0/10"; Scope = "link"; }; }
{ routeConfig.Destination = "fd00::/106"; }
];
};
@ -124,7 +97,6 @@ in
"/mnt/Data" = ntfsMount "/dev/disk/by-uuid/A90680F8BBE62FE3";
"/mnt/Stuff" = ntfsMount "/dev/disk/by-uuid/717BF2EE20BB8A62";
"/mnt/Shared" = ntfsMount "/dev/disk/by-uuid/76AC086BAC0827E7";
"/mnt/osu" = ntfsMount "/dev/disk/by-uuid/530D3E1648CD1C26";
};
# PAM
@ -153,11 +125,10 @@ in
# Music server
services.navidrome.enable = true;
services.navidrome.settings = {
Address = "11.0.0.2";
MusicFolder = "/mnt/Stuff/Music";
};
systemd.services.navidrome.serviceConfig.BindReadOnlyPaths = lib.mkAfter [ "/etc" ];
networking.firewall.allowedTCPPorts = [ 4533 8000 ];
nki.networking.allowLocalPorts = [ 4533 ];
# Printers
services.printing.enable = true;
@ -192,13 +163,5 @@ in
passwordFile = config.sops.secrets."scrobble/listenbrainz".path;
};
};
programs.virt-manager.enable = true;
users.groups.libvirtd.members = [ "nki" ];
virtualisation.libvirtd.enable = true;
virtualisation.spiceUSBRedirection.enable = true;
}

View file

@ -7,7 +7,6 @@
imports =
[
(modulesPath + "/installer/scan/not-detected.nix")
./audio
];
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
@ -20,12 +19,6 @@
device = "/dev/disk/by-uuid/32a74827-4624-43ef-b066-b52e1f11793d";
fsType = "ext4";
};
fileSystems."/home/nki/Projects" =
{
device = "/dev/disk/by-uuid/025cb533-e21b-47f2-b7d5-322b7b95b831";
fsType = "btrfs";
options = [ "compress=zstd" ];
};
fileSystems."/boot" =
{
@ -36,6 +29,10 @@
swapDevices =
[{ device = "/dev/disk/by-uuid/561f6441-1915-4059-a5e1-76a449b0c9bf"; }];
# GPU options
services.xserver.videoDrivers = [ "amdgpu" ];
hardware.opengl.enable = true;
# bluetooth usb
hardware.firmware = [ pkgs.rtl8761b-firmware ];
}

View file

@ -1,67 +0,0 @@
{ config, pkgs, lib, ... }:
let
user = "peertube-runner-nodejs";
instance = "systemd-instance";
in
{
sops.secrets."peertube/dtth-key" = {
restartUnits = [ "peertube-runner.service" ];
};
users.groups.${user} = { };
users.users.${user} = {
isSystemUser = true;
group = user;
};
sops.templates."peertube-config.toml".owner = user;
sops.templates."peertube-config.toml".content = ''
[jobs]
concurrency = 2
[ffmpeg]
threads = 12
nice = 20
[[registeredInstances]]
url = "https://peertube.dtth.ch"
runnerToken = "${config.sops.placeholder."peertube/dtth-key"}"
runnerName = "kagamipc"
'';
environment.etc."${user}/${instance}/config.toml".source = config.sops.templates."peertube-config.toml".path;
systemd.services.peertube-runner = {
description = "PeerTube runner daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
requires = [ ];
serviceConfig =
{
ExecStart = "${lib.getExe' pkgs.peertube.runner "peertube-runner"} server --id ${instance}";
User = user;
RuntimeDirectory = user;
StateDirectory = user;
CacheDirectory = user;
# Hardening
ProtectSystem = "full";
PrivateDevices = false;
NoNewPrivileges = true;
ProtectHome = true;
CapabilityBoundingSet = "~CAP_SYS_ADMIN";
};
environment = {
NODE_ENV = "production";
# Override XDG values to fit env-path
# https://github.com/sindresorhus/env-paths/blob/main/index.js
XDG_DATA_HOME = "/run";
XDG_CONFIG_HOME = "/etc";
XDG_CACHE_HOME = "/var/cache";
XDG_STATE_HOME = "/var/lib";
};
path = with pkgs; [ nodejs ffmpeg ];
};
}

View file

@ -0,0 +1,6 @@
{ config, pkgs, ... }:
{
sops.defaultSopsFile = ./secrets.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
}

View file

@ -11,12 +11,6 @@ scrobble:
dtth-wg:
private-key: ENC[AES256_GCM,data:ySxPGzOplKwNLxRnPNw7If7xzxMwRkwTasT7FaQE9n5YB04R+gaQVjDqPqg=,iv:f5t94bUoo9sCGGwWytiuhg5jcKjzRjbR3Q0OIM28VDU=,tag:fJos9Hb9XytQbfGaPMa1/A==,type:str]
preshared-key: ENC[AES256_GCM,data:96q0ZfvPz4pb53XvTGameVkcETamYH8Xbv69672RBdacH6QjRCCVvPnBTfA=,iv:Q2Yonb07/Uu6KidhMgRX4zJuNU1ZySNC7g/5TwpMU80=,tag:1qQQdk20yIQlGZmX+/25RA==,type:str]
peertube:
dtth-key: ENC[AES256_GCM,data:Gu7qOisVBZrFXKBr51165FJ7Ej4hV+lIf3AMC02R3UFNXOnTHF2xC8E=,iv:F83FuD1VjZEJFMcx3gkQuKCpJmYdHtO15fRHkYdMxJM=,tag:ScH42Tr5ZsIo9JMnXhylSw==,type:str]
nix-cache:
private-key: ENC[AES256_GCM,data:4sbfIQb10Y50CrZbgjN+1iXEbXTpDqMbIB/yA3WlaAqhLtb8HKib5aZX3DLoxFbVihJcztQsvBBgEAhT9iMijoksaT9qzBQ5yIn4NGCfFem1DK8DQdjhTLMCVTyMFCT7hQHu/2Sd7w==,iv:zTSxuKOtOLekOBKBvl9MScD/Bo1Hviqq/n8Saa+1Cgo=,tag:fx73fCDPY9d07V3KKMw3DA==,type:str]
nix-build-farm:
private-key: ENC[AES256_GCM,data:m5neeWCEdaZ1MRhNwTptfDIgv3ABNlYyNil3oTD81Jbe/6WxWaS5Q++CRlHCjc2hOoHsWsZixw8iGZVTA+QXgH6B9C6A4oOAhgR9m92EGTEfFw0qxQbdzs7U98Yonx/N8SApUycZZB/EU81+MrDNY4GGzCiO6s00/vZLkDTYnqRFgbo+8KTG0BQTl4q+VYP2q3l0wy+Ivz5CWPmbz42Xdin/sBnjeFHKDuof4iZZnN3i8gUJ/mMw3lbdiHd6A8DL0G5Ut46ljzMC2aMsZOATCID3mPOPgI0xIetDofPJLDqVsNqptRHo8WB+KwDidvl222f5F7JqdSqgAMOJYPscrX0odufApiJfg5bbXBygvrDfAlPSruW7GsWGoKAhw0qC4NC/j+qYCwhS0qdorCLnIy3zzMtA6HkHtE675hy7/7oLj7k9Y8MhE4PxztjXTmDazaVCtKhnA/DpaxP2mH84gfCkJFD1YF9jtPm+P3e+46FwkW+WnHaA2L+H7Evava30DLEBhh5y9Gd1A3JN4isn,iv:7KUWg7+GWgmGJkbIvsy9gtccZBb+1Y5uDWhXQFk0obk=,tag:qJdM684XPHxecLVxVb5pgw==,type:str]
sops:
kms: []
gcp_kms: []
@ -50,8 +44,8 @@ sops:
bUhIT0Z2b1dVWGNyS1hRVFRyZTA4d00KchP7EhSOMwBl5vFuuskzosRoi8jUu1sw
hVjJNF2a40ewgkQgVAoWEzirHbknbQORzmepDDRth7Bve3UQU64+GA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-08-16T13:59:20Z"
mac: ENC[AES256_GCM,data:ncT8fbtEb9ZcLcftXwgAKJRPPSG4TRHFMArtVgWNmIjDRcCNNT7ICa+9Dl8DAYKRJ+8pgelV9StIg2f7rvypHYlckontEP5nwSFzEApLItG3AZXewTC8VPoDYb4T8/OWKDoa5kBMvGrDr1bFP/CZz7H8No+k5TV7fVExsw0PHpg=,iv:vxbkeJtHkOAq7NcaZEIOMV3qGEqBUg/vpJYumBBfY70=,tag:T0yw2x1O5Tp0UllLpcFryg==,type:str]
lastmodified: "2023-04-29T13:03:22Z"
mac: ENC[AES256_GCM,data:ZNDRS6LLy89TZoW27c57RMnjs6M/GBH0XfKKlrhys8gL7+I0V/++ry59VDbLxvqS4nPR4C5hk777+B5dqnseyYW2xRT3NKYxocCQu5kO6A8L/wB00j3bm3SSIGwLcKJPibEqi7ymU53K0bmZdjRMChkBwv3CnDNkM3Dc6rvZ2DM=,iv:Z1ZjnYW1Yk+oEzNknQDytTengjKxcud95LZTFfKMnpw=,tag:pnZ+UGQWuRCKoTll00oUKA==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.0
version: 3.7.3

View file

@ -10,27 +10,17 @@
../modules/cloud/bitwarden
../modules/cloud/mail
../modules/cloud/conduit
../modules/cloud/writefreely
../modules/cloud/gotosocial
# Encrypted DNS
../modules/services/edns
../modules/cloud/outline.nix
./headscale.nix
./gitea.nix
./miniflux.nix
./writefreely.nix
./synapse.nix
./phanpy.nix
./invidious.nix
./owncast.nix
./peertube.nix
./outline.nix
./vikunja.nix
./n8n.nix
];
system.stateVersion = "21.11";
common.linux.enable = false; # Don't enable the "common linux" module, this is a special machine.
# Personal user
@ -56,25 +46,27 @@
environment.systemPackages = with pkgs; [
git
htop-vim
kakoune
docker-compose
];
virtualisation.docker.enable = true;
virtualisation.docker.extraOptions = "--data-root /mnt/data/docker";
services.do-agent.enable = true;
system.autoUpgrade = {
enable = true;
allowReboot = true;
flake = "github:natsukagami/nix-home#nki-personal-do";
};
nix = {
package = pkgs.nixUnstable;
extraOptions = ''
experimental-features = nix-command flakes
'';
};
nki.services.edns.enable = true;
nki.services.edns.ipv6 = true;
# Secret management
sops.defaultSopsFile = ./secrets/secrets.yaml;
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
@ -87,10 +79,6 @@
services.my-tinc.rsaPrivateKey = config.sops.secrets."tinc/rsa-private-key".path;
services.my-tinc.ed25519PrivateKey = config.sops.secrets."tinc/ed25519-private-key".path;
sops.secrets."nix-build-farm/private-key" = { mode = "0400"; };
services.nix-build-farm.hostname = "home";
services.nix-build-farm.privateKeyFile = config.sops.secrets."nix-build-farm/private-key".path;
# Set up traefik
sops.secrets.cloudflare-dns-api-token = { owner = "traefik"; };
sops.secrets.traefik-dashboard-users = { owner = "traefik"; };
@ -107,13 +95,10 @@
settings.HOST = "127.0.0.1";
settings.PORT = "16904";
};
cloud.traefik.hosts.uptime-kuma = { host = "status.nkagami.me"; port = 16904; noCloudflare = true; };
cloud.traefik.hosts.uptime-kuma = { host = "status.nkagami.me"; port = 16904; };
cloud.traefik.hosts.uptime-kuma-dtth = { host = "status.dtth.ch"; port = 16904; };
cloud.traefik.hosts.uptime-kuma-codefun = { host = "status.codefun.vn"; port = 16904; };
# Bitwarden
sops.secrets.vaultwarden-env = { };
cloud.bitwarden.envFile = config.sops.secrets.vaultwarden-env.path;
# Arion
virtualisation.arion.backend = "docker";
@ -126,7 +111,6 @@
host = "m.nkagami.me";
port = 6167;
well-known_port = 6168;
noCloudflare = true;
};
};
cloud.conduit.heisenbridge = {
@ -150,15 +134,14 @@
host = "navidrome.nkagami.me";
port = 4533;
localHost = "11.0.0.2";
noCloudflare = true;
};
# Mail
sops.secrets.mail-users = { owner = "maddy"; reloadUnits = [ "maddy.service" ]; };
sops.secrets.mail-users = { owner = "maddy"; };
cloud.mail = {
enable = true;
debug = true;
local_ip = config.secrets.ipAddresses."nki.personal";
# local_ip = (builtins.elemAt config.networking.interfaces.eth0.ipv4.addresses 0).address;
tls.certFile = "${config.cloud.traefik.certsDumper.destination}/${config.cloud.mail.hostname}/certificate.crt";
tls.keyFile = "${config.cloud.traefik.certsDumper.destination}/${config.cloud.mail.hostname}/privatekey.key";
usersFile = config.sops.secrets.mail-users.path;
@ -195,15 +178,76 @@
protocol = "udp";
};
# Outline
sops.secrets.minio-secret-key = { owner = "root"; mode = "0444"; };
sops.secrets.authentik-oidc-client-secret = { owner = "outline"; };
sops.secrets."outline/smtp-password" = { owner = "outline"; };
cloud.services.outline = {
enable = true;
package = pkgs.unstable.outline.overrideAttrs (attrs: {
patches = attrs.patches ++ [ ../modules/cloud/outline/dtth-wiki.patch ];
});
databaseUrl = "postgres://outline:outline@localhost/outline?sslmode=disable";
sequelizeArguments = "--env=production-ssl-disabled";
redisUrl = "local";
publicUrl = "https://wiki.dtth.ch";
port = 18729;
storage = {
accessKey = "minio";
secretKeyFile = config.sops.secrets.minio-secret-key.path;
region = config.services.minio.region;
uploadBucketUrl = "https://s3.dtth.ch";
uploadBucketName = "dtth-outline";
uploadMaxSize = 50 * 1024 * 1000;
};
maximumImportSize = 50 * 1024 * 1000;
oidcAuthentication = {
clientId = "3a0c10e00cdcb4a1194315577fa208a747c1a5f7";
clientSecretFile = config.sops.secrets.authentik-oidc-client-secret.path;
authUrl = "https://auth.dtth.ch/application/o/authorize/";
tokenUrl = "https://auth.dtth.ch/application/o/token/";
userinfoUrl = "https://auth.dtth.ch/application/o/userinfo/";
displayName = "DTTH Account";
};
smtp = {
fromEmail = "DTTH Wiki <dtth.wiki@nkagami.me>";
replyEmail = "";
host = "mx1.nkagami.me";
username = "dtth.wiki@nkagami.me";
passwordFile = config.sops.secrets."outline/smtp-password".path;
port = 465;
secure = true;
};
forceHttps = false;
};
cloud.postgresql.databases = [ "outline" ];
cloud.traefik.hosts.outline = { host = "wiki.dtth.ch"; port = 18729; };
# GoToSocial
sops.secrets.gts-env = { restartUnits = [ "gotosocial.service" ]; };
sops.secrets.gts-env = { };
cloud.gotosocial = {
enable = true;
envFile = config.sops.secrets.gts-env.path;
};
# Minio
sops.secrets.minio-credentials = { };
services.minio = {
enable = true;
listenAddress = ":61929";
consoleAddress = ":62929";
rootCredentialsFile = config.sops.secrets.minio-credentials.path;
dataDir = lib.mkForce [ "/mnt/data/minio" ];
};
cloud.traefik.hosts.minio = { host = "s3.dtth.ch"; port = 61929; };
system.stateVersion = "21.11";
# ntfy
cloud.traefik.hosts.ntfy-sh = { host = "ntfy.nkagami.me"; port = 11161; noCloudflare = true; };
cloud.traefik.hosts.ntfy-sh = { host = "ntfy.nkagami.me"; port = 11161; };
services.ntfy-sh = {
enable = true;
settings = {

View file

@ -9,28 +9,11 @@ let
signingKey = "0x3681E15E5C14A241";
catppuccinThemes = pkgs.fetchurl {
url = "https://github.com/catppuccin/gitea/releases/download/v0.4.1/catppuccin-gitea.tar.gz";
hash = "sha256-/P4fLvswitlfeaKaUykrEKvjbNpw5Q/nzGQ/GZaLyUI=";
catppuccinThemes = builtins.fetchurl {
url = "https://github.com/catppuccin/gitea/releases/download/v0.2.1/catppuccin-gitea.tar.gz";
sha256 = "sha256:18l67whffayrgylsf5j6g7sj95anjcjl0cy7fzqn1wrm0gg2xns0";
};
staticDir = pkgs.runCommandLocal "forgejo-static" { } ''
mkdir -p $out
tmp=$(mktemp -d)
cp -r ${config.services.forgejo.package.data}/* $tmp
chmod -R +w $tmp
# Copy icons
install -m 0644 ${./gitea/img}/* $tmp/public/assets/img
# Copy the themes
env PATH=${pkgs.gzip}/bin:${pkgs.gnutar}/bin:$PATH \
tar -xvf ${catppuccinThemes} -C $tmp/public/assets/css
cp -r $tmp/* $out
'';
default-themes = "forgejo-auto, forgejo-light, forgejo-dark, gitea-auto, gitea-light, gitea-dark, forgejo-auto-deuteranopia-protanopia, forgejo-light-deuteranopia-protanopia, forgejo-dark-deuteranopia-protanopia, forgejo-auto-tritanopia, forgejo-light-tritanopia, forgejo-dark-tritanopia";
themes = strings.concatStringsSep ", " [
themes = strings.concatStringsSep "," [
"catppuccin-macchiato-green"
"catppuccin-mocha-teal"
"catppuccin-macchiato-sky"
@ -90,15 +73,7 @@ let
];
in
{
users.users.${user} = {
home = config.services.forgejo.stateDir;
useDefaultShell = true;
isSystemUser = true;
group = user;
};
users.groups.${user} = { };
sops.secrets."gitea/signing-key".owner = user;
sops.secrets."gitea/minio-secret-key".owner = user;
sops.secrets."gitea/mailer-password".owner = user;
# database
cloud.postgresql.databases = [ user ];
@ -108,25 +83,24 @@ in
noCloudflare = true;
};
systemd.services.forgejo.requires = [ "postgresql.service" ];
services.forgejo = {
services.gitea = {
enable = true;
inherit user;
appName = "DTTHgit";
settings = {
server = {
DOMAIN = host;
ROOT_URL = "https://${host}/";
HTTP_ADDRESS = "127.0.0.1";
HTTP_PORT = port;
STATIC_ROOT_PATH = staticDir;
};
repository = {
DEFAULT_PRIVATE = "private";
PREFERRED_LICENSES = strings.concatStringsSep "," [ "AGPL-3.0-or-later" "GPL-3.0-or-later" "Apache-2.0" ];
# DISABLE_HTTP_GIT = true;
DISABLE_HTTP_GIT = true;
DEFAULT_BRANCH = "master";
ENABLE_PUSH_CREATE_USER = true;
};
@ -135,10 +109,10 @@ in
};
"repository.signing" = {
SIGNING_KEY = signingKey;
SIGNING_NAME = "DTTHgit";
SIGNING_NAME = "DTTHGit";
SIGNING_EMAIL = "dtth-gitea@nkagami.me";
};
ui.THEMES = default-themes + "," + themes;
ui.THEMES = "auto,gitea,arc-green," + themes;
"ui.meta" = {
AUTHOR = "DTTHgit - Gitea instance for GTTH";
DESCRIPTION = "DTTHGit is a custom Gitea instance hosted for DTTH members only.";
@ -175,24 +149,10 @@ in
PATH = "${pkgs.git}/bin/git";
};
storage = {
STORAGE_TYPE = "minio";
MINIO_USE_SSL = "true";
MINIO_ENDPOINT = "60c0807121eb35ef52cdcd4a33735fa6.r2.cloudflarestorage.com";
MINIO_ACCESS_KEY_ID = "704c29ade7a8b438b77ab520da2799ca";
MINIO_BUCKET = "dtth-gitea";
MINIO_LOCATION = "auto";
MINIO_CHECKSUM_ALGORITHM = "md5"; # R2 moment
};
federation.ENABLED = true;
DEFAULT.APP_NAME = "DTTHGit";
};
stateDir = "/mnt/data/gitea";
secrets.mailer.PASSWD = secrets."gitea/mailer-password".path;
secrets.storage.MINIO_SECRET_ACCESS_KEY = config.sops.secrets."gitea/minio-secret-key".path;
mailerPasswordFile = secrets."gitea/mailer-password".path;
database = {
inherit user;
@ -210,23 +170,31 @@ in
};
# Set up gpg signing key
systemd.services.forgejo = {
systemd.services.gitea = {
path = with pkgs; [ gnupg ];
environment.GNUPGHOME = "${config.services.gitea.stateDir}/.gnupg";
# https://github.com/NixOS/nixpkgs/commit/93c1d370db28ad4573fb9890c90164ba55391ce7
serviceConfig.SystemCallFilter = mkForce "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
preStart =
''
preStart = ''
# Import the signing subkey
if cat ${config.services.forgejo.stateDir}/.gnupg/gpg.conf | grep -q ${signingKey}; then
if cat ${config.services.gitea.stateDir}/.gnupg/gpg.conf | grep -q ${signingKey}; then
echo "Keys already imported"
# imported
else
echo "Import your keys!"
${pkgs.gnupg}/bin/gpg --quiet --import ${secrets."gitea/signing-key".path}
echo "trusted-key ${signingKey}" >> ${config.services.forgejo.stateDir}/.gnupg/gpg.conf
echo "trusted-key ${signingKey}" >> ${config.services.gitea.stateDir}/.gnupg/gpg.conf
exit 1
fi
# Copy icons
mkdir -p ${config.services.gitea.stateDir}/custom/public/img
install -m 0644 ${./gitea/img}/* ${config.services.gitea.stateDir}/custom/public/img
# Copy the themes
mkdir -p ${config.services.gitea.stateDir}/custom/public/css
env PATH=${pkgs.gzip}/bin:${pkgs.gnutar}/bin:$PATH \
tar -xvf ${catppuccinThemes} -C ${config.services.gitea.stateDir}/custom/public/css/
'';
};
}

View file

@ -6,14 +6,11 @@
boot.initrd.kernelModules = [ "nvme" ];
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
# swap
swapDevices = [{ device = "/var/swapfile"; size = 4 * 1024; priority = 1024; }];
zramSwap.enable = true;
swapDevices = [{ device = "/var/swapfile"; size = 2 * 1024; }];
# volumes
services.btrfs.autoScrub.enable = true;
fileSystems.data = {
device = "/dev/disk/by-id/scsi-0HC_Volume_101470796";
fsType = "btrfs";
device = "/dev/disk/by-id/scsi-0HC_Volume_31812942";
fsType = "ext4";
mountPoint = "/mnt/data";
options = [ "compress=zstd" ];
};
}

View file

@ -16,19 +16,13 @@ rec {
cloud.traefik.hosts.headscale = {
inherit port host;
filter = "Host(`hs.dtth.ch`) && !PathPrefix(`/admin`)";
noCloudflare = true;
};
# cloud.traefik.config.http.middlewares.hs-sslheader.headers.customrequestheaders.X-Forwarded-Proto = "https";
# cloud.traefik.config.http.routers.headscale-router.middlewares = [ "hs-sslheader" ];
cloud.traefik.hosts.headscale_webui = {
inherit host;
port = webuiPort;
filter = "Host(`hs.dtth.ch`) && PathPrefix(`/admin`)";
noCloudflare = true;
};
systemd.services.headscale.requires = [ "postgresql.service" "arion-authentik.service" ];
systemd.services.headscale.after = [ "postgresql.service" "arion-authentik.service" ];
services.headscale = {
enable = true;
inherit port;
@ -36,25 +30,23 @@ rec {
settings = {
server_url = "https://hs.dtth.ch";
database.type = "postgres";
database.postgres = {
host = "/var/run/postgresql"; # find out yourself
user = "headscale";
name = "headscale";
};
db_type = "postgres";
db_host = "/var/run/postgresql"; # find out yourself
db_user = "headscale";
db_name = "headscale";
dns = {
base_domain = "dtth.ts";
dns_config = {
base_domain = host;
};
noise = {
private_key_path = "/var/lib/headscale/noise_private.key";
};
prefixes = {
v6 = "fd7a:115c:a1e0::/48";
v4 = "100.64.0.0/10";
};
ip_prefixes = [
"fd7a:115c:a1e0::/48"
"100.64.0.0/10"
];
derp.paths = [
secrets."headscale/derp-servers/vnm".path

View file

@ -1,71 +0,0 @@
{ config, pkgs, lib, ... }:
let
ipv6-rotator =
let
src = pkgs.fetchFromGitHub {
owner = "iv-org";
repo = "smart-ipv6-rotator";
rev = "61c019c2b14304c2a111a6db4c492d10ab2308cf";
hash = "sha256-a4BQH2D7La51vdPqMQSlZB73twX9Zcjq8mxbT5SdCpo=";
};
in
pkgs.writeShellApplication {
name = "smart-ipv6-rotator";
runtimeInputs = [ (pkgs.python3.withPackages (p: with p; [ pyroute2 requests ])) ];
text = ''
if [ -z "$IPV6_ROTATOR_RANGE" ]; then
echo "Range required"
exit 1
fi
python3 ${src}/smart-ipv6-rotator.py "$@" --ipv6range="$IPV6_ROTATOR_RANGE"
'';
};
in
{
sops.secrets."invidious" = { mode = "0444"; };
sops.secrets."invidious-rotator-env" = { mode = "0444"; };
cloud.postgresql.databases = [ "invidious" ];
cloud.traefik.hosts.invidious = { host = "invi.dtth.ch"; port = 61191; };
services.invidious = {
enable = true;
domain = "invi.dtth.ch";
port = 61191;
extraSettingsFile = config.sops.secrets.invidious.path;
settings = {
db.user = "invidious";
db.dbname = "invidious";
external_port = 443;
https_only = true;
hsts = false;
registration_enabled = true;
login_enabled = true;
admins = [ "nki" ];
# video_loop = false;
# autoplay = true;
# continue = true;
# continue_autoplay = true;
# listen = false;
# quality = "hd720";
# comments = [ "youtube" ];
# captions = [ "en" "vi" "de" "fr" ];
};
};
systemd.timers.smart-ipv6-rotator = {
description = "Rotate ipv6 routes to Google";
timerConfig = { OnCalendar = "*-*-* 00,06,12,18:00:00"; };
wantedBy = [ "invidious.service" "timers.target" ];
unitConfig = { };
};
systemd.services.smart-ipv6-rotator = {
serviceConfig = {
Type = "oneshot";
ExecStart = "${ipv6-rotator}/bin/smart-ipv6-rotator run";
EnvironmentFile = [
config.sops.secrets.invidious-rotator-env.path
];
};
};
}

View file

@ -43,7 +43,6 @@ in
description = "Miniflux service";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "postgresql.service" ];
requires = [ "postgresql.service" ];
serviceConfig = {
ExecStart = "${package}/bin/miniflux";

View file

@ -1,78 +0,0 @@
{ config, lib, pkgs, ... }:
let
secrets = config.sops.secrets;
host = "n8n.dtth.ch";
db = "n8n";
user = db;
port = 23412;
dataFolder = "/mnt/data/n8n";
plugins = pkgs.callPackage ./n8n/plugins/package.nix { };
in
{
sops.secrets."n8n/env" = { reloadUnits = [ "n8n.service" ]; };
cloud.postgresql.databases = [ db ];
cloud.traefik.hosts.n8n = {
inherit port host;
};
# users
users.users."${user}" = {
group = "${user}";
isSystemUser = true;
};
users.groups."${user}" = { };
services.n8n = {
enable = true;
webhookUrl = "https://${host}";
};
systemd.services.n8n = {
environment = {
# Database
DB_TYPE = "postgresdb";
DB_POSTGRESDB_DATABASE = db;
DB_POSTGRESDB_HOST = "/var/run/postgresql";
DB_POSTGRESDB_USER = db;
# Deployment
N8N_EDITOR_BASE_URL = "https://${host}";
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS = "true";
N8N_USER_FOLDER = lib.mkForce dataFolder;
HOME = lib.mkForce dataFolder;
N8N_HOST = host;
N8N_PORT = toString port;
N8N_LISTEN_ADDRESS = "127.0.0.1";
N8N_HIRING_BANNER_ENABLED = "false";
N8N_PROXY_HOPS = "1";
# Logs
N8N_LOG_LEVEL = "debug";
# License
N8N_HIDE_USAGE_PAGE = "true";
# Security
N8N_BLOCK_ENV_ACCESS_IN_NODE = "true";
# Timezone
GENERIC_TIMEZONE = "Europe/Berlin";
};
serviceConfig = {
EnvironmentFile = [ secrets."n8n/env".path ];
User = user;
DynamicUser = lib.mkForce false;
ReadWritePaths = [ dataFolder ];
# ReadOnlyPaths = [ "/var/run/postgresql" ];
};
unitConfig.RequiresMountsFor = [ dataFolder ];
};
systemd.tmpfiles.settings."10-n8n" = {
${dataFolder}.d = {
user = user;
group = user;
mode = "0700";
};
"${dataFolder}/.n8n/nodes"."L+" = {
argument = "${plugins}";
};
};
}

View file

@ -1 +0,0 @@
node_modules

File diff suppressed because it is too large Load diff

View file

@ -1,8 +0,0 @@
{
"name": "n8n-plugins",
"version": "1.0.0",
"dependencies": {
"n8n-nodes-turndown-html-to-markdown": "^1.0.5",
"n8n-nodes-vikunja": "^0.2.0"
}
}

View file

@ -1,4 +0,0 @@
{ nodejs, importNpmLock }: importNpmLock.buildNodeModules {
inherit nodejs;
npmRoot = ./.;
}

View file

@ -0,0 +1,66 @@
{ lib, pkgs, config, ... }:
with lib;
let
user = "nextcloud";
host = "cloud.dtth.ch";
port = 61155;
secrets = config.sops.secrets;
in
{
sops.secrets."nextcloud/admin-password" = { owner = user; };
sops.secrets."nextcloud/minio-secret-key" = { owner = user; key = "minio-secret-key"; };
# database
cloud.postgresql.databases = [ user ];
# traefik
cloud.traefik.hosts.nextcloud = {
inherit port host;
};
services.nextcloud = {
enable = true;
hostName = host;
package = pkgs.nextcloud26;
enableBrokenCiphersForSSE = false;
home = "/mnt/data/nextcloud";
https = true;
database.createLocally = false;
extraApps = with pkgs.nextcloud26Packages.apps; {
inherit calendar contacts deck forms groupfolders news tasks;
sociallogin = pkgs.fetchNextcloudApp rec {
url = "https://github.com/zorn-v/nextcloud-social-login/releases/download/v5.4.3/release.tar.gz";
sha256 = "sha256-ZKwtF9j9WFIk3MZgng9DmN00A73S2Rb4qbehL9adaZo=";
};
};
config = {
# Database
dbtype = "pgsql";
dbname = user;
dbuser = user;
dbhost = "/run/postgresql";
# User
adminuser = "nki";
adminpassFile = secrets."nextcloud/admin-password".path;
# General
overwriteProtocol = "https";
defaultPhoneRegion = "VN";
objectstore.s3 = {
enable = true;
bucket = "nextcloud-dtth";
autocreate = true;
key = "minio";
secretFile = config.sops.secrets."nextcloud/minio-secret-key".path;
hostname = "s3.dtth.ch";
port = 443;
useSsl = true;
usePathStyle = true;
region = "us-east-1";
};
};
};
services.nginx.virtualHosts.${host}.listen = [{ inherit port; addr = "127.0.0.1"; }];
}

View file

@ -1,56 +0,0 @@
{ config, pkgs, ... }: {
sops.secrets.authentik-oidc-client-secret = { owner = "outline"; };
sops.secrets."outline/smtp-password" = { owner = "outline"; };
sops.secrets."outline/s3-secret-key" = { owner = "outline"; };
services.outline = {
enable = true;
package = pkgs.outline.overrideAttrs (attrs: {
patches = attrs.patches or [ ] ++ [
../modules/cloud/outline/dtth-wiki.patch
../modules/cloud/outline/r2.patch
];
});
databaseUrl = "postgres://outline:outline@localhost/outline?sslmode=disable";
redisUrl = "local";
publicUrl = "https://wiki.dtth.ch";
port = 18729;
storage = {
accessKey = "6ef730e13f172d2ed6ed77f0b5b9bad9";
secretKeyFile = config.sops.secrets."outline/s3-secret-key".path;
region = "auto";
uploadBucketUrl = "https://60c0807121eb35ef52cdcd4a33735fa6.r2.cloudflarestorage.com";
uploadBucketName = "dtth-outline";
uploadMaxSize = 50 * 1024 * 1000;
};
maximumImportSize = 50 * 1024 * 1000;
oidcAuthentication = {
clientId = "3a0c10e00cdcb4a1194315577fa208a747c1a5f7";
clientSecretFile = config.sops.secrets.authentik-oidc-client-secret.path;
authUrl = "https://auth.dtth.ch/application/o/authorize/";
tokenUrl = "https://auth.dtth.ch/application/o/token/";
userinfoUrl = "https://auth.dtth.ch/application/o/userinfo/";
displayName = "DTTH Account";
};
smtp = {
fromEmail = "DTTH Wiki <dtth.wiki@nkagami.me>";
replyEmail = "";
host = "mx1.nkagami.me";
username = "dtth.wiki@nkagami.me";
passwordFile = config.sops.secrets."outline/smtp-password".path;
port = 465;
secure = true;
};
forceHttps = false;
};
cloud.postgresql.databases = [ "outline" ];
systemd.services.outline.requires = [ "postgresql.service" ];
systemd.services.outline.environment = {
AWS_S3_R2 = "true";
AWS_S3_R2_PUBLIC_URL = "https://s3.wiki.dtth.ch";
};
cloud.traefik.hosts.outline = { host = "wiki.dtth.ch"; port = 18729; };
}

View file

@ -1,19 +0,0 @@
{ pkgs, config, lib, ... }:
let
host = "owncast.nkagami.me";
port = 61347;
user = "owncast";
in
{
# traefik
cloud.traefik.hosts.owncast = {
inherit port host;
};
services.owncast = {
inherit user port;
listen = "0.0.0.0"; # Listen to direct IP requests too
enable = true;
openFirewall = true;
dataDir = "${config.fileSystems.data.mountPoint}/owncast";
};
}

View file

@ -1,93 +0,0 @@
{ config, lib, pkgs, ... }:
let
secrets = config.sops.secrets;
cfg = config.services.peertube;
user = "peertube";
host = "peertube.dtth.ch";
dataFolder = "/mnt/data/peertube";
port = 19878;
in
{
sops.secrets."peertube" = { owner = cfg.user; restartUnits = [ "peertube.service" ]; };
sops.secrets."peertube-env" = { owner = cfg.user; restartUnits = [ "peertube.service" ]; };
# database
cloud.postgresql.databases = [ "peertube" ];
# traefik
cloud.traefik.hosts.peertube = {
inherit port host;
noCloudflare = true;
};
services.peertube = {
enable = true;
enableWebHttps = true;
listenWeb = 443;
listenHttp = port;
localDomain = host;
secrets.secretsFile = secrets."peertube".path;
serviceEnvironmentFile = secrets."peertube-env".path;
# Databases
redis.createLocally = true;
database = {
host = "/run/postgresql";
};
# S3
settings.object_storage = {
enabled = true;
region = "auto";
proxy.proxify_private_files = false;
web_videos = {
bucket_name = "dtthtube";
prefix = "web-videos/";
base_url = "https://content.peertube.dtth.ch";
};
streaming_playlists = {
bucket_name = "dtthtube";
prefix = "hls-playlists/";
base_url = "https://content.peertube.dtth.ch";
};
};
# Storage
settings.client.videos = {
resumable_upload.max_chunk_size = "90MB";
};
# Trust proxy
settings.trust_proxy = [ "loopback" ] ++ config.services.traefik.staticConfigOptions.entrypoints.https.forwardedHeaders.trustedIPs;
# Federation
settings.federation = {
sign_federated_fetches = true;
videos.federate_unlisted = true;
videos.cleanup_remote_interactions = true;
};
dataDirs = [ "/var/lib/peertube" "/mnt/data/peertube" ];
};
systemd.services.peertube = {
requires = [ "arion-authentik.service" ];
after = [ "arion-authentik.service" ];
unitConfig.RequiresMountsFor = [ dataFolder ];
};
systemd.tmpfiles.settings."10-peertube" = {
# The service hard-codes a lot of paths here, so it's nicer if we just symlink
"/var/lib/peertube"."L+" = {
argument = dataFolder;
};
${dataFolder}."d" = {
user = user;
group = user;
mode = "0700";
};
};
}

View file

@ -1,16 +0,0 @@
{ config, pkgs, lib, ... }:
let
host = "social.dtth.ch";
port = 61010;
in
{
cloud.traefik.hosts.phanpy = { inherit host port; };
services.nginx.virtualHosts.phanpy = {
listen = [{
inherit port;
addr = "127.0.0.1";
}];
root = "${pkgs.dtth-phanpy}/lib/phanpy";
};
}

File diff suppressed because one or more lines are too long

View file

@ -23,13 +23,10 @@ in
};
# Synapse instance for DTTH
systemd.services.matrix-synapse.requires = [ "postgresql.service" ];
services.matrix-synapse = {
enable = true;
withJemalloc = true;
dataDir = "${config.fileSystems.data.mountPoint}/matrix-synapse-dtth";
extras = [ "systemd" "url-preview" "oidc" "postgres" ];
settings = {
server_name = "dtth.ch";
enable_registration = false;
@ -76,10 +73,6 @@ in
"fec0::/10"
];
app_service_config_files = app_services;
auto_join_rooms = [
"#join-dtth-matrix:dtth.ch"
];
};
extraConfigFiles = [
(config.sops.secrets."matrix-synapse-dtth/oidc-config".path)

View file

@ -1,99 +0,0 @@
{ pkgs, lib, config, ... }:
let
secrets = config.sops.secrets;
host = "kanban.dtth.ch";
user = "vikunja";
port = 12785;
storageMount = "/mnt/data/vikunja";
in
{
sops.secrets."vikunja/env" = { restartUnits = [ "vikunja.service" ]; };
sops.secrets."vikunja/provider-clientsecret" = { restartUnits = [ "vikunja.service" ]; };
cloud.postgresql.databases = [ user ];
cloud.traefik.hosts.vikunja = {
inherit port host;
};
# users
users.users."${user}" = {
group = "${user}";
isSystemUser = true;
};
users.groups."${user}" = { };
services.vikunja = {
inherit port;
enable = true;
frontendScheme = "https";
frontendHostname = host;
environmentFiles = [ secrets."vikunja/env".path ];
database = {
type = "postgres";
host = "/var/run/postgresql";
user = user;
database = user;
};
settings = {
service = {
publicurl = "https://${host}";
enableregistration = false;
enablepublicteams = true;
};
mailer = {
enabled = true;
host = "mx1.nkagami.me";
port = 465;
forcessl = true;
};
files.basepath = lib.mkForce storageMount;
migration = {
todoist.enable = true;
trello.enable = true;
};
backgrounds.providers.unsplash.enabled = true;
auth = {
local.enabled = false;
openid = {
enabled = true;
providers.authentik = {
name = "DTTH Discord Account";
authurl = "https://auth.dtth.ch/application/o/vikunja/";
logouturl = "https://auth.dtth.ch/application/o/vikunja/end-session/";
clientid = "GvCIBtdE2ZRbAo5BJzw4FbZjer7umJlaROT1Pvlp";
scope = "openid profile email vikunja_scope";
};
};
};
defaultsettings = {
avatar_provider = "gravatar";
week_start = 1;
language = "VN";
timezone = "Asia/Ho_Chi_Minh";
};
};
};
systemd.services.vikunja = {
serviceConfig.LoadCredential = [ "VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHENTIK_CLIENTSECRET_FILE:${secrets."vikunja/provider-clientsecret".path}" ];
serviceConfig.User = user;
serviceConfig.DynamicUser = lib.mkForce false;
serviceConfig.ReadWritePaths = [ storageMount ];
environment.VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHENTIK_CLIENTSECRET_FILE = "%d/VIKUNJA_AUTH_OPENID_PROVIDERS_AUTHENTIK_CLIENTSECRET_FILE";
unitConfig = {
RequiresMountsFor = [ storageMount ];
};
};
systemd.tmpfiles.settings."10-vikunja".${storageMount}.d = {
user = user;
group = user;
mode = "0700";
};
}

View file

@ -11,7 +11,7 @@ in
# traefik
cloud.traefik.hosts.writefreely-dtth = { inherit host port; };
sops.secrets."writefreely-oauth-secret" = { owner = user; };
sops.secrets."writefreely-dtth" = { owner = user; };
users.users.${user} = {
isSystemUser = true;
@ -53,24 +53,22 @@ in
monetization = false;
};
"oauth.generic" = { };
};
oauth = {
enable = true;
clientId = "rpoTTr2Wz0h4EgOSCHe0G85O8DCQDMup7JW9U9fV";
clientSecretFile = config.sops.secrets."writefreely-oauth-secret".path;
"oauth.generic" = {
client_id = "rpoTTr2Wz0h4EgOSCHe0G85O8DCQDMup7JW9U9fV";
host = "https://auth.dtth.ch";
displayName = "DTTH";
tokenEndpoint = "/application/o/token/";
inspectEndpoint = "/application/o/userinfo/";
authEndpoint = "/application/o/authorize/";
scopes = [ "email" "openid" "profile" ];
mapUserId = "nickname";
mapUsername = "preferred_username";
mapDisplayName = "name";
display_name = "DTTH";
token_endpoint = "/application/o/token/";
inspect_endpoint = "/application/o/userinfo/";
auth_endpoint = "/application/o/authorize/";
scope = "email openid profile";
map_user_id = "nickname";
map_username = "preferred_username";
map_display_name = "name";
allow_registration = true;
};
};
extraSettingsFile = config.sops.secrets."writefreely-dtth".path;
database.type = "sqlite3";

View file

@ -15,7 +15,8 @@ let
if value == true then "true" else "false"
else
toString value);
in "${key} = ${value'}";
in
"${key} = ${value'}";
};
cfg = config.nki.services.writefreely;
@ -31,7 +32,8 @@ let
host = cfg.settings.app.host or "${hostProtocol}://${cfg.host}";
};
database = if cfg.database.type == "sqlite3" then {
database =
if cfg.database.type == "sqlite3" then {
type = "sqlite3";
filename = cfg.settings.database.filename or "writefreely.db";
database = cfg.database.name;
@ -56,24 +58,6 @@ let
cfg.settings.server.pages_parent_dir or cfg.package.src;
keys_parent_dir = cfg.settings.server.keys_parent_dir or cfg.stateDir;
};
"oauth.generic" = cfg.settings."oauth.generic" or { } // (if cfg.oauth.enable then {
client_id = cfg.oauth.clientId;
client_secret = "#oauth_client_secret#";
host = cfg.oauth.host;
display_name = cfg.oauth.displayName;
callback_proxy = cfg.oauth.callbackProxy;
callback_proxy_api = cfg.oauth.callbackProxyApi;
token_endpoint = cfg.oauth.tokenEndpoint;
inspect_endpoint = cfg.oauth.inspectEndpoint;
auth_endpoint = cfg.oauth.authEndpoint;
scope = lib.concatStringsSep " " cfg.oauth.scopes;
allow_disconnect = cfg.oauth.allowDisconnect;
map_user_id = cfg.oauth.mapUserId;
map_username = cfg.oauth.mapUsername;
map_display_name = cfg.oauth.mapDisplayName;
map_email = cfg.oauth.mapEmail;
} else { });
};
configFile = format.generate "config.ini" settings;
@ -107,14 +91,10 @@ let
optionalString (cfg.database.passwordFile != null)
"$(head -n1 ${cfg.database.passwordFile})"
}
oauth_client_secret=${
optionalString cfg.oauth.enable
"$(head -n1 ${cfg.oauth.clientSecretFile})"
}
cp -f ${configFile} '${cfg.stateDir}/config.ini'
install -m 0660 ${configFile} '${cfg.stateDir}/config.ini'
sed -e "s,#dbpass#,$db_pass,g" -i '${cfg.stateDir}/config.ini'
sed -e "s,#oauth_client_secret#,$oauth_client_secret,g" -i '${cfg.stateDir}/config.ini'
${if cfg.extraSettingsFile != null then "cat ${cfg.extraSettingsFile} >> ${cfg.stateDir}/config.ini" else ""}
chmod 440 '${cfg.stateDir}/config.ini'
${text}
@ -143,7 +123,7 @@ let
withConfigFile ''
query () {
local result=$(${sqlite}/bin/sqlite3 \
'${cfg.stateDir}/${settings.database.filename}' \
'${cfg.stateDir}/${settings.database.filename}'
"$1" \
)
@ -152,46 +132,53 @@ let
${text}
'';
in {
in
{
options.nki.services.writefreely = {
enable =
lib.mkEnableOption "Writefreely, build a digital writing community";
lib.mkEnableOption (lib.mdDoc "Writefreely, build a digital writing community");
package = lib.mkOption {
type = lib.types.package;
default = pkgs.writefreely;
defaultText = lib.literalExpression "pkgs.writefreely";
description = "Writefreely package to use.";
description = lib.mdDoc "Writefreely package to use.";
};
stateDir = mkOption {
type = types.path;
default = "/var/lib/writefreely";
description = "The state directory where keys and data are stored.";
description = lib.mdDoc "The state directory where keys and data are stored.";
};
user = mkOption {
type = types.str;
default = "writefreely";
description = "User under which Writefreely is ran.";
description = lib.mdDoc "User under which Writefreely is ran.";
};
group = mkOption {
type = types.str;
default = "writefreely";
description = "Group under which Writefreely is ran.";
description = lib.mdDoc "Group under which Writefreely is ran.";
};
host = mkOption {
type = types.str;
default = "";
description = "The public host name to serve.";
description = lib.mdDoc "The public host name to serve.";
example = "example.com";
};
extraSettingsFile = mkOption {
type = types.nullOr types.path;
default = null;
description = lib.mdDoc "Additional configs to be appended to the config file";
};
settings = mkOption {
default = { };
description = ''
description = lib.mdDoc ''
Writefreely configuration ({file}`config.ini`). Refer to
<https://writefreely.org/docs/latest/admin/config>
for details.
@ -205,7 +192,7 @@ in {
theme = mkOption {
type = types.str;
default = "write";
description = "The theme to apply.";
description = lib.mdDoc "The theme to apply.";
};
};
@ -214,7 +201,7 @@ in {
type = types.port;
default = if cfg.nginx.enable then 18080 else 80;
defaultText = "80";
description = "The port WriteFreely should listen on.";
description = lib.mdDoc "The port WriteFreely should listen on.";
};
};
};
@ -225,158 +212,74 @@ in {
type = mkOption {
type = types.enum [ "sqlite3" "mysql" ];
default = "sqlite3";
description = "The database provider to use.";
description = lib.mdDoc "The database provider to use.";
};
name = mkOption {
type = types.str;
default = "writefreely";
description = "The name of the database to store data in.";
description = lib.mdDoc "The name of the database to store data in.";
};
user = mkOption {
type = types.nullOr types.str;
default = if cfg.database.type == "mysql" then "writefreely" else null;
defaultText = "writefreely";
description = "The database user to connect as.";
description = lib.mdDoc "The database user to connect as.";
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "The file to load the database password from.";
description = lib.mdDoc "The file to load the database password from.";
};
host = mkOption {
type = types.str;
default = "localhost";
description = "The database host to connect to.";
description = lib.mdDoc "The database host to connect to.";
};
port = mkOption {
type = types.port;
default = 3306;
description = "The port used when connecting to the database host.";
description = lib.mdDoc "The port used when connecting to the database host.";
};
tls = mkOption {
type = types.bool;
default = false;
description = "Whether or not TLS should be used for the database connection.";
description =
lib.mdDoc "Whether or not TLS should be used for the database connection.";
};
migrate = mkOption {
type = types.bool;
default = true;
description = "Whether or not to automatically run migrations on startup.";
description =
lib.mdDoc "Whether or not to automatically run migrations on startup.";
};
createLocally = mkOption {
type = types.bool;
default = false;
description = ''
description = lib.mdDoc ''
When {option}`services.writefreely.database.type` is set to
`"mysql"`, this option will enable the MySQL service locally.
'';
};
};
oauth = {
enable = lib.mkEnableOption "Enable generic OAuth authentication";
clientId = mkOption {
type = types.str;
description = "The client ID associated with WriteFreely in the OAuth provider application.";
};
clientSecretFile = mkOption {
type = types.str;
description = "The file to load the OAuth client secret from.";
};
host = mkOption {
type = types.str;
description = "The base url of the OAuth provider application, including the protocol.";
example = "https://example.com";
};
displayName = mkOption {
type = types.str;
description = "The human-readable name of the OAuth service that appears on the login button, will appear as `Log in with [display_name]`.";
};
callbackProxy = mkOption {
type = types.str;
default = "";
description = "The url of an inbound proxy that sits in front of the default `/oauth/callback/generic` endpoint. Use if you want the OAuth callback to be somewhere other than that generic location. Default is blank.";
example = "https://example.com/whatever/path";
};
callbackProxyApi = mkOption {
type = types.str;
default = "";
description = "The url of an outbound proxy to send your OAuth requests through. Default is blank.";
example = "https://my-proxy.example.com";
};
tokenEndpoint = mkOption {
type = types.str;
description = "The API endpoint of the OAuth provider implementation to obtain an access token by presenting an authorization grant or refresh token. This is a fragment of a url, appended to host as described above.";
example = "/oauth/token";
};
inspectEndpoint = mkOption {
type = types.str;
description = "The API endpoint of the OAuth provider that returns basic user info given their authentication information. This is a fragment of a url, appended to host as described above.";
example = "/oauth/userinfo";
};
authEndpoint = mkOption {
type = types.str;
description = "The API endpoint of the OAuth provider that returns an authorization grant. This is a fragment of a url, appended to host as described above.";
example = "public";
};
scopes = mkOption {
type = types.listOf types.str;
default = [ "read_user" ];
description = "A scope or set of scopes required by some OAuth providers. This will usually be blank in this config file, and is set to `read_user` by default.";
};
allowDisconnect = mkOption {
type = types.bool;
default = false;
description = "Whether or not an individual user is allowed to disconnect this OAuth provider from their account.";
};
mapUserId = mkOption {
type = types.str;
default = "";
defaultText = "<none>";
description = "Use this User ID key in the provider's user info, instead of the default key (user_id).";
};
mapUsername = mkOption {
type = types.str;
default = "";
defaultText = "<none>";
description = "Use this Username key in the provider's user info, instead of the default key (username)";
};
mapDisplayName = mkOption {
type = types.str;
default = "";
defaultText = "<none>";
description = "Use this Display Name key in the provider's user info, instead of the default key (*none*)";
};
mapEmail = mkOption {
type = types.str;
default = "";
defaultText = "<none>";
description = "Use this Email key in the provider's user info, instead of the default key (email)";
};
};
admin = {
name = mkOption {
type = types.nullOr types.str;
description = "The name of the first admin user.";
description = lib.mdDoc "The name of the first admin user.";
default = null;
};
initialPasswordFile = mkOption {
type = types.path;
description = ''
description = lib.mdDoc ''
Path to a file containing the initial password for the admin user.
If not provided, the default password will be set to `nixos`.
'';
@ -389,13 +292,14 @@ in {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether or not to enable and configure nginx as a proxy for WriteFreely.";
description =
lib.mdDoc "Whether or not to enable and configure nginx as a proxy for WriteFreely.";
};
forceSSL = mkOption {
type = types.bool;
default = false;
description = "Whether or not to force the use of SSL.";
description = lib.mdDoc "Whether or not to force the use of SSL.";
};
};
@ -403,7 +307,8 @@ in {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether or not to automatically fetch and configure SSL certs.";
description =
lib.mdDoc "Whether or not to automatically fetch and configure SSL certs.";
};
};
};
@ -439,12 +344,11 @@ in {
optionalAttrs (cfg.group == "writefreely") { writefreely = { }; };
};
systemd.tmpfiles.settings."10-writefreely".${cfg.stateDir}.d = {
inherit (cfg) user group;
mode = "0750";
};
systemd.tmpfiles.rules =
[ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" ];
systemd.services.writefreely = {
path = with pkgs; [ openssl ];
after = [ "network.target" ]
++ optional isSqlite "writefreely-sqlite-init.service"
++ optional isMysql "writefreely-mysql-init.service"
@ -489,7 +393,8 @@ in {
cfg.admin.initialPasswordFile;
};
script = let
script =
let
migrateDatabase = optionalString cfg.database.migrate ''
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db migrate
'';
@ -501,7 +406,8 @@ in {
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
fi
'';
in withSqlite ''
in
withSqlite ''
if ! test -f '${settings.database.filename}'; then
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' db init
fi
@ -526,7 +432,8 @@ in {
cfg.admin.initialPasswordFile;
};
script = let
script =
let
updateUser = optionalString isMysqlLocal ''
# WriteFreely currently *requires* a password for authentication, so we
# need to update the user in MySQL accordingly. By default MySQL users
@ -545,7 +452,8 @@ in {
${cfg.package}/bin/writefreely -c '${cfg.stateDir}/config.ini' --create-admin ${cfg.admin.name}:$admin_pass
fi
'';
in withMysql ''
in
withMysql ''
${updateUser}
if [[ $(query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = '${cfg.database.name}'") == 0 ]]; then
@ -589,3 +497,4 @@ in {
};
};
}

View file

@ -12,6 +12,7 @@
# Fonts
../modules/personal/fonts
# Some PAM stuff
../modules/services/pam/gnome-keyring.nix
../modules/services/swaylock.nix
];
# Use the latest kernel
@ -115,6 +116,9 @@
pciutils
];
# Gnome-keyring is useful
services.gnome.gnome-keyring.enable = true;
## Environment variables
environment.variables = {
# Input method overrides
@ -141,6 +145,7 @@
# };
# List services that you want to enable:
nki.services.pam.enableGnomeKeyring = true;
services.input-remapper.enable = true;
services.swaylock.enable = true;

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