···1111<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
12121313- [gtklock](https://github.com/jovanlanik/gtklock), a GTK-based lockscreen for Wayland. Available as [programs.gtklock](#opt-programs.gtklock.enable).
1414+- [Chrysalis](https://github.com/keyboardio/Chrysalis), a graphical configurator for Kaleidoscope-powered keyboards. Available as [programs.chrysalis](#opt-programs.chrysalis.enable).
14151516- [FileBrowser](https://filebrowser.org/), a web application for managing and sharing files. Available as [services.filebrowser](#opt-services.filebrowser.enable).
1617
···11+{
22+ config,
33+ pkgs,
44+ lib,
55+ ...
66+}:
77+88+let
99+ cfg = config.programs.nekoray;
1010+in
1111+{
1212+ options = {
1313+ programs.nekoray = {
1414+ enable = lib.mkEnableOption "nekoray, a GUI proxy configuration manager";
1515+1616+ package = lib.mkPackageOption pkgs "nekoray" { };
1717+1818+ tunMode = {
1919+ enable = lib.mkEnableOption "TUN mode of nekoray";
2020+2121+ setuid = lib.mkEnableOption ''
2222+ setting suid bit for nekobox_core to run as root, which is less
2323+ secure than default setcap method but closer to upstream assumptions.
2424+ Enable this if you find the default setcap method configured in
2525+ this module doesn't work for you
2626+ '';
2727+ };
2828+ };
2929+ };
3030+3131+ config = lib.mkIf cfg.enable {
3232+ environment.systemPackages = [ cfg.package ];
3333+3434+ security.wrappers.nekobox_core = lib.mkIf cfg.tunMode.enable {
3535+ source = "${cfg.package}/share/nekoray/nekobox_core";
3636+ owner = "root";
3737+ group = "root";
3838+ setuid = lib.mkIf cfg.tunMode.setuid true;
3939+ # Taken from https://github.com/SagerNet/sing-box/blob/dev-next/release/config/sing-box.service
4040+ capabilities = lib.mkIf (
4141+ !cfg.tunMode.setuid
4242+ ) "cap_net_admin,cap_net_raw,cap_net_bind_service,cap_sys_ptrace,cap_dac_read_search+ep";
4343+ };
4444+4545+ # avoid resolvectl password prompt popping up three times
4646+ # https://github.com/SagerNet/sing-tun/blob/0686f8c4f210f4e7039c352d42d762252f9d9cf5/tun_linux.go#L1062
4747+ # We use a hack here to determine whether the requested process is nekobox_core
4848+ # Detect whether its capabilities contain at least `net_admin` and `net_raw`.
4949+ # This does not reduce security, as we can already bypass `resolved` with them.
5050+ # Alternatives to consider:
5151+ # 1. Use suid to execute as a specific user, and check username with polkit.
5252+ # However, NixOS module doesn't let us to set setuid and capabilities at the
5353+ # same time, and it's tricky to make both work together because of some security
5454+ # considerations in the kernel.
5555+ # 2. Check cmdline to get executable path. This is insecure because the process can
5656+ # change its own cmdline. `/proc/<pid>/exe` is reliable but kernel forbids
5757+ # checking that entry of process from different users, and polkit runs `spawn`
5858+ # as an unprivileged user.
5959+ # 3. Put nekobox_core into a systemd service, and let polkit check service name.
6060+ # This is the most secure and convenient way but requires heavy modification
6161+ # to nekoray source code. Would be good to let upstream support that eventually.
6262+ security.polkit.extraConfig =
6363+ lib.mkIf (cfg.tunMode.enable && (!cfg.tunMode.setuid) && config.services.resolved.enable)
6464+ ''
6565+ polkit.addRule(function(action, subject) {
6666+ const allowedActionIds = [
6767+ "org.freedesktop.resolve1.set-domains",
6868+ "org.freedesktop.resolve1.set-default-route",
6969+ "org.freedesktop.resolve1.set-dns-servers"
7070+ ];
7171+7272+ if (allowedActionIds.indexOf(action.id) !== -1) {
7373+ try {
7474+ var parentPid = polkit.spawn(["${lib.getExe' pkgs.procps "ps"}", "-o", "ppid=", subject.pid]).trim();
7575+ var parentCap = polkit.spawn(["${lib.getExe' pkgs.libcap "getpcaps"}", parentPid]).trim();
7676+ if (parentCap.includes("cap_net_admin") && parentCap.includes("cap_net_raw")) {
7777+ return polkit.Result.YES;
7878+ } else {
7979+ return polkit.Result.NOT_HANDLED;
8080+ }
8181+ } catch (e) {
8282+ return polkit.Result.NOT_HANDLED;
8383+ }
8484+ }
8585+ })
8686+ '';
8787+ };
8888+8989+ meta.maintainers = with lib.maintainers; [ aleksana ];
9090+}
···11+diff --git a/src/global/NekoGui.cpp b/src/global/NekoGui.cpp
22+index 7943d7a..5bb20cc 100644
33+--- a/src/global/NekoGui.cpp
44++++ b/src/global/NekoGui.cpp
55+@@ -355,6 +355,12 @@ namespace NekoGui {
66+ // System Utils
77+88+ QString FindNekoBoxCoreRealPath() {
99++ // find in PATH first
1010++ QString path = QStandardPaths::findExecutable("nekobox_core");
1111++ if (!path.isEmpty()) {
1212++ return path;
1313++ }
1414++
1515+ auto fn = QApplication::applicationDirPath() + "/nekobox_core";
1616+ auto fi = QFileInfo(fn);
1717+ if (fi.isSymLink()) return fi.symLinkTarget();
1818+diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp
1919+index 9aa46b2..ba7137a 100644
2020+--- a/src/ui/mainwindow.cpp
2121++++ b/src/ui/mainwindow.cpp
2222+@@ -125,8 +125,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
2323+ NekoGui::dataStore->core_port = MkPort();
2424+ if (NekoGui::dataStore->core_port <= 0) NekoGui::dataStore->core_port = 19810;
2525+2626+- auto core_path = QApplication::applicationDirPath() + "/";
2727+- core_path += "nekobox_core";
2828++ auto core_path = NekoGui::FindNekoBoxCoreRealPath();
2929+3030+ QStringList args;
3131+ args.push_back("nekobox");
3232+@@ -844,6 +843,15 @@ bool MainWindow::get_elevated_permissions(int reason) {
3333+ return true;
3434+ }
3535+ if (NekoGui::IsAdmin()) return true;
3636++ QMessageBox::critical(
3737++ GetMessageBoxParent(),
3838++ tr("Unable to elevate privileges when installed with Nix"),
3939++ tr("Due to the read-only property of Nix store, we cannot set suid for nekobox_core. If you are using NixOS, please set `programs.nekoray.tunMode.enable` option to elevate privileges."),
4040++ QMessageBox::Ok
4141++ );
4242++ return false;
4343++ // The following code isn't effective, preserve to avoid merge conflict
4444++
4545+ #ifdef Q_OS_LINUX
4646+ if (!Linux_HavePkexec()) {
4747+ MessageBoxWarning(software_name, "Please install \"pkexec\" first.");
+14-1
pkgs/by-name/ne/nekoray/package.nix
···6060 # we already package those two files in nixpkgs
6161 # we can't place file at that location using our builder so we must change the search directory to be relative to the built executable
6262 ./search-for-geodata-in-install-location.patch
6363+6464+ # disable suid request as it cannot be applied to nekobox_core in nix store
6565+ # and prompt users to use NixOS module instead. And use nekobox_core from PATH
6666+ # to make use of security wrappers
6767+ ./nixos-disable-setuid-request.patch
6368 ];
64696570 installPhase = ''
···99104 inherit (finalAttrs) version src;
100105 sourceRoot = "${finalAttrs.src.name}/core/server";
101106107107+ patches = [
108108+ # also check cap_net_admin so we don't have to set suid
109109+ ./core-also-check-capabilities.patch
110110+ ];
111111+102112 vendorHash = "sha256-hZiEIJ4/TcLUfT+pkqs6WfzjqppSTjKXEtQC+DS26Ug=";
103113104114 # ldflags and tags are taken from script/build_go.sh
···127137 homepage = "https://github.com/Mahdi-zarei/nekoray";
128138 license = lib.licenses.gpl3Plus;
129139 mainProgram = "nekoray";
130130- maintainers = with lib.maintainers; [ tomasajt ];
140140+ maintainers = with lib.maintainers; [
141141+ tomasajt
142142+ aleksana
143143+ ];
131144 platforms = lib.platforms.linux;
132145 };
133146})
···15041504 pipewire-media-session = throw "pipewire-media-session is no longer maintained and has been removed. Please use Wireplumber instead.";
15051505 platypus = throw "platypus is unmaintained and has not merged Python3 support"; # Added 2025-03-20
15061506 pleroma-otp = throw "'pleroma-otp' has been renamed to/replaced by 'pleroma'"; # Converted to throw 2024-10-17
15071507+ plex-media-player = throw "'plex-media-player' has been discontinued, the new official client is available as 'plex-desktop'"; # Added 2025-05-28
15071508 plots = throw "'plots' has been replaced by 'gnome-graphs'"; # Added 2025-02-05
15081509 pltScheme = racket; # just to be sure
15091510 poac = cabinpkg; # Added 2025-01-22