diff --git a/.envrc b/.envrc deleted file mode 100644 index 3550a30..0000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -use flake diff --git a/.gitignore b/.gitignore index 32cf8df..eea2bc6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,6 @@ # Ignore build outputs from performing a nix-build or `nix build` command result result-* -repl-result* -# ---> Kate -# Ignore kate's swap files -*.kate-swp - - -# ---> Direnv -.direnv +# I don't want to do a commit everytime that I want to update stuff +flake.lock diff --git a/.helix/languages.toml b/.helix/languages.toml deleted file mode 100644 index 3c8a111..0000000 --- a/.helix/languages.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[language]] -name = "nix" -formatter = { command = "alejandra" } diff --git a/.justfile b/.justfile deleted file mode 100644 index 9b4041a..0000000 --- a/.justfile +++ /dev/null @@ -1,33 +0,0 @@ -# Hide the default option from the recipes list -_default: print-recipes - -# Escape codes for text formatting -bold := `tput bold` -normal := `tput sgr0` - -find-results: - find . -name '*result*' - -print-recipes: - @just --list - -update: - nix flake update - -@edit-secrets: - git clone ssh://forgejo@git.toast003.xyz:4222/Toast/nix-secrets.git /tmp/secrets - sed -i 's\git+ssh://forgejo@git.toast003.xyz:4222/Toast/nix-secrets\/tmp/secrets\g' flake.nix - nix flake update secrets - echo "{{bold}}All done!" - echo "{{normal}}Remember to restore flake.nix" - echo "" - echo "" - -alias build := build-nixos -# Build a NixOS configuration -build-nixos host=`hostname`: - nom build .#nixosConfigurations.{{host}}.config.system.build.toplevel - -# nix-diff with some parameters piped to less -nix-diff left right: - nix-diff --color=always --skip-already-compared {{left}} {{right}} | less -F diff --git a/.vscode/settings.json b/.vscode/settings.json index 4b907f6..6321126 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,7 @@ { - "editor.detectIndentation": true, - "editor.insertSpaces": true, - "editor.defaultFormatter": "jnoortheen.nix-ide", - "nix.formatterPath": "alejandra", - "nix.serverSettings": { - "nil": { - "formatting": { - "command": [ - "alejandra" - ] - } - } - } + "editor.detectIndentation": true, + "editor.insertSpaces": false, + "editor.tabSize": 2, + "editor.renderWhitespace": "all", + "editor.defaultFormatter": "jnoortheen.nix-ide" } diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 8c9a225..0000000 --- a/flake.lock +++ /dev/null @@ -1,608 +0,0 @@ -{ - "nodes": { - "agenix": { - "inputs": { - "darwin": [], - "home-manager": "home-manager", - "nixpkgs": [ - "nixpkgs-raw" - ], - "systems": "systems" - }, - "locked": { - "lastModified": 1762618334, - "narHash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ=", - "owner": "ryantm", - "repo": "agenix", - "rev": "fcdea223397448d35d9b31f798479227e80183f6", - "type": "github" - }, - "original": { - "owner": "ryantm", - "repo": "agenix", - "type": "github" - } - }, - "catppuccin": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1765990358, - "narHash": "sha256-l8x0gU8mnYaGMl+gWrsSHKBJlZWD8KXJfHTkRlFiPI0=", - "owner": "catppuccin", - "repo": "nix", - "rev": "de1b60ca45a578f59f7d84c8d338b346017b2161", - "type": "github" - }, - "original": { - "owner": "catppuccin", - "repo": "nix", - "type": "github" - } - }, - "catppuccin-konsole": { - "flake": false, - "locked": { - "lastModified": 1720277724, - "narHash": "sha256-d5+ygDrNl2qBxZ5Cn4U7d836+ZHz77m6/yxTIANd9BU=", - "owner": "catppuccin", - "repo": "konsole", - "rev": "3b64040e3f4ae5afb2347e7be8a38bc3cd8c73a8", - "type": "github" - }, - "original": { - "owner": "catppuccin", - "repo": "konsole", - "type": "github" - } - }, - "copyparty": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": [ - "nixpkgs-raw" - ] - }, - "locked": { - "lastModified": 1766045858, - "narHash": "sha256-lsbdHVSc5EB2+XgKDbeG1DjLLY5DnzlKQIPV0uQu/bQ=", - "owner": "9001", - "repo": "copyparty", - "rev": "0e6b167167eaf04036df8576f1ea96bc116ea951", - "type": "github" - }, - "original": { - "owner": "9001", - "repo": "copyparty", - "type": "github" - } - }, - "eza-themes": { - "flake": false, - "locked": { - "lastModified": 1765813820, - "narHash": "sha256-WcwzKm2mi/tyA+zZCpyvTdrOrZ1R1ENA3t622SGzFas=", - "owner": "eza-community", - "repo": "eza-themes", - "rev": "1239cb1dd23fa8b70865550db77701b164a53cde", - "type": "github" - }, - "original": { - "owner": "eza-community", - "repo": "eza-themes", - "type": "github" - } - }, - "flake-utils": { - "locked": { - "lastModified": 1678901627, - "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { - "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flakey-profile": { - "locked": { - "lastModified": 1712898590, - "narHash": "sha256-FhGIEU93VHAChKEXx905TSiPZKga69bWl1VB37FK//I=", - "owner": "lf-", - "repo": "flakey-profile", - "rev": "243c903fd8eadc0f63d205665a92d4df91d42d9d", - "type": "github" - }, - "original": { - "owner": "lf-", - "repo": "flakey-profile", - "type": "github" - } - }, - "flakey-profile_2": { - "locked": { - "lastModified": 1712898590, - "narHash": "sha256-FhGIEU93VHAChKEXx905TSiPZKga69bWl1VB37FK//I=", - "owner": "lf-", - "repo": "flakey-profile", - "rev": "243c903fd8eadc0f63d205665a92d4df91d42d9d", - "type": "github" - }, - "original": { - "owner": "lf-", - "repo": "flakey-profile", - "type": "github" - } - }, - "home-manager": { - "inputs": { - "nixpkgs": [ - "agenix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1745494811, - "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" - } - }, - "home-manager-unstable": { - "inputs": { - "nixpkgs": [ - "nixpkgs-unstable-raw" - ] - }, - "locked": { - "lastModified": 1765980955, - "narHash": "sha256-rB45jv4uwC90vM9UZ70plfvY/2Kdygs+zlQ07dGQFk4=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "89c9508bbe9b40d36b3dc206c2483ef176f15173", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" - } - }, - "home-manager_2": { - "inputs": { - "nixpkgs": [ - "nixpkgs-raw" - ] - }, - "locked": { - "lastModified": 1765979862, - "narHash": "sha256-/r9/1KamvbHJx6I40H4HsSXnEcBAkj46ZwibhBx9kg0=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "d3135ab747fd9dac250ffb90b4a7e80634eacbe9", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "release-25.11", - "repo": "home-manager", - "type": "github" - } - }, - "jovian": { - "inputs": { - "nix-github-actions": "nix-github-actions", - "nixpkgs": [ - "nixpkgs-unstable-raw" - ] - }, - "locked": { - "lastModified": 1766067735, - "narHash": "sha256-cRC/rOYRtZNzc5y9nTccozyo/mkI4/1eFE33Aqgs+SQ=", - "owner": "Jovian-Experiments", - "repo": "Jovian-NixOS", - "rev": "34a16089be30f77ac9444907ec97c02b4b711896", - "type": "github" - }, - "original": { - "owner": "Jovian-Experiments", - "repo": "Jovian-NixOS", - "type": "github" - } - }, - "lix": { - "flake": false, - "locked": { - "lastModified": 1765883751, - "narHash": "sha256-clrWX/t2swPGBVs50Yegq2HK3q5bbwOt3kWMsL7JIZM=", - "rev": "fc0073f54095f15ee272621d4746eb9f40946385", - "type": "tarball", - "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/fc0073f54095f15ee272621d4746eb9f40946385.tar.gz?rev=fc0073f54095f15ee272621d4746eb9f40946385" - }, - "original": { - "type": "tarball", - "url": "https://git.lix.systems/lix-project/lix/archive/main.tar.gz" - } - }, - "lix-module": { - "inputs": { - "flake-utils": "flake-utils_2", - "flakey-profile": "flakey-profile", - "lix": [ - "lix" - ], - "nixpkgs": [ - "nixpkgs-raw" - ] - }, - "locked": { - "lastModified": 1764519849, - "narHash": "sha256-XnNABKfIYKSimQVvKc9FnlC2H0LurOhd9MS6l0Z67lE=", - "rev": "6c95c0b6f73f831226453fc6905c216ab634c30f", - "type": "tarball", - "url": "https://git.lix.systems/api/v1/repos/lix-project/nixos-module/archive/6c95c0b6f73f831226453fc6905c216ab634c30f.tar.gz?rev=6c95c0b6f73f831226453fc6905c216ab634c30f" - }, - "original": { - "type": "tarball", - "url": "https://git.lix.systems/lix-project/nixos-module/archive/main.tar.gz" - } - }, - "lix-module-unstable": { - "inputs": { - "flake-utils": "flake-utils_3", - "flakey-profile": "flakey-profile_2", - "lix": [ - "lix" - ], - "nixpkgs": [ - "nixpkgs-unstable-raw" - ] - }, - "locked": { - "lastModified": 1764519849, - "narHash": "sha256-XnNABKfIYKSimQVvKc9FnlC2H0LurOhd9MS6l0Z67lE=", - "rev": "6c95c0b6f73f831226453fc6905c216ab634c30f", - "type": "tarball", - "url": "https://git.lix.systems/api/v1/repos/lix-project/nixos-module/archive/6c95c0b6f73f831226453fc6905c216ab634c30f.tar.gz?rev=6c95c0b6f73f831226453fc6905c216ab634c30f" - }, - "original": { - "type": "tarball", - "url": "https://git.lix.systems/lix-project/nixos-module/archive/main.tar.gz" - } - }, - "nix-flatpak": { - "locked": { - "lastModified": 1754777568, - "narHash": "sha256-0bBqT+3XncgF8F03RFAamw9vdf0VmaDoIJLTGkjfQZs=", - "owner": "gmodena", - "repo": "nix-flatpak", - "rev": "62f636b87ef6050760a8cb325cadb90674d1e23e", - "type": "github" - }, - "original": { - "owner": "gmodena", - "ref": "main", - "repo": "nix-flatpak", - "type": "github" - } - }, - "nix-github-actions": { - "inputs": { - "nixpkgs": [ - "jovian", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1729697500, - "narHash": "sha256-VFTWrbzDlZyFHHb1AlKRiD/qqCJIripXKiCSFS8fAOY=", - "owner": "zhaofengli", - "repo": "nix-github-actions", - "rev": "e418aeb728b6aa5ca8c5c71974e7159c2df1d8cf", - "type": "github" - }, - "original": { - "owner": "zhaofengli", - "ref": "matrix-name", - "repo": "nix-github-actions", - "type": "github" - } - }, - "nix-impermanence": { - "locked": { - "lastModified": 1737831083, - "narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=", - "owner": "nix-community", - "repo": "impermanence", - "rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "impermanence", - "type": "github" - } - }, - "nix-index-db": { - "inputs": { - "nixpkgs": [ - "nixpkgs-raw" - ] - }, - "locked": { - "lastModified": 1765267181, - "narHash": "sha256-d3NBA9zEtBu2JFMnTBqWj7Tmi7R5OikoU2ycrdhQEws=", - "owner": "Mic92", - "repo": "nix-index-database", - "rev": "82befcf7dc77c909b0f2a09f5da910ec95c5b78f", - "type": "github" - }, - "original": { - "owner": "Mic92", - "repo": "nix-index-database", - "type": "github" - } - }, - "nix-index-db-unstable": { - "inputs": { - "nixpkgs": [ - "nixpkgs-unstable-raw" - ] - }, - "locked": { - "lastModified": 1765267181, - "narHash": "sha256-d3NBA9zEtBu2JFMnTBqWj7Tmi7R5OikoU2ycrdhQEws=", - "owner": "Mic92", - "repo": "nix-index-database", - "rev": "82befcf7dc77c909b0f2a09f5da910ec95c5b78f", - "type": "github" - }, - "original": { - "owner": "Mic92", - "repo": "nix-index-database", - "type": "github" - } - }, - "nixos-hardware": { - "locked": { - "lastModified": 1764440730, - "narHash": "sha256-ZlJTNLUKQRANlLDomuRWLBCH5792x+6XUJ4YdFRjtO4=", - "owner": "NixOS", - "repo": "nixos-hardware", - "rev": "9154f4569b6cdfd3c595851a6ba51bfaa472d9f3", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "master", - "repo": "nixos-hardware", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1763966396, - "narHash": "sha256-6eeL1YPcY1MV3DDStIDIdy/zZCDKgHdkCmsrLJFiZf0=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "5ae3b07d8d6527c42f17c876e404993199144b6a", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-raw": { - "locked": { - "lastModified": 1765838191, - "narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "ref": "nixos-25.11", - "type": "indirect" - } - }, - "nixpkgs-unstable-raw": { - "locked": { - "lastModified": 1765779637, - "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "ref": "nixos-unstable", - "type": "indirect" - } - }, - "plasma-manager": { - "inputs": { - "home-manager": [ - "home-manager-unstable" - ], - "nixpkgs": [ - "nixpkgs-unstable-raw" - ] - }, - "locked": { - "lastModified": 1763909441, - "narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=", - "owner": "nix-community", - "repo": "plasma-manager", - "rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "plasma-manager", - "type": "github" - } - }, - "root": { - "inputs": { - "agenix": "agenix", - "catppuccin": "catppuccin", - "catppuccin-konsole": "catppuccin-konsole", - "copyparty": "copyparty", - "eza-themes": "eza-themes", - "home-manager": "home-manager_2", - "home-manager-unstable": "home-manager-unstable", - "jovian": "jovian", - "lix": "lix", - "lix-module": "lix-module", - "lix-module-unstable": "lix-module-unstable", - "nix-flatpak": "nix-flatpak", - "nix-impermanence": "nix-impermanence", - "nix-index-db": "nix-index-db", - "nix-index-db-unstable": "nix-index-db-unstable", - "nixos-hardware": "nixos-hardware", - "nixpkgs-raw": "nixpkgs-raw", - "nixpkgs-unstable-raw": "nixpkgs-unstable-raw", - "plasma-manager": "plasma-manager", - "secrets": "secrets", - "sops-nix": "sops-nix" - } - }, - "secrets": { - "flake": false, - "locked": { - "lastModified": 1766143747, - "narHash": "sha256-bG4QoCZLUDrubYFuRvxiXhycBD3R+UjrzXrNZ+qRnio=", - "ref": "refs/heads/main", - "rev": "8921f23861a82f0f8d706c276bc738ca72c053b1", - "revCount": 41, - "type": "git", - "url": "ssh://forgejo@git.toast003.xyz:4222/Toast/nix-secrets" - }, - "original": { - "type": "git", - "url": "ssh://forgejo@git.toast003.xyz:4222/Toast/nix-secrets" - } - }, - "sops-nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs-unstable-raw" - ] - }, - "locked": { - "lastModified": 1765836173, - "narHash": "sha256-hWRYfdH2ONI7HXbqZqW8Q1y9IRbnXWvtvt/ONZovSNY=", - "owner": "Mic92", - "repo": "sops-nix", - "rev": "443a7f2e7e118c4fc63b7fae05ab3080dd0e5c63", - "type": "github" - }, - "original": { - "owner": "Mic92", - "repo": "sops-nix", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix old mode 100644 new mode 100755 index 8bd2ed9..8c8c19f --- a/flake.nix +++ b/flake.nix @@ -1,239 +1,84 @@ { - description = "Configuration for Everest"; +description = "Configuration for Everest"; - inputs = { - secrets = { - url = "git+ssh://forgejo@git.toast003.xyz:4222/Toast/nix-secrets"; - flake = false; - }; - nixpkgs-raw.url = "nixpkgs/nixos-25.11"; - nixpkgs-unstable-raw.url = "nixpkgs/nixos-unstable"; +inputs = { +nixpkgs.url = "nixpkgs/nixos-23.05"; +nixpkgs-unstable.url = "nixpkgs/nixos-unstable"; - agenix = { - url = "github:ryantm/agenix"; - inputs = { - nixpkgs.follows = "nixpkgs-raw"; - darwin.follows = ""; # Not using this on MacOS, so this doesn't pull it's dependencies - }; - }; - sops-nix = { - url = "github:Mic92/sops-nix"; - inputs.nixpkgs.follows = "nixpkgs-unstable-raw"; - }; +agenix = { + url = "github:ryantm/agenix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + darwin.follows = ""; # Not using this on MacOS, so this doesn't pull it's dependencies + }; +}; - home-manager = { - url = "github:nix-community/home-manager/release-25.11"; - inputs.nixpkgs.follows = "nixpkgs-raw"; - }; +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/"; - inputs.nixpkgs.follows = "nixpkgs-unstable-raw"; - }; +nix-impermanence.url = "github:nix-community/impermanence"; +}; - nixos-hardware.url = "github:NixOS/nixos-hardware/master"; +outputs = {nixpkgs, agenix, home-manager, nixpkgs-unstable, nix-impermanence, ... }: { + devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell { + name = "Environment for toast's nixos configurations"; + # The agenix cli is not needed to activate a configuration, so instead of installing it + # I'll just add it to de devShell, since that's the only real time I'm going to use it. + packages = [ + agenix.packages.x86_64-linux.default + nixpkgs.legacyPackages.x86_64-linux.git + ]; + shellHook ='' + export PS1="$PS1(toast-configs)> " + ''; + }; - jovian = { - url = "github:Jovian-Experiments/Jovian-NixOS"; - inputs.nixpkgs.follows = "nixpkgs-unstable-raw"; - }; + nixosConfigurations = { + Archie = nixpkgs-unstable.lib.nixosSystem { + system = "x86_64-linux"; + pkgs = import nixpkgs-unstable { + system = "x86_64-linux"; + config = { allowUnfree = true; }; # TODO: Find why this doesn't work + overlays = + let + discordOverlay = self: super: { + discord = super.discord.override { + withOpenASAR = true; + withVencord = true; + }; + }; + in + [ discordOverlay ]; + }; + modules = [ + # Needed for nix-index + { nix.nixPath = [ "nixpkgs=${nixpkgs}" ]; } + agenix.nixosModules.default + home-manager.nixosModule + ./roles/common + ./roles/desktop + ./roles/kde + ./machines/Archie + ]; + }; - nix-impermanence.url = "github:nix-community/impermanence"; - - copyparty.url = "github:9001/copyparty"; - copyparty.inputs.nixpkgs.follows = "nixpkgs-raw"; - - /* - These are the same input, just following different nixpkgs versions - This avoids some wierdness when using one that follows unstable on a stable nixpkgs - */ - nix-index-db = { - url = "github:Mic92/nix-index-database"; - inputs.nixpkgs.follows = "nixpkgs-raw"; - }; - - nix-index-db-unstable = { - url = "github:Mic92/nix-index-database"; - inputs.nixpkgs.follows = "nixpkgs-unstable-raw"; - }; - - plasma-manager = { - url = "github:nix-community/plasma-manager/"; - inputs.nixpkgs.follows = "nixpkgs-unstable-raw"; - inputs.home-manager.follows = "home-manager-unstable"; - }; - - nix-flatpak.url = "github:gmodena/nix-flatpak/main"; - - catppuccin.url = "github:catppuccin/nix"; - - lix-module = { - url = "https://git.lix.systems/lix-project/nixos-module/archive/main.tar.gz"; - inputs.nixpkgs.follows = "nixpkgs-raw"; - inputs.lix.follows = "lix"; - }; - lix-module-unstable = { - url = "https://git.lix.systems/lix-project/nixos-module/archive/main.tar.gz"; - inputs.nixpkgs.follows = "nixpkgs-unstable-raw"; - inputs.lix.follows = "lix"; - }; - - # Non flake inputs / random things - lix = { - url = "https://git.lix.systems/lix-project/lix/archive/main.tar.gz"; - flake = false; - }; - - eza-themes = { - url = "github:eza-community/eza-themes"; - flake = false; - }; - - catppuccin-konsole = { - url = "github:catppuccin/konsole"; - flake = false; - }; - }; - - outputs = {...} @ inputs: - with inputs; - # Patch nixpkgs - # https://ertt.ca/nix/patch-nixpkgs/ - let - nixpkgs-unstable-patched = nixpkgs-raw.legacyPackages.x86_64-linux.applyPatches { - name = "patched-nixpkgs-unstable"; - src = nixpkgs-unstable-raw; - patches = [ - ./nixpkgs-patches/pr471291.patch - ./nixpkgs-patches/revert-mangohud-update.patch - ]; - }; - nixpkgs-patched = nixpkgs-raw.legacyPackages.x86_64-linux.applyPatches { - name = "patched-nixpkgs"; - src = nixpkgs-raw; - patches = [ - ]; - }; - # https://discourse.nixos.org/t/proper-way-of-applying-patch-to-system-managed-via-flake/21073/26 - nixpkgs-unstable = (import "${nixpkgs-unstable-patched}/flake.nix").outputs {self = inputs.nixpkgs-unstable-raw;}; - nixpkgs = (import "${nixpkgs-patched}/flake.nix").outputs {self = inputs.nixpkgs-raw;}; - in { - formatter.x86_64-linux = nixpkgs-unstable.legacyPackages.x86_64-linux.alejandra; - devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShellNoCC { - name = "toast-devshell"; - # The agenix cli is not needed to activate a configuration, so instead of installing it - # I'll just add it to de devShell, since that's the only real time I'm going to use it. - packages = with nixpkgs.legacyPackages.x86_64-linux; [ - agenix.packages.x86_64-linux.default - git - nvd - nix-output-monitor - nix-diff - just - alejandra - ]; - shellHook = '' - export PS1="$PS1(toast-configs)> " - ''; - }; - overlays.default = final: prev: { - kasane-teto-cursor = final.callPackage ./pkgs/kasane-teto-cursor {}; - kame-editor = final.callPackage ./pkgs/kame-editor {}; - kame-tools = final.callPackage ./pkgs/kame-tools {}; - rstmcpp = final.callPackage ./pkgs/rstmcpp {}; - }; - packages = { - x86_64-linux = with import nixpkgs-unstable-raw { - system = "x86_64-linux"; - overlays = [self.overlays.default]; - }; { - inherit kasane-teto-cursor kame-editor kame-tools rstmcpp; - }; - }; - nixosConfigurations = let - mkSystems = hosts: - builtins.mapAttrs ( - host: settings: let - pkgs = - if isStable - then nixpkgs - else nixpkgs-unstable; - isStable = (settings ? stable) && (settings.stable == true); - in - mkSystem host settings.modules pkgs isStable - ) - hosts; - mkSystem = host: modules: pkgs: stable: - pkgs.lib.nixosSystem { - system = "x86_64-linux"; - specialArgs = { - flakeSelf = self; - }; - lib = import ./lib {nixpkgs = pkgs;}; - modules = - [ - agenix.nixosModules.default - sops-nix.nixosModules.sops - ( - if stable - then home-manager - else home-manager-unstable - ) - .nixosModules - .home-manager - ( - if stable - then nix-index-db - else nix-index-db-unstable - ) - .nixosModules - .nix-index - catppuccin.nixosModules.catppuccin - ( - if stable - then lix-module - else lix-module-unstable - ) - .nixosModules - .default - ./roles/common - (./machines + "/${host}") - ] - ++ modules; - }; - in - mkSystems { - Archie.modules = [ - nixos-hardware.nixosModules.common-cpu-amd-zenpower - ./roles/desktop - ./roles/kde - ./roles/gaming - ]; - SurfaceGo.modules = [ - nixos-hardware.nixosModules.microsoft-surface-go - ./roles/desktop - ./roles/kde - ./machines/SurfaceGo - ]; - SteamDeck.modules = [ - jovian.nixosModules.default - ./roles/desktop - ./roles/kde - ./roles/gaming - ]; - WinMax2.modules = [ - nixos-hardware.nixosModules.gpd-win-max-2-2023 - ./roles/desktop - ./roles/kde - ./roles/gaming - ]; - Everest = { - stable = true; - modules = [ - copyparty.nixosModules.default - ./roles/server - ]; - }; - }; - }; + Everest = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + pkgs = import nixpkgs { + system = "x86_64-linux"; + config = { allowUnfree = false; }; # TODO: Find why this doesn't work + }; + modules = [ + # Needed for nix-index + { nix.nixPath = [ "nixpkgs=${nixpkgs}" ]; } + agenix.nixosModules.default + home-manager.nixosModule + ./roles/common + ./roles/server + ./machines/Everest + ./openbox.nix + ]; + }; + }; +}; } diff --git a/lib/default.nix b/lib/default.nix deleted file mode 100644 index 67bfe4e..0000000 --- a/lib/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -{nixpkgs}: -nixpkgs.lib.extend (final: prev: { - toast = let - importLib = file: import file {lib = final;}; - in { - patches = importLib ./patches.nix; - networkManager = importLib ./networkManager.nix; - syncthing = importLib ./syncthing.nix; - }; -}) diff --git a/lib/networkManager.nix b/lib/networkManager.nix deleted file mode 100644 index b4bfbfe..0000000 --- a/lib/networkManager.nix +++ /dev/null @@ -1,23 +0,0 @@ -{lib}: { - /** - Make a NetworkManager wifi profile, to be used with ensureProfiles - */ - mkWifiProfile = { - id, - ssid, - priority ? 0, - wifi-security, - }: { - connection = { - inherit id; - type = "wifi"; - autoconnect-priority = priority; - }; - ipv4.method = "auto"; - wifi = { - mode = "infrastructure"; - inherit ssid; - }; - inherit wifi-security; - }; -} diff --git a/lib/patches.nix b/lib/patches.nix deleted file mode 100644 index 4f3c1a8..0000000 --- a/lib/patches.nix +++ /dev/null @@ -1,13 +0,0 @@ -{lib}: { - /** - Get a list of patches from a path. - */ - patchesInPath = path: let - pathContents = builtins.readDir path; - filter = name: value: - (value == "regular" || value == "symlink") && lib.strings.hasSuffix ".patch" name; - filteredContents = lib.attrsets.filterAttrs filter pathContents; - patchFilenames = builtins.attrNames filteredContents; - in - builtins.map (value: lib.path.append path value) patchFilenames; -} diff --git a/lib/syncthing.nix b/lib/syncthing.nix deleted file mode 100644 index 90553b4..0000000 --- a/lib/syncthing.nix +++ /dev/null @@ -1,5 +0,0 @@ -{lib}: let - data = import ./../syncthing.nix; -in { - devices = builtins.getAttr "devices" data; -} diff --git a/machines/Archie/configuration.nix b/machines/Archie/configuration.nix index e35d1fe..f91582d 100644 --- a/machines/Archie/configuration.nix +++ b/machines/Archie/configuration.nix @@ -1,79 +1,105 @@ # 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, ... }: + { - config, - pkgs, - ... -}: { - # Use grub boot loader - boot.loader = { - systemd-boot.enable = false; - grub = { - enable = true; - device = "nodev"; - efiSupport = true; - useOSProber = true; - }; - efi.efiSysMountPoint = "/boot/efi"; - }; - boot.loader.efi.canTouchEfiVariables = true; + # Use grub boot loader + boot.loader = { + systemd-boot.enable = false; + grub = { + enable = true; + device = "nodev"; + efiSupport = true; + useOSProber = true; + }; + efi.efiSysMountPoint = "/boot/efi"; + }; + boot.loader.efi.canTouchEfiVariables = true; - boot.kernelPackages = pkgs.linuxKernel.packages.linux_xanmod_latest; + networking.hostName = "Archie"; # Define your hostname. + networking.networkmanager.enable = true; # Enable networking - networking.hostName = "Archie"; # Define your hostname. + # Allow unfree packages + nixpkgs.config.allowUnfree = true; - # Allow unfree packages - nixpkgs.config.allowUnfree = true; + # Set your time zone. + time.timeZone = "Europe/Madrid"; - # Configure network proxy if necessary - # networking.proxy.default = "http://user:password@proxy:port/"; - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; + # Configure network proxy if necessary + # networking.proxy.default = "http://user:password@proxy:port/"; + # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; - # Configure keymap in X11 - # services.xserver.layout = "us"; - # services.xserver.xkbOptions = "eurosign:e,caps:escape"; + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; + console = { + keyMap = "es"; + }; - # Enable CUPS to print documents. - # services.printing.enable = true; + # Enable the X11 windowing system. + services.xserver.enable = true; - # Enable sound. - # sound.enable = true; - # hardware.pulseaudio.enable = true; + # Enable the pipewire sound server + services.pipewire = { + enable = true; + pulse.enable = true; + }; - # Enable touchpad support (enabled default in most desktopManager). - # services.xserver.libinput.enable = true; + # Configure keymap in X11 + # services.xserver.layout = "us"; + # services.xserver.xkbOptions = "eurosign:e,caps:escape"; - hardware.bluetooth.enable = true; + # Enable CUPS to print documents. + # services.printing.enable = true; - # List packages installed in system profile. To search, run: - # $ nix search wget - # environment.systemPackages = with pkgs; [ - # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. - # wget - # ]; + # Enable sound. + # sound.enable = true; + # hardware.pulseaudio.enable = true; - # Some programs need SUID wrappers, can be configured further or are - # started in user sessions. - # programs.mtr.enable = true; - # programs.gnupg.agent = { - # enable = true; - # enableSSHSupport = true; - # }; + # Enable touchpad support (enabled default in most desktopManager). + # services.xserver.libinput.enable = true; - # List services that you want to enable: + # Define a user account. Don't forget to set a password with ‘passwd’. + users.users.toast = { + isNormalUser = true; + extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user. + packages = with pkgs; [ + firefox + tree + ]; + }; - # Enable the OpenSSH daemon. - # services.openssh.enable = true; + # List packages installed in system profile. To search, run: + # $ nix search wget + # environment.systemPackages = with pkgs; [ + # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. + # wget + # ]; - # Open ports in the firewall. - # networking.firewall.allowedTCPPorts = [ ... ]; - # networking.firewall.allowedUDPPorts = [ ... ]; - # Or disable the firewall altogether. - # networking.firewall.enable = false; + # Some programs need SUID wrappers, can be configured further or are + # started in user sessions. + # programs.mtr.enable = true; + # programs.gnupg.agent = { + # enable = true; + # enableSSHSupport = true; + # }; + + # List services that you want to enable: + + # Enable the OpenSSH daemon. + # services.openssh.enable = true; + + # 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; - # 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; } + diff --git a/machines/Archie/default.nix b/machines/Archie/default.nix index 187a110..5bf2485 100755 --- a/machines/Archie/default.nix +++ b/machines/Archie/default.nix @@ -1,6 +1,8 @@ -{...}: { - imports = [ - ./configuration.nix - ./hardware-configuration.nix - ]; +{ ... }: + +{ + imports = [ + ./configuration.nix + ./hardware-configuration.nix + ]; } diff --git a/machines/Archie/hardware-configuration.nix b/machines/Archie/hardware-configuration.nix index cb7bd98..cd626b4 100644 --- a/machines/Archie/hardware-configuration.nix +++ b/machines/Archie/hardware-configuration.nix @@ -1,86 +1,43 @@ # 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, ... }: + { - config, - lib, - modulesPath, - ... -}: { - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; - # Enable support for the Xbox One wireless dongle - hardware.xone.enable = true; + boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-amd" ]; + boot.extraModulePackages = [ ]; - boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "usbhid" "nvme" "sd_mod"]; - boot.initrd.kernelModules = ["amdgpu"]; - boot.kernelModules = ["kvm-amd"]; - boot.extraModulePackages = []; - boot.extraModprobeConfig = "options snd_hda_intel power_save=0"; + fileSystems."/" = + { device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; + fsType = "btrfs"; + options = [ "subvol=@root" "compress=zstd" ]; + }; - fileSystems."/" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=@root"]; - }; + fileSystems."/nix" = + { device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; + fsType = "btrfs"; + options = [ "subvol=@nix" "compress=zstd" ]; + }; - fileSystems."/nix" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=@nix"]; - }; + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; + fsType = "btrfs"; + options = [ "subvol=@boot" "compress=zstd" ]; + }; - fileSystems."/boot" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=@boot"]; - }; + fileSystems."/boot/efi" = + { device = "/dev/disk/by-uuid/FB87-4CBC"; + fsType = "vfat"; + }; - fileSystems."/boot/efi" = { - device = "/dev/disk/by-uuid/FB87-4CBC"; - fsType = "vfat"; - }; + swapDevices = [ ]; - fileSystems = { - /* - Mount the root subvolume of the SSD - This is helpful for getting things from - my old Arch install, as well as for running btdu - */ - "/mnt/ssd" = { - device = config.fileSystems."/".device; - fsType = config.fileSystems."/".fsType; - options = ["subvolid=5" "ro"]; - }; - "/mnt/hdd" = { - # device = "/dev/disk/by-id/ata-SAMSUNG_HD103SI_S1Y5J9CZA19763-part1"; - label = "Archie\\x20HDD"; - fsType = "bcachefs"; - options = ["x-systemd.automount"]; - }; - "/mnt/windows" = { - device = "/dev/disk/by-uuid/B61AFDAC1AFD6A2F"; - fsType = "ntfs3"; - neededForBoot = false; - options = ["noauto" "windows_names"]; - }; - "/home" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=@home"]; - }; - "/persist" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=@persist"]; - neededForBoot = true; - }; - }; - - swapDevices = []; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; } diff --git a/machines/Everest/configuration.nix b/machines/Everest/configuration.nix index 4db3273..5b6d2fe 100755 --- a/machines/Everest/configuration.nix +++ b/machines/Everest/configuration.nix @@ -1,60 +1,103 @@ # 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’). -{lib, ...}: { - # Bootloader. - boot.loader.systemd-boot.enable = true; - boot.loader.timeout = 5; - boot.loader.efi.canTouchEfiVariables = true; - boot.loader.efi.efiSysMountPoint = "/boot/efi"; - # I'm using Nix OS, it's logo is a snowflake and the computer is - # a lot taller than the pi it's replacing, so Everest! :3 :3 - networking.hostName = "Everest"; # Define your hostname. +{ config, pkgs, lib, ... }: - # Set up networking - networking = { - wireless.enable = false; # Computer doesn't have wifi - enableIPv6 = false; - useNetworkd = true; - dhcpcd.enable = false; - interfaces.eno1 = { - wakeOnLan.enable = true; - ipv4.addresses = [ - { - address = "192.168.1.160"; - prefixLength = 24; - } - ]; - }; - # I use networkd, so I need to declare the interface for the default gateway - defaultGateway = { - address = "192.168.1.1"; - interface = "eno1"; - }; - nameservers = ["9.9.9.9"]; - }; - systemd.network.wait-online.extraArgs = ["--dns"]; +{ + # Bootloader. + boot.loader.systemd-boot.enable = true; + boot.loader.timeout = 5; + boot.loader.efi.canTouchEfiVariables = true; + boot.loader.efi.efiSysMountPoint = "/boot/efi"; - time.timeZone = "Europe/Madrid"; - services.automatic-timezoned.enable = lib.mkForce false; + # I'm using Nix OS, it's logo is a snowflake and the computer is + # a lot taller than the pi it's replacing, so Everest! :3 :3 + networking.hostName = "Everest"; # Define your hostname. - # Define a user account. Don't forget to set a password with ‘passwd’. - users.users.toast = { - extraGroups = ["networkmanager" "transmission"]; - }; + # Set up networking + networking = { + wireless.enable = false; # Computer doesn't have wifi + enableIPv6 = false; + useNetworkd = true; + dhcpcd.enable = false; + interfaces.eno1 = { + wakeOnLan.enable = true; + ipv4.addresses = [ { + address = "192.168.0.160"; + prefixLength = 24; + } ]; + }; + defaultGateway = "192.168.0.1"; + nameservers = [ "8.8.8.8" ]; + }; - # Large builds (the linux kernel) fail to build because /tmp is too small when using tmpfs - boot.tmp.useTmpfs = false; + # Set your time zone. + time.timeZone = "Europe/Madrid"; - home-manager = { - users.toast = {config, ...}: { - home = { - file = { - # This symlinks the Transmission downloads folder into my user's downloads folder for easy access - "Downloads/Transmission".source = config.lib.file.mkOutOfStoreSymlink "/var/lib/transmission/Downloads"; - }; - }; - }; - }; + # Select internationalisation properties. + i18n.defaultLocale = "en_US.UTF-8"; + + i18n.extraLocaleSettings = { + LC_ADDRESS = "es_ES.UTF-8"; + LC_IDENTIFICATION = "es_ES.UTF-8"; + LC_MEASUREMENT = "es_ES.UTF-8"; + LC_MONETARY = "es_ES.UTF-8"; + LC_NAME = "es_ES.UTF-8"; + LC_NUMERIC = "es_ES.UTF-8"; + LC_PAPER = "es_ES.UTF-8"; + LC_TELEPHONE = "es_ES.UTF-8"; + LC_TIME = "es_ES.UTF-8"; + }; + + # Configure keymap in X11 + services.xserver = { + layout = "es"; + xkbVariant = ""; + }; + + # Configure console keymap + console.keyMap = "es"; + + # Define a user account. Don't forget to set a password with ‘passwd’. + users.users.toast = { + isNormalUser = true; + description = "Toast"; + extraGroups = [ "networkmanager" "wheel" "transmission"]; + packages = with pkgs; []; + openssh.authorizedKeys.keys = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2bOVmxUNvg9qFv9DlzMmTRlzcNsyNq1F1wBuAXySwsWAzHGaO+WGdSCINxW3k2ccXn7M/o1r89LeTzRzi8sWQYCpBaIqYVszM/r7SvTS4gASyKhM6lNlyUEPOnvCXH7rdtF+fjoA1TJPv7GBk78QRhGh+eVO3qhY1m++5C1CPFlyrc6sSfgIBQJ5GQZFl/7YEgsrPo+M+0Sd7LkaCOyNmJA0Wi0BA3bbf5sJhrZVMMg/p7w+eMphz2kd1VTVjW3yeMq9zLCiu4SOTBNGCMEvKIdUZbQ83lNrqO2z1/3T1bDwJgpz3xusfkNCeNJSmhfFw5ydHEUp/9jshq38WmulKAMw2Kl/Zed62AVU7Ux7YjUkZkWvo8i3eXuLUxoG891S7cWV1/ijs9QMajOLLT14FG7RbzUYYaYlx+/iNGji9d4sp9/oMYyO45TMe+vEezFSBygP7TY0QFOr4xTi49ZRQFsszbFnGRv+k3wVKoGoeNt0xWB8pBEPFtaeHJpQyJX8= id_rsa_moon" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOeu3crGqtxwaqgoQPt5mWlC8+PL/Icvcvo0MBAaK80L Key for work laptop" + ]; + }; + + home-manager = { + backupFileExtension = "backup"; + useGlobalPkgs = true; + users.toast = { config, ... }: { + home = { + stateVersion = "23.05"; + file = { + # This symlinks the Transmission downloads folder into my user's downloads folder for easy access + "Downloads/Transmission".source = config.lib.file.mkOutOfStoreSymlink "/var/lib/transmission/Downloads"; + }; + }; + xdg = { + #enable = true; + userDirs = { + enable = true; + createDirectories = true; + publicShare = null; # Disable the public folder + }; + }; + }; + }; + + # Open ports in the firewall. + # 8384 is syncthing's webui, and 22000 is syncthing related too + # No idea what 5201 and 21027 do tho + networking.firewall.allowedTCPPorts = [ 5201 8384 22000 ]; + networking.firewall.allowedUDPPorts = [ 5201 22000 21027]; + # Or disable the firewall altogether. + # networking.firewall.enable = false; } diff --git a/machines/Everest/default.nix b/machines/Everest/default.nix index 187a110..5bf2485 100755 --- a/machines/Everest/default.nix +++ b/machines/Everest/default.nix @@ -1,6 +1,8 @@ -{...}: { - imports = [ - ./configuration.nix - ./hardware-configuration.nix - ]; +{ ... }: + +{ + imports = [ + ./configuration.nix + ./hardware-configuration.nix + ]; } diff --git a/machines/Everest/hardware-configuration.nix b/machines/Everest/hardware-configuration.nix index 5ccf3d7..528f3ba 100755 --- a/machines/Everest/hardware-configuration.nix +++ b/machines/Everest/hardware-configuration.nix @@ -1,69 +1,37 @@ # 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, ... }: + { - config, - lib, - modulesPath, - ... -}: { - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; - boot.initrd.availableKernelModules = ["xhci_pci" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod"]; - boot.initrd.kernelModules = []; - boot.kernelModules = ["kvm-intel"]; - boot.extraModulePackages = []; + boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; - fileSystems = { - "/" = { - device = "/dev/disk/by-label/Everest"; - fsType = "btrfs"; - options = ["compress=zstd" "subvol=@"]; - }; - "/nix" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=@nix-server"]; - }; - "/home" = { - device = "/dev/disk/by-label/Everest"; - fsType = "btrfs"; - options = ["compress=zstd" "subvol=@home"]; - }; - "/mnt/hdd" = { - device = "/dev/disk/by-label/Everest"; - fsType = "btrfs"; - options = ["compress=zstd" "subvol=/" "ro"]; - }; - "/mnt/ssd" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=/" "ro"]; - }; - "/persist" = { - device = "/dev/disk/by-label/Everest"; - fsType = "btrfs"; - options = ["compress=zstd" "subvol=@persist"]; - neededForBoot = true; - }; - }; + fileSystems."/" = + { device = "/dev/disk/by-label/Everest"; + fsType = "btrfs"; + }; - fileSystems."/boot/efi" = { - device = "/dev/disk/by-uuid/FB87-4CBC"; - fsType = "vfat"; - }; + fileSystems."/boot/efi" = + { device = "/dev/disk/by-label/Boot"; + fsType = "vfat"; + }; - swapDevices = []; + swapDevices = [ ]; - # 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..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.eno1.useDHCP = lib.mkDefault true; + # 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..useDHCP`. + networking.useDHCP = lib.mkDefault true; + # networking.interfaces.eno1.useDHCP = lib.mkDefault true; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; } diff --git a/machines/SteamDeck/configuration.nix b/machines/SteamDeck/configuration.nix deleted file mode 100755 index c14385e..0000000 --- a/machines/SteamDeck/configuration.nix +++ /dev/null @@ -1,103 +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, - ... -}: { - # Use grub boot loader - boot.loader = { - systemd-boot.enable = false; - grub = { - enable = true; - device = "nodev"; - efiSupport = true; - # No other OS on here :P - useOSProber = false; - }; - efi.efiSysMountPoint = config.fileSystems."efi_boot_partition".mountPoint; - }; - boot.loader.efi.canTouchEfiVariables = true; - - networking.hostName = "SteamDeck"; # Define your hostname. - - # Allow unfree packages - nixpkgs.config.allowUnfree = true; - - # Configure network proxy if necessary - # networking.proxy.default = "http://user:password@proxy:port/"; - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; - - jovian = { - devices.steamdeck = { - enable = true; - }; - # Steam Deck UI settings - steam = { - enable = true; - autoStart = true; - user = "toast"; - desktopSession = "plasma"; - }; - decky-loader = { - enable = true; - }; - }; - services.displayManager.sddm.enable = lib.mkForce false; - - # Enable bluetooth - hardware.bluetooth = { - enable = true; - }; - - # Configure keymap in X11 - # services.xserver.layout = "us"; - # services.xserver.xkbOptions = "eurosign:e,caps:escape"; - - # Enable CUPS to print documents. - # services.printing.enable = true; - - # Enable sound. - # sound.enable = true; - # hardware.pulseaudio.enable = true; - - # Enable touchpad support (enabled default in most desktopManager). - # services.xserver.libinput.enable = true; - - # Large builds (the linux kernel) fail to build because /tmp is too small when using tmpfs - boot.tmp.useTmpfs = false; - - environment.systemPackages = [pkgs.steamdeck-firmware pkgs.steamdeck-hw-theme]; - # List packages installed in system profile. To search, run: - # $ nix search wget - # environment.systemPackages = with pkgs; [ - # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. - # wget - # ]; - - # Some programs need SUID wrappers, can be configured further or are - # started in user sessions. - # programs.mtr.enable = true; - # programs.gnupg.agent = { - # enable = true; - # enableSSHSupport = true; - # }; - - # List services that you want to enable: - - # Enable the OpenSSH daemon. - # services.openssh.enable = true; - - # 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; -} diff --git a/machines/SteamDeck/default.nix b/machines/SteamDeck/default.nix deleted file mode 100755 index 187a110..0000000 --- a/machines/SteamDeck/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./configuration.nix - ./hardware-configuration.nix - ]; -} diff --git a/machines/SteamDeck/hardware-configuration.nix b/machines/SteamDeck/hardware-configuration.nix deleted file mode 100755 index 4ade12c..0000000 --- a/machines/SteamDeck/hardware-configuration.nix +++ /dev/null @@ -1,79 +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, - modulesPath, - ... -}: let - # \x20 is the escape code for a space - ssdLabel = ''Deck\\x20SSD''; -in { - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; - - # Enable support for the Xbox One wireless dongle - hardware.xone.enable = true; - - boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "usb_storage" "usbhid" "sd_mod" "sdhci_pci"]; - boot.initrd.kernelModules = []; - boot.kernelModules = ["kvm-amd"]; - boot.extraModulePackages = []; - - fileSystems = { - "efi_boot_partition" = { - mountPoint = "/boot/efi"; - label = "deckboot"; - fsType = "vfat"; - }; - /* - Mount the root subvolume of the SSD - This is helpful for getting things from - my old Arch install, as well as for running btdu - */ - "btrfs_root_subvolume" = { - mountPoint = "/mnt/ssd"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvolid=5" "ro"]; - }; - "btrfs_root" = { - mountPoint = "/"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@" "compress=zstd"]; - }; - "btrfs_persist" = { - mountPoint = "/persist"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@persist"]; - neededForBoot = true; - }; - "btrfs_boot" = { - mountPoint = "/boot"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@boot" "compress=zstd"]; - }; - "btrfs_home" = { - mountPoint = "/home"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@home" "compress=zstd"]; - }; - "btrfs_nix" = { - mountPoint = "/nix"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@nix" "compress=zstd"]; - }; - }; - - swapDevices = []; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; -} diff --git a/machines/SurfaceGo/configuration.nix b/machines/SurfaceGo/configuration.nix deleted file mode 100644 index 49c0620..0000000 --- a/machines/SurfaceGo/configuration.nix +++ /dev/null @@ -1,92 +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, - ... -}: { - boot = { - loader = { - # Use grub boot loader - systemd-boot.enable = false; - grub = { - enable = true; - device = "nodev"; - efiSupport = true; - enableCryptodisk = true; - }; - efi = { - efiSysMountPoint = "/boot/efi"; - canTouchEfiVariables = true; - }; - }; - # I need systemd for tpm luks unlocking - initrd.systemd.enable = true; - }; - - security.tpm2.enable = true; - - networking.hostName = "SurfaceGo"; # Define your hostname. - - # Allow unfree packages - nixpkgs.config.allowUnfree = true; - - # Configure network proxy if necessary - # networking.proxy.default = "http://user:password@proxy:port/"; - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; - - # Configure keymap in X11 - # services.xserver.layout = "us"; - # services.xserver.xkbOptions = "eurosign:e,caps:escape"; - - console = { - # The kernel doesn't detect the scree as being HiDPI, so I need to use a bigger font - font = "ter-i32n"; - }; - - # Enable CUPS to print documents. - # services.printing.enable = true; - - # Enable sound. - # sound.enable = true; - # hardware.pulseaudio.enable = true; - - # Enable touchpad support (enabled default in most desktopManager). - # services.xserver.libinput.enable = true; - - # List packages installed in system profile. To search, run: - # $ nix search wget - # environment.systemPackages = with pkgs; [ - # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. - # wget - # ]; - - # The surface kernel sometimes fails to suspend/shutdown and I got tired of fighting it - boot.kernelPackages = lib.mkForce pkgs.linuxPackages; - - # Some programs need SUID wrappers, can be configured further or are - # started in user sessions. - # programs.mtr.enable = true; - # programs.gnupg.agent = { - # enable = true; - # enableSSHSupport = true; - # }; - - # List services that you want to enable: - - # Enable the OpenSSH daemon. - # services.openssh.enable = true; - - # 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; -} diff --git a/machines/SurfaceGo/default.nix b/machines/SurfaceGo/default.nix deleted file mode 100644 index 187a110..0000000 --- a/machines/SurfaceGo/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - imports = [ - ./configuration.nix - ./hardware-configuration.nix - ]; -} diff --git a/machines/SurfaceGo/hardware-configuration.nix b/machines/SurfaceGo/hardware-configuration.nix deleted file mode 100644 index 81a4c56..0000000 --- a/machines/SurfaceGo/hardware-configuration.nix +++ /dev/null @@ -1,50 +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, - ... -}: { - boot.initrd.availableKernelModules = ["xhci_pci" "nvme" "usbhid" "rtsx_pci_sdmmc"]; - boot.initrd.kernelModules = []; - boot.kernelModules = ["kvm-intel"]; - boot.extraModulePackages = []; - - boot.initrd.luks.devices."SSD".device = "/dev/disk/by-uuid/1d8d7578-d3a1-4ea0-90ad-4257266a6caf"; - - fileSystems."/" = { - device = "/dev/disk/by-uuid/19a52b40-3ff6-47ff-9402-18d8b289643e"; - fsType = "btrfs"; - options = ["subvol=@" "compress=zstd"]; - }; - - fileSystems."/boot" = { - device = "/dev/disk/by-uuid/19a52b40-3ff6-47ff-9402-18d8b289643e"; - fsType = "btrfs"; - options = ["subvol=@boot" "compress=zstd"]; - }; - - fileSystems."/nix" = { - device = "/dev/disk/by-uuid/19a52b40-3ff6-47ff-9402-18d8b289643e"; - fsType = "btrfs"; - options = ["subvol=@nix" "compress=zstd"]; - }; - - fileSystems."/home" = { - device = "/dev/disk/by-uuid/19a52b40-3ff6-47ff-9402-18d8b289643e"; - fsType = "btrfs"; - options = ["subvol=@home" "compress=zstd"]; - }; - - fileSystems."/boot/efi" = { - device = "/dev/disk/by-uuid/EC76-201F"; - fsType = "vfat"; - }; - - swapDevices = []; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - powerManagement.cpuFreqGovernor = lib.mkDefault "powersave"; - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; -} diff --git a/machines/WinMax2/configuration.nix b/machines/WinMax2/configuration.nix deleted file mode 100755 index 81f0f44..0000000 --- a/machines/WinMax2/configuration.nix +++ /dev/null @@ -1,141 +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, - ... -}: { - boot = { - loader = { - systemd-boot.enable = false; - limine = { - enable = true; - enableEditor = true; - secureBoot.enable = true; - extraConfig = '' - timeout: 3 - ''; - style = { - graphicalTerminal.font.scale = "2x2"; - }; - }; - efi = { - efiSysMountPoint = config.fileSystems."efi_boot_partition".mountPoint; - canTouchEfiVariables = true; - }; - }; - /* - I use luks, and the systemd initrd works better for this - Both for tpm unlocking (soon) and for plymouth - */ - initrd.systemd.enable = true; - # Plymouth doesn't support fractional scaling :( - plymouth.extraConfig = "DeviceScale=2"; - - kernelPackages = pkgs.linuxPackages_latest; - }; - catppuccin.limine.enable = true; - - networking.hostName = "WinMax2"; # Define your hostname. - - # Sleep fixes - boot.kernelParams = ["rtc_cmos.use_acpi_alarm=1" "panic=5"]; - services.udev.extraRules = '' - ACTION=="add", SUBSYSTEM=="i2c", ATTR{name}=="GXTP7385:00", ATTR{power/wakeup}="disabled" - ACTION=="add", SUBSYSTEM=="i2c", ATTR{name}=="PNP0C50:00", ATTR{power/wakeup}="disabled" - SUBSYSTEM=="usb", ATTR{idVendor}=="2541", ATTR{idProduct}=="9711", ATTR{remove}="1" - ''; - - services = { - hardware.bolt.enable = true; - handheld-daemon = { - enable = true; - ui = { - enable = true; - }; - user = "toast"; - }; - # Input plumber conflicts with hhd, and it doesn't let me use mouse mode - inputplumber.enable = lib.mkForce false; - }; - - # Allow unfree packages - nixpkgs.config.allowUnfree = true; - - # Configure network proxy if necessary - # networking.proxy.default = "http://user:password@proxy:port/"; - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; - - services = { - xserver.xkb.layout = lib.mkForce "us"; - }; - - # Enable bluetooth - hardware.bluetooth = { - enable = true; - }; - - # Configure keymap in X11 - # services.xserver.layout = "us"; - # services.xserver.xkbOptions = "eurosign:e,caps:escape"; - - # Enable CUPS to print documents. - # services.printing.enable = true; - home-manager.sharedModules = [ - { - programs.plasma.input.keyboard.layouts = lib.mkForce [{layout = "us";} {layout = "es";}]; - } - ]; - - # Enable sound. - # sound.enable = true; - # hardware.pulseaudio.enable = true; - - # Enable touchpad support (enabled default in most desktopManager). - # services.xserver.libinput.enable = true; - - # Large builds (the linux kernel) fail to build because /tmp is too small when using tmpfs - boot.tmp.useTmpfs = false; - - # List packages installed in system profile. To search, run: - # $ nix search wget - # environment.systemPackages = with pkgs; [ - # vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default. - # wget - # ]; - - # Some programs need SUID wrappers, can be configured further or are - # started in user sessions. - # programs.mtr.enable = true; - # programs.gnupg.agent = { - # enable = true; - # enableSSHSupport = true; - # }; - - # List services that you want to enable: - - # Enable the OpenSSH daemon. - # services.openssh.enable = true; - - # 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; - specialisation.bootDebug.configuration = { - boot.kernelParams = [ - "systemd.debug-shell=1" - "systemd.log_level=debug" - "systemd.log_target=kmsg" - "log_buf_len=1M" - "printk.devkmsg=on" - ]; - }; -} diff --git a/machines/WinMax2/default.nix b/machines/WinMax2/default.nix deleted file mode 100755 index 899f761..0000000 --- a/machines/WinMax2/default.nix +++ /dev/null @@ -1,7 +0,0 @@ -{...}: { - imports = [ - ./configuration.nix - ./hardware-configuration.nix - ./remote-builder.nix - ]; -} diff --git a/machines/WinMax2/hardware-configuration.nix b/machines/WinMax2/hardware-configuration.nix deleted file mode 100755 index ac8d1cd..0000000 --- a/machines/WinMax2/hardware-configuration.nix +++ /dev/null @@ -1,87 +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, - modulesPath, - ... -}: let - # \x20 is the escape code for a space - ssdLabel = ''Win\\x20Max\\x202\\x20SSD''; -in { - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; - - boot.initrd.availableKernelModules = ["nvme" "xhci_pci" "thunderbolt" "usbhid" "sdhci_pci"]; - boot.initrd.kernelModules = []; - boot.kernelModules = ["kvm-amd"]; - boot.extraModulePackages = []; - - boot.initrd.luks.devices = { - "SSD".device = "/dev/disk/by-label/wm2-enc"; - "swap".device = "/dev/disk/by-label/wm2-swap"; - }; - - fileSystems = { - "efi_boot_partition" = { - mountPoint = "/boot"; - label = "winmax2boot"; - fsType = "vfat"; - }; - /* - Mount the root subvolume of the SSD - This is helpful for getting things from - my old Arch install, as well as for running btdu - */ - "btrfs_root_subvolume" = { - mountPoint = "/mnt/ssd"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvolid=5" "ro"]; - }; - "btrfs_root" = { - mountPoint = "/"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@"]; - }; - "btrfs_persist" = { - mountPoint = "/persist"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@persist"]; - neededForBoot = true; - }; - "btrfs_home" = { - mountPoint = "/home"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@home"]; - }; - "btrfs_nix" = { - mountPoint = "/nix"; - label = ssdLabel; - fsType = "btrfs"; - options = ["subvol=@nix"]; - }; - }; - - swapDevices = [ - { - device = "/dev/mapper/swap"; - # only want to use the swap partition for hibernating - priority = 0; - } - ]; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware = { - cpu.amd = { - updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; - ryzen-smu.enable = true; - }; - sensor.iio.bmi260.enable = true; - }; -} diff --git a/machines/WinMax2/remote-builder.nix b/machines/WinMax2/remote-builder.nix deleted file mode 100644 index 1a3f839..0000000 --- a/machines/WinMax2/remote-builder.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ - config, - flakeSelf, - ... -}: let - hostSecrets = "${flakeSelf.inputs.secrets}/" + config.networking.hostName + "/"; - hostKeyPath = "/etc/ssh/winmax2_host_key"; -in { - age.secrets = { - winmax2-host-key = { - file = hostSecrets + "host-private-key.age"; - path = hostKeyPath; - mode = "0400"; - }; - "winmax2-host-key.pub" = { - file = hostSecrets + "host-public-key.age"; - path = hostKeyPath + ".pub"; - }; - }; - - users = { - groups.nixrbld = {}; - users.nixrbld = { - isSystemUser = true; - useDefaultShell = true; - group = "nixrbld"; - openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF8v+04ZwqHZRG8P8nxdQt+fGJfzlxHXF0F6jzENb+U6 Remote builder access key" - ]; - }; - }; - - nix.settings.trusted-users = ["nixrbld"]; - - services.openssh = { - enable = true; - startWhenNeeded = true; - # I only want it to be accesible though tailscale - openFirewall = false; - allowSFTP = false; - settings = { - UseDns = true; - PermitRootLogin = "no"; - PasswordAuthentication = false; - AllowUsers = ["nixrbld"]; - }; - hostKeys = [ - { - path = hostKeyPath; - type = "ed25519"; - comment = "Everest host key"; - } - ]; - }; - networking.firewall.interfaces.tailscale0.allowedTCPPorts = [22]; -} diff --git a/nixpkgs-patches/.gitkeep b/nixpkgs-patches/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/nixpkgs-patches/pr471291.patch b/nixpkgs-patches/pr471291.patch deleted file mode 100644 index 7111330..0000000 --- a/nixpkgs-patches/pr471291.patch +++ /dev/null @@ -1,61 +0,0 @@ -From ef52b16862caa43dd4abc0aedf1814796342b664 Mon Sep 17 00:00:00 2001 -From: K900 -Date: Tue, 16 Dec 2025 11:48:37 +0300 -Subject: [PATCH] kdePackages.plasma-vault: refresh patch - -No idea how this happened. ---- - .../plasma/plasma-vault/hardcode-paths.patch | 26 +++++++++---------- - 1 file changed, 13 insertions(+), 13 deletions(-) - -diff --git a/pkgs/kde/plasma/plasma-vault/hardcode-paths.patch b/pkgs/kde/plasma/plasma-vault/hardcode-paths.patch -index d8a5f4a025de3..090df77eb15b0 100644 ---- a/pkgs/kde/plasma/plasma-vault/hardcode-paths.patch -+++ b/pkgs/kde/plasma/plasma-vault/hardcode-paths.patch -@@ -1,5 +1,5 @@ - diff --git a/kded/engine/backends/cryfs/cryfsbackend.cpp b/kded/engine/backends/cryfs/cryfsbackend.cpp --index f425eb3..5b8cd43 100644 -+index 64138b6..5d249aa 100644 - --- a/kded/engine/backends/cryfs/cryfsbackend.cpp - +++ b/kded/engine/backends/cryfs/cryfsbackend.cpp - @@ -207,7 +207,7 @@ QProcess *CryFsBackend::cryfs(const QStringList &arguments) const -@@ -44,7 +44,7 @@ index b992f6f..eb828dd 100644 - - QString GocryptfsBackend::getConfigFilePath(const Device &device) const - diff --git a/kded/engine/fusebackend_p.cpp b/kded/engine/fusebackend_p.cpp --index 8763304..e6860d2 100644 -+index 714b660..61d8bf5 100644 - --- a/kded/engine/fusebackend_p.cpp - +++ b/kded/engine/fusebackend_p.cpp - @@ -90,7 +90,7 @@ QProcess *FuseBackend::process(const QString &executable, const QStringList &arg -@@ -57,19 +57,19 @@ index 8763304..e6860d2 100644 - - FutureResult<> FuseBackend::initialize(const QString &name, const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload) - diff --git a/kded/engine/vault.cpp b/kded/engine/vault.cpp --index c101079..67c8a83 100644 -+index a7a4741..773b671 100644 - --- a/kded/engine/vault.cpp - +++ b/kded/engine/vault.cpp --@@ -485,7 +485,7 @@ FutureResult<> Vault::close() -- } else { -- // We want to check whether there is an application -- // that is accessing the vault --- AsynQt::Process::getOutput(QStringLiteral("lsof"), {QStringLiteral("-t"), mountPoint().data()}) | cast() | onError([this] { --+ AsynQt::Process::getOutput(QStringLiteral("@lsof@"), {QStringLiteral("-t"), mountPoint().data()}) | cast() | onError([this] { -- d->updateMessage(i18n("Unable to lock the vault because an application is using it")); -- }) | onSuccess([this](const QString &result) { -- // based on ksolidnotify.cpp --@@ -538,7 +538,7 @@ FutureResult<> Vault::forceClose() -+@@ -490,7 +490,7 @@ FutureResult<> Vault::close() -+ } else { -+ // We want to check whether there is an application -+ // that is accessing the vault -+- AsynQt::Process::getOutput(QStringLiteral("lsof"), { QStringLiteral("-t"), mountPoint().data() }) -++ AsynQt::Process::getOutput(QStringLiteral("@lsof@"), { QStringLiteral("-t"), mountPoint().data() }) -+ | cast() -+ | onError([this] { -+ d->updateMessage(i18n("Unable to close the vault because an application is using it")); -+@@ -546,7 +546,7 @@ FutureResult<> Vault::forceClose() - using namespace AsynQt::operators; - - AsynQt::await( diff --git a/nixpkgs-patches/revert-mangohud-update.patch b/nixpkgs-patches/revert-mangohud-update.patch deleted file mode 100644 index 067618e..0000000 --- a/nixpkgs-patches/revert-mangohud-update.patch +++ /dev/null @@ -1,49 +0,0 @@ -diff --git a/pkgs/tools/graphics/mangohud/default.nix b/pkgs/tools/graphics/mangohud/default.nix -index cf83d4254baa..992afd60d3cb 100644 ---- a/pkgs/tools/graphics/mangohud/default.nix -+++ b/pkgs/tools/graphics/mangohud/default.nix -@@ -24,8 +24,10 @@ - unzip, - wayland, - libXNVCtrl, -+ nlohmann_json, - spdlog, - libxkbcommon, -+ glew, - glfw, - libXrandr, - x11Support ? true, -@@ -93,14 +95,14 @@ let - in - stdenv.mkDerivation (finalAttrs: { - pname = "mangohud"; -- version = "0.8.2"; -+ version = "0.8.1"; - - src = fetchFromGitHub { - owner = "flightlessmango"; - repo = "MangoHud"; - tag = "v${finalAttrs.version}"; - fetchSubmodules = true; -- hash = "sha256-BZ3R7D2zOlg69rx4y2FzzjpXuPOv913TOz9kSvRN+Wg="; -+ hash = "sha256-FvPhnOvcYE8vVB5R+ZRmuZxrC9U4GA338V7VAuUlHCE="; - }; - - outputs = [ -@@ -188,6 +190,7 @@ stdenv.mkDerivation (finalAttrs: { - - buildInputs = [ - dbus -+ nlohmann_json - spdlog - ] - ++ lib.optional waylandSupport wayland -@@ -195,6 +198,7 @@ stdenv.mkDerivation (finalAttrs: { - ++ lib.optional nvidiaSupport libXNVCtrl - ++ lib.optional (x11Support || waylandSupport) libxkbcommon - ++ lib.optionals mangoappSupport [ -+ glew - glfw - libXrandr - ]; - diff --git a/openbox.nix b/openbox.nix new file mode 100755 index 0000000..505b90f --- /dev/null +++ b/openbox.nix @@ -0,0 +1,24 @@ +{ config, pkgs, ... }: + +{ + services = { + xserver = { + enable = true; + autorun = false; + windowManager.openbox.enable = true; + }; + xrdp = { + enable = true; + openFirewall = true; + defaultWindowManager = "${pkgs.openbox}/bin/openbox-session"; + #confDir = "/etc/xrdp"; + }; + }; + environment.systemPackages = with pkgs; [ + pcmanfm + obconf + firefox + gnome.gnome-calculator + alacritty + ]; +} diff --git a/pkgs/kame-editor/default.nix b/pkgs/kame-editor/default.nix deleted file mode 100644 index b3cd0a7..0000000 --- a/pkgs/kame-editor/default.nix +++ /dev/null @@ -1,59 +0,0 @@ -{ - lib, - stdenv, - fetchFromGitLab, - qt6, - portaudio, - kame-tools, - vgmstream, - rstmcpp, -}: -stdenv.mkDerivation rec { - name = "kame-editor"; - version = "1.4.1"; - - src = fetchFromGitLab { - owner = "beelzy"; - repo = name; - # tag = version; - rev = "82c9c445644b133b6d0ce3529e65b1a3df83c804"; - hash = "sha256-V2nMvVIjFRM8++XQ9tkE2OiZzCvdrg0jK69HM+ZIVyA="; - }; - - postPatch = '' - substituteInPlace kame-editor.pro \ - --replace-fail "/usr/local/bin/" "$out/bin" - ''; - - buildInputs = [ - qt6.qtbase - portaudio - ]; - - qtWrapperArgs = [ - "--prefix PATH : ${ - lib.makeBinPath [ - kame-tools - vgmstream - rstmcpp - ] - }" - ]; - - nativeBuildInputs = [ - qt6.qmake - qt6.wrapQtAppsHook - ]; - - postBuild = '' - bash ./buildicons.sh - ''; - - postInstall = '' - mkdir -p $out/share/icons/hicolor - mkdir -p $out/share/applications - - cp kame-editor.desktop $out/share/applications - cp -r icons/. $out/share/icons/hicolor - ''; -} diff --git a/pkgs/kame-tools/default.nix b/pkgs/kame-tools/default.nix deleted file mode 100644 index fe2b608..0000000 --- a/pkgs/kame-tools/default.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ - stdenv, - fetchFromGitLab, - zip, -}: -stdenv.mkDerivation rec { - name = "kame-tools"; - version = "a1fe47cc"; - - src = fetchFromGitLab { - owner = "beelzy"; - repo = name; - rev = version; - fetchSubmodules = true; - hash = "sha256-ETl5f8M4OJPFB7NEq2mVuMm4RhBtAbMzlrvGHD14zXw="; - }; - - postPatch = '' - substituteInPlace buildtools/make_base \ - --replace-fail "/usr/local/bin" "$out/bin" - ''; - - installPhase = '' - mkdir -p $out/bin - cp output/linux-x86_64/* $out/bin/ - ''; - - nativeBuildInputs = [zip]; -} diff --git a/pkgs/kasane-teto-cursor b/pkgs/kasane-teto-cursor deleted file mode 100644 index af93194..0000000 --- a/pkgs/kasane-teto-cursor +++ /dev/null @@ -1,18 +0,0 @@ -{ - stdenvNoCC, - fetchzip, -}: -stdenvNoCC.mkDerivation { - name = "kasane-teto-cursors"; - - src = fetchzip { - url = "http://dl.everest.tailscale/Kasane%20Teto%20Cursor%20-%20by%20wobb.zip"; - hash = "sha256-4neZqApkK6hwufLTilUtPmgzyBih7onSdSZ9lezQbIU="; - }; - - dontBuild = true; - installPhase = '' - mkdir -p $out/share/icons - cp -dr --no-preserve='ownership' $src/Linux/Kasane\ Teto $out/share/icons - ''; -} diff --git a/pkgs/rstmcpp/default.nix b/pkgs/rstmcpp/default.nix deleted file mode 100644 index 94c51a3..0000000 --- a/pkgs/rstmcpp/default.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ - stdenv, - fetchFromGitLab, -}: -stdenv.mkDerivation rec { - name = "rstmcpp"; - version = "fe8bee01"; - - src = fetchFromGitLab { - owner = "beelzy"; - repo = name; - rev = version; - fetchSubmodules = true; - hash = "sha256-T9mxTBj/eykvbBkbmEKTUFldtBp3cJgWAbeu44SwxiM="; - }; - - installPhase = '' - mkdir -p $out/bin - cp rstmcpp $out/bin - ''; -} diff --git a/roles/common/configuration.nix b/roles/common/configuration.nix index 998bbef..96b3b9e 100755 --- a/roles/common/configuration.nix +++ b/roles/common/configuration.nix @@ -1,192 +1,51 @@ +{ config, pkgs, ... }: + { - config, - lib, - pkgs, - flakeSelf, - ... -}: { - environment = { - # As of the 1st of May 2023, the default packages are nano, perl, rsync and strace - # I don't need any of them, so I just empty the list - defaultPackages = []; - }; + environment = { + # As of the 1st of May 2023, the default packages are nano, perl, rsync and strace + # I don't need any of them, so I just empty the list + defaultPackages = []; + variables = { + # Environment variables go here + EDITOR = "micro"; + }; + }; - # Set up /tmp - boot.tmp = { - useTmpfs = false; - # Cleaning out /tmp at boot if it's a tmpfs is quite stupid - cleanOnBoot = !config.boot.tmp.useTmpfs; - }; + # Set up secrets + age = { + identityPaths = [ + "/etc/ssh/ssh_host_rsa_key" + "/etc/ssh/ssh_host_ed25519_key" + # This key has a passcode, so if you need to use it you'll have to + # enter the password A LOT of times. Only on the first setup tho + "/tmp/id_ed25519_bootstrap" + ]; + # Copy (NOT SYMLINK) host ssh keys into place + secrets = { + "ed25519" = { + symlink = false; + file = ../../secrets/${config.networking.hostName}/host-key-ed25519; + path = "/etc/ssh/ssh_host_ed25519_key"; + }; + "rsa" = { + symlink = false; + file = ../../secrets/${config.networking.hostName}/host-key-rsa; + path= "/etc/ssh/ssh_host_rsa_key"; + }; + "ed25519-public" = { + symlink = false; + file = ../../secrets/${config.networking.hostName}/host-key-ed25519-public; + path = "/etc/ssh/ssh_host_ed25519_key.pub"; + mode = "0644"; + }; + "rsa-public" = { + symlink = false; + file = ../../secrets/${config.networking.hostName}/host-key-rsa-public; + path = "/etc/ssh/ssh_host_rsa_key.pub"; + mode = "0644"; + }; + }; + }; - environment.localBinInPath = lib.mkDefault true; - - # Set up zram - zramSwap = { - enable = true; - priority = 100; - memoryPercent = 60; - # zstd my beloved <3 - algorithm = "zstd"; - }; - # zswap with zram is not a good idea - boot.kernelParams = ["zswap.enabled=0"]; - - # Set up keyboard layout - services.xserver.xkb.layout = "es"; - - # Set up console - console = { - packages = [pkgs.terminus_font]; - earlySetup = true; - # mkDefault has 1000 priority, so that way I don't conflict with nixos-hardware - font = lib.mkOverride 999 "ter-i16n"; - # Make the console use X's keyboard configuration - useXkbConfig = true; - }; - - boot.supportedFilesystems = ["nfs"]; - - security.pki.certificates = [ - # Caddy - '' - -----BEGIN CERTIFICATE----- - MIIBqTCCAU+gAwIBAgIQceh0ZUBNrOmqLVsDr+2HBjAKBggqhkjOPQQDAjAzMTEw - LwYDVQQDEyhDYWRkeSAoRXZlcmVzdCkgbG9jYWwgQ0EgLSAyMDI0IEVDQyBSb290 - MB4XDTI0MDcxODAwMDEwM1oXDTM0MDUyNzAwMDEwM1owMzExMC8GA1UEAxMoQ2Fk - ZHkgKEV2ZXJlc3QpIGxvY2FsIENBIC0gMjAyNCBFQ0MgUm9vdDBZMBMGByqGSM49 - AgEGCCqGSM49AwEHA0IABJjrY8x6iDXncxG8exwLyaEq8N0XnCIbga8PVYiz3VLS - 07++i0Dey9k68ag6KUZICfc8dX1uZ6/ozUZb4YO3xCSjRTBDMA4GA1UdDwEB/wQE - AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTQAqZS9KeiAr9mSPV9 - RIJbSvRsTzAKBggqhkjOPQQDAgNIADBFAiBIgHrD2cUfNEctVx0WzHb0cLAsrK4Q - 3QbyxPLyenR+dAIhAMOIAyxlKKKvAJMOzAg0r+csSVRdP1YHsHiE7U1GeHWx - -----END CERTIFICATE----- - '' - ]; - - # Set up localisation - i18n = { - defaultLocale = "en_US.UTF-8"; - supportedLocales = [ - "es_US.UTF-8/UTF-8" - "es_ES.UTF-8/UTF-8" - "en_US.UTF-8/UTF-8" - ]; - extraLocaleSettings = { - LC_NUMERIC = "es_ES.UTF-8"; - # am/pm is nice but mm/dd/yy is yucky - LC_TIME = "es_US.UTF-8"; - LC_MONETARY = "es_ES.UTF-8"; - LC_MEASUREMENT = "es_ES.UTF-8"; - LC_PAPER = "es_ES.UTF-8"; - LC_ADDRESS = "es_US.UTF-8"; - LC_NAME = "es_ES.UTF-8"; - LC_TELEPHONE = "es_ES.UTF-8"; - }; - }; - - services = { - fstrim.enable = true; - fwupd.enable = true; - }; - - # Set up my user - users.mutableUsers = false; - users.users.toast = { - isNormalUser = true; - description = "Toast"; - extraGroups = ["wheel"]; - hashedPasswordFile = config.sops.secrets.toast.path; - }; - - # Set up time zone. - time.timeZone = lib.mkDefault "Europe/Madrid"; - services.automatic-timezoned.enable = true; - - nixpkgs.overlays = [ - flakeSelf.outputs.overlays.default - ( - final: prev: { - catppuccin = prev.catppuccin.override { - accent = "mauve"; - variant = "mocha"; - themeList = [ - "bat" - "btop" - "starship" - "grub" - ]; - }; - } - ) - ]; - - catppuccin = { - flavor = "mocha"; - accent = "mauve"; - }; - - programs.iotop.enable = true; - - home-manager = { - backupFileExtension = "hm-backup"; - useGlobalPkgs = true; - verbose = true; - sharedModules = with flakeSelf; [ - inputs.catppuccin.homeModules.catppuccin - inputs.sops-nix.homeManagerModules.sops - ]; - users.toast = {osConfig, ...}: { - catppuccin.flavor = osConfig.catppuccin.flavor; - catppuccin.accent = osConfig.catppuccin.accent; - home.stateVersion = "25.05"; - manual = { - manpages.enable = true; - html.enable = true; - }; - xdg = { - enable = true; - userDirs = { - enable = true; - createDirectories = true; - publicShare = null; # Disable the public folder - }; - }; - systemd.user.startServices = true; - }; - }; - - # Set up secrets - age = { - identityPaths = [ - "/persist/id_host" - ]; - }; - sops = { - age.sshKeyPaths = ["/persist/id_host"]; - defaultSopsFile = "${flakeSelf.inputs.secrets}/${config.networking.hostName}.yaml"; - secrets.toast = { - sopsFile = "${flakeSelf.inputs.secrets}/passwd.yaml"; - neededForUsers = true; - }; - }; - - catppuccin.grub.enable = true; - - /* - I used to keep the host keys in the repo as a secret, but since I use the - host keys for decrypting too I'm not sure encrypting a key with itself - is a good idea. Now the host keys will need to be placed manually where they are needed - For first time installs they are generated by services.openssh.hostKeys on servers, and - manually on everything else - */ - - system = { - stateVersion = "25.05"; - # Nix on nixos 23.05 does not have dirtyRev - configurationRevision = flakeSelf.sourceInfo.rev or flakeSelf.sourceInfo.dirtyRev or "dirty"; - nixos.variant_id = lib.mkDefault (lib.strings.toLower config.networking.hostName); - }; - image.modules.iso = { - system.nixos.variant_id = "${lib.strings.toLower config.networking.hostName}-iso"; - }; + system.stateVersion = "23.05"; } diff --git a/roles/common/default.nix b/roles/common/default.nix index c9bfd8b..91e9eb3 100755 --- a/roles/common/default.nix +++ b/roles/common/default.nix @@ -1,7 +1,9 @@ -{...}: { - imports = [ - ./programs - ./services - ./configuration.nix - ]; +{ ... }: + +{ + imports = [ + ./programs + ./services/avahi.nix + ./configuration.nix + ]; } diff --git a/roles/common/programs/atuin.nix b/roles/common/programs/atuin.nix deleted file mode 100644 index ab493cd..0000000 --- a/roles/common/programs/atuin.nix +++ /dev/null @@ -1,21 +0,0 @@ -{...}: { - home-manager.users.toast = { - catppuccin.atuin.enable = true; - programs.atuin = { - enable = true; - settings = { - enter_accept = true; - workspaces = true; - filter_mode = "workspace"; - style = "auto"; - inline_height = 0; - stats = { - common_prefix = [ - "sudo" - "," - ]; - }; - }; - }; - }; -} diff --git a/roles/common/programs/bash.nix b/roles/common/programs/bash.nix deleted file mode 100644 index 66bfbea..0000000 --- a/roles/common/programs/bash.nix +++ /dev/null @@ -1,8 +0,0 @@ -{...}: { - home-manager.users.toast = {...}: { - programs.bash = { - enable = true; - enableVteIntegration = true; - }; - }; -} diff --git a/roles/common/programs/bat.nix b/roles/common/programs/bat.nix deleted file mode 100644 index 068cc1d..0000000 --- a/roles/common/programs/bat.nix +++ /dev/null @@ -1,8 +0,0 @@ -{...}: { - home-manager = { - users.toast = { - programs.bat.enable = true; - catppuccin.bat.enable = true; - }; - }; -} diff --git a/roles/common/programs/btop.nix b/roles/common/programs/btop.nix deleted file mode 100644 index 75fd33b..0000000 --- a/roles/common/programs/btop.nix +++ /dev/null @@ -1,8 +0,0 @@ -{...}: { - home-manager = { - users.toast = { - catppuccin.btop.enable = true; - programs.btop.enable = true; - }; - }; -} diff --git a/roles/common/programs/comma.nix b/roles/common/programs/comma.nix deleted file mode 100644 index c04afcb..0000000 --- a/roles/common/programs/comma.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - # Use nix-index-database's comma wrapper - programs.nix-index-database.comma.enable = true; - # Run programs from the system's nixpkgs - environment.variables = {COMMA_NIXPKGS_FLAKE = "system";}; -} diff --git a/roles/common/programs/command-not-found.nix b/roles/common/programs/command-not-found.nix index e3dcd3a..0816b39 100755 --- a/roles/common/programs/command-not-found.nix +++ b/roles/common/programs/command-not-found.nix @@ -1,4 +1,6 @@ -{...}: { - # The nixpkgs command-not-found script does not work with flakes, so I disable it - programs.command-not-found.enable = false; +{ config, ... }: + +{ + # The nixpkgs command-not-found script does not work with flakes, so I disable it + programs.command-not-found.enable = false; } diff --git a/roles/common/programs/default.nix b/roles/common/programs/default.nix index 48b27ba..d28c74d 100755 --- a/roles/common/programs/default.nix +++ b/roles/common/programs/default.nix @@ -1,32 +1,23 @@ -{pkgs, ...}: { - imports = [ - ./htop.nix - ./nix.nix - ./nix-index.nix - ./command-not-found.nix - ./comma.nix - ./bash.nix - ./git.nix - ./starship.nix - ./bat.nix - ./btop.nix - ./helix.nix - ./direnv.nix - ./atuin.nix - ./fish.nix - ./eza.nix - ]; - # Some programs dont have a programs.*.enable option, so I install their package here - environment.systemPackages = with pkgs; [ - speedtest-cli - # Bat has a home manager module, but I want it to be available system wide - bat - file - nvd - ncdu - tree - btdu - iperf3 - restic - ]; +{ config, pkgs, ... }: + +{ + imports = [ + ./htop.nix + ./nix.nix + ./nix-index.nix + ./command-not-found.nix + ]; + # Some programs dont have a programs.*.enable option, so I install their package here + environment.systemPackages = with pkgs; [ + speedtest-cli + bat + micro + nvd + ncdu + tree + btdu + btop + iperf3 + restic + ]; } diff --git a/roles/common/programs/direnv.nix b/roles/common/programs/direnv.nix deleted file mode 100644 index e4a4e29..0000000 --- a/roles/common/programs/direnv.nix +++ /dev/null @@ -1,10 +0,0 @@ -{...}: { - home-manager.users.toast = { - programs.direnv = { - enable = true; - nix-direnv = { - enable = true; - }; - }; - }; -} diff --git a/roles/common/programs/eza.nix b/roles/common/programs/eza.nix deleted file mode 100644 index 90602d6..0000000 --- a/roles/common/programs/eza.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ - flakeSelf, - config, - ... -}: { - home-manager = { - users.toast = { - programs.eza = { - enable = true; - enableBashIntegration = true; - git = true; - icons = "auto"; - extraOptions = [ - "--group" - ]; - }; - xdg.configFile."eza/theme.yml".source = "${flakeSelf.inputs.eza-themes}/themes/catppuccin.yml"; - }; - }; -} diff --git a/roles/common/programs/fish.nix b/roles/common/programs/fish.nix deleted file mode 100644 index 3bafd1c..0000000 --- a/roles/common/programs/fish.nix +++ /dev/null @@ -1,10 +0,0 @@ -{...}: { - programs.fish = { - enable = true; - }; - - home-manager.users.toast = { - catppuccin.fish.enable = true; - programs.fish.enable = true; - }; -} diff --git a/roles/common/programs/git.nix b/roles/common/programs/git.nix deleted file mode 100644 index 2709d45..0000000 --- a/roles/common/programs/git.nix +++ /dev/null @@ -1,25 +0,0 @@ -{...}: { - programs.ssh.knownHosts = { - "[git.toast003.xyz]:4222".publicKey = '' - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKoUcWx56NZ3kqydN3d0gLNz6SlBm1ArkHhqR9Fwd8qs - ''; - }; - home-manager.users.toast = { - programs.git = { - enable = true; - settings = { - user.name = "toast"; - user.email = "toast003@tutamail.com"; - init.defaultBranch = "main"; - diff.colorMoved = "default"; - commit.verbose = "true"; - }; - }; - - programs.delta = { - enable = true; - enableGitIntegration = true; - }; - catppuccin.delta.enable = true; - }; -} diff --git a/roles/common/programs/helix.nix b/roles/common/programs/helix.nix deleted file mode 100644 index 7a52995..0000000 --- a/roles/common/programs/helix.nix +++ /dev/null @@ -1,54 +0,0 @@ -{ - pkgs, - config, - ... -}: { - programs.nano.enable = false; - home-manager.users.toast = { - catppuccin.helix = { - enable = true; - useItalics = true; - }; - programs.helix = { - enable = true; - defaultEditor = true; - extraPackages = with pkgs; [ - nixpkgs-fmt - nil - taplo - ]; - settings = { - editor = { - mouse = true; - cursorline = true; - color-modes = true; - bufferline = "multiple"; - statusline.mode = { - normal = "Normal"; - insert = "Insert"; - select = "Select"; - }; - indent-guides.render = true; - end-of-line-diagnostics = "hint"; - inline-diagnostics.cursor-line = "warning"; - }; - }; - languages = { - language = [ - { - name = "nix"; - formatter.command = "nixpkgs-fmt"; - } - ]; - language-server.nil = { - config = { - flake = { - autoArchive = false; - autoEvalInputs = true; - }; - }; - }; - }; - }; - }; -} diff --git a/roles/common/programs/htop.nix b/roles/common/programs/htop.nix index 4d8bd79..4eb7fbe 100755 --- a/roles/common/programs/htop.nix +++ b/roles/common/programs/htop.nix @@ -1,13 +1,15 @@ -{...}: { - programs.htop = { - enable = true; - settings = { - tree_view = 1; - highlight_base_name = 1; - show_program_path = 0; - show_cpu_frequency = 1; - show_cpu_temperature = 1; - hide_userland_threads = 1; - }; - }; +{ config, ... }: + +{ + programs.htop = { + enable = true; + settings = { + tree_view = 1; + highlight_base_name = 1; + show_program_path = 0; + show_cpu_frequency = 1; + show_cpu_temperature = 1; + hide_userland_threads = 1; + }; + }; } diff --git a/roles/common/programs/nix-index.nix b/roles/common/programs/nix-index.nix index c3a8177..93e88d4 100755 --- a/roles/common/programs/nix-index.nix +++ b/roles/common/programs/nix-index.nix @@ -1,15 +1,8 @@ -{...}: { - /* - environment.systemPackages = [ pkgs.nix-index ]; - programs.bash.interactiveShellInit = '' - source ${pkgs.nix-index}/etc/profile.d/command-not-found.sh - ''; - */ - programs.nix-index = { - enable = true; - enableBashIntegration = true; - # I don't use zsh or fish (yet) - enableZshIntegration = false; - enableFishIntegration = false; - }; +{ config, pkgs, ... }: + +{ + environment.systemPackages = [ pkgs.nix-index ]; + programs.bash.interactiveShellInit = '' +source ${pkgs.nix-index}/etc/profile.d/command-not-found.sh + ''; } diff --git a/roles/common/programs/nix.nix b/roles/common/programs/nix.nix index a676fbd..ea40ccd 100755 --- a/roles/common/programs/nix.nix +++ b/roles/common/programs/nix.nix @@ -1,62 +1,15 @@ +{ config, ... }: + { - config, - lib, - flakeSelf, - ... -}: { - age.secrets = { - remoteBuilderKey.file = "${flakeSelf.inputs.secrets}/WinMax2/nixrbld-private-key.age"; - }; - - programs.ssh = { - knownHosts.winmax2.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPE+ksvEq/I2LMLOztVXpLE9yuI6EkRh4EtXdlYkhl6C WinMax2 host key"; - extraConfig = '' - Host nixrbld - HostName winmax2 - IdentitiesOnly yes - IdentityFile ${config.age.secrets.remoteBuilderKey.path} - User nixrbld - ''; - }; - - system.tools.nixos-option.enable = false; - - nix = { - settings = { - auto-optimise-store = true; - experimental-features = "nix-command flakes"; - }; - distributedBuilds = true; - # Don't use remote builder on the remote builder - buildMachines = lib.mkIf (config.networking.hostName != "WinMax2") [ - { - hostName = "nixrbld"; - system = "x86_64-linux"; - protocol = "ssh-ng"; - maxJobs = 4; - supportedFeatures = [ - "big-parallel" - "kvm" - "nixos-test" - ]; - } - ]; - optimise = { - automatic = true; - dates = ["weekly"]; - }; - registry = { - agenix = { - from = { - id = "agenix"; - type = "indirect"; - }; - to = { - owner = "ryantm"; - repo = "agenix"; - type = "github"; - }; - }; - }; - }; + nix = { + extraOptions = '' +experimental-features = nix-command flakes + ''; + registry = { + agenix = { + from = { id = "agenix"; type = "indirect"; }; + to = { owner = "ryantm"; repo = "agenix"; type = "github"; }; + }; + }; + }; } diff --git a/roles/common/programs/starship.nix b/roles/common/programs/starship.nix deleted file mode 100644 index f270a27..0000000 --- a/roles/common/programs/starship.nix +++ /dev/null @@ -1,31 +0,0 @@ -{...}: { - programs.starship = { - enable = true; - presets = [ - "nerd-font-symbols" - ]; - settings = { - nix_shell = { - disabled = false; - heuristic = true; - }; - os = { - disabled = false; - }; - directory = { - disabled = false; - truncation_length = 6; - truncation_symbol = ".../"; - }; - }; - }; - # The catppuccin module only works for home-manager, so this - # sets up starship with home-manager using the system config - # home-manager.users.toast = {osConfig, ...}: { - # programs.starship = { - # enable = false; - # catppuccin.enable = true; - # settings = osConfig.programs.starship.settings; - # }; - # }; -} diff --git a/roles/common/services/avahi.nix b/roles/common/services/avahi.nix index f7f33e6..2891e8d 100755 --- a/roles/common/services/avahi.nix +++ b/roles/common/services/avahi.nix @@ -1,6 +1,8 @@ -{config, ...}: { - services.avahi = { - enable = true; - nssmdns4 = true; - }; +{ config, ... }: + +{ + services.avahi = { + enable = true; + nssmdns = true; + }; } diff --git a/roles/common/services/default.nix b/roles/common/services/default.nix deleted file mode 100644 index 6640381..0000000 --- a/roles/common/services/default.nix +++ /dev/null @@ -1,8 +0,0 @@ -{...}: { - imports = [ - ./avahi.nix - ./tailscale.nix - ./syncthing.nix - ./kmscon.nix - ]; -} diff --git a/roles/common/services/kmscon.nix b/roles/common/services/kmscon.nix deleted file mode 100644 index dbd31be..0000000 --- a/roles/common/services/kmscon.nix +++ /dev/null @@ -1,29 +0,0 @@ -{pkgs, ...}: { - services.kmscon = { - enable = true; - useXkbConfig = true; - fonts = [ - { - name = "JetBrains Mono Nerd Font"; - package = pkgs.nerd-fonts.jetbrains-mono; - } - ]; - extraConfig = '' - term=xterm-256color - font-size=10 - ''; - package = pkgs.kmscon.overrideAttrs (old: { - patches = - old.patches - ++ [ - # https://github.com/kmscon/kmscon/issues/133 - ( - pkgs.fetchpatch { - url = "https://github.com/Aetf/kmscon/pull/135.patch"; - hash = "sha256-hJrKkONdQmz9gGMXbk11+4MF8Vn4guE3Bl1Ni6SGDw4="; - } - ) - ]; - }); - }; -} diff --git a/roles/common/services/syncthing.nix b/roles/common/services/syncthing.nix deleted file mode 100755 index 820e64f..0000000 --- a/roles/common/services/syncthing.nix +++ /dev/null @@ -1,40 +0,0 @@ -{ - config, - lib, - flakeSelf, - ... -}: let - hostSecrets = "${flakeSelf.inputs.secrets}/" + config.networking.hostName; -in { - # Get secrets - age.secrets = { - syncthingKey.file = hostSecrets + "/syncthingKey.age"; - syncthingCert.file = hostSecrets + "/syncthingCert.age"; - }; - - services.syncthing = { - key = config.age.secrets.syncthingKey.path; - cert = config.age.secrets.syncthingCert.path; - overrideDevices = true; - overrideFolders = true; - openDefaultPorts = true; - settings = { - options = { - urAccepted = 3; - }; - # Set up devices and folders common to every device - devices = lib.toast.syncthing.devices; - folders = { - "passwords" = { - label = "KeePassXC Passwords"; - id = "rdyaq-ex659"; - devices = ["phone" "pc" "steamdeck" "server" "surface" "winmax2"]; - }; - }; - }; - }; - networking.firewall = { - allowedTCPPorts = [22000]; - allowedUDPPorts = [22000 21027]; - }; -} diff --git a/roles/common/services/tailscale.nix b/roles/common/services/tailscale.nix deleted file mode 100644 index a7c0338..0000000 --- a/roles/common/services/tailscale.nix +++ /dev/null @@ -1,10 +0,0 @@ -{lib, ...}: { - services.tailscale = { - enable = true; - useRoutingFeatures = lib.mkDefault "client"; - }; - - systemd.services.tailscaled.environment = { - TS_NO_LOGS_NO_SUPPORT = "true"; - }; -} diff --git a/roles/desktop/configuration.nix b/roles/desktop/configuration.nix deleted file mode 100644 index 048f8e6..0000000 --- a/roles/desktop/configuration.nix +++ /dev/null @@ -1,35 +0,0 @@ -{pkgs, ...}: { - # Enable scanning - hardware.sane = { - enable = true; - extraBackends = [pkgs.sane-airscan]; - }; - users.users.toast.extraGroups = ["scanner"]; - - services.xserver.enable = true; - - nix = { - daemonIOSchedClass = "idle"; - daemonCPUSchedPolicy = "idle"; - }; - - # Set up fonts - fonts.packages = with pkgs.nerd-fonts; [ - hack - jetbrains-mono - - # Japanese fonts - pkgs.noto-fonts-cjk-sans - pkgs.noto-fonts-cjk-serif - ]; - - # Already use electron apps (discord) so this only adds 20 mb more - environment.systemPackages = [pkgs.tetrio-desktop]; - - hardware.keyboard.qmk.enable = true; - home-manager.users.toast.home.packages = [pkgs.qmk]; - - home-manager.users.toast.xdg.autostart.enable = true; - - boot.plymouth.enable = true; -} diff --git a/roles/desktop/default.nix b/roles/desktop/default.nix old mode 100644 new mode 100755 index d8e1f2b..52f5478 --- a/roles/desktop/default.nix +++ b/roles/desktop/default.nix @@ -1,7 +1,7 @@ -{...}: { - imports = [ - ./services - ./programs - ./configuration.nix - ]; +{ ... }: + +{ + imports = [ + ./discord.nix + ]; } diff --git a/roles/desktop/discord.nix b/roles/desktop/discord.nix new file mode 100644 index 0000000..bdabdc4 --- /dev/null +++ b/roles/desktop/discord.nix @@ -0,0 +1,19 @@ +{ config, pkgs, ... }: + +{ + # TODO: Find out why this does not do anything. If I put this + # on flake.nix it does work, so it's not completely wrong. + /* nixpkgs.overlays = + let + discordOverlay = self: super: { + discord = super.discord.override { + withOpenASAR = true; + withVencord = true; + }; + }; + in + [ discordOverlay ]; */ + users.users.toast.packages = with pkgs; [ + discord + ]; +} diff --git a/roles/desktop/programs/appimage.nix b/roles/desktop/programs/appimage.nix deleted file mode 100644 index 7e90c2e..0000000 --- a/roles/desktop/programs/appimage.nix +++ /dev/null @@ -1,6 +0,0 @@ -{...}: { - programs.appimage = { - enable = true; - binfmt = true; - }; -} diff --git a/roles/desktop/programs/default.nix b/roles/desktop/programs/default.nix deleted file mode 100755 index 1e2eac3..0000000 --- a/roles/desktop/programs/default.nix +++ /dev/null @@ -1,15 +0,0 @@ -{...}: { - imports = [ - ./discord.nix - ./firefox.nix - ./keepassxc.nix - ./jamesdsp.nix - ./git.nix - ./ssh.nix - ./appimage.nix - ./mpv.nix - ./sysdvr-qt.nix - ./spotify.nix - ./distrobox.nix - ]; -} diff --git a/roles/desktop/programs/discord.nix b/roles/desktop/programs/discord.nix deleted file mode 100644 index ca48e5d..0000000 --- a/roles/desktop/programs/discord.nix +++ /dev/null @@ -1,59 +0,0 @@ -{pkgs, ...}: let - discordOverlay = _self: super: { - discord = super.discord.override { - withOpenASAR = true; - withVencord = true; - }; - }; -in { - nixpkgs.overlays = [discordOverlay]; - home-manager.users.toast = { - catppuccin.vesktop.enable = true; - programs.vesktop = { - enable = true; - settings = { - arRPC = true; - minimizeToTray = true; - discordBranch = "stable"; - customTitleBar = true; - spellCheckLanguages = ["en-US" "en" "es"]; - enableMenu = false; - }; - vencord = { - settings = { - plugins = { - BetterGifPicker.enabled = true; - BetterSettings.enabled = true; - CallTimer.enabled = true; - ClearURLs.enabled = true; - FakeNitro.enabled = true; - FixSpotifyEmbeds.enabled = true; - FixYoutubeEmbeds.enabled = true; - FriendsSince.enabled = true; - iLoveSpam.enabled = true; - LoadingQuotes = { - enabled = true; - enableDiscordPresetQuotes = true; - }; - MessageClickActions.enabled = true; - MessageLinkEmbeds.enabled = true; - MessageLogger.enabled = true; - Moyai = { - enabled = true; - volume = 1; - quality = "HD"; - }; - OpenInApp.enabled = true; - petpet.enabled = true; - PlatformIndicators.enabled = true; - ShowHiddenChannels.enabled = true; - ShowHiddenThings.enabled = true; - SpotifyControls.enabled = true; - Translate.enabled = true; - YoutubeAdblock.enabled = true; - }; - }; - }; - }; - }; -} diff --git a/roles/desktop/programs/distrobox.nix b/roles/desktop/programs/distrobox.nix deleted file mode 100644 index 6de5db5..0000000 --- a/roles/desktop/programs/distrobox.nix +++ /dev/null @@ -1,28 +0,0 @@ -{...}: { - virtualisation.podman = { - # Due to limitations with home-manager, podman has to be available system wide - enable = true; - }; - home-manager.users.toast = { - programs.distrobox = { - enable = true; - containers = { - uav = { - image = "quay.io/toolbx/ubuntu-toolbox:24.04"; - # additional_packages = "lsb-release dmidecode git"; - additional_packages = "git"; - init_hooks = [ - "git clone https://github.com/PX4/PX4-Autopilot.git --recursive /tmp/px4" - "cd /tmp/px4/Tools/setup/" - # The install scripts wants to add things to .bashrc, so it errors out - # This removes the line that does that - "sed -i '181d' ubuntu.sh" - "bash ubuntu.sh" - "rm -rf /tmp/px4 --one-file-system" - ]; - entry = true; - }; - }; - }; - }; -} diff --git a/roles/desktop/programs/firefox.nix b/roles/desktop/programs/firefox.nix deleted file mode 100644 index 538456a..0000000 --- a/roles/desktop/programs/firefox.nix +++ /dev/null @@ -1,147 +0,0 @@ -{lib, ...}: { - home-manager.sharedModules = [ - { - # System wide firefox settings - programs.firefox = { - enable = true; - policies = { - DisableTelemetry = true; - GenerativeAI = { - Chatbot = false; - LinkPreviews = false; - TabGroups = false; - Locked = true; - }; - # You need these for Spotify - EncryptedMediaExtensions.Enabled = true; - ExtensionSettings = { - # TODO: Install extensions the NUR instead of from AMO - "uBlock0@raymondhill.net" = { - installation_mode = "force_installed"; - install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"; - }; - # Decentraleyes - "jid1-BoFifL9Vbdl2zQ@jetpack" = { - installation_mode = "normal_installed"; - install_url = "https://addons.mozilla.org/firefox/downloads/latest/decentraleyes/latest.xpi"; - }; - "jid1-MnnxcxisBPnSXQ@jetpack" = { - installation_mode = "normal_installed"; - install_url = "https://addons.mozilla.org/firefox/downloads/latest/privacy-badger17/latest.xpi"; - }; - # Uninstall undeclared extensions - "*" = { - installation_mode = "blocked"; - blocked_install_message = "Extensions are managed by home-manager"; - }; - }; - Preferences = { - # Enable video hardware acceleration - "media.ffmpeg.vaapi.enabled" = { - Value = true; - Status = "default"; - }; - "dom.security.https_only_mode" = { - Value = true; - Status = "locked"; - }; - "browser.ml.chat.page" = { - Value = false; - Status = "Locked"; - }; - }; - PromptForDownloadLocation = true; - # I use an external password manager, so the built in one just bothers me - PasswordManagerEnabled = false; - Permissions = { - Autoplay = { - Allow = [ - "https://www.youtube.com" - "https://sync-tube.de" - ]; - Default = "block-audio-video"; - }; - }; - FirefoxHome.SponsoredTopSites = false; - }; - }; - } - ]; - # Per-user settings - home-manager.users.toast = { - programs.firefox.policies = { - DisablePocket = true; - ExtensionSettings = { - "sponsorBlocker@ajay.app" = { - installation_mode = "normal_installed"; - install_url = "https://addons.mozilla.org/firefox/downloads/latest/sponsorblock/latest.xpi"; - }; - "@testpilot-containers" = { - installation_mode = "normal_installed"; - install_url = "https://addons.mozilla.org/firefox/downloads/latest/multi-account-containers/latest.xpi"; - }; - "{5cce4ab5-3d47-41b9-af5e-8203eea05245}" = { - installation_mode = "normal_installed"; - install_url = "https://addons.mozilla.org/firefox/downloads/latest/control-panel-for-twitter/latest.xpi"; - }; - }; - Preferences = { - "general.smoothScroll.msdPhysics.enabled" = { - Value = true; - Status = "default"; - }; - "browser.ctrlTab.sortByRecentlyUsed" = { - Value = true; - Status = "default"; - }; - # This should be set automatically, but it isn't for some reason - "extensions.webextensions.ExtensionStorageIDB.enabled" = { - Value = false; - Status = "locked"; - }; - }; - }; - programs.firefox.profiles = { - personal = { - name = "Personal"; - id = 0; - isDefault = true; - containersForce = true; - containers = { - work = { - name = "Work"; - id = 1; - icon = "briefcase"; - color = "green"; - }; - }; - extensions.force = true; - extensions.settings = { - "@testpilot-containers".settings = { - onboarding-stage = 8; - "siteContainerMap@@_teams.microsoft.com" = { - userContextId = "1"; - neverAsk = false; - identityMacAddonUUID = "b50e5b1e-6f3b-4245-8eac-5654d889156e"; - }; - "siteContainerMap@@_outlook.office.com" = { - userContextId = "1"; - neverAsk = false; - identityMacAddonUUID = "b50e5b1e-6f3b-4245-8eac-5654d889156e"; - }; - }; - # Control panel for twitter - "{5cce4ab5-3d47-41b9-af5e-8203eea05245}".settings = { - hideForYouTimeline = false; - alwaysUseLatestTweets = false; - retweets = "ignore"; - restoreOtherInteractionLinks = true; - navBaseFontSize = false; - followButtonStyle = "themed"; - hideSidebarContent = true; - }; - }; - }; - }; - }; -} diff --git a/roles/desktop/programs/git.nix b/roles/desktop/programs/git.nix deleted file mode 100644 index 90233b0..0000000 --- a/roles/desktop/programs/git.nix +++ /dev/null @@ -1,7 +0,0 @@ -{pkgs, ...}: { - home-manager.users.toast = { - programs.git = { - package = pkgs.gitFull; - }; - }; -} diff --git a/roles/desktop/programs/jamesdsp.nix b/roles/desktop/programs/jamesdsp.nix deleted file mode 100644 index 0aaa341..0000000 --- a/roles/desktop/programs/jamesdsp.nix +++ /dev/null @@ -1,7 +0,0 @@ -{pkgs, ...}: { - home-manager.users.toast = { - home.packages = with pkgs; [ - jamesdsp - ]; - }; -} diff --git a/roles/desktop/programs/keepassxc.nix b/roles/desktop/programs/keepassxc.nix deleted file mode 100644 index a48544a..0000000 --- a/roles/desktop/programs/keepassxc.nix +++ /dev/null @@ -1,34 +0,0 @@ -{pkgs, ...}: { - home-manager = { - users.toast = { - programs.keepassxc = { - enable = true; - autostart = true; - settings = { - General = { - # Not sure what changing this does, I'll leave it alone - ConfigVersion = 2; - MinimizeAfterUnlock = true; - AutoSaveAfterEveryChange = false; - }; - GUI = { - ApplicationTheme = "classic"; - MinimizeOnStartup = false; - MinimizeOnClose = true; - MinimizeToTray = true; - ShowTrayIcon = true; - # 0 is icons, 1 is text, 2 is text next to icons, 3 is text under icons, and 4 is follow style - ToolButtonStyle = 0; # Would choose 4 but it's too big for a small window - # monochrome-light, monochrome-dark or colorful - TrayIconAppearance = "monochrome-light"; - }; - Security = { - HideNotes = true; - IconDownloadFallback = true; - }; - SSHAgent.Enabled = true; - }; - }; - }; - }; -} diff --git a/roles/desktop/programs/mpv.nix b/roles/desktop/programs/mpv.nix deleted file mode 100644 index d85b058..0000000 --- a/roles/desktop/programs/mpv.nix +++ /dev/null @@ -1,24 +0,0 @@ -{pkgs, ...}: { - nixpkgs.overlays = [ - ( - final: prev: { - mpv-unwrapped = prev.mpv-unwrapped.override { - cddaSupport = true; - }; - } - ) - ]; - home-manager.users.toast = { - programs.mpv = { - enable = true; - scripts = with pkgs.mpvScripts; [ - mpris - ]; - config = { - hwdec = "auto"; - cache = true; - cdda-speed = "8"; - }; - }; - }; -} diff --git a/roles/desktop/programs/spotify.nix b/roles/desktop/programs/spotify.nix deleted file mode 100644 index c895f52..0000000 --- a/roles/desktop/programs/spotify.nix +++ /dev/null @@ -1,5 +0,0 @@ -{pkgs, ...}: { - home-manager.users.toast = { - home.packages = [pkgs.spotify]; - }; -} diff --git a/roles/desktop/programs/ssh.nix b/roles/desktop/programs/ssh.nix deleted file mode 100644 index 8aac072..0000000 --- a/roles/desktop/programs/ssh.nix +++ /dev/null @@ -1,33 +0,0 @@ -{...}: { - programs.ssh.knownHosts = { - everest = { - hostNames = [ - "everest.tailscale" - "toast003.xyz" - ]; - publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAqfABZKnF5YYGZTOKuT7m+sOnUqBQSvLke9c3JDsF5s"; - }; - }; - - home-manager.users.toast = { - programs.ssh = { - enable = true; - enableDefaultConfig = false; - matchBlocks = { - "everest" = { - host = "toast003.xyz"; - hostname = "toast003.xyz"; - forwardAgent = true; - sendEnv = ["COLORTERM"]; - port = 69; - }; - "everest-tailscale" = { - host = "everest"; - hostname = "everest.tailscale"; - forwardAgent = true; - sendEnv = ["COLORTERM"]; - }; - }; - }; - }; -} diff --git a/roles/desktop/programs/sysdvr-qt.nix b/roles/desktop/programs/sysdvr-qt.nix deleted file mode 100644 index 342830f..0000000 --- a/roles/desktop/programs/sysdvr-qt.nix +++ /dev/null @@ -1,8 +0,0 @@ -{...}: { - services.udev.extraRules = '' - SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="4ee0", MODE="0666" - ''; - home-manager.users.toast.services.flatpak = { - packages = ["io.github.parnassius.SysDVR-Qt"]; - }; -} diff --git a/roles/desktop/services/default.nix b/roles/desktop/services/default.nix deleted file mode 100644 index 6bc1d0f..0000000 --- a/roles/desktop/services/default.nix +++ /dev/null @@ -1,11 +0,0 @@ -{...}: { - imports = [ - ./ssh-agent.nix - ./flatpak.nix - ./syncthing.nix - ./pipewire.nix - ./printing.nix - ./networkmanager.nix - ./tailscale.nix - ]; -} diff --git a/roles/desktop/services/flatpak.nix b/roles/desktop/services/flatpak.nix deleted file mode 100644 index a2f9723..0000000 --- a/roles/desktop/services/flatpak.nix +++ /dev/null @@ -1,17 +0,0 @@ -{flakeSelf, ...}: { - services.flatpak.enable = true; - - home-manager = { - sharedModules = [{imports = [flakeSelf.inputs.nix-flatpak.homeManagerModules.nix-flatpak];}]; - users.toast = { - services.flatpak = { - packages = ["tv.plex.PlexDesktop"]; - uninstallUnmanaged = true; - update.auto = { - enable = true; - onCalendar = "weekly"; - }; - }; - }; - }; -} diff --git a/roles/desktop/services/networkmanager.nix b/roles/desktop/services/networkmanager.nix deleted file mode 100644 index f19610b..0000000 --- a/roles/desktop/services/networkmanager.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - config, - lib, - flakeSelf, - ... -}: let - tailscaleName = config.services.tailscale.interfaceName; -in { - sops.secrets.wifiPasswords = { - sopsFile = "${flakeSelf.inputs.secrets}/wifi-passwords.env"; - format = "dotenv"; - }; - networking.networkmanager = { - enable = true; - unmanaged = [ - "interface-name:${tailscaleName}" - ]; - ensureProfiles = { - environmentFiles = [config.sops.secrets.wifiPasswords.path]; - profiles = with lib.toast.networkManager; { - "4g-modem" = mkWifiProfile { - id = "4G Modem"; - priority = 5; - ssid = "TP-Link_CCB4"; - wifi-security = { - auth-alg = "open"; - key-mgmt = "wpa-psk"; - psk = "$MODEM"; - }; - }; - phone = mkWifiProfile { - id = "Phone"; - priority = 5; - ssid = "Redmi Note 10 Pro_5197"; - wifi-security = { - auth-alg = "open"; - key-mgmt = "sae"; - psk = "$PHONE"; - }; - }; - home = mkWifiProfile { - id = "Home"; - ssid = "MOVISTAR-WIFI6-DC98"; - wifi-security = { - key-mgmt = "sae"; - psk = "$HOME"; - }; - }; - }; - }; - }; -} diff --git a/roles/desktop/services/pipewire.nix b/roles/desktop/services/pipewire.nix deleted file mode 100644 index f32365b..0000000 --- a/roles/desktop/services/pipewire.nix +++ /dev/null @@ -1,9 +0,0 @@ -{...}: { - services.pipewire = { - enable = true; - pulse.enable = true; - }; - - # This allows pipewire to get realtime priority, which (hopefully) gets rid of stutters - security.rtkit.enable = true; -} diff --git a/roles/desktop/services/printing.nix b/roles/desktop/services/printing.nix deleted file mode 100644 index 49a590d..0000000 --- a/roles/desktop/services/printing.nix +++ /dev/null @@ -1,7 +0,0 @@ -{...}: { - services.printing = { - enable = true; - startWhenNeeded = true; - stateless = true; - }; -} diff --git a/roles/desktop/services/ssh-agent.nix b/roles/desktop/services/ssh-agent.nix deleted file mode 100644 index 6f734f8..0000000 --- a/roles/desktop/services/ssh-agent.nix +++ /dev/null @@ -1,19 +0,0 @@ -{...}: { - programs.ssh.startAgent = true; - /* - Home assistant added an option that does this - https://github.com/nix-community/home-manager/commit/2d9210f25ed18d5d4e11e6b886de4027c0c51a94 - but since I still need to fix home-manager's envvars not applying I'll stick to the NixOS one - */ - /* - TODO: fix SSH_AUTH_SOCK not being set in Plasma - Turns out the NixOS module also has issues :3 - The env is set but only in bash, not in the DE, so - keepass can't pick it up. For now I'll just set it manually - */ - home-manager.users.toast.xdg.configFile."plasma-workspace/env/ssh-agent.sh".text = '' - if [[ -z "$SSH_AUTH_SOCK" ]]; then - export SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/ssh-agent - fi - ''; -} diff --git a/roles/desktop/services/syncthing.nix b/roles/desktop/services/syncthing.nix deleted file mode 100644 index 249f49d..0000000 --- a/roles/desktop/services/syncthing.nix +++ /dev/null @@ -1,57 +0,0 @@ -{config, ...}: { - services.syncthing = { - # enable = true; - # user = "toast"; - # group = "users"; - # dataDir = config.users.users.toast.home; - settings.folders."passwords".path = "~/Documents/Passwords"; - }; - age.secrets = { - syncthingCert = { - owner = "toast"; - group = "users"; - }; - syncthingKey = { - owner = "toast"; - group = "users"; - }; - }; - home-manager.users.toast = { - osConfig, - lib, - ... - }: let - systemConfig = osConfig.services.syncthing; - missingOptions = [ - "all_proxy" - "configDir" - "dataDir" - "databaseDir" - "declarative" - "devices" - "folders" - "extraFlags" - "user" - "group" - "systemService" - "openDefaultPorts" - "options" - "relay" - "useInotify" - "guiPasswordFile" - ]; - removeMissingOptions = rawOptions: ( - # lib.attrsets.filterAttrs (n: v: n == "all_proxy") rawOptions - builtins.removeAttrs rawOptions missingOptions - ); - in { - services.syncthing = - removeMissingOptions systemConfig - // { - enable = true; - # Renamed options - allProxy = systemConfig.all_proxy; - extraOptions = systemConfig.extraFlags; - }; - }; -} diff --git a/roles/desktop/services/tailscale.nix b/roles/desktop/services/tailscale.nix deleted file mode 100644 index 8235ae8..0000000 --- a/roles/desktop/services/tailscale.nix +++ /dev/null @@ -1,16 +0,0 @@ -{pkgs, ...}: { - home-manager.users.toast = { - services.tailscale-systray = { - enable = true; - package = pkgs.tailscale.overrideAttrs { - postPatch = '' - substituteInPlace client/systray/logo.go --replace-fail \ - "color.NRGBA{0, 0, 0, 255}" "color.NRGBA{0, 0, 0, 0}" - ''; - # Only use this for the tray, so no testing is needed - # Makes the build last a lot less too - doCheck = false; - }; - }; - }; -} diff --git a/roles/gaming/default.nix b/roles/gaming/default.nix deleted file mode 100644 index 4845419..0000000 --- a/roles/gaming/default.nix +++ /dev/null @@ -1,16 +0,0 @@ -{pkgs, ...}: { - imports = [ - ./programs - ./services - ]; - system.replaceDependencies.replacements = [ - { - oldDependency = pkgs.sdl3; - newDependency = pkgs.sdl3.overrideAttrs { - patches = [ - ./sdl-keychron-blacklist.patch - ]; - }; - } - ]; -} diff --git a/roles/gaming/programs/azahar.nix b/roles/gaming/programs/azahar.nix deleted file mode 100644 index 2b88164..0000000 --- a/roles/gaming/programs/azahar.nix +++ /dev/null @@ -1,18 +0,0 @@ -{pkgs, ...}: { - # nixpkgs.overlays = [ - # ( - # final: prev: { - # azahar = prev.azahar.overrideAttrs (old: { - # version = "2120.3"; - # src = final.fetchzip { - # url = "https://github.com/azahar-emu/azahar/releases/download/2120.3/azahar-unified-source-20250414-00e3bbb.tar.xz"; - # hash = "sha256-3QKicmpmWDM7x9GDJ8sxm2Xu+0Yfho4LkSWMp+ixzRk="; - # }; - # }); - # } - # ) - # ]; - home-manager.users.toast = { - home.packages = [pkgs.azahar]; - }; -} diff --git a/roles/gaming/programs/cemu.nix b/roles/gaming/programs/cemu.nix deleted file mode 100644 index 588fd39..0000000 --- a/roles/gaming/programs/cemu.nix +++ /dev/null @@ -1,7 +0,0 @@ -{pkgs, ...}: { - home-manager.users.toast = { - home = { - packages = [pkgs.cemu]; - }; - }; -} diff --git a/roles/gaming/programs/default.nix b/roles/gaming/programs/default.nix deleted file mode 100755 index fda1ac9..0000000 --- a/roles/gaming/programs/default.nix +++ /dev/null @@ -1,15 +0,0 @@ -{pkgs, ...}: { - imports = [ - ./steam.nix - ./mangohud.nix - ./rpcs3.nix - ./retroarch.nix - ./pcsx2.nix - ./cemu.nix - ./azahar.nix - ]; - environment.systemPackages = with pkgs; [ - heroic - prismlauncher - ]; -} diff --git a/roles/gaming/programs/mangohud.nix b/roles/gaming/programs/mangohud.nix deleted file mode 100644 index 91e4675..0000000 --- a/roles/gaming/programs/mangohud.nix +++ /dev/null @@ -1,13 +0,0 @@ -{...}: { - home-manager.users.toast = {...}: { - programs.mangohud = { - enable = true; - # This only works for Vulkan, openGL programs still need the mangohud wrapper - enableSessionWide = true; - settings = { - preset = 4; - no_display = true; - }; - }; - }; -} diff --git a/roles/gaming/programs/pcsx2.nix b/roles/gaming/programs/pcsx2.nix deleted file mode 100644 index 8cf7898..0000000 --- a/roles/gaming/programs/pcsx2.nix +++ /dev/null @@ -1,141 +0,0 @@ -{ - pkgs, - lib, - ... -}: let - pcsx2-bios = pkgs.fetchzip { - url = "https://myrient.erista.me/files/Redump/Sony%20-%20PlayStation%202%20-%20BIOS%20Images%20%28DoM%20Version%29/ps2-0200a-20040614.zip"; - hash = "sha256-wMvswgmsKl+cJl49VlVW84tvU5Jzd+2dl07SOiUDtwA="; - }; - toPcsx2INI = lib.generators.toINI {listsAsDuplicateKeys = true;}; -in { - home-manager.users.toast = { - home.packages = with pkgs; [ - pcsx2 - ]; - xdg.configFile = { - #PCSX2 silently overwrites the symlink so I need to force it's creation - "PCSX2/inis/PCSX2.ini".force = true; - "PCSX2/inis/PCSX2.ini".text = toPcsx2INI { - UI = { - SettingsVersion = 1; - # Use the system theme - Theme = ""; - HideMouseCursor = true; - }; - Folders = { - Bios = "/home/toast/.local/share/PCSX2/bios"; - }; - GameList.RecursivePaths = [ - "/home/toast/Games/PS2/" - ]; - "EmuCore/GS" = { - dithering_ps2 = 1; - upscale_multiplier = 2; - }; - EmuCore = { - EnableDiscordPresence = true; - EnableFastBoot = true; - McdFolderAutoManage = false; - }; - - MemoryCards.Slot1_Filename = "MemoryCard1.ps2"; - - # Controller settings - Pad1 = { - Up = "SDL-0/DPadUp"; - Right = "SDL-0/DPadRight"; - Down = "SDL-0/DPadDown"; - Left = "SDL-0/DPadLeft"; - Triangle = "SDL-0/Y"; - Circle = "SDL-0/B"; - Cross = "SDL-0/A"; - Square = "SDL-0/X"; - Select = "SDL-0/Back"; - Start = "SDL-0/Start"; - L1 = "SDL-0/LeftShoulder"; - L2 = "SDL-0/+LeftTrigger"; - R1 = "SDL-0/RightShoulder"; - R2 = "SDL-0/+RightTrigger"; - L3 = "SDL-0/LeftStick"; - R3 = "SDL-0/RightStick"; - Analog = "SDL-0/Guide"; - LUp = "SDL-0/-LeftY"; - LRight = "SDL-0/+LeftX"; - LDown = "SDL-0/+LeftY"; - LLeft = "SDL-0/-LeftX"; - RUp = "SDL-0/-RightY"; - RRight = "SDL-0/+RightX"; - RDown = "SDL-0/+RightY"; - RLeft = "SDL-0/-RightX"; - LargeMotor = "SDL-0/LargeMotor"; - SmallMotor = "SDL-0/SmallMotor"; - }; - # Default hotkeys - Hotkeys = { - ToggleFullscreen = "Keyboard/Alt & Keyboard/Return"; - CycleAspectRatio = "Keyboard/F6"; - CycleInterlaceMode = "Keyboard/F5"; - CycleMipmapMode = "Keyboard/Insert"; - GSDumpMultiFrame = "Keyboard/Control & Keyboard/Shift & Keyboard/F8"; - Screenshot = "Keyboard/F8"; - GSDumpSingleFrame = "Keyboard/Shift & Keyboard/F8"; - ToggleSoftwareRendering = "Keyboard/F9"; - ZoomIn = "Keyboard/Control & Keyboard/Plus"; - ZoomOut = "Keyboard/Control & Keyboard/Minus"; - InputRecToggleMode = "Keyboard/Shift & Keyboard/R"; - LoadStateFromSlot = "Keyboard/F3"; - SaveStateToSlot = "Keyboard/F1"; - NextSaveStateSlot = "Keyboard/F2"; - PreviousSaveStateSlot = "Keyboard/Shift & Keyboard/F2"; - OpenPauseMenu = "Keyboard/Escape"; - ToggleFrameLimit = "Keyboard/F4"; - TogglePause = "Keyboard/Space"; - ToggleSlowMotion = "Keyboard/Shift & Keyboard/Backtab"; - ToggleTurbo = "Keyboard/Tab"; - HoldTurbo = "Keyboard/Period"; - }; - }; - # 007 nightfire - "PCSX2/gamesettings/SLUS-20579_5B86BB62.ini".text = toPcsx2INI { - "EmuCore/GS".AspectRatio = "16:9"; - }; - "PCSX2/gamesettings/SLUS-21050_BEBF8793.ini".text = toPcsx2INI { - "EmuCore/GS".AspectRatio = "16:9"; - Patches.Enable = [ - "60 FPS for Crashes" - "60 FPS for Menus" - "Progressive Scan" - "MPH to KPH" - "Extra Particles While Driving" - ]; - }; - }; - xdg.dataFile = { - # I would prefer to use symlinkJoin like I do for the ISOs, but - # the bios folder needs to be writable to store the bios settings - "PCSX2/bios/ntsc.bin".source = pkgs.runCommandLocal "pcsx2-bios" {} '' - cp -v ${pcsx2-bios}/*.bin $out - ''; - }; - }; - - # Syncthing - services.syncthing.settings.folders."pcsx2" = { - label = "PCSX2"; - id = "qcdsp-qaaej"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "~/.config/PCSX2"; - }; - home-manager.users.toast.xdg.configFile = { - "PCSX2/.stignore".text = '' - cache - bios - gamesettings - inis/PCSX2.ini* - inis/debuggersettings - inputprofiles - logs - ''; - }; -} diff --git a/roles/gaming/programs/retroarch.nix b/roles/gaming/programs/retroarch.nix deleted file mode 100644 index 60f4bb7..0000000 --- a/roles/gaming/programs/retroarch.nix +++ /dev/null @@ -1,87 +0,0 @@ -{pkgs, ...}: { - home-manager.users.toast = { - home = { - packages = [ - ( - pkgs.wrapRetroArch { - cores = with pkgs.libretro; [ - snes9x - ]; - settings = { - video_driver = "vulkan"; - video_fullscreen = "true"; - menu_swap_ok_cancel_buttons = "true"; - input_joypad_driver = "sdl2"; - # Enable touchscreen support - menu_pointer_enable = "true"; - - # Folder stuffs - - # System/BIOS files - system_directory = "~/.local/share/retroarch/system"; - # Downloads - core_assets_directory = "~/.local/share/retroarch/downloads"; - thumbnails_directory = "~/.local/share/retroarch/thumbnails"; - content_database_path = "~/.local/share/retroarch/database/rdb"; - cheat_database_path = "~/.local/share/retroarch/cheats"; - video_filter_dir = "~/.local/share/retroarch/filters/video"; - audio_filter_dir = "~/.local/share/retroarch/filters/audio"; - video_shader_dir = "~/.local/share/retroarch/shaders"; - recording_output_directory = "~/.local/share/retroarch/records"; - overlay_directory = "~/.local/share/retroarch/overlays"; - osk_overlay_directory = "~/.local/share/retroarch/overlays/keyboards"; - screenshot_directory = "~/.local/share/retroarch/screenshots"; - playlist_directory = "~/.local/share/retroarch/playlists"; - savefile_directory = "~/.local/share/retroarch/saves"; - savestate_directory = "~/.local/share/retroarch/states"; - log_dir = "~/.local/share/retroarch/logs"; - - # By default settings has some things that this overrides, so I need to set them myself - libretro_info_path = "${pkgs.libretro-core-info}/share/retroarch/cores"; - joypad_autoconfig_dir = "${pkgs.retroarch-joypad-autoconfig}/share/libretro/autoconfig"; - assets_directory = "${pkgs.retroarch-assets}/share/retroarch/assets"; - }; - } - ) - ]; - }; - # Retroarch is dumb since it doesn't generate some folders (but it does for others) - systemd.user.tmpfiles.rules = [ - "d /%h/.local/share/retroarch/playlists" - "d /%h/.local/share/retroarch/saves" - "d /%h/.local/share/retroarch/states" - ]; - systemd.user.paths = { - snes-roms = { - Unit.Description = "Monitor SNES rom path for changes"; - Path = { - PathChanged = "/%h/Games/SNES"; - Unit = "update-retroarch-library.service"; - }; - Install.WantedBy = ["default.target"]; - }; - }; - systemd.user.services.update-retroarch-library = { - Service = { - Type = "oneshot"; - ExecStart = pkgs.writeShellScript "scan-snes-games" '' - ${pkgs.libnotify}/bin/notify-send -a RetroArch \ - -i retroarch \ - "SNES games changed!" \ - "Scanning $TRIGGER_PATH..." - ${pkgs.retroarch}/bin/retroarch --scan "/home/toast/Games/SNES" - ''; - }; - }; - }; - - # Sync saves and some other stuff - services.syncthing.settings.folders = { - "retroarch" = { - label = "RetroArch"; - id = "jxuou-2yjnu"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "~/.local/share/retroarch"; - }; - }; -} diff --git a/roles/gaming/programs/rpcs3.nix b/roles/gaming/programs/rpcs3.nix deleted file mode 100644 index 8fe81fa..0000000 --- a/roles/gaming/programs/rpcs3.nix +++ /dev/null @@ -1,15 +0,0 @@ -{pkgs, ...}: { - environment.systemPackages = with pkgs; [ - rpcs3 - ]; - - # Increase the memory lock limit - security.pam.loginLimits = [ - { - domain = "*"; - item = "memlock"; - type = "-"; # Applies to both hard and soft limits - value = "unlimited"; - } - ]; -} diff --git a/roles/gaming/programs/steam.nix b/roles/gaming/programs/steam.nix deleted file mode 100644 index c6e143f..0000000 --- a/roles/gaming/programs/steam.nix +++ /dev/null @@ -1,34 +0,0 @@ -{ - config, - pkgs, - ... -}: { - programs.steam = { - enable = true; - localNetworkGameTransfers.openFirewall = true; - # Doubt that I'll use it, but I'll enable it anyways - remotePlay.openFirewall = true; - - extraCompatPackages = with pkgs; [ - proton-ge-bin - ]; - }; - - # Some linux native games (rise of the tomb raider) use alsa for sound - services.pipewire.alsa.enable = - if config.services.pipewire.pulse.enable == true - then true - else false; - - home-manager.users.toast = { - systemd.user.tmpfiles.rules = [ - "r '/%h/.local/share/applications/Steam Linux Runtime *.desktop'" - "r '/%h/.local/share/applications/Proton *.desktop'" - ]; - services.flatpak.packages = [ - # Celeste mod manager - "io.github.everestapi.Olympus" - ]; - home.packages = [pkgs.sgdboop]; - }; -} diff --git a/roles/gaming/sdl-keychron-blacklist.patch b/roles/gaming/sdl-keychron-blacklist.patch deleted file mode 100644 index ef73b54..0000000 --- a/roles/gaming/sdl-keychron-blacklist.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c -index 5ce36de86..37bf5ca85 100644 ---- a/src/joystick/SDL_joystick.c -+++ b/src/joystick/SDL_joystick.c -@@ -281,6 +281,8 @@ static Uint32 initial_blacklist_devices[] = { - MAKE_VIDPID(0x3297, 0x1969), // Moonlander MK1 Keyboard - MAKE_VIDPID(0x3434, 0x0211), // Keychron K1 Pro System Control - MAKE_VIDPID(0x04f2, 0xa13c), // HP Deluxe Webcam KQ246AA -+ MAKE_VIDPID(0x3434, 0x0353), // Keychron V5 System Control -+ MAKE_VIDPID(0x3434, 0xd030), // Keychron Link - }; - static SDL_vidpid_list blacklist_devices = { - SDL_HINT_JOYSTICK_BLACKLIST_DEVICES, 0, 0, NULL, diff --git a/roles/gaming/services/default.nix b/roles/gaming/services/default.nix deleted file mode 100644 index 24b067d..0000000 --- a/roles/gaming/services/default.nix +++ /dev/null @@ -1,5 +0,0 @@ -{...}: { - imports = [ - ./syncthing.nix - ]; -} diff --git a/roles/gaming/services/syncthing.nix b/roles/gaming/services/syncthing.nix deleted file mode 100644 index 0219bac..0000000 --- a/roles/gaming/services/syncthing.nix +++ /dev/null @@ -1,37 +0,0 @@ -{...}: { - /* - This file will sync saves for games that don't have cloud saves - TODO: turn this into a module eventually - */ - - services.syncthing.settings.folders = { - "steam-201810" = { - label = "Wolfenstein The New Order Saves"; - id = "laxxf-t2wmy"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "~/.local/share/Steam/steamapps/compatdata/201810/pfx/drive_c/users/steamuser/Saved Games/MachineGames/Wolfenstein The New Order/"; - }; - "project-diva-mods" = { - label = "Project Diva Mods"; - id = "7pscj-6egww"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "~/.local/share/Steam/steamapps/common/Hatsune Miku Project DIVA Mega Mix Plus/mods/"; - }; - "project-eden-saves" = { - label = "Project Eden saves"; - id = "xa3qx-3ax5k"; - devices = ["server" "pc" "winmax2" "steamdeck"]; - path = "~/.local/share/Steam/steamapps/compatdata/1761390/pfx/drive_c/users/steamuser/AppData/Roaming/EDEN/"; - }; - "games" = { - label = "Games"; - id = "mwzph-gf2df"; - devices = ["server" "pc" "winmax2" "steamdeck"]; - path = "~/Games"; - }; - }; - home-manager.users.toast.home.file."steam-201810-ignore" = { - target = ".local/share/Steam/steamapps/compatdata/201810/pfx/drive_c/users/steamuser/Saved Games/MachineGames/Wolfenstein The New Order/.stignore"; - text = "base/qconsole.log\nbase/wolfConfig.cfg"; - }; -} diff --git a/roles/kde/default.nix b/roles/kde/default.nix index f80328a..fbcf748 100755 --- a/roles/kde/default.nix +++ b/roles/kde/default.nix @@ -1,8 +1,7 @@ -{...}: { - imports = [ - ./plasma.nix - ./sddm.nix - ./programs - ./patches - ]; +{ ... }: + +{ + imports = [ + ./plasma.nix + ]; } diff --git a/roles/kde/patches/default.nix b/roles/kde/patches/default.nix deleted file mode 100644 index 374881a..0000000 --- a/roles/kde/patches/default.nix +++ /dev/null @@ -1,29 +0,0 @@ -{lib, ...}: let - rootDirs = builtins.readDir ./.; - removeFiles = lib.attrsets.filterAttrs (n: v: v == "directory") rootDirs; - programsToPatch = builtins.attrNames removeFiles; - - bigOverlay = final: prev: - lib.attrsets.mergeAttrsList ( - lib.lists.forEach programsToPatch ( - program: let - unpatchedProgram = prev."${program}"; - newPatches = lib.toast.patches.patchesInPath (lib.path.append ./. program); - in { - "${program}" = unpatchedProgram.overrideAttrs { - version = "${unpatchedProgram.version}-patched"; - __intentionallyOverridingVersion = true; - patches = unpatchedProgram.patches ++ newPatches; - }; - } - ) - ); -in { - nixpkgs.overlays = [ - ( - final: prev: { - kdePackages = prev.kdePackages.overrideScope bigOverlay; - } - ) - ]; -} diff --git a/roles/kde/patches/plasma-desktop/patches.txt b/roles/kde/patches/plasma-desktop/patches.txt deleted file mode 100644 index 84f4a14..0000000 --- a/roles/kde/patches/plasma-desktop/patches.txt +++ /dev/null @@ -1,6 +0,0 @@ -Plasma 6.6.0: - -Pr 3256 https://invent.kde.org/plasma/plasma-desktop/-/merge_requests/3256 -Pr 3259 https://invent.kde.org/plasma/plasma-desktop/-/merge_requests/3259 -Pr 3269 https://invent.kde.org/plasma/plasma-desktop/-/merge_requests/3269 -Pr 3356 https://invent.kde.org/plasma/plasma-desktop/-/merge_requests/3356 diff --git a/roles/kde/patches/plasma-desktop/pr3256.patch b/roles/kde/patches/plasma-desktop/pr3256.patch deleted file mode 100644 index 61d2a0e..0000000 --- a/roles/kde/patches/plasma-desktop/pr3256.patch +++ /dev/null @@ -1,94 +0,0 @@ -From 79d010f4bcdb19d5a19d222dd3c3cb581fde891d Mon Sep 17 00:00:00 2001 -From: David Redondo -Date: Fri, 17 Oct 2025 15:15:51 +0200 -Subject: [PATCH] applets/taskmanager: Allow changing a tasks volume by - scrolling - -FEATURE:510668 -FIXED-IN:6.6 ---- - .../package/contents/config/main.xml | 1 + - .../package/contents/ui/ConfigBehavior.qml | 3 ++- - .../package/contents/ui/MouseHandler.qml | 20 +++++++++++++++++++ - 3 files changed, 23 insertions(+), 1 deletion(-) - -diff --git a/applets/taskmanager/package/contents/config/main.xml b/applets/taskmanager/package/contents/config/main.xml -index f71a7a8457..8c3b68785b 100644 ---- a/applets/taskmanager/package/contents/config/main.xml -+++ b/applets/taskmanager/package/contents/config/main.xml -@@ -90,6 +90,7 @@ - - - -+ - - 0 - -diff --git a/applets/taskmanager/package/contents/ui/ConfigBehavior.qml b/applets/taskmanager/package/contents/ui/ConfigBehavior.qml -index 2c0dc19154..73357e9dcd 100644 ---- a/applets/taskmanager/package/contents/ui/ConfigBehavior.qml -+++ b/applets/taskmanager/package/contents/ui/ConfigBehavior.qml -@@ -202,13 +202,14 @@ KCMUtils.SimpleKCM { - - QQC2.ComboBox { - id: wheelEnabled -- Kirigami.FormData.label: i18nc("@label:listbox Part of a sentence: 'Scrolling behavior does nothing/cycles through tasks/cycles through the selected task's windows'", "Scrolling behavior:") -+ Kirigami.FormData.label: i18nc("@label:listbox Part of a sentence: 'Scrolling behavior does nothing/cycles through tasks/cycles through the selected task's windows/adjusts the hovered task’s volume''", "Scrolling behavior:") - Layout.fillWidth: true - Layout.minimumWidth: Kirigami.Units.gridUnit * 14 - model: [ - i18nc("@item:inlistbox Part of a sentence: 'Scrolling behavior does nothing'", "Does nothing"), - i18nc("@item:inlistbox Part of a sentence: 'Scrolling behavior cycles through all tasks'", "Cycles through all tasks"), - i18nc("@item:inlistbox Part of a sentence: 'Scrolling behavior cycles through the hovered task's windows'", "Cycles through the hovered task’s windows"), -+ i18nc("@item:inlistbox Part of a sentence: 'Scrolling behavior adjusts the hovered task’s volume'", "Adjusts the hovered task’s volume"), - ] - } - -diff --git a/applets/taskmanager/package/contents/ui/MouseHandler.qml b/applets/taskmanager/package/contents/ui/MouseHandler.qml -index cf2362835a..3f8d703d73 100644 ---- a/applets/taskmanager/package/contents/ui/MouseHandler.qml -+++ b/applets/taskmanager/package/contents/ui/MouseHandler.qml -@@ -8,6 +8,7 @@ import QtQuick - - import org.kde.taskmanager as TaskManager - import org.kde.plasma.plasmoid -+import org.kde.plasma.private.volume as PlasmaPa - - import "code/tools.js" as TaskTools - -@@ -157,6 +158,10 @@ DropArea { - } - } - -+ PlasmaPa.GlobalConfig { -+ id: plasmaPaConfig -+ } -+ - WheelHandler { - id: wheelHandler - -@@ -179,6 +184,21 @@ DropArea { - increment--; - } - const anchor = dropArea.target.childAt(event.x, event.y); -+ if (Plasmoid.configuration.wheelEnabled === 3) { -+ const loudest = anchor?.audioStreams?.reduce((loudest, stream) => Math.max(loudest, stream.volume), 0) -+ const step = (pulseAudio.item.normalVolume - pulseAudio.item.minimalVolume) * plasmaPaConfig.volumeStep / 100; -+ anchor?.audioStreams?.forEach((stream) => { -+ let delta = step * increment; -+ if (loudest > 0) { -+ delta *= stream.volume / loudest; -+ } -+ const volume = stream.volume + delta; -+ console.log(volume, Math.max(pulseAudio.item.minimalVolume, Math.min(volume, pulseAudio.item.normalVolume))); -+ stream.model.Volume = Math.max(pulseAudio.item.minimalVolume, Math.min(volume, pulseAudio.item.normalVolume)); -+ stream.model.Muted = volume === 0 -+ }) -+ return; -+ } - while (increment !== 0) { - TaskTools.activateNextPrevTask(anchor, increment < 0, Plasmoid.configuration.wheelSkipMinimized, Plasmoid.configuration.wheelEnabled, tasks); - increment += (increment < 0) ? 1 : -1; --- -GitLab - diff --git a/roles/kde/patches/plasma-desktop/pr3259.patch b/roles/kde/patches/plasma-desktop/pr3259.patch deleted file mode 100644 index 8104e45..0000000 --- a/roles/kde/patches/plasma-desktop/pr3259.patch +++ /dev/null @@ -1,78 +0,0 @@ -From 57885ba4ec524bdc1c1326228f27c1c3a3561bba Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Tue, 21 Oct 2025 13:39:40 -0600 -Subject: [PATCH] applets/kickoff: add spacing between non-switch-on-hover - category items - -Otherwise, their highlight effects touch, and it looks bad. - -To avoid blowing up the layout as a result of this change, slightly -decrease the height of these category list items too, which also reduces -some code complexity. - -BUG: 508985 -FIXED-IN: 6.6.0 ---- - applets/kickoff/ApplicationsPage.qml | 1 + - applets/kickoff/KickoffListDelegate.qml | 9 --------- - applets/kickoff/KickoffListView.qml | 5 +++++ - 3 files changed, 6 insertions(+), 9 deletions(-) - -diff --git a/applets/kickoff/ApplicationsPage.qml b/applets/kickoff/ApplicationsPage.qml -index c2baa75b52..fe8d6eaafb 100644 ---- a/applets/kickoff/ApplicationsPage.qml -+++ b/applets/kickoff/ApplicationsPage.qml -@@ -20,6 +20,7 @@ BasePage { - id: sideBar - focus: true // needed for Loaders - model: kickoff.rootModel -+ showingCategories: true - // needed otherwise app displayed at top-level will show a first character as group. - section.property: "" - delegate: KickoffListDelegate { -diff --git a/applets/kickoff/KickoffListDelegate.qml b/applets/kickoff/KickoffListDelegate.qml -index b1f8afb3ce..02bfcfcacd 100644 ---- a/applets/kickoff/KickoffListDelegate.qml -+++ b/applets/kickoff/KickoffListDelegate.qml -@@ -72,15 +72,6 @@ AbstractKickoffItemDelegate { - id: label - Layout.fillWidth: !descriptionLabel.visible - Layout.maximumWidth: root.width - root.leftPadding - root.rightPadding - icon.width - row.spacing -- Layout.preferredHeight: { -- if (root.isCategoryListItem) { -- return root.compact ? implicitHeight : Math.round(implicitHeight * 1.5); -- } -- if (!root.compact && !descriptionLabel.visible) { -- return implicitHeight + descriptionLabel.implicitHeight -- } -- return implicitHeight; -- } - text: root.text - textFormat: root.isMultilineText ? Text.StyledText : Text.PlainText - elide: Text.ElideRight -diff --git a/applets/kickoff/KickoffListView.qml b/applets/kickoff/KickoffListView.qml -index c7787493e0..382d146428 100644 ---- a/applets/kickoff/KickoffListView.qml -+++ b/applets/kickoff/KickoffListView.qml -@@ -33,6 +33,7 @@ EmptyPage { - property alias section: view.section - property alias highlight: view.highlight - property alias view: view -+ property bool showingCategories: false - - property bool mainContentView: false - property bool hasSectionView: false -@@ -144,6 +145,10 @@ EmptyPage { - width: view.availableWidth - } - -+ // Without switch-on-hover, it's possible for the selected category and the hovered category to be adjacent. -+ // When this happens, their highlights tuoch and look ungly without some artificial spacing added. -+ spacing: root.showingCategories && !Plasmoid.configuration.switchCategoryOnHover ? Kirigami.Units.smallSpacing : 0 -+ - section { - property: "group" - criteria: ViewSection.FullString --- -GitLab - diff --git a/roles/kde/patches/plasma-desktop/pr3269.patch b/roles/kde/patches/plasma-desktop/pr3269.patch deleted file mode 100644 index 3a53322..0000000 --- a/roles/kde/patches/plasma-desktop/pr3269.patch +++ /dev/null @@ -1,66 +0,0 @@ -From a63fce38f285b59407c24f44639023d41b5d3ca9 Mon Sep 17 00:00:00 2001 -From: Nate Graham -Date: Thu, 30 Oct 2025 20:01:32 -0600 -Subject: [PATCH] applets/kickoff: add separation between Places page list - items too - -Missed in 57885ba4ec524bdc1c1326228f27c1c3a3561bba - -BUG: 508985 -FIXED-IN: 6.6.0 ---- - applets/kickoff/ApplicationsPage.qml | 2 +- - applets/kickoff/KickoffListView.qml | 4 ++-- - applets/kickoff/PlacesPage.qml | 1 + - 3 files changed, 4 insertions(+), 3 deletions(-) - -diff --git a/applets/kickoff/ApplicationsPage.qml b/applets/kickoff/ApplicationsPage.qml -index fe8d6eaafb..206ddd958d 100644 ---- a/applets/kickoff/ApplicationsPage.qml -+++ b/applets/kickoff/ApplicationsPage.qml -@@ -20,7 +20,7 @@ BasePage { - id: sideBar - focus: true // needed for Loaders - model: kickoff.rootModel -- showingCategories: true -+ isSidebar: true - // needed otherwise app displayed at top-level will show a first character as group. - section.property: "" - delegate: KickoffListDelegate { -diff --git a/applets/kickoff/KickoffListView.qml b/applets/kickoff/KickoffListView.qml -index 382d146428..2d67bd59b7 100644 ---- a/applets/kickoff/KickoffListView.qml -+++ b/applets/kickoff/KickoffListView.qml -@@ -33,7 +33,7 @@ EmptyPage { - property alias section: view.section - property alias highlight: view.highlight - property alias view: view -- property bool showingCategories: false -+ property bool isSidebar: false - - property bool mainContentView: false - property bool hasSectionView: false -@@ -147,7 +147,7 @@ EmptyPage { - - // Without switch-on-hover, it's possible for the selected category and the hovered category to be adjacent. - // When this happens, their highlights tuoch and look ungly without some artificial spacing added. -- spacing: root.showingCategories && !Plasmoid.configuration.switchCategoryOnHover ? Kirigami.Units.smallSpacing : 0 -+ spacing: root.isSidebar && !Plasmoid.configuration.switchCategoryOnHover ? Kirigami.Units.smallSpacing : 0 - - section { - property: "group" -diff --git a/applets/kickoff/PlacesPage.qml b/applets/kickoff/PlacesPage.qml -index ff92f535e4..7f81096834 100644 ---- a/applets/kickoff/PlacesPage.qml -+++ b/applets/kickoff/PlacesPage.qml -@@ -19,6 +19,7 @@ BasePage { - id: sideBar - focus: true // needed for Loaders - model: placesCategoryModel -+ isSidebar: true - delegate: KickoffListDelegate { - url: "" - description: "" --- -GitLab - diff --git a/roles/kde/patches/plasma-desktop/pr3356.patch b/roles/kde/patches/plasma-desktop/pr3356.patch deleted file mode 100644 index 3d47aca..0000000 --- a/roles/kde/patches/plasma-desktop/pr3356.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 04c747a41d8c01248c7baeec5c852c2298f76fc3 Mon Sep 17 00:00:00 2001 -From: Christoph Wolk -Date: Mon, 1 Dec 2025 14:36:09 +0100 -Subject: [PATCH] applets/kickoff: also close when triggering from footer menu - -Kickoff already closes when one of the power/session buttons from the -menu is pressed, but the same does not happen if the same action is -shown in the overflow menu. - -Instead, also close kickoff in that case. - -CCBUG: 508725 ---- - applets/kickoff/LeaveButtons.qml | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/applets/kickoff/LeaveButtons.qml b/applets/kickoff/LeaveButtons.qml -index c9a14dc3254..acf83639341 100644 ---- a/applets/kickoff/LeaveButtons.qml -+++ b/applets/kickoff/LeaveButtons.qml -@@ -214,7 +214,12 @@ RowLayout { - - text: model.display - icon: model.decoration -- onClicked: filteredMenuItemsModel.trigger(index) -+ onClicked: { -+ filteredMenuItemsModel.trigger(index) -+ if (kickoff.hideOnWindowDeactivate) { -+ kickoff.expanded = false; -+ } -+ } - } - onObjectAdded: (index, object) => contextMenu.addMenuItem(object) - onObjectRemoved: (index, object) => contextMenu.removeMenuItem(object) --- -GitLab - diff --git a/roles/kde/patches/spectacle/patches.txt b/roles/kde/patches/spectacle/patches.txt deleted file mode 100644 index e11d502..0000000 --- a/roles/kde/patches/spectacle/patches.txt +++ /dev/null @@ -1,5 +0,0 @@ -Pr 462 https://invent.kde.org/plasma/spectacle/-/merge_requests/462 -Pr 487 https://invent.kde.org/plasma/spectacle/-/merge_requests/487 - -Plasma 6.6.0: -Pr 493 https://invent.kde.org/plasma/spectacle/-/merge_requests/493 diff --git a/roles/kde/patches/spectacle/pr462.patch b/roles/kde/patches/spectacle/pr462.patch deleted file mode 100644 index 3db47e0..0000000 --- a/roles/kde/patches/spectacle/pr462.patch +++ /dev/null @@ -1,2844 +0,0 @@ -From 9ab7593321d014ff63ef12590a0c2d0e721a90f1 Mon Sep 17 00:00:00 2001 -From: Jhair Paris -Date: Sat, 7 Jun 2025 19:56:28 -0500 -Subject: [PATCH 1/3] Add OCR dependencies and build configuration - -- Add Tesseract and Leptonica dependencies -- Configure OCR support in CMake build system ---- - CMakeLists.txt | 7 +++++++ - src/CMakeLists.txt | 5 ++++- - 2 files changed, 11 insertions(+), 1 deletion(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index f62a38443..3038f472c 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -89,12 +89,19 @@ find_package(PlasmaWaylandProtocols REQUIRED) - find_package(LayerShellQt REQUIRED) - find_package(KPipeWire) - find_package(OpenCV 4.7 REQUIRED core imgproc) -+find_package(PkgConfig REQUIRED) -+pkg_check_modules(TESSERACT REQUIRED tesseract) - - set_package_properties(KPipeWire PROPERTIES DESCRIPTION - "Used to record pipewire streams into a file" - TYPE REQUIRED - ) - -+set_package_properties(TESSERACT PROPERTIES DESCRIPTION -+ "OCR (Optical Character Recognition) engine for text recognition in images" -+ TYPE REQUIRED -+) -+ - # optional components - find_package(KF6DocTools ${KF6_MIN_VERSION}) - set_package_properties(KF6DocTools PROPERTIES DESCRIPTION -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index d27c2dba4..cb000b35d 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -25,6 +25,7 @@ target_sources(spectacle PRIVATE - CommandLineOptions.cpp - ExportManager.cpp - Geometry.cpp -+ OcrManager.cpp - Gui/Annotations/AnnotationDocument.cpp - Gui/Annotations/AnnotationTool.cpp - Gui/Annotations/AnnotationViewport.cpp -@@ -104,7 +105,7 @@ ki18n_wrap_ui(spectacle - # Needed to compile with OpenCV - target_compile_options (spectacle PRIVATE -fexceptions) - --target_include_directories(spectacle PUBLIC ${OpenCV_INCLUDE_DIRS}) -+target_include_directories(spectacle PUBLIC ${OpenCV_INCLUDE_DIRS} ${TESSERACT_INCLUDE_DIRS}) - - target_link_libraries(spectacle PRIVATE - Qt::Concurrent -@@ -135,6 +136,7 @@ target_link_libraries(spectacle PRIVATE - Wayland::Client - LayerShellQt::Interface - ${OpenCV_LIBRARIES} -+ ${TESSERACT_LIBRARIES} - ) - - # qt_add_qml_module doesn't know how to deal with headers in subdirectories so -@@ -180,6 +182,7 @@ qt_target_qml_sources(spectacle - Gui/InlineMessageList.qml - Gui/Magnifier.qml - Gui/NewScreenshotToolButton.qml -+ Gui/OcrAction.qml - Gui/OptionsMenuButton.qml - Gui/Outline.qml - Gui/QmlUtils.qml --- -GitLab - - -From ae7a749c89892c8f0d5494c2d7157970578b8b3f Mon Sep 17 00:00:00 2001 -From: Jhair Paris -Date: Sat, 7 Jun 2025 19:57:10 -0500 -Subject: [PATCH 2/3] Implement OcrManager class for text recognition - -- Add OcrManager class with Tesseract integration -- Provide async OCR processing methods -- Handle OCR initialization and cleanup - -Add OCR language selection to General Options - -- Introduced a new combo box for selecting the OCR language in the settings dialog. -- Implemented methods to populate and refresh the OCR language options based on availability. - -Add OCR action and integrate into UI toolbars - -- Create OcrAction.qml for text recognition functionality -- Add OCR buttons to CaptureOverlay toolbars -- Add OCR button to ViewerPage main toolbar - -Integrate OCR functionality into capture and viewer windows - -Add OCR notifications and core integration - -- Add OCR success/error notification events to notifyrc -- Integrate OCR manager in SpectacleCore - -Remove manual translations - -Implement OCR availability checks - -Enhance OcrManager to load Tesseract library dynamically and check its availability. - -Show info cursor on OCR tooltip icon in settings - -Refactor OCR language name handling using QLocale - -- Replace hardcoded/translatable language name map with dynamic lookup via QLocale and scriptToString. - -Refactor Tesseract initialization to support dynamic language detection - -Detect Tesseract and language packs at configure time; link directly to libtesseract - -- Add tesseract_test.cpp using TessBaseAPI::GetAvailableLanguagesAsVector() -- CMake: pkg_check_modules(TESSERACT) + try_run() to check usable langpacks - - Define HAVE_TESSERACT_OCR when successful; otherwise warn and disable OCR -- OCR: refactor OcrManager to use tesseract::TessBaseAPI (C++ API) - - Remove QLibrary-based dynamic loading and manual symbol resolution - - Wrap OCR code with #ifdef HAVE_TESSERACT_OCR and provide graceful fallbacks - -Refactor OCR text recognition to use ResultIterator for improved accuracy - -Refactor the OCR core: centralize extraction in SpectacleCore, remove direct OCR handling from windows. ---- - CMakeLists.txt | 48 +- - cmake/tesseract_test.cpp | 40 + - desktop/spectacle.notifyrc | 5 + - src/CMakeLists.txt | 9 +- - src/Config.h.in | 3 + - src/Gui/CaptureOverlay.qml | 9 + - src/Gui/CaptureWindow.cpp | 4 +- - src/Gui/OcrAction.qml | 14 + - src/Gui/SettingsDialog/GeneralOptions.ui | 127 ++++ - src/Gui/SettingsDialog/GeneralOptionsPage.cpp | 80 ++ - src/Gui/SettingsDialog/GeneralOptionsPage.h | 4 + - src/Gui/SettingsDialog/SettingsDialog.cpp | 5 + - src/Gui/SettingsDialog/spectacle.kcfg | 4 + - src/Gui/ViewerPage.qml | 5 + - src/Gui/ViewerWindow.cpp | 2 +- - src/OcrManager.cpp | 716 ++++++++++++++++++ - src/OcrManager.h | 175 +++++ - src/SpectacleCore.cpp | 143 ++++ - src/SpectacleCore.h | 9 + - 19 files changed, 1393 insertions(+), 9 deletions(-) - create mode 100644 cmake/tesseract_test.cpp - create mode 100644 src/Gui/OcrAction.qml - create mode 100644 src/OcrManager.cpp - create mode 100644 src/OcrManager.h - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index 3038f472c..9b3c47fbe 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -92,16 +92,54 @@ find_package(OpenCV 4.7 REQUIRED core imgproc) - find_package(PkgConfig REQUIRED) - pkg_check_modules(TESSERACT REQUIRED tesseract) - -+# Find Tesseract for OCR functionality -+find_package(PkgConfig QUIET) -+if(PkgConfig_FOUND) -+ pkg_check_modules(TESSERACT tesseract) -+ -+ if(TESSERACT_FOUND) -+ # Test if Tesseract has usable language packs -+ try_run( -+ TESSERACT_TEST_RUN_RESULT -+ TESSERACT_TEST_COMPILE_RESULT -+ ${CMAKE_CURRENT_BINARY_DIR} -+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/tesseract_test.cpp -+ LINK_LIBRARIES ${TESSERACT_LIBRARIES} -+ CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${TESSERACT_INCLUDE_DIRS}" -+ COMPILE_OUTPUT_VARIABLE TESSERACT_COMPILE_OUTPUT -+ RUN_OUTPUT_VARIABLE TESSERACT_RUN_OUTPUT -+ ) -+ -+ if(TESSERACT_TEST_COMPILE_RESULT AND TESSERACT_TEST_RUN_RESULT EQUAL 0) -+ message(STATUS "Tesseract OCR support enabled") -+ message(STATUS "${TESSERACT_RUN_OUTPUT}") -+ set(HAVE_TESSERACT_OCR TRUE) -+ else() -+ message(WARNING "Tesseract library found but no usable language packs detected") -+ message(WARNING "${TESSERACT_RUN_OUTPUT}") -+ message(WARNING "OCR functionality will be disabled. Install language data packages (e.g., tesseract-ocr-eng)") -+ set(HAVE_TESSERACT_OCR FALSE) -+ endif() -+ else() -+ message(STATUS "Tesseract not found - OCR functionality disabled") -+ set(HAVE_TESSERACT_OCR FALSE) -+ endif() -+ -+ set_package_properties(TESSERACT PROPERTIES -+ DESCRIPTION "OCR engine for text recognition in screenshots" -+ TYPE OPTIONAL -+ PURPOSE "Enables optical character recognition functionality" -+ ) -+else() -+ message(STATUS "PkgConfig not found - Tesseract detection disabled") -+ set(HAVE_TESSERACT_OCR FALSE) -+endif() -+ - set_package_properties(KPipeWire PROPERTIES DESCRIPTION - "Used to record pipewire streams into a file" - TYPE REQUIRED - ) - --set_package_properties(TESSERACT PROPERTIES DESCRIPTION -- "OCR (Optical Character Recognition) engine for text recognition in images" -- TYPE REQUIRED --) -- - # optional components - find_package(KF6DocTools ${KF6_MIN_VERSION}) - set_package_properties(KF6DocTools PROPERTIES DESCRIPTION -diff --git a/cmake/tesseract_test.cpp b/cmake/tesseract_test.cpp -new file mode 100644 -index 000000000..4ebae9779 ---- /dev/null -+++ b/cmake/tesseract_test.cpp -@@ -0,0 +1,40 @@ -+#include -+#include -+#include -+#include -+ -+int main() -+{ -+ tesseract::TessBaseAPI api; -+ -+ if (api.Init(nullptr, nullptr) != 0) { -+ std::cerr << "Failed to initialize Tesseract" << std::endl; -+ return 1; -+ } -+ -+ std::vector languages; -+ api.GetAvailableLanguagesAsVector(&languages); -+ -+ // Filter out 'osd' as it's not a usable language for OCR -+ std::vector usableLanguages; -+ for (const auto &lang : languages) { -+ if (lang != "osd") { -+ usableLanguages.push_back(lang); -+ } -+ } -+ -+ if (usableLanguages.empty()) { -+ std::cerr << "No usable Tesseract language packs found. Install language data files (e.g., tesseract-ocr-eng)" << std::endl; -+ return 1; -+ } -+ -+ std::cout << "Found " << usableLanguages.size() << " Tesseract language pack(s): "; -+ for (size_t i = 0; i < usableLanguages.size(); ++i) { -+ std::cout << usableLanguages[i]; -+ if (i < usableLanguages.size() - 1) -+ std::cout << ", "; -+ } -+ std::cout << std::endl; -+ -+ return 0; -+} -diff --git a/desktop/spectacle.notifyrc b/desktop/spectacle.notifyrc -index 5c4166f0b..f3f65f679 100644 ---- a/desktop/spectacle.notifyrc -+++ b/desktop/spectacle.notifyrc -@@ -306,3 +306,8 @@ Comment[uk]=Було створено і збережено новий запи - Comment[zh_CN]=已录制并保存新的屏幕录像 - Comment[zh_TW]=新的螢幕錄製已擷取並儲存 - Action=Popup -+ -+[Event/ocrTextExtracted] -+Name=Text Extracted -+Comment=Text has been extracted from image using OCR -+Action=Popup -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index cb000b35d..c57535e34 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -105,7 +105,7 @@ ki18n_wrap_ui(spectacle - # Needed to compile with OpenCV - target_compile_options (spectacle PRIVATE -fexceptions) - --target_include_directories(spectacle PUBLIC ${OpenCV_INCLUDE_DIRS} ${TESSERACT_INCLUDE_DIRS}) -+target_include_directories(spectacle PUBLIC ${OpenCV_INCLUDE_DIRS}) - - target_link_libraries(spectacle PRIVATE - Qt::Concurrent -@@ -136,9 +136,14 @@ target_link_libraries(spectacle PRIVATE - Wayland::Client - LayerShellQt::Interface - ${OpenCV_LIBRARIES} -- ${TESSERACT_LIBRARIES} - ) - -+# Link against Tesseract when OCR support is enabled -+if(HAVE_TESSERACT_OCR) -+ target_include_directories(spectacle PRIVATE ${TESSERACT_INCLUDE_DIRS}) -+ target_link_libraries(spectacle PRIVATE ${TESSERACT_LIBRARIES}) -+endif() -+ - # qt_add_qml_module doesn't know how to deal with headers in subdirectories so - # make sure to add those so the headers can be found. - target_include_directories(spectacle PRIVATE -diff --git a/src/Config.h.in b/src/Config.h.in -index 15313542a..aadb22252 100644 ---- a/src/Config.h.in -+++ b/src/Config.h.in -@@ -7,6 +7,9 @@ - /* Define to 1 if we have Purpose */ - #cmakedefine PURPOSE_FOUND 1 - -+/* Define to 1 if we have Tesseract OCR */ -+#cmakedefine HAVE_TESSERACT_OCR 1 -+ - /* Set the Spectacle version from CMake */ - #cmakedefine SPECTACLE_VERSION "@SPECTACLE_VERSION@" - -diff --git a/src/Gui/CaptureOverlay.qml b/src/Gui/CaptureOverlay.qml -index d9ca9a11c..37f3dcf85 100644 ---- a/src/Gui/CaptureOverlay.qml -+++ b/src/Gui/CaptureOverlay.qml -@@ -506,6 +506,11 @@ MouseArea { - visible: action.enabled - action: CopyImageAction {} - } -+ ToolButton { -+ display: TtToolButton.IconOnly -+ visible: action.enabled && !SpectacleCore.videoMode && SpectacleCore.ocrAvailable -+ action: OcrAction {} -+ } - ExportMenuButton { - focusPolicy: Qt.NoFocus - } -@@ -532,6 +537,10 @@ MouseArea { - visible: action.enabled - action: CopyImageAction {} - } -+ ToolButton { -+ visible: action.enabled && !SpectacleCore.videoMode && SpectacleCore.ocrAvailable -+ action: OcrAction {} -+ } - ExportMenuButton { - focusPolicy: Qt.NoFocus - } -diff --git a/src/Gui/CaptureWindow.cpp b/src/Gui/CaptureWindow.cpp -index fc4509cf3..cb8ce97ab 100644 ---- a/src/Gui/CaptureWindow.cpp -+++ b/src/Gui/CaptureWindow.cpp -@@ -8,11 +8,13 @@ - #include "CaptureWindow.h" - - #include "Config.h" --#include "SpectacleCore.h" - #include "Gui/SelectionEditor.h" -+#include "SpectacleCore.h" - - #include - #include -+#include -+#include - - using namespace Qt::StringLiterals; - -diff --git a/src/Gui/OcrAction.qml b/src/Gui/OcrAction.qml -new file mode 100644 -index 000000000..f887ec0ee ---- /dev/null -+++ b/src/Gui/OcrAction.qml -@@ -0,0 +1,14 @@ -+/* SPDX-FileCopyrightText: 2025 Jhair Paris -+ * SPDX-License-Identifier: LGPL-2.0-or-later -+ */ -+ -+import QtQuick.Templates as T -+import org.kde.spectacle.private -+ -+T.Action { -+ // OCR is only available for screenshots, not videos, and only when OCR is properly available -+ enabled: !SpectacleCore.videoMode && SpectacleCore.ocrAvailable -+ icon.name: "document-scan" -+ text: i18nc("@action", "Extract Text") -+ onTriggered: contextWindow.extractText() -+} -diff --git a/src/Gui/SettingsDialog/GeneralOptions.ui b/src/Gui/SettingsDialog/GeneralOptions.ui -index 1d99e9a33..ddbbf3e5a 100644 ---- a/src/Gui/SettingsDialog/GeneralOptions.ui -+++ b/src/Gui/SettingsDialog/GeneralOptions.ui -@@ -239,6 +239,132 @@ - - - -+ -+ -+ -+ Qt::Vertical -+ -+ -+ QSizePolicy::Fixed -+ -+ -+ -+ 10 -+ 10 -+ -+ -+ -+ -+ -+ -+ -+ Text Recognition (OCR) -+ -+ -+ -+ -+ -+ -+ Language: -+ -+ -+ -+ -+ -+ -+ -+ 0 -+ 0 -+ -+ -+ -+ currentData -+ -+ -+ -+ -+ -+ -+ false -+ -+ -+ -+ 0 -+ 0 -+ -+ -+ -+ -+ 0 -+ -+ -+ 6 -+ -+ -+ -+ -+ OCR functionality is not available -+ -+ -+ -+ 0 -+ 0 -+ -+ -+ -+ false -+ -+ -+ -+ -+ -+ -+ Please install the required packages: -+• tesseract -+• tesseract language data (e.g., tesseract-ocr-eng for English) -+ -+ -+ -+ 0 -+ 0 -+ -+ -+ -+ Qt::AlignCenter -+ -+ -+ -+ 16 -+ 16 -+ -+ -+ -+ -+ 16 -+ 16 -+ -+ -+ -+ true -+ -+ -+ -+ -+ -+ -+ Qt::Horizontal -+ -+ -+ -+ 40 -+ 20 -+ -+ -+ -+ -+ -+ -+ - - - -@@ -257,6 +383,7 @@ - kcfg_useReleaseToCapture - kcfg_showCaptureInstructions - kcfg_rememberSelectionRect -+ kcfg_ocrLanguage - - - -diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -index fcea6f671..5b8a5d9fc 100644 ---- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -+++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -@@ -1,4 +1,5 @@ - /* -+ * SPDX-FileCopyrightText: 2025 Jhair Paris - * SPDX-FileCopyrightText: 2019 David Redondo - * SPDX-FileCopyrightText: 2015 Boudhayan Gupta - * -@@ -9,10 +10,13 @@ - - #include "settings.h" - #include "ui_GeneralOptions.h" -+#include "OcrManager.h" - - #include -+#include - - #include -+#include - - GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - : QWidget(parent) -@@ -20,8 +24,16 @@ GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - { - m_ui->setupUi(this); - -+ m_ui->ocrInfoIcon->setPixmap(QIcon::fromTheme(QStringLiteral("help-hint")).pixmap(16, 16)); -+ m_ui->ocrInfoIcon->setCursor(Qt::WhatsThisCursor); -+ - m_ui->runningTitle->setLevel(2); - m_ui->regionTitle->setLevel(2); -+ m_ui->ocrTitle->setLevel(2); -+ -+ setupOcrLanguageComboBox(); -+ -+ connect(OcrManager::instance(), &OcrManager::statusChanged, this, &GeneralOptionsPage::refreshOcrLanguageSettings); - - //On Wayland we can't programmatically raise and focus the window so we have to hide the option - if (KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE").constData(), "wayland") == 0) { -@@ -31,4 +43,72 @@ GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - - GeneralOptionsPage::~GeneralOptionsPage() = default; - -+void GeneralOptionsPage::setupOcrLanguageComboBox() -+{ -+ OcrManager *ocrManager = OcrManager::instance(); -+ -+ if (!ocrManager->isAvailable()) { -+ m_ui->kcfg_ocrLanguage->setEnabled(false); -+ m_ui->kcfg_ocrLanguage->addItem(i18n("OCR not available")); -+ m_ui->ocrLanguageLabel->setVisible(false); -+ m_ui->kcfg_ocrLanguage->setVisible(false); -+ m_ui->ocrUnavailableWidget->setVisible(true); -+ return; -+ } -+ -+ const auto availableLanguages = ocrManager->availableLanguagesWithNames(); -+ -+ if (availableLanguages.isEmpty()) { -+ m_ui->kcfg_ocrLanguage->addItem(i18n("No languages found")); -+ m_ui->kcfg_ocrLanguage->setEnabled(false); -+ return; -+ } -+ -+ m_ui->kcfg_ocrLanguage->clear(); -+ m_ui->ocrLanguageLabel->setVisible(true); -+ m_ui->kcfg_ocrLanguage->setVisible(true); -+ m_ui->ocrUnavailableWidget->setVisible(false); -+ -+ for (auto it = availableLanguages.constBegin(); it != availableLanguages.constEnd(); ++it) { -+ m_ui->kcfg_ocrLanguage->addItem(it.value(), it.key()); -+ } -+} -+ -+void GeneralOptionsPage::refreshOcrLanguageSettings() -+{ -+ OcrManager *ocrManager = OcrManager::instance(); -+ -+ if (!ocrManager->isAvailable()) { -+ m_ui->ocrLanguageLabel->setVisible(false); -+ m_ui->kcfg_ocrLanguage->setVisible(false); -+ m_ui->ocrUnavailableWidget->setVisible(true); -+ return; -+ } -+ -+ const auto availableLanguages = ocrManager->availableLanguagesWithNames(); -+ -+ if (availableLanguages.isEmpty()) { -+ return; -+ } -+ -+ m_ui->kcfg_ocrLanguage->clear(); -+ m_ui->kcfg_ocrLanguage->setEnabled(true); -+ m_ui->ocrLanguageLabel->setVisible(true); -+ m_ui->kcfg_ocrLanguage->setVisible(true); -+ m_ui->ocrUnavailableWidget->setVisible(false); -+ -+ for (auto it = availableLanguages.constBegin(); it != availableLanguages.constEnd(); ++it) { -+ m_ui->kcfg_ocrLanguage->addItem(it.value(), it.key()); -+ } -+ -+ const QString currentLanguage = Settings::ocrLanguage(); -+ -+ for (int i = 0; i < m_ui->kcfg_ocrLanguage->count(); ++i) { -+ if (m_ui->kcfg_ocrLanguage->itemData(i).toString() == currentLanguage) { -+ m_ui->kcfg_ocrLanguage->setCurrentIndex(i); -+ break; -+ } -+ } -+} -+ - #include "moc_GeneralOptionsPage.cpp" -diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.h b/src/Gui/SettingsDialog/GeneralOptionsPage.h -index d8e7c5003..c184d6ba8 100644 ---- a/src/Gui/SettingsDialog/GeneralOptionsPage.h -+++ b/src/Gui/SettingsDialog/GeneralOptionsPage.h -@@ -19,8 +19,12 @@ class GeneralOptionsPage : public QWidget - public: - explicit GeneralOptionsPage(QWidget *parent = nullptr); - ~GeneralOptionsPage() override; -+ -+ void refreshOcrLanguageSettings(); - - private: -+ void setupOcrLanguageComboBox(); -+ - QScopedPointer m_ui; - }; - -diff --git a/src/Gui/SettingsDialog/SettingsDialog.cpp b/src/Gui/SettingsDialog/SettingsDialog.cpp -index a37d8344c..a19a47627 100644 ---- a/src/Gui/SettingsDialog/SettingsDialog.cpp -+++ b/src/Gui/SettingsDialog/SettingsDialog.cpp -@@ -64,6 +64,9 @@ void SettingsDialog::showEvent(QShowEvent *event) - auto parent = parentWidget(); - bool onTop = parent && parent->windowHandle()->flags().testFlag(Qt::WindowStaysOnTopHint); - windowHandle()->setFlag(Qt::WindowStaysOnTopHint, onTop); -+ -+ m_generalPage->refreshOcrLanguageSettings(); -+ - KConfigDialog::showEvent(event); - } - -@@ -87,6 +90,8 @@ void SettingsDialog::updateWidgets() - { - KConfigDialog::updateWidgets(); - m_shortcutsPage->resetChanges(); -+ -+ m_generalPage->refreshOcrLanguageSettings(); - } - - void SettingsDialog::updateWidgetsDefault() -diff --git a/src/Gui/SettingsDialog/spectacle.kcfg b/src/Gui/SettingsDialog/spectacle.kcfg -index e37b9e5b4..4517e2344 100644 ---- a/src/Gui/SettingsDialog/spectacle.kcfg -+++ b/src/Gui/SettingsDialog/spectacle.kcfg -@@ -70,6 +70,10 @@ - - UntilClosed - -+ -+ -+ eng -+ - - - -diff --git a/src/Gui/ViewerPage.qml b/src/Gui/ViewerPage.qml -index 6e77887a8..602e4431b 100644 ---- a/src/Gui/ViewerPage.qml -+++ b/src/Gui/ViewerPage.qml -@@ -61,6 +61,11 @@ EmptyPage { - visible: action.enabled - action: CopyImageAction {} - } -+ TtToolButton { -+ display: TtToolButton.IconOnly -+ visible: action.enabled && SpectacleCore.ocrAvailable -+ action: OcrAction {} -+ } - // We only show this in video mode to save space in screenshot mode - TtToolButton { - visible: SpectacleCore.videoMode -diff --git a/src/Gui/ViewerWindow.cpp b/src/Gui/ViewerWindow.cpp -index 68812495d..8c0d9941f 100644 ---- a/src/Gui/ViewerWindow.cpp -+++ b/src/Gui/ViewerWindow.cpp -@@ -8,9 +8,9 @@ - #include "ViewerWindow.h" - - #include "Config.h" --#include "SpectacleCore.h" - #include "Gui/ExportMenu.h" - #include "InlineMessageModel.h" -+#include "SpectacleCore.h" - - #include - #include -diff --git a/src/OcrManager.cpp b/src/OcrManager.cpp -new file mode 100644 -index 000000000..1d09db8ef ---- /dev/null -+++ b/src/OcrManager.cpp -@@ -0,0 +1,716 @@ -+/* This file is part of Spectacle, the KDE screenshot utility -+ * SPDX-FileCopyrightText: 2025 Jhair Paris -+ * SPDX-License-Identifier: LGPL-2.0-or-later -+ */ -+ -+#include "OcrManager.h" -+#include "settings.h" -+#include "spectacle_debug.h" -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#include -+ -+using namespace Qt::StringLiterals; -+ -+OcrManager *OcrManager::s_instance = nullptr; -+ -+OcrManager::OcrManager(QObject *parent) -+ : QObject(parent) -+#ifdef HAVE_TESSERACT_OCR -+ , m_tesseract(nullptr) -+ , m_worker(nullptr) -+#endif -+ , m_workerThread(std::make_unique()) -+ , m_timeoutTimer(new QTimer(this)) -+ , m_status(OcrStatus::Ready) -+ , m_currentLanguageCode() // Current language code ("eng+spa") -+ , m_configuredLanguages() // Languages from Settings (persistent) -+ , m_activeLanguages() -+ , m_shouldRestoreToConfigured(false) // Flag to restore after temp language use -+ , m_initialized(false) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ m_timeoutTimer->setSingleShot(true); -+ m_timeoutTimer->setInterval(30000); -+ -+ connect(m_timeoutTimer, &QTimer::timeout, this, [this]() { -+ qCWarning(SPECTACLE_LOG) << "OCR recognition timed out"; -+ setStatus(OcrStatus::Error); -+ }); -+ -+ m_worker = new OcrWorker(); -+ m_worker->moveToThread(m_workerThread.get()); -+ connect(m_worker, &OcrWorker::imageProcessed, this, &OcrManager::handleRecognitionComplete); -+ m_workerThread->start(); -+ -+ connect(Settings::self(), &Settings::ocrLanguagesChanged, this, [this]() { -+ const QStringList newLanguages = Settings::ocrLanguages(); -+ const QString combinedLanguages = newLanguages.join(u"+"_s); -+ if (combinedLanguages != m_currentLanguageCode) { -+ setLanguagesByCode(newLanguages); -+ } -+ }); -+ -+ QTimer::singleShot(0, this, &OcrManager::initializeTesseract); -+#endif -+} -+ -+OcrManager::~OcrManager() -+{ -+#ifdef HAVE_TESSERACT_OCR -+ if (m_worker) { -+ if (m_workerThread && m_workerThread->isRunning()) { -+ QMetaObject::invokeMethod(m_worker, &QObject::deleteLater, Qt::QueuedConnection); -+ } else { -+ delete m_worker; -+ } -+ m_worker = nullptr; -+ } -+#endif -+ if (m_workerThread && m_workerThread->isRunning()) { -+ m_workerThread->quit(); -+ m_workerThread->wait(3000); -+ } -+#ifdef HAVE_TESSERACT_OCR -+ if (m_tesseract) { -+ m_tesseract->End(); -+ delete m_tesseract; -+ m_tesseract = nullptr; -+ } -+#endif -+} -+ -+OcrManager *OcrManager::instance() -+{ -+ if (!s_instance) { -+ s_instance = new OcrManager(qApp); -+ } -+ return s_instance; -+} -+ -+bool OcrManager::isAvailable() const -+{ -+#ifdef HAVE_TESSERACT_OCR -+ return m_initialized && m_tesseract != nullptr; -+#else -+ return false; -+#endif -+} -+ -+OcrManager::OcrStatus OcrManager::status() const -+{ -+ return m_status; -+} -+ -+QMap OcrManager::availableLanguagesWithNames() const -+{ -+ QMap result; -+ for (const QString &langCode : m_availableLanguages) { -+ result[langCode] = m_languageNames.value(langCode, langCode); -+ } -+ return result; -+} -+ -+void OcrManager::setLanguagesByCode(const QStringList &languageCodes) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ if (languageCodes.isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "No OCR languages specified"; -+ return; -+ } -+ -+ if (validateAndApplyLanguages(languageCodes)) { -+ m_configuredLanguages = m_activeLanguages; -+ Settings::setOcrLanguages(m_activeLanguages); -+ Settings::self()->save(); -+ qCDebug(SPECTACLE_LOG) << "OCR languages successfully changed to:" << m_currentLanguageCode; -+ } else { -+ qCWarning(SPECTACLE_LOG) << "Failed to set OCR languages"; -+ } -+#else -+ Q_UNUSED(languageCodes); -+ qCWarning(SPECTACLE_LOG) << "OCR not available - Tesseract not compiled in"; -+#endif -+} -+ -+QString OcrManager::currentLanguageCode() const -+{ -+ return m_currentLanguageCode; -+} -+ -+void OcrManager::recognizeText(const QImage &image) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ if (!isAvailable()) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR: engine is not available"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ -+ if (m_status == OcrStatus::Processing) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR: text extraction already running"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ -+ if (image.isNull() || image.size().isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR: invalid image provided"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ -+ // Ensure configured languages are active -+ if (m_configuredLanguages.isEmpty() || m_activeLanguages != m_configuredLanguages) { -+ if (!validateAndApplyLanguages(m_configuredLanguages)) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR: failed to activate configured languages"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ } -+ -+ beginRecognition(image); -+#else -+ Q_UNUSED(image); -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR: Spectacle built without Tesseract support"; -+ Q_EMIT textRecognized(QString(), false); -+#endif -+} -+ -+void OcrManager::recognizeTextWithLanguage(const QImage &image, const QString &languageCode) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ if (languageCode.isEmpty()) { -+ recognizeText(image); -+ return; -+ } -+ -+ if (!isAvailable()) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR with language" << languageCode << ": engine is not available"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ -+ if (m_status == OcrStatus::Processing) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR with language" << languageCode << ": text extraction already running"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ -+ if (image.isNull() || image.size().isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR with language" << languageCode << ": invalid image provided"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ -+ const QStringList tempLanguages{languageCode}; -+ if (!validateAndApplyLanguages(tempLanguages)) { -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR with language" << languageCode << ": failed to activate language"; -+ Q_EMIT textRecognized(QString(), false); -+ return; -+ } -+ -+ // Store that we need to restore after recognition -+ m_shouldRestoreToConfigured = (m_activeLanguages != m_configuredLanguages); -+ -+ beginRecognition(image); -+#else -+ Q_UNUSED(image); -+ Q_UNUSED(languageCode); -+ qCWarning(SPECTACLE_LOG) << "Cannot start OCR: Spectacle built without Tesseract support"; -+ Q_EMIT textRecognized(QString(), false); -+#endif -+} -+ -+void OcrManager::handleRecognitionComplete(const QString &text, bool success) -+{ -+ m_timeoutTimer->stop(); -+ -+ if (success) { -+ setStatus(OcrStatus::Ready); -+ -+ if (!text.isEmpty()) { -+ QApplication::clipboard()->setText(text); -+ } -+ -+ Q_EMIT textRecognized(text, true); -+ qCDebug(SPECTACLE_LOG) << "OCR recognition completed successfully"; -+ } else { -+ setStatus(OcrStatus::Error); -+ Q_EMIT textRecognized(QString(), false); -+ qCWarning(SPECTACLE_LOG) << "OCR recognition failed"; -+ } -+ -+ // Restore configured languages if we used temporary ones -+ if (m_shouldRestoreToConfigured && !m_configuredLanguages.isEmpty()) { -+ validateAndApplyLanguages(m_configuredLanguages); -+ m_shouldRestoreToConfigured = false; -+ } -+} -+ -+bool OcrManager::validateAndApplyLanguages(const QStringList &languageCodes) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ if (languageCodes.isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "No OCR languages provided"; -+ return false; -+ } -+ -+ QStringList validLanguages; -+ for (const QString &lang : languageCodes) { -+ if (lang == u"osd"_s) { -+ qCDebug(SPECTACLE_LOG) << "Skipping 'osd' language"; -+ continue; -+ } -+ -+ if (!isLanguageAvailable(lang)) { -+ qCWarning(SPECTACLE_LOG) << "OCR language not available:" << lang; -+ continue; -+ } -+ -+ if (!validLanguages.contains(lang)) { -+ validLanguages.append(lang); -+ } -+ } -+ -+ if (validLanguages.isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "No valid OCR languages after filtering"; -+ return false; -+ } -+ -+ if (validLanguages.size() > MAX_OCR_LANGUAGES) { -+ validLanguages = validLanguages.mid(0, MAX_OCR_LANGUAGES); -+ qCInfo(SPECTACLE_LOG) << "Limited to" << MAX_OCR_LANGUAGES << "languages:" << validLanguages; -+ } -+ -+ const QString combinedLanguages = validLanguages.join(u"+"_s); -+ -+ if (m_currentLanguageCode == combinedLanguages && !m_activeLanguages.isEmpty()) { -+ qCDebug(SPECTACLE_LOG) << "Languages already active, no change needed"; -+ return true; -+ } -+ -+ if (!setupTesseractLanguages(validLanguages)) { -+ qCWarning(SPECTACLE_LOG) << "Failed to apply OCR languages:" << combinedLanguages; -+ return false; -+ } -+ -+ m_activeLanguages = validLanguages; -+ m_currentLanguageCode = combinedLanguages; -+ -+ qCDebug(SPECTACLE_LOG) << "OCR languages applied:" << combinedLanguages; -+ return true; -+#else -+ Q_UNUSED(languageCodes); -+ return false; -+#endif -+} -+ -+void OcrManager::beginRecognition(const QImage &image) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ setStatus(OcrStatus::Processing); -+ m_timeoutTimer->start(); -+ -+ QMetaObject::invokeMethod( -+ m_worker, -+ [worker = m_worker, image, tesseract = m_tesseract]() { -+ worker->processImage(image, tesseract); -+ }, -+ Qt::QueuedConnection); -+#else -+ Q_UNUSED(image); -+#endif -+} -+ -+void OcrManager::initializeTesseract() -+{ -+#ifdef HAVE_TESSERACT_OCR -+ try { -+ m_tesseract = new tesseract::TessBaseAPI(); -+ -+ if (m_tesseract->Init(nullptr, nullptr) != 0) { -+ qCWarning(SPECTACLE_LOG) << "Failed to initialize Tesseract OCR engine with auto-detection"; -+ setStatus(OcrStatus::Error); -+ delete m_tesseract; -+ m_tesseract = nullptr; -+ return; -+ } -+ -+ const char *datapath = m_tesseract->GetDatapath(); -+ QString tessdataPath = datapath ? QString::fromUtf8(datapath) : QString(); -+ qCDebug(SPECTACLE_LOG) << "Using tessdata path: " << tessdataPath; -+ -+ setupAvailableLanguages(tessdataPath); -+ -+ if (m_availableLanguages.isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "No language data files found in tessdata directory"; -+ setStatus(OcrStatus::Error); -+ delete m_tesseract; -+ m_tesseract = nullptr; -+ return; -+ } -+ -+ m_tesseract->End(); -+ -+ QStringList configLanguages = Settings::ocrLanguages(); -+ QStringList initLanguages; -+ -+ // Use configured languages if valid, otherwise fallback to first available -+ for (const QString &lang : configLanguages) { -+ if (!lang.isEmpty() && m_availableLanguages.contains(lang) && lang != u"osd"_s) { -+ initLanguages.append(lang); -+ } -+ } -+ -+ if (initLanguages.isEmpty()) { -+ auto it = std::find_if(m_availableLanguages.begin(), m_availableLanguages.end(), [](const QString &lang) { -+ return lang != u"osd"_s; -+ }); -+ -+ if (it != m_availableLanguages.end()) { -+ initLanguages.append(*it); -+ } else { -+ qCCritical(SPECTACLE_LOG) << "No fallback language available (only osd present)"; -+ setStatus(OcrStatus::Error); -+ delete m_tesseract; -+ m_tesseract = nullptr; -+ return; -+ } -+ } -+ -+ const QString combinedInitLanguages = initLanguages.join(u"+"_s); -+ qCDebug(SPECTACLE_LOG) << "Initializing Tesseract with languages:" << combinedInitLanguages; -+ -+ if (m_tesseract->Init(nullptr, combinedInitLanguages.toUtf8().constData()) != 0) { -+ qCWarning(SPECTACLE_LOG) << "Failed to initialize Tesseract with languages:" << combinedInitLanguages; -+ setStatus(OcrStatus::Error); -+ delete m_tesseract; -+ m_tesseract = nullptr; -+ return; -+ } -+ -+ m_currentLanguageCode = combinedInitLanguages; -+ m_tesseract->SetPageSegMode(tesseract::PSM_AUTO); -+ -+ m_initialized = true; -+ setStatus(OcrStatus::Ready); -+ qCDebug(SPECTACLE_LOG) << "Tesseract OCR engine initialized successfully with languages:" << combinedInitLanguages; -+ -+ loadSavedLanguageSetting(); -+ } catch (const std::exception &e) { -+ qCWarning(SPECTACLE_LOG) << "Exception during Tesseract initialization:" << e.what(); -+ setStatus(OcrStatus::Error); -+ if (m_tesseract) { -+ delete m_tesseract; -+ m_tesseract = nullptr; -+ } -+ } -+#else -+ qCDebug(SPECTACLE_LOG) << "Tesseract OCR not available - compiled out"; -+ setStatus(OcrStatus::Error); -+#endif -+} -+ -+void OcrManager::loadSavedLanguageSetting() -+{ -+ if (!isAvailable()) { -+ qCDebug(SPECTACLE_LOG) << "OCR not available, skipping language loading"; -+ return; -+ } -+ -+ QStringList savedLanguages = Settings::ocrLanguages(); -+ qCDebug(SPECTACLE_LOG) << "Loaded OCR languages setting from config:" << savedLanguages; -+ qCDebug(SPECTACLE_LOG) << "Current OCR language code:" << m_currentLanguageCode; -+ qCDebug(SPECTACLE_LOG) << "Available languages:" << m_availableLanguages; -+ -+ QStringList validLanguages; -+ for (const QString &lang : savedLanguages) { -+ if (lang != u"osd"_s && isLanguageAvailable(lang)) { -+ validLanguages.append(lang); -+ } -+ } -+ -+ if (validLanguages.isEmpty()) { -+ // Find first valid language as fallback -+ auto it = std::find_if(m_availableLanguages.begin(), m_availableLanguages.end(), [](const QString &lang) { -+ return lang != u"osd"_s; -+ }); -+ if (it != m_availableLanguages.end()) { -+ validLanguages.append(*it); -+ } else { -+ qCWarning(SPECTACLE_LOG) << "No usable languages available (only osd present), cannot set default"; -+ return; -+ } -+ qCDebug(SPECTACLE_LOG) << "No valid saved languages, using default:" << validLanguages; -+ Settings::setOcrLanguages(validLanguages); -+ Settings::self()->save(); -+ } -+ -+ m_configuredLanguages = validLanguages; -+ -+ const QString combinedLanguages = validLanguages.join(u"+"_s); -+ if (combinedLanguages != m_currentLanguageCode) { -+ qCDebug(SPECTACLE_LOG) << "Loading OCR languages setting:" << validLanguages; -+ validateAndApplyLanguages(validLanguages); -+ } else { -+ qCDebug(SPECTACLE_LOG) << "OCR languages already set to:" << combinedLanguages; -+ m_activeLanguages = validLanguages; -+ } -+} -+ -+void OcrManager::setStatus(OcrStatus status) -+{ -+ if (m_status == status) { -+ return; -+ } -+ -+ m_status = status; -+ Q_EMIT statusChanged(status); -+} -+ -+bool OcrManager::isLanguageAvailable(const QString &languageCode) const -+{ -+ return m_availableLanguages.contains(languageCode); -+} -+ -+bool OcrManager::setupTesseractLanguages(const QStringList &langCodes) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ if (!m_tesseract || langCodes.isEmpty()) { -+ return false; -+ } -+ -+ const char *datapath = m_tesseract->GetDatapath(); -+ QString tessdataPath = datapath ? QString::fromUtf8(datapath) : QString(); -+ -+ if (tessdataPath.isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "Tessdata path not found"; -+ return false; -+ } -+ -+ for (const QString &langCode : langCodes) { -+ const QString langFile = QDir(tessdataPath).filePath(langCode + u".traineddata"_s); -+ if (!QFile::exists(langFile)) { -+ qCWarning(SPECTACLE_LOG) << "Language file not found:" << langFile; -+ return false; -+ } -+ } -+ -+ try { -+ m_tesseract->End(); -+ -+ const QString combinedLangs = langCodes.join(u"+"_s); -+ -+ if (m_tesseract->Init(nullptr, combinedLangs.toUtf8().constData()) != 0) { -+ qCWarning(SPECTACLE_LOG) << "Failed to initialize Tesseract with languages:" << combinedLangs; -+ -+ // Fallback to first available language -+ QString fallbackLang; -+ if (!m_availableLanguages.isEmpty()) { -+ auto it = std::find_if(m_availableLanguages.begin(), m_availableLanguages.end(), [](const QString &lang) { -+ return lang != u"osd"_s; -+ }); -+ if (it != m_availableLanguages.end()) { -+ fallbackLang = *it; -+ } -+ } -+ -+ if (!fallbackLang.isEmpty() && m_tesseract->Init(nullptr, fallbackLang.toUtf8().constData()) != 0) { -+ qCCritical(SPECTACLE_LOG) << "Failed to fallback to language:" << fallbackLang; -+ return false; -+ } -+ return false; -+ } -+ -+ m_tesseract->SetPageSegMode(tesseract::PSM_AUTO); -+ return true; -+ } catch (const std::exception &e) { -+ qCWarning(SPECTACLE_LOG) << "Exception while setting up Tesseract languages:" << e.what(); -+ return false; -+ } -+#else -+ Q_UNUSED(langCodes); -+ return false; -+#endif -+} -+ -+void OcrManager::setupAvailableLanguages(const QString &tessdataPath) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ m_availableLanguages.clear(); -+ m_languageNames.clear(); -+ -+ if (!m_tesseract) { -+ qCWarning(SPECTACLE_LOG) << "Cannot enumerate OCR languages: Tesseract not initialized"; -+ return; -+ } -+ -+ QStringList detectedLanguages; -+ -+ try { -+ std::vector available; -+ m_tesseract->GetAvailableLanguagesAsVector(&available); -+ detectedLanguages.reserve(static_cast(available.size())); -+ -+ for (const std::string &language : available) { -+ const QString langCode = QString::fromStdString(language); -+ if (langCode.isEmpty()) { -+ continue; -+ } -+ -+ if (!tessdataPath.isEmpty()) { -+ const QString trainedDataPath = QDir(tessdataPath).filePath(langCode + u".traineddata"_s); -+ if (!QFile::exists(trainedDataPath)) { -+ qCDebug(SPECTACLE_LOG) << "Skipping OCR language" << langCode << "- missing traineddata at" << trainedDataPath; -+ continue; -+ } -+ } -+ -+ if (!detectedLanguages.contains(langCode)) { -+ detectedLanguages.append(langCode); -+ } -+ } -+ } catch (const std::exception &e) { -+ qCWarning(SPECTACLE_LOG) << "Exception while enumerating Tesseract languages:" << e.what(); -+ } -+ -+ std::sort(detectedLanguages.begin(), detectedLanguages.end()); -+ m_availableLanguages = detectedLanguages; -+ -+ for (const QString &langCode : std::as_const(m_availableLanguages)) { -+ if (langCode == u"osd"_s) { -+ m_languageNames.insert(langCode, i18nc("@item:inlistbox", "Orientation and Script Detection")); -+ continue; -+ } -+ -+ const QString displayName = tesseractLangName(langCode); -+ m_languageNames.insert(langCode, displayName); -+ } -+ -+ qCDebug(SPECTACLE_LOG) << "Detected OCR languages:" << m_availableLanguages; -+#else -+ Q_UNUSED(tessdataPath); -+#endif -+} -+ -+QString OcrManager::tesseractLangName(const QString &tesseractCode) const -+{ -+ static const QMap tesseractToIsoMap = { -+ {u"afr"_s, u"af"_s}, {u"ara"_s, u"ar"_s}, {u"aze"_s, u"az"_s}, {u"aze_cyrl"_s, u"az"_s}, {u"bel"_s, u"be"_s}, -+ {u"ben"_s, u"bn"_s}, {u"bul"_s, u"bg"_s}, {u"cat"_s, u"ca"_s}, {u"ces"_s, u"cs"_s}, {u"chi_sim"_s, u"zh_CN"_s}, -+ {u"chi_tra"_s, u"zh_TW"_s}, {u"cym"_s, u"cy"_s}, {u"dan"_s, u"da"_s}, {u"dan_frak"_s, u"da"_s}, {u"deu"_s, u"de"_s}, -+ {u"deu_frak"_s, u"de"_s}, {u"deu_latf"_s, u"de"_s}, {u"ell"_s, u"el"_s}, {u"eng"_s, u"en"_s}, {u"epo"_s, u"eo"_s}, -+ {u"est"_s, u"et"_s}, {u"eus"_s, u"eu"_s}, {u"fas"_s, u"fa"_s}, {u"fin"_s, u"fi"_s}, {u"fra"_s, u"fr"_s}, -+ {u"frk"_s, u"de"_s}, {u"gla"_s, u"gd"_s}, {u"gle"_s, u"ga"_s}, {u"glg"_s, u"gl"_s}, {u"heb"_s, u"he"_s}, -+ {u"hin"_s, u"hi"_s}, {u"hrv"_s, u"hr"_s}, {u"hun"_s, u"hu"_s}, {u"ind"_s, u"id"_s}, {u"isl"_s, u"is"_s}, -+ {u"ita"_s, u"it"_s}, {u"ita_old"_s, u"it"_s}, {u"jpn"_s, u"ja"_s}, {u"kor"_s, u"ko"_s}, {u"kor_vert"_s, u"ko"_s}, -+ {u"lav"_s, u"lv"_s}, {u"lit"_s, u"lt"_s}, {u"nld"_s, u"nl"_s}, {u"nor"_s, u"no"_s}, {u"pol"_s, u"pl"_s}, -+ {u"por"_s, u"pt"_s}, {u"ron"_s, u"ro"_s}, {u"rus"_s, u"ru"_s}, {u"slk"_s, u"sk"_s}, {u"slk_frak"_s, u"sk"_s}, -+ {u"slv"_s, u"sl"_s}, {u"spa"_s, u"es"_s}, {u"spa_old"_s, u"es"_s}, {u"srp"_s, u"sr"_s}, {u"srp_latn"_s, u"sr"_s}, -+ {u"swe"_s, u"sv"_s}, {u"tur"_s, u"tr"_s}, {u"ukr"_s, u"uk"_s}, {u"vie"_s, u"vi"_s}, {u"amh"_s, u"am"_s}, -+ {u"asm"_s, u"as"_s}, {u"bod"_s, u"bo"_s}, {u"dzo"_s, u"dz"_s}, {u"guj"_s, u"gu"_s}, {u"kan"_s, u"kn"_s}, -+ {u"kat"_s, u"ka"_s}, {u"kat_old"_s, u"ka"_s}, {u"kaz"_s, u"kk"_s}, {u"khm"_s, u"km"_s}, {u"kir"_s, u"ky"_s}, -+ {u"lao"_s, u"lo"_s}, {u"mal"_s, u"ml"_s}, {u"mar"_s, u"mr"_s}, {u"mya"_s, u"my"_s}, {u"nep"_s, u"ne"_s}, -+ {u"ori"_s, u"or"_s}, {u"pan"_s, u"pa"_s}, {u"sin"_s, u"si"_s}, {u"tam"_s, u"ta"_s}, {u"tel"_s, u"te"_s}, -+ {u"tha"_s, u"th"_s}, {u"urd"_s, u"ur"_s}, {u"bos"_s, u"bs"_s}, {u"bre"_s, u"br"_s}, {u"cos"_s, u"co"_s}, -+ {u"fao"_s, u"fo"_s}, {u"fil"_s, u"tl"_s}, {u"fry"_s, u"fy"_s}, {u"hat"_s, u"ht"_s}, {u"hye"_s, u"hy"_s}, -+ {u"iku"_s, u"iu"_s}, {u"jav"_s, u"jv"_s}, {u"kmr"_s, u"ku"_s}, {u"kur"_s, u"ku"_s}, {u"lat"_s, u"la"_s}, -+ {u"ltz"_s, u"lb"_s}, {u"mkd"_s, u"mk"_s}, {u"mlt"_s, u"mt"_s}, {u"mon"_s, u"mn"_s}, {u"mri"_s, u"mi"_s}, -+ {u"msa"_s, u"ms"_s}, {u"oci"_s, u"oc"_s}, {u"pus"_s, u"ps"_s}, {u"que"_s, u"qu"_s}, {u"san"_s, u"sa"_s}, -+ {u"snd"_s, u"sd"_s}, {u"sqi"_s, u"sq"_s}, {u"sun"_s, u"su"_s}, {u"swa"_s, u"sw"_s}, {u"tat"_s, u"tt"_s}, -+ {u"tgk"_s, u"tg"_s}, {u"tgl"_s, u"tl"_s}, {u"tir"_s, u"ti"_s}, {u"ton"_s, u"to"_s}, {u"uig"_s, u"ug"_s}, -+ {u"uzb"_s, u"uz"_s}, {u"uzb_cyrl"_s, u"uz"_s}, {u"yid"_s, u"yi"_s}, {u"yor"_s, u"yo"_s}, -+ }; -+ -+ if (tesseractCode == u"equ"_s) { -+ return i18n("Math/Equation Detection"); -+ } -+ if (tesseractCode == u"osd"_s) { -+ return i18n("Orientation and Script Detection"); -+ } -+ -+ const QString isoCode = tesseractToIsoMap.value(tesseractCode); -+ if (!isoCode.isEmpty()) { -+ QLocale locale(isoCode); -+ QString name = locale.nativeLanguageName(); -+ -+ if (!name.isEmpty()) { -+ name[0] = name[0].toUpper(); -+ return name; -+ } -+ -+ QString languageName = QLocale::languageToString(locale.language()); -+ if (!languageName.isEmpty()) { -+ languageName[0] = languageName[0].toUpper(); -+ return languageName; -+ } -+ } -+ -+ return tesseractCode; -+} -+ -+OcrWorker::OcrWorker(QObject *parent) -+ : QObject(parent) -+{ -+} -+ -+void OcrWorker::processImage(const QImage &image, tesseract::TessBaseAPI *tesseract) -+{ -+#ifdef HAVE_TESSERACT_OCR -+ QMutexLocker locker(&m_mutex); -+ -+ if (!tesseract || image.isNull()) { -+ Q_EMIT imageProcessed(QString(), false); -+ return; -+ } -+ -+ try { -+ QImage rgbImage = image.convertToFormat(QImage::Format_RGB888); -+ -+ tesseract->SetImage(rgbImage.bits(), rgbImage.width(), rgbImage.height(), 3, rgbImage.bytesPerLine()); -+ -+ if (tesseract->Recognize(0) != 0) { -+ Q_EMIT imageProcessed(QString(), false); -+ return; -+ } -+ -+ QStringList lines; -+ std::unique_ptr iterator(tesseract->GetIterator()); -+ -+ if (iterator) { -+ do { -+ const char *lineText = iterator->GetUTF8Text(tesseract::RIL_TEXTLINE); -+ if (lineText != nullptr) { -+ QString line = QString::fromUtf8(lineText).trimmed(); -+ if (!line.isEmpty()) { -+ lines.append(line); -+ } -+ delete[] lineText; -+ } -+ } while (iterator->Next(tesseract::RIL_TEXTLINE)); -+ } -+ -+ const QString result = lines.join(QLatin1Char('\n')).trimmed(); -+ Q_EMIT imageProcessed(result, true); -+ } catch (const std::exception &e) { -+ qCWarning(SPECTACLE_LOG) << "Exception in OCR worker:" << e.what(); -+ Q_EMIT imageProcessed(QString(), false); -+ } -+#else -+ Q_UNUSED(image); -+ Q_UNUSED(tesseract); -+ Q_EMIT imageProcessed(QString(), false); -+#endif -+} -diff --git a/src/OcrManager.h b/src/OcrManager.h -new file mode 100644 -index 000000000..c71505b3e ---- /dev/null -+++ b/src/OcrManager.h -@@ -0,0 +1,175 @@ -+/* This file is part of Spectacle, the KDE screenshot utility -+ * SPDX-FileCopyrightText: 2025 Jhair Paris -+ * SPDX-License-Identifier: LGPL-2.0-or-later -+ */ -+ -+#pragma once -+ -+#include "Config.h" -+ -+#ifdef HAVE_TESSERACT_OCR -+#include -+#else -+namespace tesseract -+{ -+class TessBaseAPI; -+} -+#endif -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+/** -+ * @brief Worker class for OCR processing in background thread -+ */ -+class OcrWorker : public QObject -+{ -+ Q_OBJECT -+ -+public: -+ explicit OcrWorker(QObject *parent = nullptr); -+ -+public Q_SLOTS: -+ void processImage(const QImage &image, tesseract::TessBaseAPI *tesseract); -+ -+Q_SIGNALS: -+ void imageProcessed(const QString &text, bool success); -+ -+private: -+ QMutex m_mutex; -+}; -+ -+/** -+ * This class uses Tesseract OCR engine to extract text from images. -+ * It provides both synchronous and asynchronous text recognition capabilities. -+ */ -+class OcrManager : public QObject -+{ -+ Q_OBJECT -+ -+public: -+ static constexpr int MAX_OCR_LANGUAGES = 4; -+ static constexpr int MIN_OCR_LANGUAGES = 1; -+ enum class OcrStatus { -+ Ready = 0, -+ Processing = 1, -+ Error = 2 -+ }; -+ Q_ENUM(OcrStatus) -+ -+ explicit OcrManager(QObject *parent = nullptr); -+ ~OcrManager() override; -+ -+ static OcrManager *instance(); -+ -+ /** -+ * @brief Check if OCR engine is available and properly initialized -+ * @return true if OCR is available, false otherwise -+ */ -+ bool isAvailable() const; -+ -+ /** -+ * @brief Get the current OCR processing status -+ * @return Current status of the OCR engine -+ */ -+ OcrStatus status() const; -+ -+ /** -+ * @brief Get a map of available languages with human-readable names -+ * @return QMap where key is language code and value is display name -+ */ -+ QMap availableLanguagesWithNames() const; -+ -+ /** -+ * @brief Set multiple languages for OCR processing -+ * @param languageCodes List of language codes to use (e.g., ["eng", "spa", "fra"]) -+ */ -+ void setLanguagesByCode(const QStringList &languageCodes); -+ -+ /** -+ * @brief Get the current language code -+ * @return Current language code (e.g., "eng", "spa") -+ */ -+ QString currentLanguageCode() const; -+ -+public Q_SLOTS: -+ /** -+ * @brief Extract text from an image asynchronously -+ * @param image The image to process -+ * -+ * This method processes the image in a background thread and emits -+ * textRecognized() signal when complete. -+ */ -+ void recognizeText(const QImage &image); -+ -+ /** -+ * @brief Extract text from an image using a temporary language selection -+ * @param image The image to process -+ * @param languageCode The one-off language code to use (e.g. "eng") -+ * -+ * The provided language is applied only for this recognition request and -+ * does not persist the user's saved configuration. -+ */ -+ void recognizeTextWithLanguage(const QImage &image, const QString &languageCode); -+ -+Q_SIGNALS: -+ /** -+ * @brief Emitted when text recognition is complete -+ * @param text The recognized text -+ * @param success true if recognition was successful -+ */ -+ void textRecognized(const QString &text, bool success); -+ -+ /** -+ * @brief Emitted when OCR status changes -+ * @param status New status -+ */ -+ void statusChanged(OcrStatus status); -+ -+private Q_SLOTS: -+ void handleRecognitionComplete(const QString &text, bool success); -+ -+private: -+ void initializeTesseract(); -+ void setStatus(OcrStatus status); -+ bool setupTesseractLanguages(const QStringList &langCodes); -+ void setupAvailableLanguages(const QString &tessdataPath); -+ void loadSavedLanguageSetting(); -+ bool isLanguageAvailable(const QString &languageCode) const; -+ QString tesseractLangName(const QString &tesseractCode) const; -+ -+ /** -+ * @brief Validate, filter, and apply languages to Tesseract -+ * @param languageCodes Languages to validate and apply -+ * @return true if languages were successfully applied -+ */ -+ bool validateAndApplyLanguages(const QStringList &languageCodes); -+ void beginRecognition(const QImage &image); -+ -+ static OcrManager *s_instance; -+ -+#ifdef HAVE_TESSERACT_OCR -+ tesseract::TessBaseAPI *m_tesseract; -+ OcrWorker *m_worker; -+#endif -+ std::unique_ptr m_workerThread; -+ QTimer *m_timeoutTimer; -+ -+ OcrStatus m_status; -+ QString m_currentLanguageCode; -+ QStringList m_configuredLanguages; -+ QStringList m_activeLanguages; -+ bool m_shouldRestoreToConfigured; -+ QStringList m_availableLanguages; -+ QMap m_languageNames; -+ bool m_initialized; -+ -+private: -+}; -\ No newline at end of file -diff --git a/src/SpectacleCore.cpp b/src/SpectacleCore.cpp -index 7371ce768..caada874e 100644 ---- a/src/SpectacleCore.cpp -+++ b/src/SpectacleCore.cpp -@@ -1,6 +1,7 @@ - /* - * SPDX-FileCopyrightText: 2019 David Redondo - * SPDX-FileCopyrightText: 2015 Boudhayan Gupta -+ * SPDX-FileCopyrightText: 2025 Jhair Paris - * - * SPDX-License-Identifier: LGPL-2.0-or-later - */ -@@ -20,6 +21,7 @@ - #include "Gui/HelpMenu.h" - #include "Gui/OptionsMenu.h" - #include "Gui/InlineMessageModel.h" -+#include "OcrManager.h" - #include "Platforms/ImagePlatformXcb.h" - #include "Platforms/VideoPlatform.h" - #include "ShortcutActions.h" -@@ -49,6 +51,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -59,6 +62,8 @@ - #include - #include - #include -+#include -+#include - #include - #include - #include -@@ -538,6 +543,63 @@ SpectacleCore::SpectacleCore(QObject *parent) - InlineMessageModel::instance()->push(InlineMessageModel::Scanned, text, result); - }; - connect(exportManager, &ExportManager::qrCodeScanned, this, onQRCodeScanned); -+ -+ auto onOcrTextRecognized = [this](const QString &text, bool success) { -+ if (!success) { -+ InlineMessageModel::instance()->push(InlineMessageModel::Error, -+ i18nc("@info", "Text extraction failed")); -+ return; -+ } -+ -+ if (text.isEmpty()) { -+ InlineMessageModel::instance()->push(InlineMessageModel::Copied, -+ i18nc("@info", "No text found in the image")); -+ return; -+ } -+ -+ InlineMessageModel::instance()->push(InlineMessageModel::Copied, -+ i18nc("@info", "Text extraction completed")); -+ -+ auto notification = new KNotification(u"ocrTextExtracted"_s, KNotification::CloseOnTimeout, this); -+ notification->setTitle(i18nc("@info:notification title", "Text Extracted")); -+ -+ notification->setText(i18nc("@info:notification", "Text copied to clipboard")); -+ notification->setIconName(u"document-scan"_s); -+ -+ if (!text.isEmpty()) { -+ auto openEditorAction = notification->addAction(i18nc("@action:button", "Open in Text Editor")); -+ connect(openEditorAction, &KNotificationAction::activated, this, [text]() { -+ // Create temporary file with extracted text -+ auto exportManager = ExportManager::instance(); -+ exportManager->updateTimestamp(); -+ auto timestamp = exportManager->timestamp(); -+ -+ QString filename = QStringLiteral("spectacle_ocr_%1.txt").arg(timestamp.toString(QStringLiteral("yyyyMMdd_HHmmss"))); -+ QString templatePath = QDir::tempPath() + QStringLiteral("/") + filename; -+ -+ QTemporaryFile tempFile; -+ tempFile.setFileTemplate(templatePath); -+ tempFile.setAutoRemove(false); -+ -+ if (tempFile.open()) { -+ QTextStream stream(&tempFile); -+ stream << text; -+ tempFile.close(); -+ -+ auto job = new KIO::OpenUrlJob(QUrl::fromLocalFile(tempFile.fileName())); -+ job->start(); -+ } -+ }); -+ } -+ -+ notification->sendEvent(); -+ }; -+ -+ // Connect to OCR manager -+ connect(OcrManager::instance(), &OcrManager::textRecognized, this, onOcrTextRecognized); -+ connect(OcrManager::instance(), &OcrManager::statusChanged, this, [this](OcrManager::OcrStatus) { -+ Q_EMIT ocrStatusChanged(); -+ }); - - connect(exportManager, &ExportManager::errorMessage, this, &SpectacleCore::showErrorMessage); - -@@ -582,6 +644,87 @@ SpectacleCore::SpectacleCore(QObject *parent) - }); - } - -+bool SpectacleCore::ocrAvailable() const -+{ -+ return OcrManager::instance()->isAvailable(); -+} -+ -+OcrManager::OcrStatus SpectacleCore::ocrStatus() const -+{ -+ return OcrManager::instance()->status(); -+} -+ -+QVariantMap SpectacleCore::ocrAvailableLanguages() const -+{ -+ auto ocrManager = OcrManager::instance(); -+ if (!ocrManager->isAvailable()) { -+ return QVariantMap(); -+ } -+ -+ auto languageMap = ocrManager->availableLanguagesWithNames(); -+ QVariantMap result; -+ for (auto it = languageMap.constBegin(); it != languageMap.constEnd(); ++it) { -+ result[it.key()] = it.value(); -+ } -+ return result; -+} -+ -+bool SpectacleCore::startOcrExtraction(const QString &languageCode) -+{ -+ if (m_videoMode) { -+ return false; -+ } -+ -+ const bool hasCaptureWindows = !CaptureWindow::instances().isEmpty(); -+ -+ if (hasCaptureWindows) { -+ auto selectionEditor = SelectionEditor::instance(); -+ auto inlineMessages = InlineMessageModel::instance(); -+ -+ if (!selectionEditor->acceptSelection(ExportManager::UserAction)) { -+ inlineMessages->push(InlineMessageModel::Error, i18nc("@info", "Please select a region before extracting text")); -+ return false; -+ } -+ -+ QMetaObject::invokeMethod( -+ this, -+ [this, languageCode]() { -+ performOcrExtraction(languageCode); -+ }, -+ Qt::QueuedConnection); -+ return true; -+ } -+ -+ return performOcrExtraction(languageCode); -+} -+ -+bool SpectacleCore::performOcrExtraction(const QString &languageCode) -+{ -+ auto ocrManager = OcrManager::instance(); -+ auto inlineMessages = InlineMessageModel::instance(); -+ -+ if (!ocrManager->isAvailable()) { -+ inlineMessages->push(InlineMessageModel::Error, i18nc("@info", "OCR is not available.")); -+ return false; -+ } -+ -+ const QImage image = m_annotationDocument->renderToImage(); -+ if (image.isNull()) { -+ inlineMessages->push(InlineMessageModel::Error, i18nc("@info", "No screenshot available.")); -+ return false; -+ } -+ -+ inlineMessages->push(InlineMessageModel::Copied, i18nc("@info", "Extracting text from image...")); -+ -+ if (languageCode.isEmpty()) { -+ ocrManager->recognizeText(image); -+ } else { -+ ocrManager->recognizeTextWithLanguage(image, languageCode); -+ } -+ -+ return true; -+} -+ - SpectacleCore::~SpectacleCore() noexcept - { - s_self = nullptr; -diff --git a/src/SpectacleCore.h b/src/SpectacleCore.h -index 23d65ead9..2c87ff8f4 100644 ---- a/src/SpectacleCore.h -+++ b/src/SpectacleCore.h -@@ -17,6 +17,7 @@ - #include "Gui/Annotations/AnnotationDocument.h" - #include "Gui/CaptureWindow.h" - #include "Gui/ViewerWindow.h" -+#include "OcrManager.h" - #include "Platforms/PlatformLoader.h" - #include "RecordingModeModel.h" - #include "VideoFormatModel.h" -@@ -40,6 +41,8 @@ class SpectacleCore : public QObject - Q_PROPERTY(bool videoMode READ videoMode WRITE setVideoMode NOTIFY videoModeChanged) - Q_PROPERTY(QUrl currentVideo READ currentVideo NOTIFY currentVideoChanged) - Q_PROPERTY(AnnotationDocument *annotationDocument READ annotationDocument CONSTANT FINAL) -+ Q_PROPERTY(bool ocrAvailable READ ocrAvailable NOTIFY ocrStatusChanged FINAL) -+ Q_PROPERTY(OcrManager::OcrStatus ocrStatus READ ocrStatus NOTIFY ocrStatusChanged FINAL) - - public: - enum class StartMode { -@@ -74,6 +77,10 @@ public: - - QUrl currentVideo() const; - -+ bool ocrAvailable() const; -+ OcrManager::OcrStatus ocrStatus() const; -+ Q_INVOKABLE QVariantMap ocrAvailableLanguages() const; -+ Q_INVOKABLE bool startOcrExtraction(const QString &languageCode = QString()); - - void initGuiNoScreenshot(); - -@@ -125,6 +132,7 @@ Q_SIGNALS: - void videoModeChanged(bool videoMode); - void currentVideoChanged(const QUrl ¤tVideo); - void recordedTimeChanged(); -+ void ocrStatusChanged(); - - private: - explicit SpectacleCore(QObject *parent = nullptr); -@@ -148,6 +156,7 @@ private: - void unityLauncherUpdate(const QVariantMap &properties) const; - void setCurrentVideo(const QUrl ¤tVideo); - QUrl videoOutputUrl() const; -+ bool performOcrExtraction(const QString &languageCode); - - static SpectacleCore *s_self; - std::unique_ptr m_annotationDocument = nullptr; --- -GitLab - - -From a1f7ac0b716ea295cfec120bf8691dd86e56413b Mon Sep 17 00:00:00 2001 -From: Jhair Paris -Date: Mon, 13 Oct 2025 22:58:17 -0500 -Subject: [PATCH 3/3] add support for multiple OCR languages in preferences - dialog - -- Switch from single ocrLanguage string to ocrLanguages string list in settings -- Add OcrLanguageSelector widget for multi-language selection -- Integrate new selector into GeneralOptionsPage and SettingsDialog - -add OCR language menu to main interface - -- Introduce OcrLanguageMenu and OcrLanguageMenuButton components -- Expose language selection in ViewerPage and CaptureOverlay -- Move OCR extraction logic to SpectacleCore::startOcrExtraction - -Remove OCR language menu components and references from the project - -Add OCR language submenu to ExportMenu ---- - src/CMakeLists.txt | 1 + - src/Gui/CaptureOverlay.qml | 8 +- - src/Gui/ExportMenu.cpp | 84 ++++++ - src/Gui/ExportMenu.h | 5 + - src/Gui/OcrAction.qml | 7 +- - src/Gui/SettingsDialog/GeneralOptions.ui | 38 ++- - src/Gui/SettingsDialog/GeneralOptionsPage.cpp | 81 ++---- - src/Gui/SettingsDialog/GeneralOptionsPage.h | 16 +- - .../SettingsDialog/OcrLanguageSelector.cpp | 271 ++++++++++++++++++ - src/Gui/SettingsDialog/OcrLanguageSelector.h | 111 +++++++ - src/Gui/SettingsDialog/SettingsDialog.cpp | 17 +- - src/Gui/SettingsDialog/spectacle.kcfg | 4 +- - src/Gui/ViewerPage.qml | 4 +- - 13 files changed, 561 insertions(+), 86 deletions(-) - create mode 100644 src/Gui/SettingsDialog/OcrLanguageSelector.cpp - create mode 100644 src/Gui/SettingsDialog/OcrLanguageSelector.h - -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index c57535e34..6efeff637 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -44,6 +44,7 @@ target_sources(spectacle PRIVATE - Gui/SelectionEditor.cpp - Gui/SettingsDialog/GeneralOptionsPage.cpp - Gui/SettingsDialog/ImageSaveOptionsPage.cpp -+ Gui/SettingsDialog/OcrLanguageSelector.cpp - Gui/SettingsDialog/SettingsDialog.cpp - Gui/SettingsDialog/ShortcutsOptionsPage.cpp - Gui/SettingsDialog/VideoFormatComboBox.cpp -diff --git a/src/Gui/CaptureOverlay.qml b/src/Gui/CaptureOverlay.qml -index 37f3dcf85..431d76479 100644 ---- a/src/Gui/CaptureOverlay.qml -+++ b/src/Gui/CaptureOverlay.qml -@@ -506,11 +506,13 @@ MouseArea { - visible: action.enabled - action: CopyImageAction {} - } -+ - ToolButton { - display: TtToolButton.IconOnly -- visible: action.enabled && !SpectacleCore.videoMode && SpectacleCore.ocrAvailable -+ visible: !SpectacleCore.videoMode && SpectacleCore.ocrAvailable - action: OcrAction {} - } -+ - ExportMenuButton { - focusPolicy: Qt.NoFocus - } -@@ -537,10 +539,12 @@ MouseArea { - visible: action.enabled - action: CopyImageAction {} - } -+ - ToolButton { -- visible: action.enabled && !SpectacleCore.videoMode && SpectacleCore.ocrAvailable -+ visible: !SpectacleCore.videoMode && SpectacleCore.ocrAvailable - action: OcrAction {} - } -+ - ExportMenuButton { - focusPolicy: Qt.NoFocus - } -diff --git a/src/Gui/ExportMenu.cpp b/src/Gui/ExportMenu.cpp -index e2c7dd5b6..57e1480fa 100644 ---- a/src/Gui/ExportMenu.cpp -+++ b/src/Gui/ExportMenu.cpp -@@ -6,6 +6,7 @@ - - #include "ExportMenu.h" - #include "CaptureWindow.h" -+#include "OcrManager.h" - #include "SpectacleCore.h" - #include "WidgetWindowUtils.h" - #include "settings.h" -@@ -54,6 +55,8 @@ ExportMenu::ExportMenu(QWidget *parent) - this, &ExportMenu::openScreenshotsFolder); - addAction(KStandardActions::print(this, &ExportMenu::openPrintDialog, this)); - -+ createOcrLanguageSubmenu(); -+ - #ifdef PURPOSE_FOUND - loadPurposeMenu(); - connect(ExportManager::instance(), &ExportManager::imageChanged, this, &ExportMenu::onImageChanged); -@@ -233,4 +236,85 @@ void ExportMenu::openPrintDialog() - dialog->setVisible(true); - } - -+void ExportMenu::createOcrLanguageSubmenu() -+{ -+ Q_ASSERT(!m_ocrLanguageMenu); -+ -+ auto ocrManager = OcrManager::instance(); -+ -+ if (!ocrManager || !ocrManager->isAvailable()) { -+ return; -+ } -+ -+ m_ocrLanguageMenu = addMenu(i18nc("@action:menu", "Extract Text by Language")); -+ m_ocrLanguageMenu->setIcon(QIcon::fromTheme(u"document-scan"_s)); -+ -+ // Keep the submenu in sync with OCR status changes -+ if (ocrManager) { -+ connect(ocrManager, &OcrManager::statusChanged, this, &ExportMenu::buildOcrLanguageSubmenu); -+ } -+ -+ if (auto settings = Settings::self()) { -+ connect(settings, &Settings::ocrLanguagesChanged, this, &ExportMenu::buildOcrLanguageSubmenu); -+ } -+ -+ connect(m_ocrLanguageMenu, &QMenu::aboutToShow, this, &ExportMenu::buildOcrLanguageSubmenu); -+ -+ buildOcrLanguageSubmenu(); -+} -+ -+void ExportMenu::buildOcrLanguageSubmenu() -+{ -+ if (!m_ocrLanguageMenu) { -+ return; -+ } -+ -+ m_ocrLanguageMenu->clear(); -+ -+ auto ocrManager = OcrManager::instance(); -+ -+ if (!ocrManager) { -+ QAction *action = m_ocrLanguageMenu->addAction(i18n("OCR engine is not available.")); -+ action->setEnabled(false); -+ return; -+ } -+ -+ const bool initializationFailed = ocrManager->status() == OcrManager::OcrStatus::Error; -+ if (!ocrManager->isAvailable()) { -+ QAction *action = m_ocrLanguageMenu->addAction(initializationFailed ? i18n("OCR is not available. Please install Tesseract OCR.") -+ : i18n("OCR engine is initializing…")); -+ action->setEnabled(false); -+ return; -+ } -+ -+ const bool busy = ocrManager->status() == OcrManager::OcrStatus::Processing; -+ const QMap languages = ocrManager->availableLanguagesWithNames(); -+ -+ if (languages.isEmpty()) { -+ QAction *action = m_ocrLanguageMenu->addAction(i18n("No OCR language data available.")); -+ action->setEnabled(false); -+ return; -+ } -+ -+ for (auto it = languages.cbegin(); it != languages.cend(); ++it) { -+ const QString &code = it.key(); -+ -+ if (code == u"osd"_s) { -+ continue; -+ } -+ -+ QAction *languageAction = m_ocrLanguageMenu->addAction(it.value()); -+ languageAction->setEnabled(!busy); -+ -+ connect(languageAction, &QAction::triggered, this, [this, code]() { -+ triggerExtraction(code); -+ }); -+ } -+} -+ -+void ExportMenu::triggerExtraction(const QString &languageCode) -+{ -+ SpectacleCore::instance()->startOcrExtraction(languageCode); -+} -+ - #include "moc_ExportMenu.cpp" -diff --git a/src/Gui/ExportMenu.h b/src/Gui/ExportMenu.h -index e0533a708..bfac0b990 100644 ---- a/src/Gui/ExportMenu.h -+++ b/src/Gui/ExportMenu.h -@@ -9,6 +9,7 @@ - - #include "SpectacleMenu.h" - -+#include - #include - - #include "Config.h" -@@ -49,8 +50,11 @@ private: - - Q_SLOT void onImageChanged(); - Q_SLOT void openScreenshotsFolder(); -+ Q_SLOT void buildOcrLanguageSubmenu(); -+ Q_SLOT void triggerExtraction(const QString &languageCode); - - void getKServiceItems(); -+ void createOcrLanguageSubmenu(); - - #ifdef PURPOSE_FOUND - void loadPurposeMenu(); -@@ -59,6 +63,7 @@ private: - bool mUpdatedImageAvailable; - std::unique_ptr mPurposeMenu; - #endif -+ QMenu *m_ocrLanguageMenu = nullptr; - friend class ExportMenuSingleton; - }; - -diff --git a/src/Gui/OcrAction.qml b/src/Gui/OcrAction.qml -index f887ec0ee..a22efec16 100644 ---- a/src/Gui/OcrAction.qml -+++ b/src/Gui/OcrAction.qml -@@ -6,9 +6,10 @@ import QtQuick.Templates as T - import org.kde.spectacle.private - - T.Action { -- // OCR is only available for screenshots, not videos, and only when OCR is properly available -- enabled: !SpectacleCore.videoMode && SpectacleCore.ocrAvailable -+ enabled: !SpectacleCore.videoMode && -+ SpectacleCore.ocrAvailable && -+ SpectacleCore.ocrStatus !== 1 - icon.name: "document-scan" - text: i18nc("@action", "Extract Text") -- onTriggered: contextWindow.extractText() -+ onTriggered: SpectacleCore.startOcrExtraction() - } -diff --git a/src/Gui/SettingsDialog/GeneralOptions.ui b/src/Gui/SettingsDialog/GeneralOptions.ui -index ddbbf3e5a..048639b89 100644 ---- a/src/Gui/SettingsDialog/GeneralOptions.ui -+++ b/src/Gui/SettingsDialog/GeneralOptions.ui -@@ -265,21 +265,39 @@ - - - -- Language: -+ Languages for OCR: - - - - -- -- -- -- 0 -- 0 -- -+ -+ -+ true - -- -- currentData -+ -+ QFrame::StyledPanel - -+ -+ 120 -+ -+ -+ 60 -+ -+ -+ Qt::ScrollBarAlwaysOff -+ -+ -+ -+ -+ 0 -+ 0 -+ 69 -+ 69 -+ -+ -+ -+ -+ - - - -@@ -383,7 +401,7 @@ - kcfg_useReleaseToCapture - kcfg_showCaptureInstructions - kcfg_rememberSelectionRect -- kcfg_ocrLanguage -+ ocrLanguageScrollArea - - - -diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -index 5b8a5d9fc..f6be13d56 100644 ---- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -+++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -@@ -8,19 +8,22 @@ - - #include "GeneralOptionsPage.h" - -+#include "OcrLanguageSelector.h" -+#include "OcrManager.h" - #include "settings.h" - #include "ui_GeneralOptions.h" --#include "OcrManager.h" - --#include - #include -+#include - --#include - #include - -+using namespace Qt::Literals::StringLiterals; -+ - GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - : QWidget(parent) - , m_ui(new Ui_GeneralOptions) -+ , m_ocrLanguageSelector(new OcrLanguageSelector(this)) - { - m_ui->setupUi(this); - -@@ -31,9 +34,12 @@ GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - m_ui->regionTitle->setLevel(2); - m_ui->ocrTitle->setLevel(2); - -- setupOcrLanguageComboBox(); -+ m_ui->ocrLanguageScrollArea->setWidget(m_ocrLanguageSelector); -+ m_ui->ocrLanguageScrollArea->setWidgetResizable(true); -+ -+ connect(m_ocrLanguageSelector, &OcrLanguageSelector::selectedLanguagesChanged, this, &GeneralOptionsPage::ocrLanguageChanged); - -- connect(OcrManager::instance(), &OcrManager::statusChanged, this, &GeneralOptionsPage::refreshOcrLanguageSettings); -+ refreshOcrLanguageSettings(); - - //On Wayland we can't programmatically raise and focus the window so we have to hide the option - if (KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE").constData(), "wayland") == 0) { -@@ -43,71 +49,20 @@ GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - - GeneralOptionsPage::~GeneralOptionsPage() = default; - --void GeneralOptionsPage::setupOcrLanguageComboBox() --{ -- OcrManager *ocrManager = OcrManager::instance(); -- -- if (!ocrManager->isAvailable()) { -- m_ui->kcfg_ocrLanguage->setEnabled(false); -- m_ui->kcfg_ocrLanguage->addItem(i18n("OCR not available")); -- m_ui->ocrLanguageLabel->setVisible(false); -- m_ui->kcfg_ocrLanguage->setVisible(false); -- m_ui->ocrUnavailableWidget->setVisible(true); -- return; -- } -- -- const auto availableLanguages = ocrManager->availableLanguagesWithNames(); -- -- if (availableLanguages.isEmpty()) { -- m_ui->kcfg_ocrLanguage->addItem(i18n("No languages found")); -- m_ui->kcfg_ocrLanguage->setEnabled(false); -- return; -- } -- -- m_ui->kcfg_ocrLanguage->clear(); -- m_ui->ocrLanguageLabel->setVisible(true); -- m_ui->kcfg_ocrLanguage->setVisible(true); -- m_ui->ocrUnavailableWidget->setVisible(false); -- -- for (auto it = availableLanguages.constBegin(); it != availableLanguages.constEnd(); ++it) { -- m_ui->kcfg_ocrLanguage->addItem(it.value(), it.key()); -- } --} -- - void GeneralOptionsPage::refreshOcrLanguageSettings() - { - OcrManager *ocrManager = OcrManager::instance(); - - if (!ocrManager->isAvailable()) { - m_ui->ocrLanguageLabel->setVisible(false); -- m_ui->kcfg_ocrLanguage->setVisible(false); -+ m_ui->ocrLanguageScrollArea->setVisible(false); - m_ui->ocrUnavailableWidget->setVisible(true); -- return; -- } -- -- const auto availableLanguages = ocrManager->availableLanguagesWithNames(); -- -- if (availableLanguages.isEmpty()) { -- return; -- } -- -- m_ui->kcfg_ocrLanguage->clear(); -- m_ui->kcfg_ocrLanguage->setEnabled(true); -- m_ui->ocrLanguageLabel->setVisible(true); -- m_ui->kcfg_ocrLanguage->setVisible(true); -- m_ui->ocrUnavailableWidget->setVisible(false); -- -- for (auto it = availableLanguages.constBegin(); it != availableLanguages.constEnd(); ++it) { -- m_ui->kcfg_ocrLanguage->addItem(it.value(), it.key()); -- } -- -- const QString currentLanguage = Settings::ocrLanguage(); -- -- for (int i = 0; i < m_ui->kcfg_ocrLanguage->count(); ++i) { -- if (m_ui->kcfg_ocrLanguage->itemData(i).toString() == currentLanguage) { -- m_ui->kcfg_ocrLanguage->setCurrentIndex(i); -- break; -- } -+ } else { -+ m_ui->ocrLanguageLabel->setVisible(true); -+ m_ui->ocrLanguageScrollArea->setVisible(true); -+ m_ui->ocrUnavailableWidget->setVisible(false); -+ -+ m_ocrLanguageSelector->refresh(); - } - } - -diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.h b/src/Gui/SettingsDialog/GeneralOptionsPage.h -index c184d6ba8..a3a5cb17d 100644 ---- a/src/Gui/SettingsDialog/GeneralOptionsPage.h -+++ b/src/Gui/SettingsDialog/GeneralOptionsPage.h -@@ -11,6 +11,7 @@ - #include - - class Ui_GeneralOptions; -+class OcrLanguageSelector; - - class GeneralOptionsPage : public QWidget - { -@@ -22,10 +23,21 @@ public: - - void refreshOcrLanguageSettings(); - -+ /** -+ * @brief Get direct access to the OCR language selector widget -+ * @return Pointer to the OcrLanguageSelector widget for direct manipulation -+ */ -+ OcrLanguageSelector *ocrLanguageSelector() const -+ { -+ return m_ocrLanguageSelector; -+ } -+ -+Q_SIGNALS: -+ void ocrLanguageChanged(); -+ - private: -- void setupOcrLanguageComboBox(); -- - QScopedPointer m_ui; -+ OcrLanguageSelector *m_ocrLanguageSelector; - }; - - #endif // GENERALOPTIONSPAGE_H -diff --git a/src/Gui/SettingsDialog/OcrLanguageSelector.cpp b/src/Gui/SettingsDialog/OcrLanguageSelector.cpp -new file mode 100644 -index 000000000..d1d809323 ---- /dev/null -+++ b/src/Gui/SettingsDialog/OcrLanguageSelector.cpp -@@ -0,0 +1,271 @@ -+/* -+ * SPDX-FileCopyrightText: 2025 Jhair Paris -+ * -+ * SPDX-License-Identifier: LGPL-2.0-or-later -+ */ -+ -+#include "OcrLanguageSelector.h" -+#include "OcrManager.h" -+#include "settings.h" -+#include "spectacle_debug.h" -+ -+#include -+ -+#include -+#include -+ -+using namespace Qt::Literals::StringLiterals; -+ -+OcrLanguageSelector::OcrLanguageSelector(QWidget *parent) -+ : QWidget(parent) -+ , m_layout(new QVBoxLayout(this)) -+ , m_blockSignals(false) -+ , m_ocrManager(OcrManager::instance()) -+{ -+ m_layout->setContentsMargins(0, 0, 0, 0); -+ m_layout->setSpacing(0); -+ setContentsMargins(0, 0, 0, 0); -+ -+ setupLanguageCheckboxes(); -+ -+ connect(m_ocrManager, &OcrManager::statusChanged, this, &OcrLanguageSelector::onOcrManagerStatusChanged); -+} -+ -+OcrLanguageSelector::~OcrLanguageSelector() = default; -+ -+QStringList OcrLanguageSelector::selectedLanguages() const -+{ -+ QStringList result; -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ if (checkbox->isChecked()) { -+ result.append(checkbox->property("languageCode").toString()); -+ } -+ } -+ return result; -+} -+ -+void OcrLanguageSelector::setSelectedLanguages(const QStringList &languages) -+{ -+ m_blockSignals = true; -+ -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ const QString langCode = checkbox->property("languageCode").toString(); -+ checkbox->setChecked(languages.contains(langCode)); -+ } -+ -+ m_blockSignals = false; -+ -+ enforceSelectionLimits(); -+} -+ -+bool OcrLanguageSelector::isDefault() const -+{ -+ const QStringList current = selectedLanguages(); -+ -+ // Default state is exactly one language selected -+ if (current.size() != 1) { -+ return false; -+ } -+ -+ // Check if it's English (preferred default) -+ for (const QCheckBox *checkbox : m_languageCheckboxes) { -+ if (checkbox->property("languageCode").toString() == u"eng"_s) { -+ // English is available, so default is English -+ return current.contains(u"eng"_s); -+ } -+ } -+ -+ // English not available, default is the first available language -+ if (!m_languageCheckboxes.isEmpty()) { -+ QString firstLangCode = m_languageCheckboxes.first()->property("languageCode").toString(); -+ return current.contains(firstLangCode); -+ } -+ -+ return false; -+} -+ -+bool OcrLanguageSelector::hasChanges() const -+{ -+ return selectedLanguages() != Settings::ocrLanguages(); -+} -+ -+void OcrLanguageSelector::applyDefaults() -+{ -+ if (!m_languageCheckboxes.isEmpty()) { -+ m_blockSignals = true; -+ -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ checkbox->setChecked(false); -+ } -+ -+ // Try to select English first -+ bool foundDefault = false; -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ if (checkbox->property("languageCode").toString() == u"eng"_s) { -+ checkbox->setChecked(true); -+ foundDefault = true; -+ break; -+ } -+ } -+ -+ // If English not available, select first language -+ if (!foundDefault) { -+ m_languageCheckboxes.first()->setChecked(true); -+ } -+ -+ m_blockSignals = false; -+ -+ const QStringList selected = selectedLanguages(); -+ Settings::setOcrLanguages(selected); -+ -+ // Emit signal to notify changes -+ Q_EMIT selectedLanguagesChanged(selected); -+ } -+} -+ -+void OcrLanguageSelector::refresh() -+{ -+ setupLanguageCheckboxes(); -+} -+ -+void OcrLanguageSelector::saveSettings() -+{ -+ const QStringList selected = selectedLanguages(); -+ Settings::setOcrLanguages(selected); -+} -+ -+void OcrLanguageSelector::updateWidgets() -+{ -+ const QStringList savedLanguages = Settings::ocrLanguages(); -+ setSelectedLanguages(savedLanguages); -+} -+ -+void OcrLanguageSelector::onLanguageCheckboxChanged() -+{ -+ if (m_blockSignals) { -+ return; -+ } -+ -+ enforceSelectionLimits(); -+ -+ const QStringList selected = selectedLanguages(); -+ Q_EMIT selectedLanguagesChanged(selected); -+} -+ -+void OcrLanguageSelector::onOcrManagerStatusChanged() -+{ -+ refresh(); -+} -+ -+void OcrLanguageSelector::setupLanguageCheckboxes() -+{ -+ while (QLayoutItem *item = m_layout->takeAt(0)) { -+ if (auto widget = item->widget()) { -+ widget->deleteLater(); -+ } -+ delete item; -+ } -+ -+ m_languageCheckboxes.clear(); -+ m_availableLanguages.clear(); -+ -+ if (!m_ocrManager || !m_ocrManager->isAvailable()) { -+ qCWarning(SPECTACLE_LOG) << "OCR is not available; language selector will remain empty."; -+ return; -+ } -+ -+ m_availableLanguages = m_ocrManager->availableLanguagesWithNames(); -+ -+ if (m_availableLanguages.isEmpty()) { -+ qCWarning(SPECTACLE_LOG) << "No OCR language data available."; -+ return; -+ } -+ -+ for (auto it = m_availableLanguages.cbegin(); it != m_availableLanguages.cend(); ++it) { -+ const QString &langCode = it.key(); -+ if (langCode == u"osd"_s) { -+ continue; -+ } -+ -+ QCheckBox *checkbox = new QCheckBox(it.value(), this); -+ checkbox->setProperty("languageCode", langCode); -+ connect(checkbox, &QCheckBox::toggled, this, &OcrLanguageSelector::onLanguageCheckboxChanged); -+ m_layout->addWidget(checkbox); -+ m_languageCheckboxes.append(checkbox); -+ } -+ -+ if (m_layout->count() > 0) { -+ m_layout->addStretch(); -+ } -+ -+ const QStringList savedLanguages = Settings::ocrLanguages(); -+ setSelectedLanguages(savedLanguages); -+ -+ if (savedLanguages.isEmpty() && !m_languageCheckboxes.isEmpty()) { -+ applyDefaults(); -+ } -+} -+ -+void OcrLanguageSelector::enforceSelectionLimits() -+{ -+ const QStringList selected = selectedLanguages(); -+ const int count = selected.size(); -+ -+ if (count > OcrManager::MAX_OCR_LANGUAGES) { // Max languages for performance -+ for (int i = m_languageCheckboxes.size() - 1; i >= 0; --i) { -+ QCheckBox *checkbox = m_languageCheckboxes[i]; -+ if (checkbox->isChecked()) { -+ blockSignalsAndSetChecked(checkbox, false); -+ break; -+ } -+ } -+ } -+ -+ updateCheckboxEnabledStates(); -+ -+ if (selectedLanguages().size() == 0 && !m_languageCheckboxes.isEmpty()) { -+ applyDefaults(); -+ } -+} -+ -+QString OcrLanguageSelector::getDefaultLanguageCode() const -+{ -+ if (m_languageCheckboxes.isEmpty()) { -+ return QString(); -+ } -+ -+ // Try English first -+ for (const QCheckBox *checkbox : m_languageCheckboxes) { -+ if (checkbox->property("languageCode").toString() == u"eng"_s) { -+ return u"eng"_s; -+ } -+ } -+ -+ // Fallback to first available -+ return m_languageCheckboxes.first()->property("languageCode").toString(); -+} -+ -+void OcrLanguageSelector::updateCheckboxEnabledStates() -+{ -+ const QStringList selected = selectedLanguages(); -+ const int count = selected.size(); -+ -+ // If we have max languages selected, disable all unchecked checkboxes -+ // If we have less than max, enable all checkboxes -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ if (checkbox->isChecked()) { -+ checkbox->setEnabled(true); -+ } else { -+ checkbox->setEnabled(count < OcrManager::MAX_OCR_LANGUAGES); -+ } -+ } -+} -+ -+void OcrLanguageSelector::blockSignalsAndSetChecked(QCheckBox *checkbox, bool checked) -+{ -+ m_blockSignals = true; -+ checkbox->setChecked(checked); -+ m_blockSignals = false; -+} -+ -+#include "moc_OcrLanguageSelector.cpp" -\ No newline at end of file -diff --git a/src/Gui/SettingsDialog/OcrLanguageSelector.h b/src/Gui/SettingsDialog/OcrLanguageSelector.h -new file mode 100644 -index 000000000..59b1a3d42 ---- /dev/null -+++ b/src/Gui/SettingsDialog/OcrLanguageSelector.h -@@ -0,0 +1,111 @@ -+/* -+ * SPDX-FileCopyrightText: 2025 Jhair Paris -+ * -+ * SPDX-License-Identifier: LGPL-2.0-or-later -+ */ -+ -+#ifndef OCRLANGUAGESELECTOR_H -+#define OCRLANGUAGESELECTOR_H -+ -+#include -+#include -+#include -+ -+class OcrManager; -+ -+/** -+ * @brief Specialized widget for OCR language selection with multi-language support -+ * -+ * This widget encapsulates all the logic for OCR language selection: -+ * - Displays available languages as checkboxes (excluding 'osd') -+ * - Enforces limits: minimum 1, maximum languages defined by OcrManager -+ * - Handles defaults: English preferred, fallback to first available -+ * - Follows KConfigDialog pattern: no auto-persistence, explicit save/update methods -+ * - Updates dynamically when OCR manager state changes -+ */ -+class OcrLanguageSelector : public QWidget -+{ -+ Q_OBJECT -+ Q_PROPERTY(QStringList selectedLanguages READ selectedLanguages WRITE setSelectedLanguages NOTIFY selectedLanguagesChanged USER true) -+ Q_PROPERTY(bool isDefault READ isDefault NOTIFY selectedLanguagesChanged) -+ Q_PROPERTY(bool hasChanges READ hasChanges NOTIFY selectedLanguagesChanged) -+ -+public: -+ explicit OcrLanguageSelector(QWidget *parent = nullptr); -+ ~OcrLanguageSelector() override; -+ -+ /** -+ * @brief Get currently selected language codes -+ * @return List of selected language codes (e.g., ["eng", "spa"]) -+ */ -+ QStringList selectedLanguages() const; -+ -+ /** -+ * @brief Set selected languages -+ * @param languages List of language codes to select -+ */ -+ void setSelectedLanguages(const QStringList &languages); -+ -+ /** -+ * @brief Check if current selection is the default state -+ * @return true if selection represents default configuration -+ */ -+ bool isDefault() const; -+ -+ /** -+ * @brief Check if there are unsaved changes -+ * @return true if current selection differs from saved configuration -+ */ -+ bool hasChanges() const; -+ -+ /** -+ * @brief Apply default language selection -+ * Selects English if available, otherwise first available language -+ */ -+ void applyDefaults(); -+ -+ /** -+ * @brief Refresh the widget when OCR manager state changes -+ * Rebuilds checkboxes based on current available languages -+ */ -+ void refresh(); -+ -+ /** -+ * @brief Save current selection to settings (called by KConfigDialog) -+ * Follows KConfigDialog pattern for saving changes -+ */ -+ void saveSettings(); -+ -+ /** -+ * @brief Update widget to reflect current settings (called by KConfigDialog) -+ * Reloads settings when user cancels or dialog is reopened -+ */ -+ void updateWidgets(); -+ -+Q_SIGNALS: -+ /** -+ * @brief Emitted when language selection changes -+ * @param languages New list of selected languages -+ */ -+ void selectedLanguagesChanged(const QStringList &languages); -+ -+private Q_SLOTS: -+ void onLanguageCheckboxChanged(); -+ void onOcrManagerStatusChanged(); -+ -+private: -+ void setupLanguageCheckboxes(); -+ void enforceSelectionLimits(); -+ void updateCheckboxEnabledStates(); -+ QString getDefaultLanguageCode() const; -+ void blockSignalsAndSetChecked(QCheckBox *checkbox, bool checked); -+ -+ QVBoxLayout *m_layout; -+ QList m_languageCheckboxes; -+ QMap m_availableLanguages; // code -> display name -+ bool m_blockSignals; -+ -+ OcrManager *m_ocrManager; -+}; -+ -+#endif // OCRLANGUAGESELECTOR_H -\ No newline at end of file -diff --git a/src/Gui/SettingsDialog/SettingsDialog.cpp b/src/Gui/SettingsDialog/SettingsDialog.cpp -index a19a47627..532bfd3c3 100644 ---- a/src/Gui/SettingsDialog/SettingsDialog.cpp -+++ b/src/Gui/SettingsDialog/SettingsDialog.cpp -@@ -1,4 +1,5 @@ - /* -+ * SPDX-FileCopyrightText: 2025 Jhair Paris - * SPDX-FileCopyrightText: 2019 David Redondo - * SPDX-FileCopyrightText: 2015 Boudhayan Gupta - * -@@ -9,8 +10,9 @@ - - #include "GeneralOptionsPage.h" - #include "ImageSaveOptionsPage.h" --#include "VideoSaveOptionsPage.h" -+#include "OcrLanguageSelector.h" - #include "ShortcutsOptionsPage.h" -+#include "VideoSaveOptionsPage.h" - #include "settings.h" - - #include -@@ -38,6 +40,9 @@ SettingsDialog::SettingsDialog(QWidget *parent) - connect(m_shortcutsPage, &ShortcutsOptionsPage::shortCutsChanged, this, [this] { - updateButtons(); - }); -+ connect(m_generalPage, &GeneralOptionsPage::ocrLanguageChanged, this, [this] { -+ updateButtons(); -+ }); - connect(this, &KConfigDialog::currentPageChanged, this, &SettingsDialog::updateButtons); - } - -@@ -72,18 +77,20 @@ void SettingsDialog::showEvent(QShowEvent *event) - - bool SettingsDialog::hasChanged() - { -- return m_shortcutsPage->isModified() || KConfigDialog::hasChanged(); -+ return m_shortcutsPage->isModified() || m_generalPage->ocrLanguageSelector()->hasChanges() || KConfigDialog::hasChanged(); - } - - bool SettingsDialog::isDefault() - { -- return currentPage()->name() != i18n("Shortcuts") && KConfigDialog::isDefault(); -+ return currentPage()->name() != i18n("Shortcuts") && m_generalPage->ocrLanguageSelector()->isDefault() && KConfigDialog::isDefault(); - } - - void SettingsDialog::updateSettings() - { - KConfigDialog::updateSettings(); - m_shortcutsPage->saveChanges(); -+ -+ m_generalPage->ocrLanguageSelector()->saveSettings(); - } - - void SettingsDialog::updateWidgets() -@@ -91,6 +98,7 @@ void SettingsDialog::updateWidgets() - KConfigDialog::updateWidgets(); - m_shortcutsPage->resetChanges(); - -+ m_generalPage->ocrLanguageSelector()->updateWidgets(); - m_generalPage->refreshOcrLanguageSettings(); - } - -@@ -98,6 +106,9 @@ void SettingsDialog::updateWidgetsDefault() - { - KConfigDialog::updateWidgetsDefault(); - m_shortcutsPage->defaults(); -+ -+ m_generalPage->ocrLanguageSelector()->applyDefaults(); -+ m_generalPage->refreshOcrLanguageSettings(); - } - - #include "moc_SettingsDialog.cpp" -diff --git a/src/Gui/SettingsDialog/spectacle.kcfg b/src/Gui/SettingsDialog/spectacle.kcfg -index 4517e2344..2062f7cc4 100644 ---- a/src/Gui/SettingsDialog/spectacle.kcfg -+++ b/src/Gui/SettingsDialog/spectacle.kcfg -@@ -70,8 +70,8 @@ - - UntilClosed - -- -- -+ -+ - eng - - -diff --git a/src/Gui/ViewerPage.qml b/src/Gui/ViewerPage.qml -index 602e4431b..133793964 100644 ---- a/src/Gui/ViewerPage.qml -+++ b/src/Gui/ViewerPage.qml -@@ -61,11 +61,13 @@ EmptyPage { - visible: action.enabled - action: CopyImageAction {} - } -+ - TtToolButton { - display: TtToolButton.IconOnly -- visible: action.enabled && SpectacleCore.ocrAvailable -+ visible: !SpectacleCore.videoMode && SpectacleCore.ocrAvailable - action: OcrAction {} - } -+ - // We only show this in video mode to save space in screenshot mode - TtToolButton { - visible: SpectacleCore.videoMode --- -GitLab - diff --git a/roles/kde/patches/spectacle/pr487.patch b/roles/kde/patches/spectacle/pr487.patch deleted file mode 100644 index 759e43a..0000000 --- a/roles/kde/patches/spectacle/pr487.patch +++ /dev/null @@ -1,704 +0,0 @@ -From d72a6fcb76053139ea709d7b1a4f45aa430a066d Mon Sep 17 00:00:00 2001 -From: Jhair Paris -Date: Sat, 8 Nov 2025 17:32:20 -0500 -Subject: [PATCH 1/4] Enhance OCR language settings management and - synchronization - -- Improved signal handling in OcrLanguageSelector to prevent unnecessary updates. -- Added config sync suspension functionality in OcrManager to manage settings changes. -- Adjusted SettingsDialog to ensure proper synchronization of OCR configurations. ---- - src/Gui/SettingsDialog/GeneralOptionsPage.cpp | 7 +- - src/Gui/SettingsDialog/GeneralOptionsPage.h | 4 +- - .../SettingsDialog/OcrLanguageSelector.cpp | 132 ++++++++---------- - src/Gui/SettingsDialog/OcrLanguageSelector.h | 7 +- - src/Gui/SettingsDialog/SettingsDialog.cpp | 25 +++- - src/Gui/SettingsDialog/SettingsDialog.h | 1 + - src/OcrManager.cpp | 25 ++++ - src/OcrManager.h | 3 + - 8 files changed, 117 insertions(+), 87 deletions(-) - -diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -index f6be13d56..ae0a997c3 100644 ---- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -+++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -@@ -10,7 +10,6 @@ - - #include "OcrLanguageSelector.h" - #include "OcrManager.h" --#include "settings.h" - #include "ui_GeneralOptions.h" - - #include -@@ -49,7 +48,7 @@ GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - - GeneralOptionsPage::~GeneralOptionsPage() = default; - --void GeneralOptionsPage::refreshOcrLanguageSettings() -+void GeneralOptionsPage::refreshOcrLanguageSettings(bool rebuildSelector) - { - OcrManager *ocrManager = OcrManager::instance(); - -@@ -62,7 +61,9 @@ void GeneralOptionsPage::refreshOcrLanguageSettings() - m_ui->ocrLanguageScrollArea->setVisible(true); - m_ui->ocrUnavailableWidget->setVisible(false); - -- m_ocrLanguageSelector->refresh(); -+ if (rebuildSelector) { -+ m_ocrLanguageSelector->refresh(); -+ } - } - } - -diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.h b/src/Gui/SettingsDialog/GeneralOptionsPage.h -index a3a5cb17d..bbb6d79a8 100644 ---- a/src/Gui/SettingsDialog/GeneralOptionsPage.h -+++ b/src/Gui/SettingsDialog/GeneralOptionsPage.h -@@ -20,8 +20,8 @@ class GeneralOptionsPage : public QWidget - public: - explicit GeneralOptionsPage(QWidget *parent = nullptr); - ~GeneralOptionsPage() override; -- -- void refreshOcrLanguageSettings(); -+ -+ void refreshOcrLanguageSettings(bool rebuildSelector = true); - - /** - * @brief Get direct access to the OCR language selector widget -diff --git a/src/Gui/SettingsDialog/OcrLanguageSelector.cpp b/src/Gui/SettingsDialog/OcrLanguageSelector.cpp -index d1d809323..815b4c707 100644 ---- a/src/Gui/SettingsDialog/OcrLanguageSelector.cpp -+++ b/src/Gui/SettingsDialog/OcrLanguageSelector.cpp -@@ -12,6 +12,7 @@ - #include - - #include -+#include - #include - - using namespace Qt::Literals::StringLiterals; -@@ -19,7 +20,6 @@ using namespace Qt::Literals::StringLiterals; - OcrLanguageSelector::OcrLanguageSelector(QWidget *parent) - : QWidget(parent) - , m_layout(new QVBoxLayout(this)) -- , m_blockSignals(false) - , m_ocrManager(OcrManager::instance()) - { - m_layout->setContentsMargins(0, 0, 0, 0); -@@ -46,15 +46,14 @@ QStringList OcrLanguageSelector::selectedLanguages() const - - void OcrLanguageSelector::setSelectedLanguages(const QStringList &languages) - { -- m_blockSignals = true; -+ QSignalBlocker blocker(this); - - for (QCheckBox *checkbox : m_languageCheckboxes) { - const QString langCode = checkbox->property("languageCode").toString(); -+ QSignalBlocker checkboxBlocker(checkbox); - checkbox->setChecked(languages.contains(langCode)); - } - -- m_blockSignals = false; -- - enforceSelectionLimits(); - } - -@@ -67,18 +66,11 @@ bool OcrLanguageSelector::isDefault() const - return false; - } - -- // Check if it's English (preferred default) -- for (const QCheckBox *checkbox : m_languageCheckboxes) { -- if (checkbox->property("languageCode").toString() == u"eng"_s) { -- // English is available, so default is English -- return current.contains(u"eng"_s); -- } -- } -+ QCheckBox *defaultCheckbox = findDefaultCheckbox(); - -- // English not available, default is the first available language -- if (!m_languageCheckboxes.isEmpty()) { -- QString firstLangCode = m_languageCheckboxes.first()->property("languageCode").toString(); -- return current.contains(firstLangCode); -+ if (defaultCheckbox) { -+ QString defaultLangCode = defaultCheckbox->property("languageCode").toString(); -+ return current.contains(defaultLangCode); - } - - return false; -@@ -91,36 +83,28 @@ bool OcrLanguageSelector::hasChanges() const - - void OcrLanguageSelector::applyDefaults() - { -- if (!m_languageCheckboxes.isEmpty()) { -- m_blockSignals = true; -- -- for (QCheckBox *checkbox : m_languageCheckboxes) { -- checkbox->setChecked(false); -- } -+ if (m_languageCheckboxes.isEmpty()) { -+ return; -+ } - -- // Try to select English first -- bool foundDefault = false; -- for (QCheckBox *checkbox : m_languageCheckboxes) { -- if (checkbox->property("languageCode").toString() == u"eng"_s) { -- checkbox->setChecked(true); -- foundDefault = true; -- break; -- } -- } -+ QSignalBlocker blocker(this); - -- // If English not available, select first language -- if (!foundDefault) { -- m_languageCheckboxes.first()->setChecked(true); -- } -+ QCheckBox *defaultCheckbox = findDefaultCheckbox(); - -- m_blockSignals = false; -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ QSignalBlocker checkboxBlocker(checkbox); -+ checkbox->setChecked(checkbox == defaultCheckbox); -+ } - -- const QStringList selected = selectedLanguages(); -- Settings::setOcrLanguages(selected); -+ const int selectedCount = defaultCheckbox ? 1 : 0; -+ updateCheckboxEnabledStates(selectedCount); - -- // Emit signal to notify changes -- Q_EMIT selectedLanguagesChanged(selected); -+ QStringList selected; -+ if (defaultCheckbox) { -+ selected.append(defaultCheckbox->property("languageCode").toString()); - } -+ -+ Q_EMIT selectedLanguagesChanged(selected); - } - - void OcrLanguageSelector::refresh() -@@ -142,13 +126,17 @@ void OcrLanguageSelector::updateWidgets() - - void OcrLanguageSelector::onLanguageCheckboxChanged() - { -- if (m_blockSignals) { -- return; -- } -- - enforceSelectionLimits(); - -- const QStringList selected = selectedLanguages(); -+ QStringList selected; -+ selected.reserve(OcrManager::MAX_OCR_LANGUAGES); -+ -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ if (checkbox->isChecked()) { -+ selected.append(checkbox->property("languageCode").toString()); -+ } -+ } -+ - Q_EMIT selectedLanguagesChanged(selected); - } - -@@ -167,21 +155,20 @@ void OcrLanguageSelector::setupLanguageCheckboxes() - } - - m_languageCheckboxes.clear(); -- m_availableLanguages.clear(); - - if (!m_ocrManager || !m_ocrManager->isAvailable()) { - qCWarning(SPECTACLE_LOG) << "OCR is not available; language selector will remain empty."; - return; - } - -- m_availableLanguages = m_ocrManager->availableLanguagesWithNames(); -+ const QMap availableLanguages = m_ocrManager->availableLanguagesWithNames(); - -- if (m_availableLanguages.isEmpty()) { -+ if (availableLanguages.isEmpty()) { - qCWarning(SPECTACLE_LOG) << "No OCR language data available."; - return; - } - -- for (auto it = m_availableLanguages.cbegin(); it != m_availableLanguages.cend(); ++it) { -+ for (auto it = availableLanguages.cbegin(); it != availableLanguages.cend(); ++it) { - const QString &langCode = it.key(); - if (langCode == u"osd"_s) { - continue; -@@ -208,64 +195,57 @@ void OcrLanguageSelector::setupLanguageCheckboxes() - - void OcrLanguageSelector::enforceSelectionLimits() - { -- const QStringList selected = selectedLanguages(); -- const int count = selected.size(); -+ int selectedCount = 0; -+ -+ for (QCheckBox *checkbox : m_languageCheckboxes) { -+ if (checkbox->isChecked()) { -+ ++selectedCount; -+ } -+ } - -- if (count > OcrManager::MAX_OCR_LANGUAGES) { // Max languages for performance -+ if (selectedCount > OcrManager::MAX_OCR_LANGUAGES) { - for (int i = m_languageCheckboxes.size() - 1; i >= 0; --i) { - QCheckBox *checkbox = m_languageCheckboxes[i]; - if (checkbox->isChecked()) { -- blockSignalsAndSetChecked(checkbox, false); -+ QSignalBlocker blocker(checkbox); -+ checkbox->setChecked(false); -+ --selectedCount; - break; - } - } - } - -- updateCheckboxEnabledStates(); -+ updateCheckboxEnabledStates(selectedCount); - -- if (selectedLanguages().size() == 0 && !m_languageCheckboxes.isEmpty()) { -+ if (selectedCount == 0 && !m_languageCheckboxes.isEmpty()) { - applyDefaults(); - } - } - --QString OcrLanguageSelector::getDefaultLanguageCode() const -+QCheckBox *OcrLanguageSelector::findDefaultCheckbox() const - { - if (m_languageCheckboxes.isEmpty()) { -- return QString(); -+ return nullptr; - } - - // Try English first -- for (const QCheckBox *checkbox : m_languageCheckboxes) { -+ for (QCheckBox *checkbox : m_languageCheckboxes) { - if (checkbox->property("languageCode").toString() == u"eng"_s) { -- return u"eng"_s; -+ return checkbox; - } - } - - // Fallback to first available -- return m_languageCheckboxes.first()->property("languageCode").toString(); -+ return m_languageCheckboxes.first(); - } - --void OcrLanguageSelector::updateCheckboxEnabledStates() -+void OcrLanguageSelector::updateCheckboxEnabledStates(int selectedCount) - { -- const QStringList selected = selectedLanguages(); -- const int count = selected.size(); -+ const bool enableUnchecked = selectedCount < OcrManager::MAX_OCR_LANGUAGES; - -- // If we have max languages selected, disable all unchecked checkboxes -- // If we have less than max, enable all checkboxes - for (QCheckBox *checkbox : m_languageCheckboxes) { -- if (checkbox->isChecked()) { -- checkbox->setEnabled(true); -- } else { -- checkbox->setEnabled(count < OcrManager::MAX_OCR_LANGUAGES); -- } -+ checkbox->setEnabled(checkbox->isChecked() || enableUnchecked); - } - } - --void OcrLanguageSelector::blockSignalsAndSetChecked(QCheckBox *checkbox, bool checked) --{ -- m_blockSignals = true; -- checkbox->setChecked(checked); -- m_blockSignals = false; --} -- - #include "moc_OcrLanguageSelector.cpp" -\ No newline at end of file -diff --git a/src/Gui/SettingsDialog/OcrLanguageSelector.h b/src/Gui/SettingsDialog/OcrLanguageSelector.h -index 59b1a3d42..e938e06fa 100644 ---- a/src/Gui/SettingsDialog/OcrLanguageSelector.h -+++ b/src/Gui/SettingsDialog/OcrLanguageSelector.h -@@ -96,14 +96,11 @@ private Q_SLOTS: - private: - void setupLanguageCheckboxes(); - void enforceSelectionLimits(); -- void updateCheckboxEnabledStates(); -- QString getDefaultLanguageCode() const; -- void blockSignalsAndSetChecked(QCheckBox *checkbox, bool checked); -+ void updateCheckboxEnabledStates(int selectedCount); -+ QCheckBox *findDefaultCheckbox() const; - - QVBoxLayout *m_layout; - QList m_languageCheckboxes; -- QMap m_availableLanguages; // code -> display name -- bool m_blockSignals; - - OcrManager *m_ocrManager; - }; -diff --git a/src/Gui/SettingsDialog/SettingsDialog.cpp b/src/Gui/SettingsDialog/SettingsDialog.cpp -index 532bfd3c3..696636685 100644 ---- a/src/Gui/SettingsDialog/SettingsDialog.cpp -+++ b/src/Gui/SettingsDialog/SettingsDialog.cpp -@@ -11,6 +11,7 @@ - #include "GeneralOptionsPage.h" - #include "ImageSaveOptionsPage.h" - #include "OcrLanguageSelector.h" -+#include "OcrManager.h" - #include "ShortcutsOptionsPage.h" - #include "VideoSaveOptionsPage.h" - #include "settings.h" -@@ -46,6 +47,16 @@ SettingsDialog::SettingsDialog(QWidget *parent) - connect(this, &KConfigDialog::currentPageChanged, this, &SettingsDialog::updateButtons); - } - -+SettingsDialog::~SettingsDialog() -+{ -+ // Ensure OCR config sync is resumed -+ if (OcrManager *ocrManager = OcrManager::instance()) { -+ if (ocrManager->isConfigSyncSuspended()) { -+ ocrManager->setConfigSyncSuspended(false); -+ } -+ } -+} -+ - QSize SettingsDialog::sizeHint() const - { - // Avoid having pages that need to be scrolled, -@@ -91,6 +102,10 @@ void SettingsDialog::updateSettings() - m_shortcutsPage->saveChanges(); - - m_generalPage->ocrLanguageSelector()->saveSettings(); -+ -+ if (OcrManager *ocrManager = OcrManager::instance()) { -+ ocrManager->setConfigSyncSuspended(false); -+ } - } - - void SettingsDialog::updateWidgets() -@@ -100,15 +115,23 @@ void SettingsDialog::updateWidgets() - - m_generalPage->ocrLanguageSelector()->updateWidgets(); - m_generalPage->refreshOcrLanguageSettings(); -+ -+ if (OcrManager *ocrManager = OcrManager::instance()) { -+ ocrManager->setConfigSyncSuspended(false); -+ } - } - - void SettingsDialog::updateWidgetsDefault() - { -+ if (OcrManager *ocrManager = OcrManager::instance()) { -+ ocrManager->setConfigSyncSuspended(true); -+ } -+ - KConfigDialog::updateWidgetsDefault(); - m_shortcutsPage->defaults(); - - m_generalPage->ocrLanguageSelector()->applyDefaults(); -- m_generalPage->refreshOcrLanguageSettings(); -+ m_generalPage->refreshOcrLanguageSettings(false); - } - - #include "moc_SettingsDialog.cpp" -diff --git a/src/Gui/SettingsDialog/SettingsDialog.h b/src/Gui/SettingsDialog/SettingsDialog.h -index 50f6d85bf..64281dd09 100644 ---- a/src/Gui/SettingsDialog/SettingsDialog.h -+++ b/src/Gui/SettingsDialog/SettingsDialog.h -@@ -20,6 +20,7 @@ class SettingsDialog : public KConfigDialog - - public: - explicit SettingsDialog(QWidget *parent = nullptr); -+ ~SettingsDialog() override; - - protected: - QSize sizeHint() const override; -diff --git a/src/OcrManager.cpp b/src/OcrManager.cpp -index 1d09db8ef..56d467993 100644 ---- a/src/OcrManager.cpp -+++ b/src/OcrManager.cpp -@@ -56,6 +56,9 @@ OcrManager::OcrManager(QObject *parent) - m_workerThread->start(); - - connect(Settings::self(), &Settings::ocrLanguagesChanged, this, [this]() { -+ if (m_configSyncSuspended) { -+ return; -+ } - const QStringList newLanguages = Settings::ocrLanguages(); - const QString combinedLanguages = newLanguages.join(u"+"_s); - if (combinedLanguages != m_currentLanguageCode) { -@@ -150,6 +153,28 @@ QString OcrManager::currentLanguageCode() const - return m_currentLanguageCode; - } - -+void OcrManager::setConfigSyncSuspended(bool suspended) -+{ -+ if (m_configSyncSuspended == suspended) { -+ return; -+ } -+ -+ m_configSyncSuspended = suspended; -+ -+ // On resume, apply any changes made to Settings -+ if (!m_configSyncSuspended) { -+ const QStringList settingsLanguages = Settings::ocrLanguages(); -+ if (settingsLanguages != m_configuredLanguages) { -+ setLanguagesByCode(settingsLanguages); -+ } -+ } -+} -+ -+bool OcrManager::isConfigSyncSuspended() const -+{ -+ return m_configSyncSuspended; -+} -+ - void OcrManager::recognizeText(const QImage &image) - { - #ifdef HAVE_TESSERACT_OCR -diff --git a/src/OcrManager.h b/src/OcrManager.h -index c71505b3e..37f490600 100644 ---- a/src/OcrManager.h -+++ b/src/OcrManager.h -@@ -98,6 +98,8 @@ public: - * @return Current language code (e.g., "eng", "spa") - */ - QString currentLanguageCode() const; -+ void setConfigSyncSuspended(bool suspended); -+ bool isConfigSyncSuspended() const; - - public Q_SLOTS: - /** -@@ -169,6 +171,7 @@ private: - bool m_shouldRestoreToConfigured; - QStringList m_availableLanguages; - QMap m_languageNames; -+ bool m_configSyncSuspended = false; - bool m_initialized; - - private: --- -GitLab - - -From 642600410714c783515f2416a9be08ef3406b0d9 Mon Sep 17 00:00:00 2001 -From: Jhair Paris -Date: Sat, 8 Nov 2025 21:30:41 -0500 -Subject: [PATCH 2/4] Fix flickering in settings dialog during OCR - initialization - ---- - src/Gui/SettingsDialog/GeneralOptionsPage.cpp | 13 +++++++------ - 1 file changed, 7 insertions(+), 6 deletions(-) - -diff --git a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -index ae0a997c3..adfc045e6 100644 ---- a/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -+++ b/src/Gui/SettingsDialog/GeneralOptionsPage.cpp -@@ -38,7 +38,7 @@ GeneralOptionsPage::GeneralOptionsPage(QWidget *parent) - - connect(m_ocrLanguageSelector, &OcrLanguageSelector::selectedLanguagesChanged, this, &GeneralOptionsPage::ocrLanguageChanged); - -- refreshOcrLanguageSettings(); -+ refreshOcrLanguageSettings(false); - - //On Wayland we can't programmatically raise and focus the window so we have to hide the option - if (KWindowSystem::isPlatformWayland() || qstrcmp(qgetenv("XDG_SESSION_TYPE").constData(), "wayland") == 0) { -@@ -51,8 +51,9 @@ GeneralOptionsPage::~GeneralOptionsPage() = default; - void GeneralOptionsPage::refreshOcrLanguageSettings(bool rebuildSelector) - { - OcrManager *ocrManager = OcrManager::instance(); -- -- if (!ocrManager->isAvailable()) { -+ const bool ocrAvailable = ocrManager->isAvailable(); -+ -+ if (!ocrAvailable) { - m_ui->ocrLanguageLabel->setVisible(false); - m_ui->ocrLanguageScrollArea->setVisible(false); - m_ui->ocrUnavailableWidget->setVisible(true); -@@ -60,10 +61,10 @@ void GeneralOptionsPage::refreshOcrLanguageSettings(bool rebuildSelector) - m_ui->ocrLanguageLabel->setVisible(true); - m_ui->ocrLanguageScrollArea->setVisible(true); - m_ui->ocrUnavailableWidget->setVisible(false); -+ } - -- if (rebuildSelector) { -- m_ocrLanguageSelector->refresh(); -- } -+ if (ocrAvailable && rebuildSelector) { -+ m_ocrLanguageSelector->refresh(); - } - } - --- -GitLab - - -From 6c6d95f3fd87ff70f8c1d25786a5a7f047e9d74f Mon Sep 17 00:00:00 2001 -From: Jhair Paris -Date: Sat, 8 Nov 2025 21:51:05 -0500 -Subject: [PATCH 3/4] Improve OCR language handling in settings dialog - ---- - src/Gui/SettingsDialog/SettingsDialog.cpp | 24 ++++++++++++++++++----- - 1 file changed, 19 insertions(+), 5 deletions(-) - -diff --git a/src/Gui/SettingsDialog/SettingsDialog.cpp b/src/Gui/SettingsDialog/SettingsDialog.cpp -index 696636685..0bfab8bb9 100644 ---- a/src/Gui/SettingsDialog/SettingsDialog.cpp -+++ b/src/Gui/SettingsDialog/SettingsDialog.cpp -@@ -88,12 +88,20 @@ void SettingsDialog::showEvent(QShowEvent *event) - - bool SettingsDialog::hasChanged() - { -- return m_shortcutsPage->isModified() || m_generalPage->ocrLanguageSelector()->hasChanges() || KConfigDialog::hasChanged(); -+ bool ocrHasChanges = false; -+ if (OcrManager::instance()->isAvailable()) { -+ ocrHasChanges = m_generalPage->ocrLanguageSelector()->hasChanges(); -+ } -+ return m_shortcutsPage->isModified() || ocrHasChanges || KConfigDialog::hasChanged(); - } - - bool SettingsDialog::isDefault() - { -- return currentPage()->name() != i18n("Shortcuts") && m_generalPage->ocrLanguageSelector()->isDefault() && KConfigDialog::isDefault(); -+ bool ocrIsDefault = true; -+ if (OcrManager::instance()->isAvailable()) { -+ ocrIsDefault = m_generalPage->ocrLanguageSelector()->isDefault(); -+ } -+ return currentPage()->name() != i18n("Shortcuts") && ocrIsDefault && KConfigDialog::isDefault(); - } - - void SettingsDialog::updateSettings() -@@ -101,7 +109,9 @@ void SettingsDialog::updateSettings() - KConfigDialog::updateSettings(); - m_shortcutsPage->saveChanges(); - -- m_generalPage->ocrLanguageSelector()->saveSettings(); -+ if (OcrManager::instance()->isAvailable()) { -+ m_generalPage->ocrLanguageSelector()->saveSettings(); -+ } - - if (OcrManager *ocrManager = OcrManager::instance()) { - ocrManager->setConfigSyncSuspended(false); -@@ -113,7 +123,9 @@ void SettingsDialog::updateWidgets() - KConfigDialog::updateWidgets(); - m_shortcutsPage->resetChanges(); - -- m_generalPage->ocrLanguageSelector()->updateWidgets(); -+ if (OcrManager::instance()->isAvailable()) { -+ m_generalPage->ocrLanguageSelector()->updateWidgets(); -+ } - m_generalPage->refreshOcrLanguageSettings(); - - if (OcrManager *ocrManager = OcrManager::instance()) { -@@ -130,7 +142,9 @@ void SettingsDialog::updateWidgetsDefault() - KConfigDialog::updateWidgetsDefault(); - m_shortcutsPage->defaults(); - -- m_generalPage->ocrLanguageSelector()->applyDefaults(); -+ if (OcrManager::instance()->isAvailable()) { -+ m_generalPage->ocrLanguageSelector()->applyDefaults(); -+ } - m_generalPage->refreshOcrLanguageSettings(false); - } - --- -GitLab - - -From 174b4a4a10e2c42fa28eb361cd4b6a833af60dc7 Mon Sep 17 00:00:00 2001 -From: Jhair Paris -Date: Mon, 10 Nov 2025 22:10:51 -0500 -Subject: [PATCH 4/4] Remove Tesseract language pack validation test and - simplify OCR support check - ---- - CMakeLists.txt | 24 ++---------------------- - cmake/tesseract_test.cpp | 40 ---------------------------------------- - 2 files changed, 2 insertions(+), 62 deletions(-) - delete mode 100644 cmake/tesseract_test.cpp - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index ea44e71d4..2adf8a9ac 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -96,28 +96,8 @@ if(PkgConfig_FOUND) - pkg_check_modules(TESSERACT tesseract) - - if(TESSERACT_FOUND) -- # Test if Tesseract has usable language packs -- try_run( -- TESSERACT_TEST_RUN_RESULT -- TESSERACT_TEST_COMPILE_RESULT -- ${CMAKE_CURRENT_BINARY_DIR} -- ${CMAKE_CURRENT_SOURCE_DIR}/cmake/tesseract_test.cpp -- LINK_LIBRARIES ${TESSERACT_LIBRARIES} -- CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${TESSERACT_INCLUDE_DIRS}" -- COMPILE_OUTPUT_VARIABLE TESSERACT_COMPILE_OUTPUT -- RUN_OUTPUT_VARIABLE TESSERACT_RUN_OUTPUT -- ) -- -- if(TESSERACT_TEST_COMPILE_RESULT AND TESSERACT_TEST_RUN_RESULT EQUAL 0) -- message(STATUS "Tesseract OCR support enabled") -- message(STATUS "${TESSERACT_RUN_OUTPUT}") -- set(HAVE_TESSERACT_OCR TRUE) -- else() -- message(WARNING "Tesseract library found but no usable language packs detected") -- message(WARNING "${TESSERACT_RUN_OUTPUT}") -- message(WARNING "OCR functionality will be disabled. Install language data packages (e.g., tesseract-ocr-eng)") -- set(HAVE_TESSERACT_OCR FALSE) -- endif() -+ message(STATUS "Tesseract OCR support enabled") -+ set(HAVE_TESSERACT_OCR TRUE) - else() - message(STATUS "Tesseract not found - OCR functionality disabled") - set(HAVE_TESSERACT_OCR FALSE) -diff --git a/cmake/tesseract_test.cpp b/cmake/tesseract_test.cpp -deleted file mode 100644 -index 4ebae9779..000000000 ---- a/cmake/tesseract_test.cpp -+++ /dev/null -@@ -1,40 +0,0 @@ --#include --#include --#include --#include -- --int main() --{ -- tesseract::TessBaseAPI api; -- -- if (api.Init(nullptr, nullptr) != 0) { -- std::cerr << "Failed to initialize Tesseract" << std::endl; -- return 1; -- } -- -- std::vector languages; -- api.GetAvailableLanguagesAsVector(&languages); -- -- // Filter out 'osd' as it's not a usable language for OCR -- std::vector usableLanguages; -- for (const auto &lang : languages) { -- if (lang != "osd") { -- usableLanguages.push_back(lang); -- } -- } -- -- if (usableLanguages.empty()) { -- std::cerr << "No usable Tesseract language packs found. Install language data files (e.g., tesseract-ocr-eng)" << std::endl; -- return 1; -- } -- -- std::cout << "Found " << usableLanguages.size() << " Tesseract language pack(s): "; -- for (size_t i = 0; i < usableLanguages.size(); ++i) { -- std::cout << usableLanguages[i]; -- if (i < usableLanguages.size() - 1) -- std::cout << ", "; -- } -- std::cout << std::endl; -- -- return 0; --} --- -GitLab - diff --git a/roles/kde/patches/spectacle/pr493.patch b/roles/kde/patches/spectacle/pr493.patch deleted file mode 100644 index d6db2f7..0000000 --- a/roles/kde/patches/spectacle/pr493.patch +++ /dev/null @@ -1,129 +0,0 @@ -From 49c615c1989d9fcfce7ed1be805538a9dca6a8a8 Mon Sep 17 00:00:00 2001 -From: Taras Oleksyn -Date: Sat, 29 Nov 2025 10:17:28 +0200 -Subject: [PATCH] Add cancel button to capture window to improve touchscreen - usability - -BUG: 490980 ---- - src/CMakeLists.txt | 1 + - src/Gui/CancelAction.qml | 11 +++++++++++ - src/Gui/CaptureOverlay.qml | 14 ++++++++++++++ - src/Gui/CaptureWindow.cpp | 5 +++++ - src/Gui/CaptureWindow.h | 1 + - src/PlasmaVersion.h | 2 +- - 6 files changed, 33 insertions(+), 1 deletion(-) - create mode 100644 src/Gui/CancelAction.qml - -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 23fc4f483..f4957bda7 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -158,6 +158,7 @@ qt_target_qml_sources(spectacle - Gui/AnnotationOptionsToolBarContents.qml - Gui/AnnotationsToolBarContents.qml - Gui/ButtonGrid.qml -+ Gui/CancelAction.qml - Gui/CaptureModeButtonsColumn.qml - Gui/CaptureOptions.qml - Gui/CaptureOverlay.qml -diff --git a/src/Gui/CancelAction.qml b/src/Gui/CancelAction.qml -new file mode 100644 -index 000000000..74bf54816 ---- /dev/null -+++ b/src/Gui/CancelAction.qml -@@ -0,0 +1,11 @@ -+/* SPDX-FileCopyrightText: 2025 Noah Davis -+ * SPDX-License-Identifier: LGPL-2.0-or-later -+ */ -+ -+import QtQuick.Templates as T -+ -+T.Action { -+ icon.name: "dialog-cancel" -+ text: i18nc("@action cancel selection", "Cancel") -+ onTriggered: contextWindow.cancel() -+} -diff --git a/src/Gui/CaptureOverlay.qml b/src/Gui/CaptureOverlay.qml -index e5089934d..5c15d9939 100644 ---- a/src/Gui/CaptureOverlay.qml -+++ b/src/Gui/CaptureOverlay.qml -@@ -491,6 +491,11 @@ MouseArea { - visible: action.enabled - action: AcceptAction {} - } -+ ToolButton { -+ display: TtToolButton.TextBesideIcon -+ visible: action.enabled -+ action: CancelAction {} -+ } - ToolButton { - display: TtToolButton.IconOnly - visible: action.enabled -@@ -527,6 +532,10 @@ MouseArea { - visible: action.enabled - action: AcceptAction {} - } -+ ToolButton { -+ visible: action.enabled -+ action: CancelAction {} -+ } - ToolButton { - visible: action.enabled - action: SaveAction {} -@@ -560,6 +569,11 @@ MouseArea { - visible: action.enabled - action: RecordAction {} - } -+ ToolButton { -+ display: TtToolButton.TextBesideIcon -+ visible: action.enabled -+ action: CancelAction {} -+ } - } - } - -diff --git a/src/Gui/CaptureWindow.cpp b/src/Gui/CaptureWindow.cpp -index cb8ce97ab..c263d1b45 100644 ---- a/src/Gui/CaptureWindow.cpp -+++ b/src/Gui/CaptureWindow.cpp -@@ -129,6 +129,11 @@ bool CaptureWindow::accept() - return SelectionEditor::instance()->acceptSelection(); - } - -+void CaptureWindow::cancel() -+{ -+ SpectacleCore::instance()->cancelScreenshot(); -+} -+ - void CaptureWindow::save() - { - SelectionEditor::instance()->acceptSelection(ExportManager::Save | ExportManager::UserAction); -diff --git a/src/Gui/CaptureWindow.h b/src/Gui/CaptureWindow.h -index b5e87a834..0d50aac90 100644 ---- a/src/Gui/CaptureWindow.h -+++ b/src/Gui/CaptureWindow.h -@@ -34,6 +34,7 @@ public: - - public Q_SLOTS: - bool accept(); -+ void cancel(); - void save() override; - void saveAs() override; - void copyImage() override; -diff --git a/src/PlasmaVersion.h b/src/PlasmaVersion.h -index 08502c895..9518da62b 100644 ---- a/src/PlasmaVersion.h -+++ b/src/PlasmaVersion.h -@@ -15,7 +15,7 @@ public: - static quint32 get(); - - /** -- * Use this for plasama versions the same way you'd use QT_VERSION_CHECK() -+ * Use this for plasma versions the same way you'd use QT_VERSION_CHECK() - */ - static quint32 check(quint8 major, quint8 minor, quint8 patch); - --- -GitLab - diff --git a/roles/kde/plasma.nix b/roles/kde/plasma.nix index 9d4e8b9..d2e7ab0 100644 --- a/roles/kde/plasma.nix +++ b/roles/kde/plasma.nix @@ -1,141 +1,62 @@ +{ config, pkgs, lib, ... }: + +let + themeName = "Breeze"; + gtk3and4settings = lib.generators.toINI {} { + Settings = { + gtk-theme-name = themeName; + gtk-cursor-theme-name = "breeze_cursors"; + }; + }; + + # The configuration files that Plasma made have set the theme in gtkrc, not gtkrc-2.0 + # which is weird cause applying the theme to gtk1 but not 2 is dumb. I'll write it to + # both files just in case :3 + gtk1and2settings = '' + include "/run/current-system/sw/share/themes/${themeName}/gtk-2.0/gtkrc" + gtk-theme-name="${themeName}" + ''; +in + { - config, - pkgs, - lib, - flakeSelf, - ... -}: let - # Set up the default kde options - balooExcludedDirs = lib.strings.intersperse "," [ - "$HOME/.cache/" - "$HOME/.config/" - "$HOME/.local/" - ]; + # Enable the Plasma 5 Desktop Environment. + services.xserver.displayManager.sddm.enable = true; + services.xserver.desktopManager.plasma5.enable = true; - baloofilerc = lib.generators.toINI {} { - General = { - # The [$e] part allows you to use environment variables - "exclude folders[$e]" = lib.strings.concatStrings balooExcludedDirs; - }; - }; + # GTK apps need dconf to grab the correct theme on Wayland + programs.dconf.enable = true; - # /etc/xdg is not read by plasma, so to change the default settings you need to put them in a package - plasmaDefaults = pkgs.stdenv.mkDerivation { - name = "toast-plasma-defaults"; - dontUnpack = true; - installPhase = '' - runHook preInstall + # The NixOS dconf module doesn't support creating databases + # (https://github.com/NixOS/nixpkgs/issues/54150), so I'm + # doing it manualy - set -x - mkdir -p $out/etc/xdg - echo '${baloofilerc}' > $out/etc/xdg/baloofilerc + # https://help.gnome.org/admin/system-admin-guide/stable/dconf-custom-defaults.html.en + environment.etc = { + "dconf/profile/user".text = "user-db:user\nsystem-db:local"; + "dconf/db/local.d/00-defaultTheme".text = '' + [org/gnome/desktop/interface] + gtk-theme='${themeName}' + ''; + }; - runHook postInstall - ''; - }; -in { - # Enable the Plasma 6 Desktop Environment - services.desktopManager.plasma6.enable = true; + system.activationScripts = { + dconf = { + text= '' + echo "updating system dconf database..." + ${pkgs.dconf}/bin/dconf update + touch /testfile + ''; + deps = [ "etc" ]; + }; + }; - qt.enable = true; - - # GTK apps need dconf to grab the correct theme on Wayland - programs.dconf.enable = true; - - # Install the plasma default configs - environment.systemPackages = with pkgs.kdePackages; [ - plasmaDefaults - plasma-thunderbolt - plasma-vault - ]; - - # Allows controlling brightness on external monitors - hardware.i2c.enable = true; - - # Plasma configs should be on all users - home-manager.sharedModules = [ - { - imports = [flakeSelf.inputs.plasma-manager.homeModules.plasma-manager]; - home.packages = [ - ( - pkgs.catppuccin-kde.override { - flavour = ["mocha"]; - accents = ["mauve"]; - winDecStyles = ["classic"]; - } - ) - ]; - programs.plasma = { - enable = true; - workspace = { - clickItemTo = "select"; - cursor.theme = "Breeze_Light"; - iconTheme = "breeze-dark"; - lookAndFeel = "Catppuccin-Mocha-Mauve"; - theme = "default"; - colorScheme = "CatppuccinMochaMauve"; - }; - input = { - keyboard = { - layouts = [{layout = "es";}]; - numlockOnStartup = "off"; - }; - mice = let - settings = { - enable = true; - accelerationProfile = "none"; - }; - mice = [ - { - productId = "d030"; - vendorId = "3434"; - name = "Keychron Keychron Link "; - } - { - productId = "d03f"; - vendorId = "3434"; - name = "Keychron Keychron M6 "; - } - { - productId = "d03f"; - vendorId = "3434"; - name = "Keychron M6 Mouse"; - } - ]; - in - lib.lists.forEach mice (miceInfo: miceInfo // settings); - }; - panels = [ - { - location = "bottom"; - height = 44; - floating = true; - widgets = [ - { - kickoff = { - icon = "nix-snowflake-white"; - settings.General.switchCategoryOnHover = true; - }; - } - "org.kde.plasma.pager" - "org.kde.plasma.icontasks" - "org.kde.plasma.marginsseparator" - "org.kde.plasma.systemtray" - { - digitalClock = { - time.showSeconds = "always"; - }; - } - "org.kde.plasma.showdesktop" - ]; - } - ]; - configFile = { - "kdeglobals"."General"."AccentColor".value = null; - "auroraerc"."CatppuccinMocha-Classic"."ButtonSize".value = 0; - "plasmanotifyrc"."Notifications"."NormalAlwaysOnTop".value = true; - }; - }; - } - ]; + # Set up GTK to use Breeze instead of adwaita by default + # Theese only seem to work on X11, not wayland + environment.etc = { + "xdg/gtk-4.0/settings.ini".text = gtk3and4settings; + "xdg/gtk-3.0/settings.ini".text = gtk3and4settings; + "xdg/gtkrc-2.0".text = gtk1and2settings; + "xdg/gtkrc".text = gtk1and2settings; + }; + environment.systemPackages = [pkgs.yaru-theme]; } diff --git a/roles/kde/programs/baloo.nix b/roles/kde/programs/baloo.nix deleted file mode 100644 index 572376a..0000000 --- a/roles/kde/programs/baloo.nix +++ /dev/null @@ -1,16 +0,0 @@ -{lib, ...}: { - home-manager.users.toast = { - programs.plasma.configFile = { - "baloofilerc"."General" = { - "exclude folders".shellExpand = true; - "exclude folders".value = with lib.strings; - concatStrings ( - intersperse "," [ - "$HOME/Documents/Repos" - "$HOME/Documents/Android" - ] - ); - }; - }; - }; -} diff --git a/roles/kde/programs/default.nix b/roles/kde/programs/default.nix deleted file mode 100644 index 0ac9c9f..0000000 --- a/roles/kde/programs/default.nix +++ /dev/null @@ -1,17 +0,0 @@ -{pkgs, ...}: { - imports = [ - ./kate.nix - ./firefox.nix - ./skanpage.nix - # Neochat depends on olm which is unsafe now - # ./neochat.nix - ./konsole.nix - ./kwin.nix - ./baloo.nix - ./spectacle.nix - ]; - - # Enable the kde partition manager - programs.partition-manager.enable = true; - programs.steam.extraPackages = [pkgs.kdePackages.breeze]; -} diff --git a/roles/kde/programs/firefox.nix b/roles/kde/programs/firefox.nix deleted file mode 100644 index 41f81e2..0000000 --- a/roles/kde/programs/firefox.nix +++ /dev/null @@ -1,35 +0,0 @@ -{pkgs, ...}: { - home-manager.sharedModules = [ - { - # KDE specific firefox settings - programs.firefox = { - nativeMessagingHosts = [pkgs.kdePackages.plasma-browser-integration]; - policies = { - ExtensionSettings = { - # TODO: Install extensions the NUR instead of from AMO - "plasma-browser-integration@kde.org" = { - installation_mode = "normal_installed"; - install_url = "https://addons.mozilla.org/firefox/downloads/latest/plasma-integration/latest.xpi"; - }; - }; - Preferences = { - # Make firefox use the kde file picker - "widget.use-xdg-desktop-portal.file-picker" = { - Value = 1; - Status = "default"; - }; - /* - https://wiki.archlinux.org/title/Firefox#KDE_integration tells me to enable this, - but strangely enough doing so makes firefox ask to be set as the default browser - every time you start it up, so I'll disable it - */ - "widget.use-xdg-desktop-portal.mime-handler" = { - Value = 0; - Status = "default"; - }; - }; - }; - }; - } - ]; -} diff --git a/roles/kde/programs/kate.nix b/roles/kde/programs/kate.nix deleted file mode 100644 index 3e8ff36..0000000 --- a/roles/kde/programs/kate.nix +++ /dev/null @@ -1,8 +0,0 @@ -{pkgs, ...}: { - environment.systemPackages = [pkgs.kdePackages.kate]; - - # Use kwrite to open text files, and kate if I'm developing stuff - xdg.mime.defaultApplications = { - "text/plain" = "org.kde.kwrite.desktop"; - }; -} diff --git a/roles/kde/programs/konsole.nix b/roles/kde/programs/konsole.nix deleted file mode 100644 index ef3b429..0000000 --- a/roles/kde/programs/konsole.nix +++ /dev/null @@ -1,19 +0,0 @@ -{flakeSelf, ...}: let - catppuccinKonsole = "${flakeSelf.inputs.catppuccin-konsole}/themes/catppuccin-mocha.colorscheme"; -in { - home-manager.users.toast = { - xdg.dataFile = { - "konsole/Catppuccin-Mocha.colorscheme".source = catppuccinKonsole; - }; - programs.konsole = { - enable = true; - defaultProfile = "Toast"; - profiles.toast = { - name = "Toast"; - colorScheme = "Catppuccin-Mocha"; - font.name = "JetBrainsMono Nerd Font"; - font.size = 10; - }; - }; - }; -} diff --git a/roles/kde/programs/kwin.nix b/roles/kde/programs/kwin.nix deleted file mode 100644 index 1e4f3ae..0000000 --- a/roles/kde/programs/kwin.nix +++ /dev/null @@ -1,33 +0,0 @@ -{pkgs, ...}: { - environment.plasma6.excludePackages = [pkgs.kdePackages.kwin-x11]; - environment.variables = { - KWIN_WAYLAND_SUPPORT_XX_PIP_V1 = 1; - KWIN_USE_OVERLAYS = 1; - }; - home-manager.users.toast = { - programs.plasma = { - kwin = { - titlebarButtons = { - left = ["on-all-desktops" "keep-above-windows"]; - right = ["minimize" "maximize" "close"]; - }; - virtualDesktops = { - rows = 1; - number = 2; - }; - }; - configFile = { - "kwinrc" = { - "org.kde.kdecoration2"."BorderSize".value = "None"; - "TabBox"."LayoutName".value = "thumbnail_grid"; - }; - }; - shortcuts = { - "kwin" = { - "Switch One Desktop to the Left" = ["Meta+Ctrl+Left"]; - "Switch One Desktop to the Right" = ["Meta+Ctrl+Right"]; - }; - }; - }; - }; -} diff --git a/roles/kde/programs/neochat.nix b/roles/kde/programs/neochat.nix deleted file mode 100644 index 0e666d4..0000000 --- a/roles/kde/programs/neochat.nix +++ /dev/null @@ -1,5 +0,0 @@ -{pkgs, ...}: { - home-manager.users.toast = { - home.packages = [pkgs.neochat]; - }; -} diff --git a/roles/kde/programs/skanpage.nix b/roles/kde/programs/skanpage.nix deleted file mode 100644 index 114ef84..0000000 --- a/roles/kde/programs/skanpage.nix +++ /dev/null @@ -1,12 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: { - # Only install skanpage if scanning is set up - config = lib.mkIf config.hardware.sane.enable { - environment.systemPackages = [pkgs.kdePackages.skanpage]; - }; - # environment.systemPackages = if config.hardware.sane.enable == true then [ pkgs.skanpage ] else []; -} diff --git a/roles/kde/programs/spectacle.nix b/roles/kde/programs/spectacle.nix deleted file mode 100644 index 3dfc0e5..0000000 --- a/roles/kde/programs/spectacle.nix +++ /dev/null @@ -1,24 +0,0 @@ -{...}: { - nixpkgs.overlays = [ - ( - final: prev: { - kdePackages = prev.kdePackages.overrideScope ( - kFinal: kPrev: { - # Needed for OCR - spectacle = kPrev.spectacle.overrideAttrs (old: { - nativeBuildInputs = old.nativeBuildInputs ++ [final.pkg-config]; - buildInputs = with final; - old.buildInputs - ++ [ - tesseract - leptonica - libarchive - curl - ]; - }); - } - ); - } - ) - ]; -} diff --git a/roles/kde/sddm.nix b/roles/kde/sddm.nix deleted file mode 100644 index 1c296b9..0000000 --- a/roles/kde/sddm.nix +++ /dev/null @@ -1,62 +0,0 @@ -{ - lib, - pkgs, - ... -}: let - sddm-sugar-candy = pkgs.stdenv.mkDerivation { - pname = "sddm-sugar-candy"; - version = "master"; - src = pkgs.fetchgit { - url = "https://framagit.org/MarianArlt/sddm-sugar-candy.git"; - hash = "sha256-XggFVsEXLYklrfy1ElkIp9fkTw4wvXbyVkaVCZq4ZLU="; - }; - installPhase = '' - runHook preInstall - - mkdir -p $out/share/sddm/themes/sugar-candy - cp -r /build/sddm-sugar-candy/* $out/share/sddm/themes/sugar-candy - - runHook postInstall - ''; - }; - - /* - Adds a theme.conf.user file to the current sddm theme's folder, - allowing you to change it's configuration without needing to - repackage it - */ - customcfg = pkgs.stdenv.mkDerivation { - name = "sddm-theme-customizer"; - dontUnpack = true; - installPhase = let - config = lib.generators.toINI {} { - # Add the custom config here - General = { - background = "${pkgs.kdePackages.breeze}/share/wallpapers/Next/contents/images_dark/5120x2880.png"; - }; - }; - in '' - runHook preInstall - - mkdir -p $out/share/sddm/themes/breeze/ - echo "${config}" >> $out/share/sddm/themes/breeze/theme.conf.user - - runHook postInstall - ''; - }; -in { - # Enable SDDM. - services.displayManager.sddm = { - enable = true; - wayland.enable = true; - # theme = "sugar-candy"; - settings = { - General = {Numlock = "off";}; - Theme = {CursorTheme = "Breeze_Light";}; - }; - }; - - # Sugar candy doesn't seem to work on qt6 :( - # environment.systemPackages = [sddm-sugar-candy customcfg]; - environment.systemPackages = [customcfg]; -} diff --git a/roles/server/adguard.nix b/roles/server/adguard.nix deleted file mode 100644 index 8a57525..0000000 --- a/roles/server/adguard.nix +++ /dev/null @@ -1,45 +0,0 @@ -{ - lib, - config, - ... -}: let - domain = "adguard.everest.tailscale"; - port = 3001; -in { - services = { - adguardhome = { - enable = true; - host = "127.0.0.1"; - port = port; - settings = { - dns = { - bind_hosts = [ - ((lib.lists.last config.networking.interfaces.eno1.ipv4.addresses).address) - "100.100.0.1" - ]; - bootstrap_dns = ["9.9.9.9"]; - }; - }; - }; - - headscale.settings.dns = { - nameservers.global = lib.mkForce ["100.100.0.1"]; - extra_records = [ - { - name = domain; - type = "A"; - value = "100.100.0.1"; - } - ]; - }; - - caddy.virtualHosts.adguardhome = { - hostName = domain; - extraConfig = '' - import tailscale - reverse_proxy 127.0.0.1:${builtins.toString port} - ''; - }; - }; - programs.rust-motd.settings.service_status."AdGuard Home" = "adguardhome"; -} diff --git a/roles/server/avahi.nix b/roles/server/avahi.nix index b0d3798..9302bb7 100755 --- a/roles/server/avahi.nix +++ b/roles/server/avahi.nix @@ -1,9 +1,11 @@ -{...}: { - services.avahi = { - openFirewall = true; - publish = { - enable = true; - userServices = true; - }; - }; -} +{ config, ... }: + +{ + services.avahi = { + openFirewall = true; + publish = { + enable = true; + userServices = true; + }; + }; +} \ No newline at end of file diff --git a/roles/server/beep.nix b/roles/server/beep.nix index f8be721..b454726 100755 --- a/roles/server/beep.nix +++ b/roles/server/beep.nix @@ -1,16 +1,16 @@ -{pkgs, ...}: { - # Beep as soon as possible in the initrd - boot.initrd = { - kernelModules = ["pcspkr"]; - extraFiles.beep.source = pkgs.beep; - postDeviceCommands = "/beep/bin/beep -f 3000 -l 50 -r 2"; - }; - /* - systemd.services.startupBeep = { - description = "Beep when system started booting"; - wantedBy = [ "sysinit.target" ]; - script = "${pkgs.beep}/bin/beep -f 3000 -l 50 -r 2"; - serviceConfig = { Type = "oneshot"; }; - }; - */ +{ config, pkgs, ... }: + +{ + # Beep as soon as possible in the initrd + boot.initrd = { + kernelModules = [ "pcspkr" ]; + extraFiles.beep.source = pkgs.beep; + postDeviceCommands = "/beep/bin/beep -f 3000 -l 50 -r 2"; + }; + /*systemd.services.startupBeep = { + description = "Beep when system started booting"; + wantedBy = [ "sysinit.target" ]; + script = "${pkgs.beep}/bin/beep -f 3000 -l 50 -r 2"; + serviceConfig = { Type = "oneshot"; }; + };*/ } diff --git a/roles/server/beets.nix b/roles/server/beets.nix deleted file mode 100644 index 67d76c9..0000000 --- a/roles/server/beets.nix +++ /dev/null @@ -1,59 +0,0 @@ -{...}: let - musicDir = "/srv/music"; -in { - users = { - users.music = { - isSystemUser = true; - group = "music"; - }; - # Intended for other programs to get write permission - groups.music = { - members = ["toast"]; - }; - }; - systemd.tmpfiles.settings = { - music."${musicDir}" = { - d = { - age = "-"; - user = "music"; - group = "music"; - mode = "2775"; - }; - }; - }; - - services.copyparty = { - volumes."/Music" = { - path = "/srv/music"; - access.r = "*"; - }; - }; - - home-manager.users.toast = {config, ...}: { - programs.beets = { - enable = true; - settings = { - directory = musicDir; - library = "${config.xdg.dataHome}/beets/library.db"; - - import = { - move = true; - }; - ui.color = true; - - plugins = [ - "unimported" - "fetchart" - "chroma" - "permissions" - "mbsync" - "random" - ]; - permissions = { - file = "644"; - folder = "755"; - }; - }; - }; - }; -} diff --git a/roles/server/borg.nix b/roles/server/borg.nix deleted file mode 100644 index d69af2f..0000000 --- a/roles/server/borg.nix +++ /dev/null @@ -1,19 +0,0 @@ -{...}: { - services.borgbackup = { - repos = { - backups = { - allowSubRepos = true; - authorizedKeys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEMtbHUcYanH/guWaKNjGr/IGa8gvI/xRTcNAI9yXhnK BorgBackup backups key" - ]; - }; - }; - }; - services.openssh.settings = { - AllowUsers = [ - "borg@*.tailscale" - "borg@192.168.1.0/24" - "borg@localhost" - ]; - }; -} diff --git a/roles/server/caddy.nix b/roles/server/caddy.nix deleted file mode 100644 index 14ef62a..0000000 --- a/roles/server/caddy.nix +++ /dev/null @@ -1,91 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - manualHostname = "manual.everest.tailscale"; - downloadsHostname = "dl.everest.tailscale"; - downloadsConfig = '' - import tailscale - file_server browse - root * /srv/dl/ - ''; - script = pkgs.writeShellApplication { - name = "wait-for-tailscale-ip"; - runtimeInputs = [pkgs.iproute2]; - text = '' - # Based on https://github.com/tailscale/tailscale/issues/11504#issuecomment-2113331262 - echo Waiting for tailscale0 to get an IP adress.. - for i in {1..300}; do - if ip addr show dev tailscale0 | grep -q 'inet '; then break; fi - echo "Waiting $i/240 seconds" - sleep 1 - done - ''; - }; -in { - services.caddy = { - enable = true; - globalConfig = '' - pki { - ca local { - name "Caddy (Everest) local CA" - } - } - ''; - extraConfig = '' - (tailscale) { - tls internal - # Old tailscale IP - # bind 100.73.96.48 - bind 100.100.0.1 - } - ''; - virtualHosts = { - nixos-manual = { - hostName = manualHostname; - extraConfig = let - manual = pkgs.compressDrvWeb config.system.build.manual.manualHTML {}; - in '' - import tailscale - file_server { - precompressed zstd br gzip - } - root * ${manual}/share/doc/nixos - ''; - }; - downloads = { - hostName = downloadsHostname; - extraConfig = downloadsConfig; - }; - downloads-http = { - hostName = "http://${downloadsHostname}"; - extraConfig = downloadsConfig; - }; - }; - }; - services.headscale.settings.dns.extra_records = let - makeRecords = builtins.map (recordName: { - name = recordName; - type = "A"; - value = "100.100.0.1"; - }); - in - makeRecords [ - manualHostname - downloadsHostname - ]; - systemd = { - services.caddy.after = ["tailscaled.service"]; - # We have somewhat frequent power outages, and our ISP router takes - # ages to boot up. If I don't add a delay, caddy tries to bind to - # the tailscale interface before it's ready, making it crash too much - # in too little time - services.caddy.serviceConfig.RestartSec = lib.mkForce "120s"; - services.caddy.unitConfig.StartLimitBurst = lib.mkForce "infinity"; - services.caddy.preStart = "${script}/bin/wait-for-tailscale-ip"; - }; - programs.rust-motd.settings.service_status.Caddy = "caddy"; - networking.firewall.allowedTCPPorts = [443 80]; -} diff --git a/roles/server/copyparty.nix b/roles/server/copyparty.nix deleted file mode 100644 index 1cb52cd..0000000 --- a/roles/server/copyparty.nix +++ /dev/null @@ -1,37 +0,0 @@ -{flakeSelf, ...}: { - nixpkgs.overlays = [flakeSelf.inputs.copyparty.overlays.default]; - services.copyparty = { - enable = true; - - settings = { - i = ["unix:770:caddy:/dev/shm/copyparty.socket"]; - hist = "/var/cache/copyparty"; - rproxy = 1; - }; - - volumes = { - "/Files" = { - path = "/srv/dl"; - access.r = "*"; - }; - }; - }; - - users.users.copyparty.extraGroups = ["caddy"]; - programs.rust-motd.settings.service_status.Copyparty = "copyparty"; - - services.headscale.settings.dns.extra_records = [ - { - name = "files.everest.tailscale"; - type = "A"; - value = "100.100.0.1"; - } - ]; - services.caddy.virtualHosts.copyparty = { - hostName = "files.everest.tailscale"; - extraConfig = '' - import tailscale - reverse_proxy unix//dev/shm/copyparty.socket - ''; - }; -} diff --git a/roles/server/ddclient.nix b/roles/server/ddclient.nix index a4dfd83..e4520d9 100755 --- a/roles/server/ddclient.nix +++ b/roles/server/ddclient.nix @@ -1,15 +1,16 @@ -{config, ...}: { - # Set up secrets - sops.secrets.ddclientPassword = {}; +{ config, ... }: - services.ddclient = { - enable = true; - usev4 = "webv4, webv4=https://api.ipify.org"; - usev6 = ""; - protocol = "namecheap"; - server = "dynamicdns.park-your-domain.com"; - username = "toast003.xyz"; - passwordFile = config.sops.secrets.ddclientPassword.path; - domains = ["@"]; - }; -} +{ + # Set up secrets + age.secrets = { ddclient-passwd.file = ../../secrets/ddclient-passwd; }; + + services.ddclient = { + enable = true; + use = "web, web=dynamicdns.park-your-domain.com/getip"; + protocol = "namecheap"; + server = "dynamicdns.park-your-domain.com"; + username = "toast003.xyz"; + passwordFile = config.age.secrets.ddclient-passwd.path; + domains = [ "@" ]; + }; +} \ No newline at end of file diff --git a/roles/server/default.nix b/roles/server/default.nix index 8bdf5dc..515d4bf 100755 --- a/roles/server/default.nix +++ b/roles/server/default.nix @@ -1,27 +1,16 @@ -{...}: { - imports = [ - ./avahi.nix - ./nfs.nix - ./samba.nix - ./ssh.nix - ./forgejo.nix - ./syncthing.nix - ./endlessh.nix - ./transmission.nix - ./ddclient.nix - ./beep.nix - ./tailscale.nix - ./headscale.nix - ./caddy.nix - ./rust_motd.nix - ./borg.nix - ./adguard.nix - ./grafana.nix - ./prometheus.nix - ./immich.nix - ./copyparty.nix - ./beets.nix - ./navidrome.nix - ./minecraft.nix - ]; +{ ... }: + +{ + imports = [ + ./avahi.nix + ./nfs.nix + ./samba.nix + ./ssh.nix + ./gitea.nix + ./syncthing.nix + ./endlessh.nix + ./transmission.nix + ./ddclient.nix + ./beep.nix + ]; } diff --git a/roles/server/endlessh.nix b/roles/server/endlessh.nix index b4dcfaa..6646d1e 100755 --- a/roles/server/endlessh.nix +++ b/roles/server/endlessh.nix @@ -1,21 +1,10 @@ -{config, ...}: { - # I prefer using the go implementation - services.endlessh-go = { - enable = true; - openFirewall = true; - prometheus = { - enable = true; - listenAddress = "127.0.0.1"; - }; - extraOptions = ["-geoip_supplier ip-api"]; - }; +{ config, ... }: - services.prometheus.scrapeConfigs = [ - { - job_name = "everest-endlessh"; - static_configs = [ - {targets = ["127.0.0.1:${builtins.toString config.services.endlessh-go.prometheus.port}"];} - ]; - } - ]; -} +{ + # I prefer using the go implementation + services.endlessh-go = { + enable = true; + openFirewall = true; + extraOptions = [ "-alsologtostderr" "-v=1"] ; + }; +} \ No newline at end of file diff --git a/roles/server/forgejo.nix b/roles/server/forgejo.nix deleted file mode 100644 index 95210c4..0000000 --- a/roles/server/forgejo.nix +++ /dev/null @@ -1,64 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: { - sops.secrets = let - owner = config.services.forgejo.user; - group = config.services.forgejo.group; - in { - "forgejoHostKey/private" = { - inherit owner group; - name = "id_forgejo"; - }; - "forgejoHostKey/public" = { - inherit owner group; - name = "id_forgejo.pub"; - }; - }; - - specialisation.forgejoEnableRegistration.configuration.services.forgejo.settings.service.DISABLE_REGISTRATION = false; - services.forgejo = { - enable = true; - package = pkgs.forgejo-lts; - settings = { - service = { - DISABLE_REGISTRATION = lib.mkDefault true; - }; - server = { - OFFLINE_MODE = false; - PROTOCOL = "http+unix"; - ROOT_URL = "https://git.toast003.xyz"; - START_SSH_SERVER = true; - SSH_PORT = 4222; - SSH_SERVER_HOST_KEYS = config.sops.secrets."forgejoHostKey/private".path; - SSH_SERVER_HOST_KEY = "id_forgejo"; - }; - repository = { - ENABLE_PUSH_CREATE_USER = true; - DEFAULT_PUSH_CREATE_PRIVATE = true; - DEFAULT_BRANCH = "main"; - }; - indexer = { - REPO_INDEXER_ENABLED = true; - }; - }; - }; - - networking.firewall.allowedTCPPorts = with config; [ - services.forgejo.settings.server.SSH_PORT - ]; - - catppuccin.forgejo = { - enable = true; - }; - - # Set up caddy as the reverse proxy for Forgejo - services.caddy.virtualHosts.forgejo = { - hostName = "git.toast003.xyz"; - extraConfig = '' - reverse_proxy unix/${config.services.forgejo.settings.server.HTTP_ADDR} - ''; - }; -} diff --git a/roles/server/gitea.nix b/roles/server/gitea.nix new file mode 100644 index 0000000..40c8cb9 --- /dev/null +++ b/roles/server/gitea.nix @@ -0,0 +1,19 @@ +{ config, lib, ... }: + +{ + specialisation.giteaEnableRegistration.configuration.services.gitea.settings.service.DISABLE_REGISTRATION = false; + services.gitea = { + enable = true; + # TODO: Make this not be hardcoded + settings = { + server = { + #server.SSH_PORT = 69; + DISABLE_REGISTRATION = lib.mkDefault true; + ROOT_URL = "http://everest.local:3000"; + }; + }; + }; + networking.firewall = { + allowedTCPPorts = [ 3000 ]; + }; +} diff --git a/roles/server/grafana.nix b/roles/server/grafana.nix deleted file mode 100644 index 7563efb..0000000 --- a/roles/server/grafana.nix +++ /dev/null @@ -1,58 +0,0 @@ -{config, ...}: let - domain = "monitoring.everest.tailscale"; -in { - users.users.caddy.extraGroups = ["grafana"]; - sops.secrets = let - owner = "grafana"; - group = "grafana"; - in { - "grafanaAdmin/username" = { - inherit owner group; - }; - "grafanaAdmin/password" = { - inherit owner group; - }; - }; - services = { - grafana = { - enable = true; - - provision = { - enable = true; - datasources.settings = { - apiVersion = 1; - }; - }; - - settings = { - analytics.reporting_enabled = false; - security = { - admin_user = "$__file{${config.sops.secrets."grafanaAdmin/username".path}}"; - admin_password = "$__file{${config.sops.secrets."grafanaAdmin/password".path}}"; - cookie_secure = true; - strict_transport_security = true; - content_security_policy = true; - }; - server = { - protocol = "socket"; - root_url = "https://${domain}"; - }; - }; - }; - - headscale.settings.dns.extra_records = [ - { - name = domain; - type = "A"; - value = "100.100.0.1"; - } - ]; - caddy.virtualHosts.grafana = { - hostName = domain; - extraConfig = '' - import tailscale - reverse_proxy unix/${config.services.grafana.settings.server.socket} - ''; - }; - }; -} diff --git a/roles/server/headscale.nix b/roles/server/headscale.nix deleted file mode 100644 index c926c0b..0000000 --- a/roles/server/headscale.nix +++ /dev/null @@ -1,30 +0,0 @@ -{lib, ...}: { - services.headscale = { - enable = true; - settings = { - server_url = "https://headscale.toast003.xyz"; - prefixes.v4 = "100.100.0.0/16"; - dns = { - base_domain = "tailscale"; - nameservers.global = ["9.9.9.9"]; - override_local_dns = true; - }; - }; - }; - services.caddy = { - virtualHosts.headscale = { - hostName = "headscale.toast003.xyz"; - extraConfig = '' - reverse_proxy localhost:8080 - ''; - }; - }; - programs.rust-motd.settings.service_status.Headscale = "headscale"; - systemd = { - services.tailscaled.after = ["headscale.service"]; - services.headscale = { - serviceConfig.RestartSec = lib.mkForce "120s"; - unitConfig.StartLimitBurst = lib.mkForce "infinity"; - }; - }; -} diff --git a/roles/server/immich.nix b/roles/server/immich.nix deleted file mode 100644 index 5992181..0000000 --- a/roles/server/immich.nix +++ /dev/null @@ -1,25 +0,0 @@ -{...}: { - services.immich = { - enable = true; - settings = { - server.externalDomain = "https://photos.everest.tailscale"; - server.publicUsers = false; - }; - }; - # Add a record for transmission - services.headscale.settings.dns.extra_records = [ - { - name = "photos.everest.tailscale"; - type = "A"; - value = "100.100.0.1"; - } - ]; - services.caddy.virtualHosts.immich = { - hostName = "photos.everest.tailscale"; - extraConfig = '' - import tailscale - reverse_proxy localhost:2283 - ''; - }; - programs.rust-motd.settings.service_status."Immich" = "immich-server"; -} diff --git a/roles/server/minecraft.nix b/roles/server/minecraft.nix deleted file mode 100644 index 9985df0..0000000 --- a/roles/server/minecraft.nix +++ /dev/null @@ -1,99 +0,0 @@ -{ - pkgs, - config, - ... -}: let - stopScript = pkgs.writeShellScript "minecraft-server-stop" '' - echo stop > ${config.systemd.sockets.minecraft-server-sf5.socketConfig.ListenFIFO} - - # Wait for the PID of the minecraft server to disappear before - # returning, so systemd doesn't attempt to SIGKILL it. - while kill -0 "$1" 2> /dev/null; do - sleep 1s - done - ''; -in { - fileSystems = { - "/var/lib/minecraft" = { - device = "/dev/disk/by-uuid/5322c217-b87b-4150-8b4c-a8fa17a899bf"; - fsType = "btrfs"; - options = ["subvol=@minecraft"]; - }; - }; - users.users.sf5 = { - isSystemUser = true; - group = "sf5"; - }; - users.groups.sf5 = {}; - systemd.tmpfiles.settings = { - music."/var/lib/minecraft/sf5" = { - d = { - age = "-"; - user = "sf5"; - group = "sf5"; - mode = "0755"; - }; - }; - }; - networking.firewall.allowedTCPPorts = [25565]; - systemd.sockets.minecraft-server-sf5 = { - bindsTo = ["minecraft-server-sf5.service"]; - socketConfig = { - ListenFIFO = "/run/minecraft-server-sf5.stdin"; - SocketMode = "0660"; - SocketUser = "sf5"; - SocketGroup = "sf5"; - RemoveOnStop = true; - FlushPending = true; - }; - }; - systemd.services.minecraft-server-sf5 = { - description = "Minecraft Server (Sky Factory 5)"; - wantedBy = ["multi-user.target"]; - requires = ["minecraft-server-sf5.socket"]; - after = [ - "network.target" - "minecraft-server-sf5.socket" - ]; - - path = [pkgs.jdk17 pkgs.bash]; - - serviceConfig = { - ExecStart = "/var/lib/minecraft/sf5/run.sh"; - ExecStop = "${stopScript} $MAINPID"; - Restart = "always"; - User = "sf5"; - WorkingDirectory = "/var/lib/minecraft/sf5"; - - StandardInput = "socket"; - StandardOutput = "journal"; - StandardError = "journal"; - - # Hardening - CapabilityBoundingSet = [""]; - DeviceAllow = [""]; - LockPersonality = true; - PrivateDevices = true; - PrivateTmp = true; - PrivateUsers = true; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - UMask = "0077"; - }; - }; - programs.rust-motd.settings.service_status."Minecraft (SkyFactory 5)" = "minecraft-server-sf5"; -} diff --git a/roles/server/navidrome.nix b/roles/server/navidrome.nix deleted file mode 100644 index cab624f..0000000 --- a/roles/server/navidrome.nix +++ /dev/null @@ -1,31 +0,0 @@ -{lib, ...}: { - services = rec { - navidrome = { - enable = true; - settings = { - BaseUrl = "https://${caddy.virtualHosts.navidrome.hostName}"; - Address = "unix:/run/navidrome/navidrome.socket"; - MusicFolder = "/srv/music"; - EnableStarRating = false; - # Better to download in copyparty / smb - EnableDownloads = false; - }; - }; - caddy.virtualHosts.navidrome = { - hostName = "music.everest.tailscale"; - extraConfig = '' - import tailscale - reverse_proxy ${lib.strings.replaceString ":" "/" navidrome.settings.Address} - ''; - }; - headscale.settings.dns.extra_records = [ - { - name = caddy.virtualHosts.navidrome.hostName; - type = "A"; - value = "100.100.0.1"; - } - ]; - }; - programs.rust-motd.settings.service_status.Navidrome = "navidrome"; - users.users.caddy.extraGroups = ["navidrome"]; -} diff --git a/roles/server/nfs.nix b/roles/server/nfs.nix index 37e472d..2b83b68 100755 --- a/roles/server/nfs.nix +++ b/roles/server/nfs.nix @@ -1,17 +1,36 @@ -{config, ...}: { - services = { - nfs.server = { - enable = true; - exports = "/srv/nfs *.tailscale(ro,fsid=root)"; - # NFSv3 uses random ports, so you need to make them static to be able to pass though the firewall - statdPort = 4000; - lockdPort = 4001; - mountdPort = 4002; - }; - }; +{ config, lib, ... }: - networking.firewall = { - allowedTCPPorts = [111 2049 4000 40001 4002]; - allowedUDPPorts = [111 2049 4000 40001 4002]; - }; +{ + services = { + nfs.server = { + enable = true; + exports = '' +${config.services.transmission.settings.download-dir} *.local(ro,all_squash,anonuid=${toString config.users.users.transmission.uid},anongid=${toString config.users.groups.transmission.gid}) + ''; + # NFSv3 uses random ports, so you need to make them static to be able to pass though the firewall + statdPort = 4000; + lockdPort = 4001; + mountdPort = 4002; + }; + + avahi.extraServiceFiles = { + Transmission-downloads-nfs = '' + + + + Transmission Downloads on %h (NFS) + + _nfs._tcp + 2049 + path=${config.services.transmission.settings.download-dir} + + +''; + }; + }; + + networking.firewall = { + allowedTCPPorts = [ 111 2049 4000 40001 4002 ]; + allowedUDPPorts = [ 111 2049 4000 40001 4002 ]; + }; } diff --git a/roles/server/prometheus.nix b/roles/server/prometheus.nix deleted file mode 100644 index fb32f05..0000000 --- a/roles/server/prometheus.nix +++ /dev/null @@ -1,33 +0,0 @@ -{config, ...}: { - services.prometheus = { - enable = true; - enableReload = true; - exporters = { - node = { - enable = true; - enabledCollectors = ["systemd"]; - listenAddress = "127.0.0.1"; - port = 9002; - }; - }; - scrapeConfigs = [ - { - job_name = "everest-node"; - static_configs = [ - { - targets = ["127.0.0.1:9002"]; - } - ]; - } - ]; - }; - services.grafana.provision = { - datasources.settings.datasources = [ - { - name = "Prometheus"; - type = "prometheus"; - url = "http://127.0.0.1:${builtins.toString config.services.prometheus.port}"; - } - ]; - }; -} diff --git a/roles/server/rust_motd.nix b/roles/server/rust_motd.nix deleted file mode 100644 index f8560c0..0000000 --- a/roles/server/rust_motd.nix +++ /dev/null @@ -1,14 +0,0 @@ -{...}: { - programs.rust-motd = { - enable = true; - enableMotdInSSHD = true; - settings = { - filesystems = { - Root = "/"; - "Nix Store" = "/nix"; - Boot = "/boot/efi"; - }; - last_run.last_run = true; - }; - }; -} diff --git a/roles/server/samba.nix b/roles/server/samba.nix index 3e551ac..dcd25aa 100755 --- a/roles/server/samba.nix +++ b/roles/server/samba.nix @@ -1,30 +1,37 @@ -{config, ...}: { - services = { - samba = { - enable = true; - openFirewall = true; - settings = { - "Transmission downloads" = { - "path" = "${config.services.transmission.settings.download-dir}"; - "public" = true; - "writable" = false; - "force user" = "transmission"; - }; - }; - }; +{ config, lib, ... }: - avahi.extraServiceFiles = { - everest-smb = '' - - - - SMB shares on %h - - _smb._tcp - 139 - - - ''; - }; - }; +{ + services = { + samba = { + enable = true; + openFirewall = true; + extraConfig = '' +map to guest = bad user +guest account = transmission +''; + shares = { + "Transmission downloads" = { + path = "${config.services.transmission.settings.download-dir}"; + "read only" = true; + public = true; + "guest only" = true; + browseable = true; + }; + }; + }; + + avahi.extraServiceFiles = { + Transmission-downloads-smb = '' + + + + SMB shares on %h + + _smb._tcp + 139 + + +''; + }; + }; } diff --git a/roles/server/ssh.nix b/roles/server/ssh.nix index 33694c3..576cd3b 100755 --- a/roles/server/ssh.nix +++ b/roles/server/ssh.nix @@ -1,71 +1,12 @@ +{ config, ... }: + { - config, - pkgs, - lib, - ... -}: let - hostKeyPath = "/etc/ssh/everest_host_key"; - notify = - pkgs.writers.writePython3 "send-discord-login-notification" { - libraries = [pkgs.python3Packages.requests]; - } '' - import requests - import os - - if os.environ["PAM_TYPE"] != "open_session": - raise SystemExit - secretPath = "${config.sops.secrets.discordWebhook.path}" - - webhookUrl: str - - with open(secretPath) as file: - webhookUrl = file.read().strip() - - user = os.environ["PAM_USER"] - rhost = os.environ["PAM_RHOST"] - - data = { - "username": "SSH Login", - "content": user + " logged in from " + rhost - } - - result = requests.post(webhookUrl, json=data) - ''; -in { - sops.secrets = { - discordWebhook = {}; - "hostKey/public".path = "${hostKeyPath}.pub"; - "hostKey/private".path = hostKeyPath; - }; - - users.users.toast.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEas0RvecJpUA1rG87fHunxDb4O0Q7bQG7X8h3hZnrsE Everest access key" - ]; - - services.openssh = { - enable = true; - settings = { - UseDns = true; - PermitRootLogin = "no"; - PasswordAuthentication = false; - AllowUsers = ["toast"]; - }; - # The forgejo module is fucky so I can't set this with the nixos option - # https://github.com/NixOS/nixpkgs/issues/306205 - extraConfig = '' - AcceptEnv COLORTERM - ''; - hostKeys = [ - { - path = hostKeyPath; - type = "ed25519"; - comment = "Everest host key"; - } - ]; - startWhenNeeded = true; - }; - - security.pam.services.sshd.text = lib.mkDefault (lib.mkAfter '' - session optional pam_exec.so debug stdout ${notify} - ''); + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "no"; + PasswordAuthentication = false; + }; + startWhenNeeded = true; + }; } diff --git a/roles/server/syncthing.nix b/roles/server/syncthing.nix index c45a7ae..f16a7ba 100755 --- a/roles/server/syncthing.nix +++ b/roles/server/syncthing.nix @@ -1,74 +1,33 @@ -{config, ...}: { - services.syncthing = { - enable = true; - key = config.age.secrets.syncthingKey.path; - cert = config.age.secrets.syncthingCert.path; - guiAddress = "127.0.0.1:8384"; - settings.folders = { - "passwords" = { - path = "${config.services.syncthing.dataDir}/passwords"; - }; - "steam-201810" = { - label = "Wolfenstein The New Order Saves"; - id = "laxxf-t2wmy"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "${config.services.syncthing.dataDir}/steam-201810"; - }; - "project-diva-mods" = { - label = "Project Diva Mods"; - id = "7pscj-6egww"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "${config.services.syncthing.dataDir}/project-diva-mods"; - }; - "retroarch" = { - label = "RetroArch"; - id = "jxuou-2yjnu"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "${config.services.syncthing.dataDir}/retroarch"; - }; - "pcsx2" = { - label = "PCSX2"; - id = "qcdsp-qaaej"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "${config.services.syncthing.dataDir}/pcsx2"; - }; - "project-eden-saves" = { - label = "Project Eden saves"; - id = "xa3qx-3ax5k"; - devices = ["server" "pc" "winmax2" "steamdeck"]; - path = "${config.services.syncthing.dataDir}/project-eden-saves"; - }; - "games" = { - label = "Games"; - id = "mwzph-gf2df"; - devices = ["steamdeck" "server" "pc" "winmax2"]; - path = "${config.services.syncthing.dataDir}/games"; - }; - }; - }; - systemd.services.syncthing.serviceConfig = { - # Allow syncthing to change ownership of files - AmbientCapabilities = "CAP_CHOWN CAP_FOWNER"; - }; +{ config, ... }: - # Add a record for syncthing - services.headscale.settings.dns.extra_records = [ - { - name = "sync.everest.tailscale"; - type = "A"; - value = "100.100.0.1"; - } - ]; - - # Set up caddy as the reverse proxy for syncthing - services.caddy.virtualHosts.syncthing = { - hostName = "sync.everest.tailscale"; - extraConfig = '' - import tailscale - reverse_proxy localhost:8384 { - header_up Host {upstream_hostport} - } - ''; - }; - programs.rust-motd.settings.service_status.Syncthing = "syncthing"; -} +{ + age.secrets = { + syncthingKey.file = ../../secrets/syncthing/key; + syncthingCert.file = ../../secrets/syncthing/cert; + }; + services.syncthing = { + enable = true; + key = config.age.secrets.syncthingKey.path; + cert = config.age.secrets.syncthingCert.path; + guiAddress = "0.0.0.0:8384"; + devices = { + "phone" = { + id = "K7KNZ5V-XREUADL-CROQXPV-6AA4H65-2VUD34Z-VQWKJ6S-LWWW4EE-XPNEZQ6"; + name = "Xiaomi Redmi Note 10 Pro"; + }; + "pc" = { + name = "Archie"; + id = "MGMYYA2-4PXGHHH-2LOVD5N-I7IYBBS-4Y4UQNK-H73S2JG-ZCK5GCN-NHTWMAR"; + addresses = [ "tcp://archie.local:22000" "tcp://192.168.0.160:22000"]; + }; + }; + folders = { + "passwords" = { + label = "KeePassXC Passwords"; + id = "rdyaq-ex659"; + path = "${config.services.syncthing.dataDir}/passwords"; + devices = [ "phone" "pc" ]; + }; + }; + }; +} \ No newline at end of file diff --git a/roles/server/tailscale.nix b/roles/server/tailscale.nix deleted file mode 100644 index 7ed054e..0000000 --- a/roles/server/tailscale.nix +++ /dev/null @@ -1,21 +0,0 @@ -{pkgs, ...}: let - script = pkgs.writeShellApplication { - name = "tailscale-wait-for-ip"; - runtimeInputs = [pkgs.iproute2]; - text = '' - # Based on https://github.com/tailscale/tailscale/issues/11504#issuecomment-2113331262 - echo Waiting for tailscale0 to get an IP adress.. - for i in {1..240}; do - if ip addr show dev tailscale0 | grep -q 'inet '; then break; fi - echo "Waiting $i/240 seconds" - sleep 1 - done - ''; - }; -in { - services.tailscale = { - # This is needed for being an exit node - useRoutingFeatures = "server"; - }; - systemd.services.tailscaled.postStart = "${script}/bin/tailscale-wait-for-ip"; -} diff --git a/roles/server/transmission.nix b/roles/server/transmission.nix index ce18dbd..4f60c0d 100755 --- a/roles/server/transmission.nix +++ b/roles/server/transmission.nix @@ -1,62 +1,14 @@ +{ config , ... }: + { - config, - pkgs, - ... -}: let - transmissionUid = toString config.users.users.transmission.uid; - transmissionGid = toString config.users.groups.transmission.gid; - mountPoint = config.fileSystems."nfs_transmission".mountPoint; -in { - services.transmission = { - enable = true; - openFirewall = true; - package = pkgs.transmission_4; - settings = { - incomplete-dir-enabled = false; - rpc-bind-address = "0.0.0.0"; - rpc-host-whitelist = "transmission.everest.tailscale"; - rpc-whitelist = "127.0.0.1"; - }; - }; - - # Allow my devices to access the downloads folder though NFS - fileSystems."nfs_transmission" = { - device = config.services.transmission.settings.download-dir; - mountPoint = "/srv/nfs/transmission"; - options = ["bind"]; - }; - services.nfs.server.exports = "${mountPoint} *.tailscale(ro,all_squash,anonuid=${transmissionUid},anongid=${transmissionGid})"; - - services.avahi.extraServiceFiles = { - Transmission-downloads-nfs = '' - - - - Transmission Downloads on %h (NFS) - - _nfs._tcp - 2049 - path=${mountPoint} - - - ''; - }; - - # Add a record for transmission - services.headscale.settings.dns.extra_records = [ - { - name = "transmission.everest.tailscale"; - type = "A"; - value = "100.100.0.1"; - } - ]; - - # Set up caddy as the reverse proxy for transmission - services.caddy.virtualHosts.transmission = { - hostName = "transmission.everest.tailscale"; - extraConfig = '' - import tailscale - reverse_proxy localhost:${toString config.services.transmission.settings.rpc-port} - ''; - }; -} + services.transmission = { + enable = true; + openFirewall = true; + openRPCPort = true; + settings = { + incomplete-dir-enabled = false; + rpc-bind-address = "0.0.0.0"; + rpc-whitelist = "127.0.0.1,192.168.0.16*"; + }; + }; +} \ No newline at end of file diff --git a/secrets/Archie/host-key-ed25519 b/secrets/Archie/host-key-ed25519 new file mode 100644 index 0000000..ddd8e91 --- /dev/null +++ b/secrets/Archie/host-key-ed25519 @@ -0,0 +1,14 @@ +age-encryption.org/v1 +-> ssh-ed25519 zhSyTg v0zMwf3PyU8i5Z8cKQAM8G/egqkmPONA7twvIsTtFUU +4BlqeR6PpQrYwf7BT1UXqzaiiNwHAxsbbvX1Sk7YG7M +-> ssh-ed25519 AuWU1Q m0nCQcYG0Jz8AeouayMRTPiQvZxWDbci88ouaaW1kBE +FMRP4tDLTQ8wo/9j6AaVhl4/amQAjgZDPKqmtzTwHbI +-> tR-grease jXU +zPQZdJy9DQ9MUenFWBk +--- NY5Z2u04JmXtfy09gfYTziCNqdXfSXQLe3n/e7wburg + +KQoa|ɗ .hS +^aɹL)m. At}BR!7J%f#f_/=d:\[ TxȔUs(:I~-i -l!(̮SG^٢Vڗig~MDdnWqÕb7P\CαI}msU4="1.:aT-Ooy%v$iBN)s8OV(EDžtWi;nP7Q0·tR+W1BdTTOWf>6C>nT¾ +K)D81il3JPQw.w\&6j T:8E`,"a҂<dKrc2䴃<~ +h?Fc + ΣJtoD \ No newline at end of file diff --git a/secrets/Archie/host-key-ed25519-public b/secrets/Archie/host-key-ed25519-public new file mode 100644 index 0000000..91e279e --- /dev/null +++ b/secrets/Archie/host-key-ed25519-public @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> ssh-ed25519 zhSyTg Xkk6wPQm3Sm3RuNyKhnKVz/evGJtr0UwhB7m2iuhrR4 +RMheqKeCD+Py22+xmvp3Se1z84t60+6y1Bbt7uYGxFs +-> ssh-ed25519 AuWU1Q 5l5/vuIGxW+6ZzlDKjLzNCxyiW1+Kh651xpnwjfF3FQ +ZIx/zZZMPpO8zDW5JdkucIBVH1xK4KtoA7Kovw+bcOU +-> 7%-grease [ wwEC MxP UF:U6Cy +Hp7t6AxdTAfm4r/LMWAt22vOYvhfHJLX4BIB7eEUfQnNAPIx43SrK8QIrAGHWbxN +hdO18C5g6xoE5HHz5uM5ASzUWC4Nws3OXwY +--- 2kwRA1NakiMhvMQgkaiEiJ93SkjTmOt77m0tO+e/p/w +^^I=*='V [$-ʲ} .=&ɭl@l5׏pIKVNCԎ I_<g.mf}O4( @ ; \ No newline at end of file diff --git a/secrets/Archie/host-key-rsa b/secrets/Archie/host-key-rsa new file mode 100644 index 0000000..e323c7a Binary files /dev/null and b/secrets/Archie/host-key-rsa differ diff --git a/secrets/Archie/host-key-rsa-public b/secrets/Archie/host-key-rsa-public new file mode 100644 index 0000000..8bb561d Binary files /dev/null and b/secrets/Archie/host-key-rsa-public differ diff --git a/secrets/Everest/host-key-ed25519 b/secrets/Everest/host-key-ed25519 new file mode 100644 index 0000000..0fe034f Binary files /dev/null and b/secrets/Everest/host-key-ed25519 differ diff --git a/secrets/Everest/host-key-ed25519-public b/secrets/Everest/host-key-ed25519-public new file mode 100644 index 0000000..6b23715 --- /dev/null +++ b/secrets/Everest/host-key-ed25519-public @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 5qrYxA OECuD3X/YhnhNDjXFBsoq+mOQmadIQch2DhcVM2es3g +Y9tNL/OXgxSrWtvrLDHBnaWGxDoSopQAVoFwx6WiHFE +-> ssh-ed25519 AuWU1Q RawOBsHa1yGd0Nn3QPaZNlh3Qy5D5TNU0VVc6t7uwmU +M0OgClrDATN23KARdN8kee/tDSolbdVQwxclOwUlCY8 +-> }|y:w-grease [|V >/-D+*J +zPzM +--- st6EavuBsvVd84P9CGhxLpgckxCsYjucYvpMiNS0YVY +wav\GU.<8\<ڂ>^=„0[f,!S0z%/eo48&J?@ZJ;1/ႄ*/t{ʹ-dna8.ES$˖: \ No newline at end of file diff --git a/secrets/Everest/host-key-rsa b/secrets/Everest/host-key-rsa new file mode 100644 index 0000000..18618b9 Binary files /dev/null and b/secrets/Everest/host-key-rsa differ diff --git a/secrets/Everest/host-key-rsa-public b/secrets/Everest/host-key-rsa-public new file mode 100644 index 0000000..cbfbf9f Binary files /dev/null and b/secrets/Everest/host-key-rsa-public differ diff --git a/secrets/ddclient-passwd b/secrets/ddclient-passwd new file mode 100755 index 0000000..fb143cb --- /dev/null +++ b/secrets/ddclient-passwd @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 5qrYxA YZag1cf+LCNznpoLx8wXN0lqaDfcxpP8Axmgt1gyiDo +DujRQ8hZtv6CyKWmOGK82jFoRkT/72Y1OmWcTb+aiVw +-> tR{ rv:Iړ`%-וvMpD9, \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix new file mode 100755 index 0000000..5c1d5a7 --- /dev/null +++ b/secrets/secrets.nix @@ -0,0 +1,18 @@ +let + everest = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID7GzKZIK/UAMfRjsaxWWKOBqG7sa1ttJ+Gp0zTQSBXM root@Everest"; + archie = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINuqKOfYb2lyhoQYBQbuIEyMomze872rnpxDnax8BsC5 root@Archie"; + bootsrtrap = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMKloSXSeF4dNXebd93uMuiFuXRHfxo/he4+O9SFTz1s bootstrap key"; +in +{ + "ddclient-passwd".publicKeys = [ everest ]; + "syncthing/key".publicKeys = [ everest ]; + "syncthing/cert".publicKeys = [ everest ]; + "Everest/host-key-ed25519".publicKeys = [ everest bootsrtrap ]; + "Everest/host-key-ed25519-public".publicKeys = [ everest bootsrtrap ]; + "Everest/host-key-rsa".publicKeys = [ everest bootsrtrap ]; + "Everest/host-key-rsa-public".publicKeys = [ everest bootsrtrap ]; + "Archie/host-key-ed25519".publicKeys = [ archie bootsrtrap ]; + "Archie/host-key-ed25519-public".publicKeys = [ archie bootsrtrap ]; + "Archie/host-key-rsa".publicKeys = [ archie bootsrtrap ]; + "Archie/host-key-rsa-public".publicKeys = [ archie bootsrtrap ]; +} diff --git a/secrets/syncthing/cert b/secrets/syncthing/cert new file mode 100755 index 0000000..9711922 Binary files /dev/null and b/secrets/syncthing/cert differ diff --git a/secrets/syncthing/key b/secrets/syncthing/key new file mode 100755 index 0000000..e0dd47d --- /dev/null +++ b/secrets/syncthing/key @@ -0,0 +1,12 @@ +age-encryption.org/v1 +-> ssh-ed25519 5qrYxA Pm14+K3RvYrvafBLUm9H885rQ8puPvRkzL+MEljElig +PGl15jcRfHEooB+vHs0OtWcKSm+zQrUMetasHHSW898 +-> z(FyRLA--grease _1~=z } w0eJ^ j +6vNkqQF4u0tAzFcvi8XvLOPfQVzye3/9sMBCmtAgpw9tXAm7SBbcVb5szY6e49WX +EiP+QmD3VOU4Ygc +--- YmXm1xwJbZ/RgMqGcDsrWSBrQmgSWuBR07Ar0OQwZug +YjqVoJ +Fj\Q`' E4 g31 +q%-Wһ *{;7q=Ak4RP&aq6fiȍ>j | Y|+۸rycxϐ,ra: 0r©,iғԤ7ܼ#|vK +O 0WD8* P… la3ɫ,˴# \ No newline at end of file diff --git a/syncthing.nix b/syncthing.nix deleted file mode 100644 index 353964a..0000000 --- a/syncthing.nix +++ /dev/null @@ -1,28 +0,0 @@ -{ - devices = { - "phone" = { - name = "Xiaomi Redmi Note 10 Pro"; - id = "K7KNZ5V-XREUADL-CROQXPV-6AA4H65-2VUD34Z-VQWKJ6S-LWWW4EE-XPNEZQ6"; - }; - "pc" = { - name = "Archie"; - id = "NJPX754-64AQNP3-7GZFIRZ-W2EDRJQ-27ORWYM-X5YXEXQ-ERRTRTQ-BSYD4AY"; - }; - "steamdeck" = { - name = "Steam Deck"; - id = "DNFEGEA-PDEVW5A-O5VBVQK-IUXI7J5-MAHCQAG-2JLEFFM-DSXB6AS-TX6ZHAN"; - }; - "server" = { - name = "Everest"; - id = "2GXFZJZ-CF56ER2-SISBGOF-VNXJIG5-GQC6ECA-NHCHAPX-677RSJT-RI5POAZ"; - }; - "surface" = { - name = "Surface Go"; - id = "HTVSF3O-AHY3TNH-BLVSEGK-HRRSMHC-H5LJWVF-NDKGM6O-ATWZALC-YXNV2Q4"; - }; - "winmax2" = { - name = "Win Max 2"; - id = "X2NILRM-ADRBQ23-AFREAZA-62GVFDF-UVMPR4L-KGHMUNY-BJ2C3CQ-RBT43QS"; - }; - }; -}