├── .gitignore ├── pkgs ├── qubes-sshd │ └── default.nix ├── qubes-gui-common │ └── default.nix ├── qubes-core-vchan-xen │ └── default.nix ├── qubes-core-qubesdb │ └── default.nix ├── qubes-core-qrexec │ └── default.nix ├── qubes-gpg-split │ └── default.nix ├── qubes-linux-utils │ └── default.nix ├── qubes-usb-proxy │ └── default.nix ├── qubes-gui-agent-linux │ └── default.nix └── qubes-core-agent-linux │ └── default.nix ├── examples ├── configuration.nix └── flake.nix ├── modules └── qubes │ ├── sshd.nix │ ├── usb.nix │ ├── db.nix │ ├── networking.nix │ ├── qrexec.nix │ ├── gui.nix │ ├── updates.nix │ └── core.nix ├── flake.lock ├── profiles └── qubes.nix ├── LICENSE ├── tools ├── rpm.nix └── iso.nix ├── flake.nix └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | -------------------------------------------------------------------------------- /pkgs/qubes-sshd/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | stdenv, 4 | }: 5 | pkgs.writeTextFile { 6 | name = "qubes-rpc-sshd"; 7 | text = '' 8 | #!${stdenv.shell} 9 | ${pkgs.socat}/bin/socat STDIO TCP:localhost:22 10 | ''; 11 | executable = true; 12 | destination = "/etc/qubes-rpc/qubes.Sshd"; 13 | } 14 | -------------------------------------------------------------------------------- /examples/configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: { 7 | nix = { 8 | settings = { 9 | experimental-features = ["nix-command" "flakes"]; 10 | }; 11 | }; 12 | 13 | hardware.graphics.enable = true; 14 | 15 | environment.systemPackages = with pkgs; [ 16 | xterm 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /modules/qubes/sshd.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | with lib; { 8 | options.services.qubes.sshd.enable = mkEnableOption "enable sshd over qrexec"; 9 | 10 | config = mkIf config.services.qubes.sshd.enable { 11 | services.qubes.networking.enable = true; 12 | services.qubes.qrexec.enable = true; 13 | 14 | services.qubes.qrexec.packages = [pkgs.qubes-sshd]; 15 | services.openssh.enable = true; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /modules/qubes/usb.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | with lib; { 8 | options.services.qubes.usb.enable = mkEnableOption "enable usb over qrexec"; 9 | 10 | config = mkIf config.services.qubes.usb.enable { 11 | environment.systemPackages = [pkgs.usbutils]; 12 | services.qubes.qrexec.enable = true; 13 | services.qubes.qrexec.packages = [pkgs.qubes-usb-proxy]; 14 | services.udev.packages = [ 15 | pkgs.qubes-usb-proxy 16 | ]; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1739866667, 6 | "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixos-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "root": { 19 | "inputs": { 20 | "nixpkgs": "nixpkgs" 21 | } 22 | } 23 | }, 24 | "root": "root", 25 | "version": 7 26 | } 27 | -------------------------------------------------------------------------------- /profiles/qubes.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: { 7 | services.qubes.qrexec.enable = true; 8 | services.qubes.gui.enable = true; 9 | services.qubes.networking.enable = true; 10 | services.qubes.usb.enable = true; 11 | 12 | fonts.enableDefaultPackages = true; 13 | 14 | # When running in PVH mode, the qubes init script will bind mount the kernel modules here 15 | systemd.tmpfiles.rules = [ 16 | "d /lib/modules 0755 root root" 17 | ]; 18 | # When running in PVH mode, the qubes init script expects /sbin/init to exist 19 | boot.loader.initScript.enable = true; 20 | 21 | # Don't use the GRUB 2 boot loader since it conflicts with initScript.enable 22 | boot.loader.grub.enable = false; 23 | } 24 | -------------------------------------------------------------------------------- /pkgs/qubes-gui-common/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | fetchFromGitHub, 5 | }: 6 | stdenv.mkDerivation rec { 7 | pname = "qubes-gui-common"; 8 | version = "4.2.5"; 9 | 10 | src = fetchFromGitHub { 11 | owner = "QubesOS"; 12 | repo = pname; 13 | rev = "v${version}"; 14 | hash = "sha256-rv80X/wecXRtJ3HhHgksJd43AKvLQTHyX8e1EJPwEO0="; 15 | }; 16 | 17 | buildPhase = '' 18 | true 19 | ''; 20 | 21 | installPhase = '' 22 | mkdir -p $out/include 23 | cp include/*.h $out/include/ 24 | ''; 25 | 26 | meta = with lib; { 27 | description = "Common files for Qubes GUI - protocol headers"; 28 | homepage = "https://qubes-os.org"; 29 | license = licenses.gpl2Plus; 30 | maintainers = []; 31 | platforms = platforms.linux; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /examples/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "example nixos templatevm configuration"; 3 | 4 | inputs = { 5 | nixpkgs.url = "nixpkgs/nixos-unstable"; 6 | 7 | qubes-nixos-template = { 8 | url = "github:evq/qubes-nixos-template"; 9 | inputs.nixpkgs.follows = "nixpkgs"; 10 | }; 11 | }; 12 | 13 | outputs = { 14 | self, 15 | nixpkgs, 16 | qubes-nixos-template, 17 | ... 18 | }: let 19 | system = "x86_64-linux"; 20 | pkgs = import nixpkgs { 21 | inherit system; 22 | overlays = [ 23 | qubes-nixos-template.overlays.default 24 | ]; 25 | }; 26 | in { 27 | nixosConfigurations = { 28 | nixos = nixpkgs.lib.nixosSystem { 29 | inherit pkgs system; 30 | modules = [ 31 | qubes-nixos-template.nixosModules.default 32 | qubes-nixos-template.nixosProfiles.default 33 | ./configuration.nix 34 | ]; 35 | }; 36 | }; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /pkgs/qubes-core-vchan-xen/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | fetchFromGitHub, 5 | xen, 6 | }: 7 | stdenv.mkDerivation rec { 8 | pname = "qubes-core-vchan-xen"; 9 | version = "4.2.4"; 10 | 11 | src = fetchFromGitHub { 12 | owner = "QubesOS"; 13 | repo = pname; 14 | rev = "v${version}"; 15 | hash = "sha256-O7i5zK7S+d/O8oPMvm6szNR1Xq6qSBNE2+uFI/1mDEg="; 16 | }; 17 | 18 | buildInputs = [xen]; 19 | 20 | buildPhase = '' 21 | make all PREFIX=/ LIBDIR="$out/lib" INCLUDEDIR="$out/include" 22 | ''; 23 | 24 | installPhase = '' 25 | make install DESTDIR=$out PREFIX=/ 26 | ''; 27 | 28 | env.CFLAGS = "-DHAVE_XC_DOMAIN_GETINFO_SINGLE"; 29 | 30 | meta = with lib; { 31 | description = "Libraries required for the higher-level Qubes daemons and tools"; 32 | homepage = "https://qubes-os.org"; 33 | license = licenses.gpl2Plus; 34 | maintainers = []; 35 | platforms = platforms.linux; 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 eV Quirk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /modules/qubes/db.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | init = pkgs.writeShellScriptBin "qubes-db-init" '' 8 | ${pkgs.coreutils}/bin/mkdir -p /var/log/qubes 9 | ${pkgs.coreutils}/bin/mkdir -m 0775 -p /var/run/qubes 10 | ''; 11 | in 12 | with lib; { 13 | options.services.qubes.db.enable = mkEnableOption "the qubes db daemon"; 14 | 15 | config = mkIf config.services.qubes.db.enable { 16 | boot.kernelModules = ["xen_gntdev" "xen_evtchn"]; 17 | 18 | environment.systemPackages = [ 19 | pkgs.qubes-core-qubesdb 20 | ]; 21 | # TODO just override parts of existing service? 22 | systemd.services.qubes-db = { 23 | description = "Qubes DB agent"; 24 | after = ["systemd-modules-load.service"]; 25 | 26 | unitConfig = { 27 | DefaultDependencies = false; 28 | }; 29 | 30 | serviceConfig = { 31 | Group = "qubes"; 32 | Type = "notify"; 33 | ExecStartPre = "${init}/bin/qubes-db-init"; 34 | ExecStart = "${pkgs.qubes-core-qubesdb}/sbin/qubesdb-daemon 0"; 35 | }; 36 | }; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /modules/qubes/networking.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | with lib; { 8 | options.services.qubes.networking = { 9 | enable = mkEnableOption "the qubes networking services"; 10 | }; 11 | 12 | config = mkIf config.services.qubes.networking.enable { 13 | services.qubes.core.enable = true; 14 | services.qubes.core.networking = true; 15 | 16 | services.resolved.enable = true; 17 | 18 | systemd.services.qubes-network-uplink = { 19 | # ensure the service is started on boot, since Install is ignored 20 | wantedBy = ["multi-user.target"]; 21 | serviceConfig = { 22 | ExecStart = ["" "${config.services.qubes.core.package}/lib/qubes/init/network-uplink-wait.sh"]; 23 | }; 24 | }; 25 | 26 | systemd.services."qubes-network-uplink@" = { 27 | # explicitly add qubes-db as a requirement, otherwise on upgrade they may be restarted 28 | # simultaneously which causes setup-ip to fail. 29 | requires = ["network-pre.target" "qubes-db.service"]; 30 | 31 | serviceConfig = { 32 | ExecStart = ["" "${config.services.qubes.core.package}/lib/qubes/setup-ip add \"%i\""]; 33 | ExecStop = ["" "${config.services.qubes.core.package}/lib/qubes/setup-ip remove \"%i\""]; 34 | }; 35 | }; 36 | 37 | # prevents renaming of xenlight net interfaces, to avoid race conditions 38 | systemd.network.links."80-qubes-vif" = { 39 | matchConfig.Driver = "vif"; 40 | linkConfig.NamePolicy = ""; 41 | }; 42 | 43 | # ensure that dhcpcd doesn't conflict with the qubes network configuration 44 | networking.dhcpcd.enable = false; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /tools/rpm.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | fetchFromGitHub, 4 | nixpkgs, 5 | pkgs, 6 | nixosConfig, 7 | qubesVersion, 8 | }: let 9 | version = "4.0.6"; 10 | rootImg = import "${nixpkgs}/nixos/lib/make-disk-image.nix" { 11 | inherit lib pkgs; 12 | config = nixosConfig.config; 13 | contents = [ 14 | { 15 | source = ../examples/configuration.nix; 16 | target = "/etc/nixos/configuration.nix"; 17 | } 18 | { 19 | source = ../examples/flake.nix; 20 | target = "/etc/nixos/flake.nix"; 21 | } 22 | ]; 23 | diskSize = 10240; # 10G 24 | partitionTableType = "hybrid"; 25 | name = "root"; 26 | }; 27 | in 28 | pkgs.stdenvNoCC.mkDerivation { 29 | name = "qubes-template-rpm"; 30 | 31 | src = fetchFromGitHub { 32 | owner = "QubesOS"; 33 | repo = "qubes-linux-template-builder"; 34 | rev = "v${version}"; 35 | hash = "sha256-ABfhqyg9PypuKWYe6yhEr99hxf7qWsYCwRyToGhPKZA="; 36 | }; 37 | 38 | nativeBuildInputs = [ 39 | pkgs.rpm 40 | pkgs.coreutils 41 | pkgs.gnutar 42 | ]; 43 | 44 | dontConfigure = true; 45 | dontFixup = true; 46 | 47 | buildPhase = '' 48 | set -x 49 | 50 | mkdir -p qubeized_images/nixos 51 | ln -s ${rootImg}/nixos.img qubeized_images/nixos/root.img 52 | 53 | ln -s "appmenus_generic" appmenus 54 | cp template_generic.conf template.conf 55 | 56 | date +"%Y%m%d%H%M" > build_timestamp_nixos 57 | echo ${qubesVersion} > version 58 | 59 | substituteInPlace templates.spec --replace qubeized_images "$(pwd)/qubeized_images" 60 | substituteInPlace templates.spec --replace " appmenus" " $(pwd)/appmenus" 61 | substituteInPlace templates.spec --replace " template.conf" " $(pwd)/template.conf" 62 | 63 | DIST=nixos ./build_template_rpm nixos 64 | ''; 65 | 66 | installPhase = '' 67 | mkdir $out/ 68 | mv rpm/noarch/*.rpm $out/ 69 | ''; 70 | } 71 | -------------------------------------------------------------------------------- /pkgs/qubes-core-qubesdb/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | fetchFromGitHub, 5 | makeWrapper, 6 | bash, 7 | glibc, 8 | pkg-config, 9 | python3, 10 | python3Packages, 11 | qubes-core-vchan-xen, 12 | systemd, 13 | }: let 14 | qubesdb-cmds = "qubesdb-read qubesdb-write qubesdb-rm qubesdb-multiread qubesdb-list qubesdb-watch"; 15 | in 16 | stdenv.mkDerivation rec { 17 | pname = "qubes-core-qubesdb"; 18 | version = "4.2.6"; 19 | 20 | src = fetchFromGitHub { 21 | owner = "QubesOS"; 22 | repo = pname; 23 | rev = "v${version}"; 24 | hash = "sha256-vPv74tBD7elYNqpgKLFKAanMH8D18OdDj0xhmw8aWwM="; 25 | }; 26 | 27 | nativeBuildInputs = [ 28 | bash 29 | makeWrapper 30 | pkg-config 31 | python3Packages.setuptools 32 | ]; 33 | 34 | buildInputs = [ 35 | glibc 36 | qubes-core-vchan-xen 37 | python3 38 | systemd 39 | ]; 40 | 41 | buildPhase = '' 42 | make all PREFIX=/ LIBDIR="$out/lib" INCLUDEDIR="$out/include" BINDIR="$out/bin" SBINDIR="$out/sbin" 43 | ''; 44 | 45 | installPhase = '' 46 | make install DESTDIR=$out PREFIX=/ PYTHON_PREFIX_ARG="--prefix ." LIBDIR="/lib" INCLUDEDIR="/include" BINDIR="/bin" SBINDIR="/sbin" 47 | 48 | # dashes in the full nix store path conflict with command parsing for the qubesdb-cmd symlinks 49 | # we will replace them with wrappers that set the argv0 in postFixup 50 | for cmd in ${qubesdb-cmds}; do 51 | rm "$out/bin/$cmd"; 52 | done 53 | ''; 54 | 55 | postFixup = '' 56 | for cmd in ${qubesdb-cmds}; do 57 | makeWrapper "$out/bin/qubesdb-cmd" "$out/bin/$cmd" \ 58 | --argv0 "$cmd" 59 | done 60 | ''; 61 | 62 | meta = with lib; { 63 | description = "QubesDB libs and daemon service"; 64 | homepage = "https://qubes-os.org"; 65 | license = licenses.gpl2Plus; 66 | maintainers = []; 67 | platforms = platforms.linux; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /modules/qubes/qrexec.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | qrexec_services = ["${pkgs.qubes-core-qrexec}/etc/qubes-rpc" "${pkgs.qubes-core-agent-linux}/etc/qubes-rpc"] ++ map (x: "${x}/etc/qubes-rpc") config.services.qubes.qrexec.packages; 8 | in 9 | with lib; { 10 | options.services.qubes.qrexec = { 11 | enable = mkEnableOption "the qubes remote exec agent daemon"; 12 | packages = mkOption { 13 | type = types.listOf types.path; 14 | default = []; 15 | description = '' 16 | List of packages containing {command}`qrexec` services. 17 | All files found in 18 | {file}`«pkg»/etc/qubes-rpc/` 19 | will be included. 20 | ''; 21 | apply = map getBin; 22 | }; 23 | }; 24 | 25 | config = mkIf config.services.qubes.qrexec.enable { 26 | services.qubes.core.enable = true; 27 | 28 | boot.kernelModules = ["xen_evtchn" "xen_gntalloc"]; 29 | 30 | # adding to system packages will cause their xdg autostart files to be picked up 31 | environment.systemPackages = [ 32 | pkgs.qubes-core-qrexec 33 | ]; 34 | 35 | security.polkit.enable = true; 36 | security.pam.services.qrexec = { 37 | rootOK = true; 38 | }; 39 | 40 | # TODO just override parts of existing service? 41 | systemd.services.qubes-qrexec-agent = { 42 | description = "Qubes remote exec agent"; 43 | requires = ["qubes-db.service"]; 44 | wantedBy = ["multi-user.target"]; 45 | after = ["systemd-modules-load.service" "xendriverdomain.service" "systemd-user-sessions.service"]; 46 | environment = { 47 | QREXEC_SERVICE_PATH = concatStringsSep ":" qrexec_services; 48 | QREXEC_MULTIPLEXER_PATH = "${pkgs.qubes-core-qrexec}/lib/qubes/qubes-rpc-multiplexer"; 49 | }; 50 | 51 | serviceConfig = { 52 | Type = "notify"; 53 | ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/log/qubes"; 54 | ExecStart = "${pkgs.qubes-core-qrexec}/lib/qubes/qrexec-agent"; 55 | KillMode = "process"; 56 | SELinuxContext = "system_u:system_r:local_login_t:s0-s0:c0.c1023"; 57 | }; 58 | }; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /pkgs/qubes-core-qrexec/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | fetchFromGitHub, 4 | resholve, 5 | bash, 6 | coreutils, 7 | glibc, 8 | lsb-release, 9 | pam, 10 | pandoc, 11 | pkg-config, 12 | python3, 13 | python3Packages, 14 | qubes-core-vchan-xen, 15 | util-linux, 16 | }: 17 | resholve.mkDerivation rec { 18 | pname = "qubes-core-qrexec"; 19 | version = "4.2.21"; 20 | 21 | src = fetchFromGitHub { 22 | owner = "QubesOS"; 23 | repo = pname; 24 | rev = "v${version}"; 25 | hash = "sha256-an/jvcVJoCjhlcaWvf3pJbEukg9yei8oUoCvDkMiYKk="; 26 | }; 27 | 28 | nativeBuildInputs = [ 29 | bash 30 | pkg-config 31 | python3Packages.distutils 32 | python3Packages.setuptools 33 | lsb-release 34 | pandoc 35 | ]; 36 | 37 | buildInputs = [ 38 | glibc 39 | qubes-core-vchan-xen 40 | python3 41 | pam 42 | ]; 43 | 44 | buildPhase = '' 45 | make all-base 46 | make all-vm 47 | ''; 48 | 49 | # FIXME 50 | # - need to rewrite lib/qubes-qrexec-policy-agent autostart ( `exec qrexec-policy-agent "$@"` ) 51 | # - need to add qubes-qrexec-agent.service service 52 | # - need to rewrite /etc/qubes-rpc in a few places 53 | # - subs in qubes-rpc-multiplexer 54 | 55 | installPhase = '' 56 | make install-base DESTDIR=$out PREFIX=/ PYTHON_PREFIX_ARG="--prefix ." LIBDIR="/lib" SYSLIBDIR="/lib" 57 | make install-vm DESTDIR=$out PREFIX=/ PYTHON_PREFIX_ARG="--prefix ." LIBDIR="/lib" SYSLIBDIR="/lib" 58 | 59 | mv $out/usr/bin $out/bin 60 | mv $out/usr/include $out/include 61 | mv $out/usr/lib/qubes $out/lib/qubes 62 | mv $out/usr/share $out/share 63 | 64 | substituteInPlace "$out/etc/xdg/autostart/qrexec-policy-agent.desktop" --replace '/usr/lib/qubes/qrexec-policy-agent-autostart' "$out/lib/qubes/qrexec-policy-agent-autostart" 65 | 66 | rm -rf $out/usr 67 | ''; 68 | 69 | solutions = { 70 | default = { 71 | scripts = ["lib/qubes/qubes-rpc-multiplexer"]; 72 | interpreter = "none"; 73 | inputs = [coreutils util-linux]; 74 | }; 75 | }; 76 | 77 | meta = with lib; { 78 | description = "The Qubes qrexec files (qube side)"; 79 | homepage = "https://qubes-os.org"; 80 | license = licenses.gpl2Plus; 81 | maintainers = []; 82 | platforms = platforms.linux; 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /pkgs/qubes-gpg-split/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | fetchFromGitHub, 3 | resholve, 4 | coreutils, 5 | qubes-core-qrexec, 6 | gnupg, 7 | pandoc, 8 | }: 9 | resholve.mkDerivation rec { 10 | pname = "qubes-gpg-split"; 11 | version = "2.0.77"; 12 | 13 | src = fetchFromGitHub { 14 | owner = "QubesOS"; 15 | repo = "qubes-app-linux-split-gpg"; 16 | rev = "v${version}"; 17 | hash = "sha256-AGYJV+moLh58dbKi9K2aguPNHYE4ntQ8OmZvADAec0s="; 18 | }; 19 | 20 | postPatch = '' 21 | substituteInPlace src/gpg-client.c --replace \ 22 | '#define QREXEC_CLIENT_PATH "/usr/lib/qubes/qrexec-client-vm"' \ 23 | '#define QREXEC_CLIENT_PATH "${qubes-core-qrexec}/bin/qrexec-client-vm"' 24 | ''; 25 | 26 | buildInputs = [ 27 | qubes-core-qrexec 28 | gnupg 29 | ]; 30 | 31 | nativeBuildInputs = [ 32 | pandoc 33 | ]; 34 | 35 | buildPhase = '' 36 | make 37 | ''; 38 | 39 | installPhase = '' 40 | make install-vm \ 41 | DESTDIR="$out" \ 42 | LIBDIR=/lib \ 43 | USRLIBDIR=/lib \ 44 | SYSLIBDIR=/lib 45 | 46 | mv $out/usr/bin $out/bin 47 | mv $out/usr/share $out/share 48 | # FIXME usr/lib/tmpfiles.d is probably needed in order to allow a nixos qube to be used as the gpg domain 49 | rm -rf $out/usr 50 | ''; 51 | 52 | solutions = { 53 | default = { 54 | scripts = [ 55 | "bin/qubes-gpg-client-wrapper" 56 | "bin/qubes-gpg-import-key" 57 | "etc/profile.d/qubes-gpg.sh" 58 | ]; 59 | interpreter = "none"; 60 | fix = { 61 | source = ["/etc/profile.d/qubes-gpg.sh"]; 62 | "/usr/bin/gpg" = true; 63 | "/usr/lib/qubes/qrexec-client-vm" = true; 64 | }; 65 | inputs = [ 66 | "bin" 67 | "etc/profile.d" 68 | coreutils 69 | gnupg 70 | qubes-core-qrexec 71 | ]; 72 | execer = [ 73 | "cannot:bin/qubes-gpg-client" 74 | "cannot:bin/qubes-gpg-import-key" 75 | "cannot:${gnupg}/bin/gpg" 76 | # FIXME this is a lie 77 | # NOTE the invocation in qubes-gpg-import-key passes absolute paths 78 | # but for now we won't support nixos as the gpg domain 79 | # and instead only as the client 80 | "cannot:${qubes-core-qrexec}/bin/qrexec-client-vm" 81 | ]; 82 | }; 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "nixos templatevm configurations"; 3 | 4 | inputs = { 5 | nixpkgs.url = "nixpkgs/nixos-unstable"; 6 | }; 7 | 8 | outputs = { 9 | self, 10 | nixpkgs, 11 | ... 12 | }: let 13 | lib = nixpkgs.lib; 14 | system = "x86_64-linux"; 15 | qubesPackages = final: prev: { 16 | qubes-core-qubesdb = prev.callPackage ./pkgs/qubes-core-qubesdb {}; 17 | qubes-core-vchan-xen = prev.callPackage ./pkgs/qubes-core-vchan-xen {}; 18 | qubes-core-qrexec = prev.callPackage ./pkgs/qubes-core-qrexec {}; 19 | qubes-core-agent-linux = prev.callPackage ./pkgs/qubes-core-agent-linux {}; 20 | qubes-linux-utils = prev.callPackage ./pkgs/qubes-linux-utils {}; 21 | qubes-gui-common = prev.callPackage ./pkgs/qubes-gui-common {}; 22 | qubes-gui-agent-linux = prev.callPackage ./pkgs/qubes-gui-agent-linux {}; 23 | qubes-sshd = prev.callPackage ./pkgs/qubes-sshd {}; 24 | qubes-usb-proxy = prev.callPackage ./pkgs/qubes-usb-proxy {}; 25 | qubes-gpg-split = prev.callPackage ./pkgs/qubes-gpg-split {}; 26 | }; 27 | 28 | pkgs = import nixpkgs { 29 | inherit system; 30 | overlays = [ 31 | qubesPackages 32 | ]; 33 | }; 34 | in rec { 35 | overlays.default = qubesPackages; 36 | nixosModules.default = { 37 | config, 38 | lib, 39 | pkgs, 40 | ... 41 | }: { 42 | imports = [ 43 | ./modules/qubes/core.nix 44 | ./modules/qubes/db.nix 45 | ./modules/qubes/gui.nix 46 | ./modules/qubes/networking.nix 47 | ./modules/qubes/qrexec.nix 48 | ./modules/qubes/sshd.nix 49 | ./modules/qubes/updates.nix 50 | ./modules/qubes/usb.nix 51 | ]; 52 | }; 53 | nixosProfiles.default = { 54 | config, 55 | lib, 56 | pkgs, 57 | ... 58 | }: { 59 | imports = [ 60 | ./profiles/qubes.nix 61 | ]; 62 | }; 63 | nixosConfigurations = { 64 | nixos = 65 | lib.nixosSystem 66 | { 67 | inherit pkgs system; 68 | modules = [ 69 | self.nixosModules.default 70 | self.nixosProfiles.default 71 | ./examples/configuration.nix 72 | ]; 73 | }; 74 | iso = lib.nixosSystem { 75 | inherit system; 76 | specialArgs = { 77 | targetSystem = nixosConfigurations.nixos; 78 | }; 79 | modules = [ 80 | ./tools/iso.nix 81 | ]; 82 | }; 83 | }; 84 | rpm = pkgs.callPackage ./tools/rpm.nix { 85 | inherit nixpkgs; 86 | qubesVersion = "4.2.0"; 87 | nixosConfig = nixosConfigurations.nixos; 88 | }; 89 | iso = nixosConfigurations.iso.config.system.build.isoImage; 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /modules/qubes/gui.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | # configure PATH so qubes-gui can find qubes-run-xorg 8 | # NOTE ideally this would be a normal makeWrapper, however the wrapper is created 9 | # before resholve rewrites the shell scripts and thus has the unresholved PATH. 10 | # also attempted to set via `path = [pkgs.qubes-gui-agent-linux];` in the systemd unit 11 | # however this seems to break the environment causing systemctl calls to fail and thus 12 | # qubes-gui-agent.service 13 | qubes-gui = pkgs.writeShellScriptBin "qubes-gui" '' 14 | export PATH='${pkgs.qubes-gui-agent-linux}/bin' 15 | exec -a "$0" "${pkgs.qubes-gui-agent-linux}/bin/qubes-gui" "$@" 16 | ''; 17 | in 18 | with lib; { 19 | options.services.qubes.gui.enable = mkEnableOption "the qubes gui agent daemon"; 20 | 21 | config = mkIf config.services.qubes.gui.enable { 22 | services.qubes.core.enable = true; 23 | services.qubes.db.enable = true; 24 | services.qubes.qrexec.enable = true; 25 | 26 | services.udev.packages = [ 27 | pkgs.qubes-linux-utils 28 | pkgs.qubes-gui-agent-linux 29 | ]; 30 | 31 | services.xserver.displayManager.startx.enable = true; 32 | environment.etc."X11/Xsession".source = config.services.displayManager.sessionData.wrapper; 33 | services.xserver.displayManager.sessionCommands = '' 34 | if [ -d ${pkgs.qubes-gui-agent-linux}/etc/X11/xinit/xinitrc.d ] ; then 35 | for f in ${pkgs.qubes-gui-agent-linux}/etc/X11/xinit/xinitrc.d/?*.sh ; do 36 | [ -x "$f" ] && . "$f" 37 | done 38 | unset f 39 | fi 40 | ''; 41 | services.xserver.displayManager.session = [ 42 | { 43 | manage = "window"; 44 | name = "qubes-session"; 45 | start = '' 46 | ${pkgs.qubes-gui-agent-linux}/bin/qubes-session 47 | ''; 48 | } 49 | ]; 50 | 51 | xdg.autostart.enable = true; 52 | systemd.user.targets.nixos-fake-graphical-session = { 53 | requires = ["xdg-desktop-autostart.target" "graphical-session.target"]; 54 | before = ["xdg-desktop-autostart.target" "graphical-session.target"]; 55 | }; 56 | # adding to system packages will cause their xdg autostart files to be picked up 57 | environment.systemPackages = [ 58 | pkgs.qubes-gui-agent-linux 59 | ]; 60 | 61 | security.polkit.enable = true; 62 | security.pam.services.qubes-gui-agent = { 63 | rootOK = true; 64 | startSession = true; 65 | }; 66 | 67 | systemd.packages = [pkgs.qubes-gui-agent-linux]; 68 | systemd.services.qubes-gui-agent = { 69 | # 70 | requires = ["qubes-db.service"]; 71 | # ensure the service is started on boot, since Install is ignored 72 | wantedBy = ["multi-user.target"]; 73 | serviceConfig = { 74 | ExecStartPre = ["" "${pkgs.bash}/bin/sh -c ${pkgs.qubes-gui-agent-linux}/lib/qubes/qubes-gui-agent-pre.sh"]; 75 | ExecStart = ["" "${qubes-gui}/bin/qubes-gui $GUI_OPTS"]; 76 | }; 77 | }; 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /pkgs/qubes-linux-utils/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | fetchFromGitHub, 5 | resholve, 6 | coreutils, 7 | gnugrep, 8 | icu, 9 | lsb-release, 10 | kmod, 11 | graphicsmagick, 12 | pkg-config, 13 | python3Packages, 14 | qubes-core-vchan-xen, 15 | qubes-core-qubesdb, 16 | xen, 17 | }: let 18 | version = "4.3.3"; 19 | name = "qubes-linux-utils"; 20 | resholved = resholve.mkDerivation rec { 21 | inherit version; 22 | pname = "${name}-resholved"; 23 | 24 | src = fetchFromGitHub { 25 | owner = "QubesOS"; 26 | repo = name; 27 | rev = "v${version}"; 28 | hash = "sha256-XHx1wt2whMQC+TUc2U97KCOJ8memT6cH0BAp2zxYQyQ="; 29 | }; 30 | 31 | nativeBuildInputs = 32 | [ 33 | icu 34 | pkg-config 35 | qubes-core-vchan-xen 36 | xen 37 | ] 38 | ++ (with python3Packages; [ 39 | distutils 40 | setuptools 41 | ]); 42 | 43 | buildInputs = 44 | [ 45 | graphicsmagick 46 | icu 47 | ] 48 | ++ (with python3Packages; [ 49 | pycairo 50 | pillow 51 | numpy 52 | ]); 53 | 54 | postPatch = '' 55 | substituteInPlace qmemman/Makefile --replace '_XENSTORE_H=$(shell ls /usr/include/xenstore.h)' '_XENSTORE_H=1' 56 | ''; 57 | 58 | buildPhase = '' 59 | make all 60 | ''; 61 | 62 | # FIXME need to sub and move qubes-meminfo-writer 63 | installPhase = '' 64 | make install \ 65 | PYTHON_PREFIX_ARG="--prefix ." \ 66 | DESTDIR="$out" \ 67 | LIBDIR=/lib \ 68 | SYSLIBDIR=/lib \ 69 | SBINDIR=/bin \ 70 | SCRIPTSDIR=/lib/qubes \ 71 | INCLUDEDIR=/include 72 | 73 | 74 | mv "$out/usr/lib/systemd" "$out/lib/systemd" 75 | 76 | rm -rf "$out/usr" 77 | ''; 78 | 79 | solutions = { 80 | default = { 81 | scripts = [ 82 | "lib/qubes/udev-usb-add-change" 83 | "lib/qubes/udev-usb-remove" 84 | ]; 85 | interpreter = "none"; 86 | fix = { 87 | "/sbin/modprobe" = true; 88 | }; 89 | inputs = [ 90 | coreutils 91 | gnugrep 92 | kmod 93 | qubes-core-qubesdb 94 | ]; 95 | execer = [ 96 | "cannot:${kmod}/bin/modprobe" 97 | ]; 98 | }; 99 | }; 100 | 101 | meta = with lib; { 102 | description = "Common Linux files for Qubes VM."; 103 | homepage = "https://qubes-os.org"; 104 | license = licenses.gpl2Plus; 105 | maintainers = []; 106 | platforms = platforms.linux; 107 | }; 108 | }; 109 | in 110 | # FIXME stupid hack, can't figure out how to do these fixups otherwise 111 | lib.extendDerivation true {} (stdenv.mkDerivation { 112 | src = resholved; 113 | inherit version; 114 | pname = name; 115 | 116 | dontConfigure = true; 117 | dontBuild = true; 118 | 119 | installPhase = '' 120 | cp -R $src $out 121 | substituteInPlace "$out/lib/udev/rules.d/99-qubes-usb.rules" --replace '/usr/lib/qubes/' "${resholved}/lib/qubes/" 122 | substituteInPlace "$out/lib/udev/rules.d/99-qubes-block.rules" --replace '/usr/lib/qubes/' "${resholved}/lib/qubes/" 123 | ''; 124 | }) 125 | -------------------------------------------------------------------------------- /pkgs/qubes-usb-proxy/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | fetchFromGitHub, 4 | resholve, 5 | coreutils, 6 | gnugrep, 7 | kmod, 8 | nettools, 9 | python3, 10 | qubes-core-qrexec, 11 | qubes-core-qubesdb, 12 | systemd, 13 | usbutils, 14 | }: 15 | resholve.mkDerivation rec { 16 | pname = "qubes-usb-proxy"; 17 | version = "1.3.2"; 18 | 19 | src = fetchFromGitHub { 20 | owner = "QubesOS"; 21 | repo = "qubes-app-linux-usb-proxy"; 22 | rev = "v${version}"; 23 | hash = "sha256-VyHDFKO0jaCeOkLWubfXOBw+PVHvPwq6iNqSYmFWOR0="; 24 | }; 25 | 26 | buildInputs = [ 27 | qubes-core-qrexec 28 | ]; 29 | 30 | dontBuild = true; 31 | 32 | installPhase = '' 33 | mkdir -p $out/lib 34 | 35 | make install-vm DESTDIR=$out 36 | 37 | mv $out/usr/lib/qubes $out/lib/qubes 38 | mv "$out/usr/lib/udev" "$out/lib/udev" 39 | 40 | # overwrite the broken symlink created by make install-vm 41 | ln -sf ../../../lib/qubes/usb-detach-all "$out/etc/qubes/suspend-pre.d/usb-detach-all.sh" 42 | 43 | substituteInPlace "$out/lib/qubes/usb-reset" --replace "#!/usr/bin/python3" "#!${python3}/bin/python3" 44 | 45 | # we have udevadm by way of kmod, skip the check since resholve won't handle it 46 | substituteInPlace "$out/lib/qubes/usb-import" --replace '[ -f "/usr/bin/udevadm" ] && ' ' ' 47 | 48 | # sudo isn't handled by resholve. ideally we'd just do a single substituteInPlace for sudo here 49 | # but the keep statement would result in usb-export being left unresolved. we can hack around this 50 | # by turning it into a variable and adding an explicit fix resolution 51 | substituteInPlace "$out/etc/qubes-rpc/qubes.USB" --replace "sudo" "/run/wrappers/bin/sudo" \ 52 | --replace "/usr/lib/qubes/usb-export" "\$QUBES_USB_EXPORT" 53 | 54 | substituteInPlace "$out/etc/qubes-rpc/qubes.USBAttach" --replace "/usr/lib/qubes/usb-import" "\$QUBES_USB_IMPORT" 55 | 56 | rm -rf $out/usr 57 | ''; 58 | 59 | solutions = { 60 | default = { 61 | scripts = [ 62 | "lib/qubes/usb-detach-all" 63 | "lib/qubes/usb-export" 64 | "lib/qubes/usb-import" 65 | "etc/qubes-rpc/qubes.USB" 66 | "etc/qubes-rpc/qubes.USBAttach" 67 | "etc/qubes-rpc/qubes.USBDetach" 68 | ]; 69 | interpreter = "none"; 70 | fix = { 71 | "/usr/lib/qubes/usb-reset" = true; 72 | "/usr/lib/qubes/usb-export" = true; 73 | "$QUBES_USB_EXPORT" = ["${placeholder "out"}/lib/qubes/usb-export"]; 74 | "$QUBES_USB_IMPORT" = ["${placeholder "out"}/lib/qubes/usb-import"]; 75 | }; 76 | fake = { 77 | external = [ 78 | "usbguard" 79 | ]; 80 | }; 81 | inputs = [ 82 | "lib/qubes" 83 | coreutils 84 | gnugrep 85 | kmod 86 | nettools 87 | qubes-core-qrexec 88 | qubes-core-qubesdb 89 | systemd 90 | usbutils 91 | ]; 92 | keep = { 93 | "/run/wrappers/bin/sudo" = true; 94 | "${placeholder "out"}/lib/qubes/usb-export" = true; 95 | "${placeholder "out"}/lib/qubes/usb-import" = true; 96 | }; 97 | execer = [ 98 | "cannot:${kmod}/bin/modprobe" 99 | "cannot:${qubes-core-qrexec}/bin/qrexec-client-vm" 100 | "cannot:${systemd}/bin/udevadm" 101 | "cannot:lib/qubes/usb-reset" 102 | "cannot:lib/qubes/usb-export" 103 | ]; 104 | }; 105 | }; 106 | 107 | meta = with lib; { 108 | description = "The Qubes service for proxying USB devices"; 109 | homepage = "https://qubes-os.org"; 110 | license = licenses.gpl2Plus; 111 | maintainers = []; 112 | platforms = platforms.linux; 113 | }; 114 | } 115 | -------------------------------------------------------------------------------- /tools/iso.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | lib, 5 | modulesPath, 6 | targetSystem, 7 | ... 8 | }: let 9 | # this installer is based on 10 | # https://gitlab.com/misuzu/nixos-unattended-install-iso 11 | # Copyright (c) 2024 misuzu 12 | # MIT License 13 | # and https://github.com/NixOS/nixpkgs/blob/master/nixos/lib/make-disk-image.nix 14 | # Copyright (c) 2003-2025 Eelco Dolstra and the Nixpkgs/NixOS contributors 15 | # MIT License 16 | label = "nixos"; 17 | bootSize = "256M"; 18 | blockSize = toString (4 * 1024); # ext4fs block size (not block device sector size) 19 | configuration = ../examples/configuration.nix; 20 | flake = ../examples/flake.nix; 21 | installer = pkgs.writeShellApplication { 22 | name = "installer"; 23 | runtimeInputs = with pkgs; [ 24 | dosfstools 25 | e2fsprogs 26 | gawk 27 | nixos-install-tools 28 | parted 29 | util-linux 30 | config.nix.package 31 | ]; 32 | text = '' 33 | set -euo pipefail 34 | 35 | echo "Setting up disks..." 36 | for i in $(lsblk -pln -o NAME,TYPE | grep disk | awk '{ print $1 }'); do 37 | if [[ "$i" == "/dev/fd0" ]]; then 38 | echo "$i is a floppy, skipping..." 39 | continue 40 | fi 41 | if grep -ql "^$i" <(mount); then 42 | echo "$i is in use, skipping..." 43 | else 44 | DEVICE_MAIN="$i" 45 | break 46 | fi 47 | done 48 | if [[ -z "$DEVICE_MAIN" ]]; then 49 | echo "ERROR: No usable disk found on this machine!" 50 | exit 1 51 | else 52 | echo "Found $DEVICE_MAIN, erasing..." 53 | fi 54 | 55 | mebibyte=$(( 1024 * 1024 )) 56 | round_to_nearest() { 57 | echo $(( ( $1 / $2 + 1) * $2 )) 58 | } 59 | bootSize=$(round_to_nearest "$(numfmt --from=iec '${bootSize}')" $mebibyte) 60 | bootSizeMiB=$(( bootSize / 1024 / 1024 ))MiB 61 | 62 | parted --script "$DEVICE_MAIN" -- \ 63 | mklabel gpt \ 64 | mkpart ESP fat32 8MiB $bootSizeMiB \ 65 | set 1 boot on \ 66 | align-check optimal 1 \ 67 | mkpart no-fs 0 1024KiB \ 68 | set 2 bios_grub on \ 69 | mkpart primary ext4 $bootSizeMiB 100% \ 70 | align-check optimal 3 \ 71 | print 72 | 73 | mkfs.ext4 -b ${blockSize} -L ${label} "$DEVICE_MAIN"3 74 | 75 | mkdir /mnt 76 | mount "$DEVICE_MAIN"3 /mnt 77 | 78 | mkdir -p /mnt/boot 79 | mkfs.vfat -n ESP "$DEVICE_MAIN"1 80 | mount "$DEVICE_MAIN"1 /mnt/boot 81 | 82 | echo "Installing the system..." 83 | nixos-install --no-channel-copy --no-root-password --option substituters "" --system ${targetSystem.config.system.build.toplevel} 84 | 85 | mkdir -p /mnt/etc/nixos 86 | cp ${configuration} /mnt/etc/nixos/configuration.nix 87 | cp ${flake} /mnt/etc/nixos/flake.nix 88 | 89 | echo "Done! Rebooting..." 90 | sleep 3 91 | reboot 92 | ''; 93 | }; 94 | installerFailsafe = pkgs.writeShellScript "failsafe" '' 95 | ${lib.getExe installer} || echo "ERROR: Installation failure!" 96 | sleep 3600 97 | ''; 98 | in { 99 | imports = [ 100 | (modulesPath + "/installer/cd-dvd/iso-image.nix") 101 | (modulesPath + "/profiles/all-hardware.nix") 102 | ]; 103 | 104 | boot.kernelParams = ["systemd.unit=serial-getty.target"]; 105 | 106 | console = { 107 | earlySetup = true; 108 | font = "ter-v16n"; 109 | packages = [pkgs.terminus_font]; 110 | }; 111 | 112 | services.getty.autologinUser = "root"; 113 | programs.bash.interactiveShellInit = '' 114 | if [[ "$(tty)" =~ /dev/(tty1)$ ]]; then 115 | # workaround for https://github.com/NixOS/nixpkgs/issues/219239 116 | systemctl restart systemd-vconsole-setup.service 117 | 118 | reset 119 | 120 | ${installerFailsafe} 121 | fi 122 | ''; 123 | 124 | isoImage.isoName = "${config.isoImage.isoBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.iso"; 125 | isoImage.makeEfiBootable = true; 126 | isoImage.makeUsbBootable = true; 127 | isoImage.squashfsCompression = "zstd -Xcompression-level 15"; # xz takes forever 128 | } 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nix expressions for creating a qubes templatevm 2 | 3 | ## getting started 4 | 5 | *warning*: proceed at your own risk, this involves copying files to dom0 and installing a template 6 | without gpg signature verification 7 | 8 | 1. download the template rpm from github releases or build it yourself via `nix build .#rpm` ( preferred ) 9 | 2. copy the template rpm to dom0 10 | ``` 11 | qvm-run --pass-io 'cat ' > qubes-template-nixos-4.2.0-unavailable.noarch.rpm 12 | ``` 13 | 3. install the template 14 | ``` 15 | qvm-template install qubes-template-nixos-4.2.0-unavailable.noarch.rpm --nogpgcheck 16 | ``` 17 | 4. start the template and wait about 30s ( see qrexec notes. ) 18 | ``` 19 | qvm-start nixos 20 | ``` 21 | 5. start a terminal in the template 22 | ``` 23 | qvm-run nixos xterm 24 | ``` 25 | 26 | at this point you can customize the template and use it like any other NixOS install. the example config has been copied to `/etc/nixos`. 27 | 28 | ## alternative install via iso 29 | 30 | for those that want to avoid installing anything in dom0, these instructions will allow you to install to 31 | a fresh hvm template. 32 | 33 | 1. download the custom installer iso from github releases 34 | 2. create a new qube, select type "TemplateVM", template "(none)", name "nixos", networking "(none)", tick "Launch settings after creation", press "OK" button 35 | 3. in the settings for the new qube, go to the advanced tab, change the kernel to "(provided by qube)" and virtualization mode to "HVM", press "Apply" button 36 | 4. click the "boot qube from CD-ROM" button, click the "from file in qube" option and browse for the downloaded iso. press "OK" button, the qube will launch a boot console 37 | 5. wait for about 15s then press enter to begin the install ( the boot console will say "Press Enter to continue" ) 38 | 6. the system will auto shutdown on successful install 39 | 7. open the settings for the qube, go to the advanced tab, change the kernel to "default (...)" and virtualization mode to "default (PVH)" 40 | 8. start the template and wait about 30s ( see qrexec notes. ) 41 | ``` 42 | qvm-start nixos 43 | ``` 44 | 9. start a terminal in the template 45 | ``` 46 | qvm-run nixos xterm 47 | ``` 48 | 49 | ## issues with the qubes updates proxy 50 | 51 | by default a qubes template does not have direct internet access and instead uses the qubes updates proxy 52 | over qrpc. nix does not have a concept of a global proxy setting and as such is tricky to correctly 53 | configure in a way that doesn't involve simply setting `all_proxy` everywhere. 54 | 55 | as a compromise the packaging sets `all_proxy` for nix-daemon but not all downloads go through nix-daemon. the qubes packaging in this repo creates aliases for interactive shells that wrap a few of the common nix programs to pass proxy info. however this leaves various edge cases, a few of which are noted below. remember that you can always set `all_proxy` in your environment manually or in the worst case, switch to giving the template direct internet access. 56 | 57 | ### issues with sudo nix commands 58 | 59 | due to the above, you're likely to run into issues when running `sudo nix...` - in these cases you can instead first get an interactive root shell e.g. via `sudo su`. 60 | 61 | ### issues with remote nix configs on github 62 | 63 | you may run into issues if you pull a remote nix config over ssh from github. to workaround 64 | you can add the following to `~/.ssh/config` ( the host and port overrides are necessary since these 65 | qubes updates proxy filters port 22. ): 66 | ``` 67 | Host github.com 68 | HostName ssh.github.com 69 | Port 443 70 | ProxyCommand nc -X connect -x 127.0.0.1:8082 %h %p 71 | ``` 72 | 73 | ## notes 74 | 75 | ### what works 76 | - qrexec eventually works 77 | - appvm networking 78 | - xorg 79 | - copy / paste 80 | - qvm-copy 81 | - ssh over qrexec ( handy for using --target-host with nixos-rebuild ) 82 | - memory reporting / ballooning 83 | - qubes update checks 84 | - qubes update triggers ( requires unmerged upstream changes ) 85 | - usb proxy 86 | - building an rpm for the templatevm 87 | - update proxy 88 | 89 | ### what doesn't work / untested 90 | - qrexec startup isn't clean, commands can fail initially 91 | - populating application shortcuts 92 | - using a non-xen provided kernel 93 | - using as netvm or usbvm 94 | - time sync via rpc ( currently handled is systemd-timesyncd, but per vm ntp sync creates more attack surface area? ) 95 | - audio 96 | - grow root fs 97 | 98 | ### bugs 99 | - memory resizing seems to cause crashes in ff 100 | 101 | ### todo 102 | - deal with substituteInPlace deprecation 103 | - should be using 4.2.x package versions across the board, there's a couple 4.3.x packages atm 104 | -------------------------------------------------------------------------------- /modules/qubes/updates.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: 7 | with lib; { 8 | options.services.qubes.updates = { 9 | check = mkEnableOption "enable updates check, can be resource intensive due to required nix build"; 10 | flags = lib.mkOption { 11 | type = lib.types.listOf lib.types.str; 12 | default = [ 13 | "--update-input" 14 | "nixpkgs" 15 | "--update-input" 16 | "qubes-nixos-template" 17 | ]; 18 | example = [ 19 | "-I" 20 | "stuff=/home/alice/nixos-stuff" 21 | "--option" 22 | "extra-binary-caches" 23 | "http://my-cache.example.org/" 24 | ]; 25 | description = '' 26 | Any additional flags passed to {command}`nixos-rebuild`, used for both the check and actual update. 27 | 28 | If you are using flakes and use a local repo you can add 29 | {command}`[ "--update-input" "nixpkgs" "--commit-lock-file" ]` 30 | to update nixpkgs. 31 | ''; 32 | }; 33 | extraPackages = lib.mkOption { 34 | type = lib.types.listOf lib.types.package; 35 | default = []; 36 | example = [pkgs.git pkgs.openssh]; 37 | description = '' 38 | Any additional packages which should be available in the path for {command}`nixos-rebuild`, used for both the check and actual update. 39 | ''; 40 | }; 41 | }; 42 | config = mkMerge [ 43 | ( 44 | mkIf config.services.qubes.updates.check { 45 | systemd.timers.qubes-update-check = { 46 | wantedBy = ["timers.target"]; 47 | }; 48 | } 49 | ) 50 | ( 51 | let 52 | upgradesStatusNotify = pkgs.writeShellScriptBin "upgrades-status-notify" '' 53 | set -e 54 | 55 | export PATH=${lib.makeBinPath config.services.qubes.updates.extraPackages}:$PATH 56 | 57 | if [ "$1" = "started-by-init" ]; then 58 | true "INFO: Started by systemd unit (timer.) Continuing..." 59 | else 60 | true "INFO: Not started by systemd unit (timer.) Probably started by package manager hook script." 61 | if test -e /run/qubes/persistent-full; then 62 | true "INFO: Running inside Template and Standalone. Continuing..." 63 | else 64 | true "INFO: Probably running inside App Qube. Stop." 65 | exit 0 66 | fi 67 | fi 68 | 69 | # FIXME a lot of assumptions here... 70 | tempdir=$(mktemp -d /tmp/tmp.nix-updateinfo.XXX) 71 | cp -r /etc/nixos/. $tempdir 72 | cd $tempdir 73 | ${config.nix.package.out}/bin/nix build ".#nixosConfigurations.$(${pkgs.nettools}/bin/hostname).config.system.build.toplevel" ${toString config.services.qubes.updates.flags} 1>&2 74 | nix_diff=$(${config.nix.package.out}/bin/nix store diff-closures /run/current-system ./result \ 75 | | ${pkgs.gawk}/bin/awk '/[0-9] →|→ [0-9]/ && !/nixos/' || true) 76 | echo "$nix_diff" 1>&2 77 | if [ -z "$nix_diff" ]; then 78 | ${pkgs.qubes-core-qrexec}/lib/qubes/qrexec-client-vm dom0 qubes.NotifyUpdates /bin/sh -c 'echo 0' 79 | else 80 | ${pkgs.qubes-core-qrexec}/lib/qubes/qrexec-client-vm dom0 qubes.NotifyUpdates /bin/sh -c 'echo 1' 81 | fi 82 | cd ~- 83 | rm -rf "$tempdir" 84 | ''; 85 | 86 | getPackages = pkgs.writeShellScriptBin "qubes-nixos-get-packages" '' 87 | empty=$(${config.nix.package.out}/bin/nix build --impure --no-link --print-out-paths --expr '(with import { }; pkgs.runCommand "empty" { } "mkdir -p $out")') 88 | ${config.nix.package.out}/bin/nix store diff-closures "$empty" /run/current-system | ${pkgs.gawk}/bin/awk '/→ [0-9]/ && !/nixos/' | ${pkgs.gnused}/bin/sed 's/\x1b\[[0-9;]*m//g' 89 | ''; 90 | 91 | nixosRebuildWrapper = pkgs.writeShellScriptBin "qubes-nixos-rebuild" '' 92 | export PATH=${lib.makeBinPath config.services.qubes.updates.extraPackages}:$PATH 93 | 94 | # in update-proxy-configs we might set proxy via an override 95 | export all_proxy=$(systemctl show nix-daemon -p Environment | grep -oP '(?<=all_proxy=)[^ ]*') 96 | 97 | # by default switch to the new generation, updating the system 98 | if [ $# -eq 0 ]; then 99 | ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch ${toString config.services.qubes.updates.flags} 100 | else 101 | ${config.system.build.nixos-rebuild}/bin/nixos-rebuild "$@" 102 | fi 103 | ''; 104 | 105 | vmexec = pkgs.writeTextFile { 106 | name = "qubes-rpc-vmexec"; 107 | # NOTE: in order to perform updates, qubes `vmupdate` injects a python agent into the vm and then 108 | # executes it. the agent then calls our scripts to perform various actions. 109 | # we need to ensure the VMExec RPC has the correct PATH to find the dependencies and 110 | # our update scripts. 111 | text = '' 112 | #!${pkgs.stdenv.shell} 113 | 114 | export PATH=${lib.makeBinPath (with pkgs; [coreutils gnutar python3 upgradesStatusNotify getPackages nixosRebuildWrapper])}:$PATH 115 | exec ${config.services.qubes.core.package.out}/bin/qubes-vmexec "$@" 116 | ''; 117 | executable = true; 118 | destination = "/etc/qubes-rpc/qubes.VMExec"; 119 | }; 120 | in { 121 | environment.systemPackages = [ 122 | nixosRebuildWrapper 123 | ]; 124 | services.qubes.qrexec.packages = [vmexec]; 125 | systemd.services.qubes-update-check = { 126 | serviceConfig = { 127 | ExecStart = ["" "${upgradesStatusNotify}/bin/upgrades-status-notify started-by-init"]; 128 | }; 129 | }; 130 | } 131 | ) 132 | ]; 133 | } 134 | -------------------------------------------------------------------------------- /modules/qubes/core.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | lib, 4 | pkgs, 5 | ... 6 | }: let 7 | cfg = config.services.qubes.core; 8 | in 9 | with lib; { 10 | options.services.qubes.core = { 11 | enable = mkEnableOption "the core qubes services"; 12 | networking = mkEnableOption "include core qubes networking services"; 13 | package = mkOption { 14 | type = types.package; 15 | description = "qubes-core-agent-linux package as configured by the qubes module options"; 16 | internal = true; 17 | defaultText = literalExpression "pkgs.qubes-core-agent-linux"; 18 | default = pkgs.qubes-core-agent-linux; 19 | }; 20 | }; 21 | config = mkIf cfg.enable ( 22 | let 23 | qubes-core-agent-linux = 24 | if cfg.networking 25 | then (pkgs.qubes-core-agent-linux.override {enableNetworking = true;}) 26 | else (cfg.package.default); 27 | in { 28 | services.qubes.core.package = qubes-core-agent-linux; 29 | services.qubes.db.enable = true; 30 | 31 | # TODO make the username configurable? 32 | users.groups = { 33 | qubes = { 34 | # supposedly this should be 98, however 995 matches the debian value 35 | gid = 995; 36 | }; 37 | user = { 38 | gid = 1000; 39 | }; 40 | }; 41 | users.users.user = { 42 | createHome = true; 43 | group = "user"; 44 | extraGroups = ["qubes" "wheel"]; 45 | home = "/home/user"; 46 | isNormalUser = true; 47 | password = ""; 48 | shell = pkgs.bash; 49 | uid = 1000; 50 | }; 51 | security.sudo.wheelNeedsPassword = false; 52 | security.pam.services.su.text = lib.mkDefault (lib.mkBefore '' 53 | auth sufficient ${pkgs.linux-pam}/lib/security/pam_succeed_if.so use_uid user ingroup qubes 54 | ''); 55 | # ensure qvm-console-dispvm is logged in 56 | services.getty.autologinUser = "user"; 57 | 58 | fileSystems = { 59 | "/" = { 60 | device = "/dev/mapper/dmroot"; 61 | fsType = "ext4"; 62 | }; 63 | "/proc/xen" = { 64 | device = "xen"; 65 | fsType = "xenfs"; 66 | noCheck = true; 67 | }; 68 | "/rw" = { 69 | device = "/dev/xvdb"; 70 | fsType = "auto"; 71 | options = [ 72 | "noauto" 73 | "defaults" 74 | "discard" 75 | "nosuid" 76 | "nodev" 77 | ]; 78 | }; 79 | "/home" = { 80 | depends = ["/rw"]; 81 | device = "/rw/home"; 82 | fsType = "none"; 83 | options = [ 84 | "noauto" 85 | "bind" 86 | "defaults" 87 | "nosuid" 88 | "nodev" 89 | ]; 90 | }; 91 | "/usr/local" = { 92 | depends = ["/rw"]; 93 | device = "/rw/usrlocal"; 94 | fsType = "none"; 95 | options = [ 96 | "noauto" 97 | "bind" 98 | "defaults" 99 | ]; 100 | }; 101 | }; 102 | systemd.tmpfiles.rules = [ 103 | # create mount point 104 | "d /rw 0755 root root" 105 | # create mount point 106 | "d /usr/local 0755 root root" 107 | # mkdir so that first-boot-completed can be created here 108 | "d /var/lib/qubes 0755 root root" 109 | ]; 110 | swapDevices = [ 111 | { 112 | device = "/dev/xvdc1"; 113 | } 114 | ]; 115 | 116 | # qfile-unpacker needs setuid otherwise it fails during initgroups 117 | security.wrappers.qfile-unpacker = { 118 | owner = "root"; 119 | group = "root"; 120 | source = "${qubes-core-agent-linux}/bin/qfile-unpacker"; 121 | setuid = true; 122 | }; 123 | 124 | # adding to system packages will cause their xdg autostart files to be picked up 125 | environment.systemPackages = [ 126 | qubes-core-agent-linux 127 | ]; 128 | services.udev.packages = [ 129 | pkgs.qubes-linux-utils 130 | qubes-core-agent-linux 131 | ]; 132 | systemd.packages = [ 133 | pkgs.qubes-linux-utils 134 | qubes-core-agent-linux 135 | ]; 136 | 137 | # on other distros this is added on install of the package, 138 | # rather than create another module we just include in core 139 | systemd.services.qubes-meminfo-writer = { 140 | # ensure the service is started on boot, since Install is ignored 141 | wantedBy = ["multi-user.target"]; 142 | 143 | serviceConfig = { 144 | ExecStart = ["" "${pkgs.qubes-linux-utils}/bin/meminfo-writer 30000 100000 /run/meminfo-writer.pid"]; 145 | }; 146 | }; 147 | 148 | systemd.services.qubes-early-vm-config = { 149 | # ensure the service is started on boot, since Install is ignored 150 | wantedBy = ["sysinit.target"]; 151 | 152 | serviceConfig = { 153 | ExecStart = ["" "${qubes-core-agent-linux}/lib/qubes/init/qubes-early-vm-config.sh"]; 154 | }; 155 | }; 156 | 157 | systemd.services.qubes-misc-post = { 158 | # ensure the service is started on boot, since Install is ignored 159 | wantedBy = ["multi-user.target"]; 160 | 161 | serviceConfig = { 162 | ExecStart = ["" "${qubes-core-agent-linux}/lib/qubes/init/misc-post.sh"]; 163 | }; 164 | }; 165 | 166 | systemd.services.qubes-mount-dirs = { 167 | # ensure the service is started on boot, since Install is ignored 168 | wantedBy = ["multi-user.target"]; 169 | 170 | serviceConfig = { 171 | ExecStart = ["" "${qubes-core-agent-linux}/lib/qubes/init/mount-dirs.sh"]; 172 | }; 173 | }; 174 | 175 | systemd.services.qubes-rootfs-resize = { 176 | # ensure the service is started on boot, since Install is ignored 177 | #wantedBy = ["multi-user.target"]; 178 | 179 | serviceConfig = { 180 | ExecStart = ["" "${qubes-core-agent-linux}/lib/qubes/init/resize-rootfs-if-needed.sh"]; 181 | }; 182 | }; 183 | 184 | #systemd.services.qubes-sync-time = { 185 | # TODO how to setup the timer? 186 | 187 | systemd.services.qubes-sysinit = { 188 | # ensure the service is started on boot, since Install is ignored 189 | wantedBy = ["sysinit.target"]; 190 | 191 | serviceConfig = { 192 | ExecStart = ["" "${qubes-core-agent-linux}/lib/qubes/init/qubes-sysinit.sh"]; 193 | }; 194 | }; 195 | 196 | systemd.sockets."qubes-updates-proxy-forwarder" = { 197 | # ensure the socket is activated, since Install is ignored 198 | wantedBy = ["multi-user.target"]; 199 | }; 200 | 201 | systemd.services."qubes-updates-proxy-forwarder@" = { 202 | serviceConfig = { 203 | ExecStart = ["" "${pkgs.qubes-core-qrexec}/bin/qrexec-client-vm --use-stdin-socket '' qubes.UpdatesProxy"]; 204 | }; 205 | }; 206 | 207 | systemd.services.xendriverdomain = { 208 | serviceConfig = { 209 | ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/log/xen"; 210 | # Note: the first "" overrides the ExecStart from the upstream unit 211 | ExecStart = ["" "${pkgs.xen}/bin/xl devd"]; 212 | }; 213 | }; 214 | 215 | # since there is no global nix proxy setting, add aliases which will 216 | # inherit the proxy settings from nix-daemon set by update-proxy-configs 217 | environment.interactiveShellInit = '' 218 | alias nix="all_proxy=\$(systemctl show nix-daemon -p Environment | grep -oP '(?<=all_proxy=)[^ ]*') nix" 219 | alias nix-shell="all_proxy=\$(systemctl show nix-daemon -p Environment | grep -oP '(?<=all_proxy=)[^ ]*') nix-shell" 220 | alias nixos-rebuild="all_proxy=\$(systemctl show nix-daemon -p Environment | grep -oP '(?<=all_proxy=)[^ ]*') nixos-rebuild" 221 | ''; 222 | } 223 | ); 224 | } 225 | -------------------------------------------------------------------------------- /pkgs/qubes-gui-agent-linux/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | fetchFromGitHub, 4 | makeWrapper, 5 | resholve, 6 | autoPatchelfHook, 7 | autoconf, 8 | automake, 9 | bash, 10 | gnugrep, 11 | coreutils, 12 | libtool, 13 | libXt, 14 | lsb-release, 15 | git, 16 | gnused, 17 | libgbm, 18 | pam, 19 | patch, 20 | pipewire, 21 | pixman, 22 | pkg-config, 23 | python3Packages, 24 | pulseaudio, 25 | qubes-core-qrexec, 26 | qubes-core-agent-linux, 27 | qubes-core-vchan-xen, 28 | qubes-core-qubesdb, 29 | qubes-gui-common, 30 | systemd, 31 | util-linux, 32 | which, 33 | xen, 34 | xfce, 35 | xorg, 36 | zenity, 37 | }: 38 | resholve.mkDerivation rec { 39 | version = "4.2.17"; 40 | pname = "qubes-gui-agent-linux"; 41 | 42 | src = fetchFromGitHub { 43 | owner = "QubesOS"; 44 | repo = pname; 45 | rev = "v${version}"; 46 | hash = "sha256-dELBBU0sRtp62QwrZKvV9SJQysMG5Eo1oQMaQy3lXUg="; 47 | }; 48 | 49 | nativeBuildInputs = 50 | [ 51 | autoPatchelfHook 52 | makeWrapper 53 | pkg-config 54 | patch 55 | git 56 | libgbm 57 | automake 58 | autoconf 59 | libtool 60 | pam 61 | pulseaudio 62 | pipewire 63 | libXt 64 | pixman 65 | lsb-release 66 | qubes-gui-common 67 | qubes-core-vchan-xen 68 | qubes-core-qubesdb 69 | xen 70 | ] 71 | ++ (with xorg; [ 72 | libXdamage 73 | libXcomposite 74 | utilmacros 75 | xorgserver 76 | ]); 77 | 78 | buildInputs = 79 | [ 80 | coreutils 81 | qubes-core-vchan-xen 82 | qubes-core-qubesdb 83 | pam 84 | zenity 85 | python3Packages.xcffib 86 | systemd 87 | xfce.xfconf 88 | # xdg-user-dirs-update 89 | ] 90 | ++ (with xorg; [ 91 | libXcomposite 92 | libXdamage 93 | xinit 94 | xrandr 95 | xprop 96 | xsetroot 97 | ]); 98 | 99 | postPatch = '' 100 | rm -f pulse/pulsecore 101 | ln -s "pulsecore-17.0" pulse/pulsecore 102 | 103 | # since we don't know the final resholved package 104 | # path, it's easiest if we instead configure PATH later 105 | sed -i -e 's#execl("/usr/bin/qubes-run-xorg",#execlp("qubes-run-xorg",#' gui-agent/vmside.c 106 | ''; 107 | 108 | buildPhase = '' 109 | make appvm 110 | ''; 111 | 112 | # FIXME sub xdg autostart paths 113 | # FIXME nixgl 114 | installPhase = '' 115 | make install-rh-agent \ 116 | DESTDIR="$out" \ 117 | LIBDIR=/lib \ 118 | USRLIBDIR=/lib \ 119 | SYSLIBDIR=/lib 120 | 121 | # overwrite the broken symlink created by make install-rh-agent 122 | ln -sf ../../bin/qubes-set-monitor-layout $out/etc/qubes-rpc/qubes.SetMonitorLayout 123 | ln -sf ../../bin/qubes-start-xephyr $out/etc/qubes-rpc/qubes.GuiVMSession 124 | 125 | # this will point to the unresholved package but it is not an 126 | # issue since our wrapper only refers to external resources 127 | substituteInPlace "$out/etc/xdg/autostart/qubes-qrexec-fork-server.desktop" --replace '/usr/bin/qrexec-fork-server' "$out/bin/qrexec-fork-server" 128 | 129 | # these are nested within runuser calls, easier to just substituteInPlace 130 | # and pretend to resholve that runuser is not executing it's args 131 | substituteInPlace "$out/usr/bin/qubes-run-xorg" --replace ' /bin/sh' ' ${bash}/bin/sh' 132 | substituteInPlace "$out/usr/bin/qubes-run-xorg" --replace '/usr/bin/xinit' '${xorg.xinit}/bin/xinit' 133 | # skip the wrapper since it's just to determine which binary to call 134 | substituteInPlace "$out/usr/bin/qubes-run-xorg" --replace '/usr/lib/qubes/qubes-xorg-wrapper' "${xorg.xorgserver}/bin/Xorg" 135 | 136 | # config file template and rendered config relocation 137 | substituteInPlace "$out/usr/bin/qubes-run-xorg" --replace '/etc/X11/xorg-qubes.conf.template' "$out/etc/X11/xorg-qubes.conf.template" 138 | substituteInPlace "$out/usr/bin/qubes-run-xorg" --replace ' /etc/X11/xorg-qubes.conf' ' /var/run/xorg-qubes.conf' 139 | substituteInPlace "$out/usr/bin/qubes-run-xorg" --replace '-config xorg-qubes.conf' '-config /var/run/xorg-qubes.conf' 140 | 141 | # resholve won't replace the absolute path reference in this conditional, 142 | # we can just substitute with true 143 | # FIXME this wasn't actually replaced properly before... 144 | # substituteInPlace "$out/usr/bin/qubes-run-xorg" --replace 'if [ -x /bin/loginctl ]; then' 'if [ true ]; then' 145 | 146 | # replace xdg autostart since we generate systemd units instead 147 | # FIXME probably needs to be moved earlier in process 148 | substituteInPlace "$out/usr/bin/qubes-session" --replace '/usr/bin/qubes-session-autostart QUBES X-QUBES "X-$VMTYPE" "X-$UPDTYPE"' 'systemctl --user set-environment XDG_CURRENT_DESKTOP="QUBES:X-QUBES:X-$VMTYPE:X-$UPDTYPE"' 149 | 150 | cat >> $out/etc/X11/xorg-qubes.conf.template <&2; exit 1;;\ 205 | esac; \ 206 | ln -sf "/run/current-system/sw/share/''$i" $out/usr/share/qubes/xdg-override; \ 207 | done < misc/data-dirs 208 | rm $out/usr/share/applications/defaults.list 209 | 210 | # install cron bindmount 211 | mkdir -p "$out/lib/qubes-bind-dirs.d" 212 | install -m 0644 "filesystem/30_cron.conf" "$out/lib/qubes-bind-dirs.d/30_cron.conf" 213 | 214 | # nixos does not have /etc/skel, initialize_home() requires it 215 | substituteInPlace "$out/lib/qubes/init/functions" --replace "/etc/skel" "/var/empty" 216 | 217 | # Fixup paths 218 | substituteInPlace "$out/bin/qubes-session-autostart" --replace "QUBES_XDG_CONFIG_DROPINS = '/etc/qubes/autostart'" "QUBES_XDG_CONFIG_DROPINS = \"$out/etc/qubes/autostart\"" 219 | 220 | # use suid wrapper we will create in the module 221 | substituteInPlace "$out/etc/qubes-rpc/qubes.Filecopy" --replace "/usr/lib/qubes/qfile-unpacker" "/run/wrappers/bin/qfile-unpacker" 222 | 223 | for path in ${lib.concatStringsSep " " scripts_using_functions}; do 224 | substituteInPlace "$out/$path" --replace '/usr/lib/qubes/init/functions' "functions" 225 | done 226 | 227 | substituteInPlace "$out/lib/qubes/init/bind-dirs.sh" --replace "for source_folder in /usr/lib/qubes-bind-dirs.d /etc/qubes-bind-dirs.d /rw/config/qubes-bind-dirs.d ; do" "for source_folder in $out/lib/qubes-bind-dirs.d /rw/config/qubes-bind-dirs.d ; do" 228 | 229 | # Install systemd script allowing to automount /lib/modules 230 | # install -m 644 "archlinux/PKGBUILD.qubes-ensure-lib-modules.service" "$out/usr/lib/systemd/system/qubes-ensure-lib-modules.service" 231 | 232 | # Install pacman hook to update desktop icons 233 | # mkdir -p "$out/usr/share/libalpm/hooks/" 234 | # install -m 644 "archlinux/PKGBUILD.qubes-update-desktop-icons.hook" "$out/usr/share/libalpm/hooks/qubes-update-desktop-icons.hook" 235 | 236 | # Install pacman hook to notify dom0 about successful upgrade 237 | # install -m 644 "archlinux/PKGBUILD.qubes-post-upgrade.hook" "$out/usr/share/libalpm/hooks/qubes-post-upgrade.hook" 238 | 239 | # Install pacman.d drop-ins (at least 1 drop-in must be installed or pacman will fail) 240 | # mkdir -p -m 0755 "$out/etc/pacman.d" 241 | # install -m 644 "archlinux/PKGBUILD-qubes-pacman-options.conf" "$out/etc/pacman.d/10-qubes-options.conf" 242 | 243 | # remove the default VMExec definition since we need to modify it's PATH based on user args in the updates module 244 | rm "$out/etc/qubes-rpc/qubes.VMExec" 245 | # also remove VMExecGUI since it points to VMExec and will be a dangling link 246 | rm "$out/etc/qubes-rpc/qubes.VMExecGUI" 247 | 248 | mv "$out/usr/bin/qubes-vmexec" "$out/bin/" 249 | mv "$out/usr/share" "$out/share" 250 | mv "$out/etc/systemd/system/xendriverdomain.service" "$out/lib/systemd/system/" 251 | 252 | rm -rf "$out/usr/bin" 253 | rm -rf "$out/var/run" 254 | '' 255 | + lib.optionalString (!enableNetworking) '' 256 | # mock update-proxy-configs with an empty script 257 | echo "#!${bash}/bin/sh" > "$out/lib/qubes/update-proxy-configs" 258 | chmod +x "$out/lib/qubes/update-proxy-configs" 259 | '' 260 | + lib.optionalString enableNetworking '' 261 | make -C network install \ 262 | PYTHON_PREFIX_ARG="--prefix ." \ 263 | DESTDIR="$out" \ 264 | BINDIR=/bin \ 265 | SBINDIR=/bin \ 266 | LIBDIR=/lib \ 267 | SYSLIBDIR=/lib \ 268 | SYSTEM_DROPIN_DIR=/usr/lib/systemd/system \ 269 | USER_DROPIN_DIR=/usr/lib/systemd/user \ 270 | DIST=nixos 271 | make install-netvm \ 272 | PYTHON_PREFIX_ARG="--prefix ." \ 273 | DESTDIR="$out" \ 274 | BINDIR=/bin \ 275 | SBINDIR=/bin \ 276 | LIBDIR=/lib \ 277 | SYSLIBDIR=/lib \ 278 | SYSTEM_DROPIN_DIR=/usr/lib/systemd/system \ 279 | USER_DROPIN_DIR=/usr/lib/systemd/user \ 280 | DIST=nixos 281 | 282 | # overwrite the broken symlink created by make install-netvm 283 | ln -sf ../../lib/qubes/qubes-setup-dnat-to-ns $out/etc/dhclient.d/qubes-setup-dnat-to-ns.sh 284 | 285 | for path in lib/qubes/init/network-uplink-wait.sh lib/qubes/setup-ip lib/qubes/update-proxy-configs ; do 286 | substituteInPlace "$out/$path" --replace '/usr/lib/qubes/init/functions' "functions" 287 | done 288 | 289 | cat >> "$out/lib/qubes/update-proxy-configs" < /run/systemd/system/nix-daemon.service.d/override.conf <