├── .gitignore ├── vars.nix ├── system ├── internalisation.nix ├── packages.nix ├── services.nix ├── nixos.nix └── users.nix ├── host ├── configuration.nix └── hardware-configuration.nix ├── LICENSE ├── justfile ├── .github └── workflows │ └── update-flake.yaml ├── README.md ├── flake.lock └── flake.nix /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | result-* 3 | -------------------------------------------------------------------------------- /vars.nix: -------------------------------------------------------------------------------- 1 | { 2 | user = "kev"; 3 | timeZone = "Asia/Shanghai"; 4 | defaultLocale = "en_US.UTF-8"; 5 | } 6 | -------------------------------------------------------------------------------- /system/internalisation.nix: -------------------------------------------------------------------------------- 1 | _: 2 | 3 | { 4 | # Locales 5 | i18n.defaultLocale = (import ../vars.nix).defaultLocale; 6 | time.timeZone = (import ../vars.nix).timeZone; 7 | } 8 | -------------------------------------------------------------------------------- /system/packages.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | { 4 | # Packages 5 | environment.systemPackages = with pkgs; [ 6 | nixpkgs-fmt 7 | bash-completion 8 | git 9 | vim 10 | wget 11 | ]; 12 | } 13 | -------------------------------------------------------------------------------- /host/configuration.nix: -------------------------------------------------------------------------------- 1 | _: 2 | 3 | { 4 | imports = [ 5 | ./hardware-configuration.nix 6 | 7 | # system modules 8 | ../system/nixos.nix 9 | ../system/users.nix 10 | ../system/services.nix 11 | ../system/packages.nix 12 | ../system/internalisation.nix 13 | ]; 14 | 15 | # set hostname 16 | networking.hostName = "nixos-proxmox-host"; 17 | } 18 | -------------------------------------------------------------------------------- /system/services.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | 3 | { 4 | # enable openssh 5 | services.openssh = { 6 | enable = true; 7 | settings = { 8 | PasswordAuthentication = false; 9 | KbdInteractiveAuthentication = false; 10 | PermitRootLogin = lib.mkForce "prohibit-password"; # enable root login for remote deploy 11 | }; 12 | }; 13 | 14 | # enable qemu-guest-agent 15 | services.qemuGuest.enable = true; 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 @NixOS-Pilots 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 | -------------------------------------------------------------------------------- /system/nixos.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, user, ... }: 2 | 3 | { 4 | # Nix settings 5 | # Ref: https://nixos.wiki/wiki/Nixos-rebuild 6 | nix = { 7 | # enable flake 8 | package = pkgs.nixFlakes; 9 | extraOptions = "experimental-features = nix-command flakes"; 10 | settings = { 11 | # enable auto-cleanup 12 | auto-optimise-store = true; 13 | # set max-jobs 14 | max-jobs = lib.mkDefault 8; 15 | # enable ccache (local compilation) 16 | # extra-sandbox-paths = [ config.programs.ccache.cacheDir ]; 17 | trusted-users = [ "root" user ]; 18 | # trusted-public-keys = [ ]; 19 | 20 | # substituers will be appended to the default substituters when fetching packages 21 | extra-substituters = [ ]; 22 | extra-trusted-public-keys = [ ]; 23 | # ref: https://github.com/NixOS/nix/issues/4894 24 | # workaround to fix ssh signature issues 25 | require-sigs = false; 26 | }; 27 | 28 | # garbage collection 29 | gc = { 30 | automatic = true; 31 | dates = "daily"; 32 | options = "--delete older-than 3d"; 33 | }; 34 | }; 35 | 36 | system.stateVersion = "24.05"; 37 | } 38 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | # justfile 2 | # cheatsheet: https://cheatography.com/linux-china/cheat-sheets/justfile/ 3 | 4 | # define alias 5 | 6 | # set options 7 | set positional-arguments := true 8 | 9 | # default recipe to display help information 10 | default: 11 | @just --list 12 | 13 | # build pkg 14 | build pkg: 15 | @nom build .#{{ pkg }} 16 | 17 | # check version 18 | version pkg: 19 | @./result/bin/{{ pkg }} --version 20 | 21 | # update all flake inputs 22 | update: 23 | @nix flake update 24 | 25 | # show flake 26 | show: 27 | @nix flake show 28 | 29 | # check flake 30 | check: 31 | @nix flake check 32 | 33 | # apply fix from linters 34 | lint: 35 | @statix fix --ignore 'templates/' . 36 | @deadnix --edit --exclude 'templates/' . 37 | 38 | # update a particular flake input 39 | update-input input: 40 | @nix flake lock --update-input {{ input }} 41 | 42 | # nix-prefetch-url 43 | prefetch-url url: 44 | @nix-prefetch-url --type sha256 '{{ url }}' | xargs nix hash to-sri --type sha256 45 | 46 | # nix-prefetch-git 47 | prefetch-git repo rev: 48 | @nix-prefetch-git --url 'git@github.com:{{ repo }}' --rev '{{ rev }}' --fetch-submodules 49 | 50 | # stage all files 51 | add: 52 | @git add . 53 | -------------------------------------------------------------------------------- /system/users.nix: -------------------------------------------------------------------------------- 1 | { pkgs, config, user, ... }: 2 | 3 | let 4 | keyFiles = [ 5 | (pkgs.fetchurl { 6 | # replace with your own ssh key! 7 | # command to generate the hash: 8 | # nix-prefetch-url --type sha256 'https://github.com/.gpg' | xargs nix hash to-sri --type sha256 9 | url = "https://github.com/.keys"; 10 | hash = "sha256-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 11 | }) 12 | ]; 13 | in 14 | { 15 | # Users 16 | users = { 17 | defaultUserShell = pkgs.bash; 18 | users = { 19 | ${user} = { 20 | isNormalUser = true; 21 | extraGroups = [ "wheel" ]; 22 | home = "/home/${user}"; 23 | shell = pkgs.bash; 24 | # generated by `mkpasswd -m scrypt` 25 | initialHashedPassword = "$7$CU..../....qDkQV/MO7behVrojFN4ML/$dz4kD8CE8PV7koHidmGnIboE3NwZrl/4eUYUvXZ5HH9"; # value: passwd 26 | # /etc/ssh/authorized_keys.d/${user} 27 | openssh.authorizedKeys.keyFiles = keyFiles; 28 | }; 29 | root = { 30 | inherit (config.users.users."${user}") initialHashedPassword; 31 | openssh.authorizedKeys.keyFiles = keyFiles; 32 | }; 33 | }; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /host/hardware-configuration.nix: -------------------------------------------------------------------------------- 1 | { lib, system, ... }: 2 | 3 | { 4 | # settings specific to this VM setup 5 | # enable after installation! 6 | 7 | boot = { 8 | # use latest kernel 9 | # kernelPackages = pkgs.linuxPackages_latest; 10 | supportedFilesystems = [ "ext4" "btrfs" "xfs" "fat" "vfat" "cifs" "nfs" ]; 11 | growPartition = true; 12 | kernelModules = [ "kvm-amd" ]; 13 | kernelParams = lib.mkForce [ ]; 14 | 15 | loader = { 16 | grub = { 17 | enable = true; 18 | device = "nodev"; 19 | efiSupport = true; 20 | efiInstallAsRemovable = true; 21 | }; 22 | # wait for 3 seconds to select the boot entry 23 | # timeout = lib.mkForce 3; 24 | }; 25 | 26 | initrd = { 27 | availableKernelModules = [ "9p" "9pnet_virtio" "ata_piix" "uhci_hcd" "virtio_blk" "virtio_mmio" "virtio_net" "virtio_pci" "virtio_scsi" ]; 28 | kernelModules = [ "virtio_balloon" "virtio_console" "virtio_rng" ]; 29 | }; 30 | 31 | # clear /tmp on boot to get a stateless /tmp directory. 32 | tmp.cleanOnBoot = true; 33 | }; 34 | 35 | fileSystems."/" = { 36 | device = "/dev/disk/by-label/nixos"; 37 | autoResize = true; 38 | fsType = "ext4"; 39 | }; 40 | 41 | fileSystems."/boot" = { 42 | device = "/dev/disk/by-label/ESP"; 43 | fsType = "vfat"; 44 | }; 45 | 46 | 47 | # reduce size of the VM 48 | services.fstrim = { 49 | enable = true; 50 | interval = "weekly"; 51 | }; 52 | 53 | nixpkgs.hostPlatform = lib.mkDefault system; 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/update-flake.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: (Cron) Synchronize upstream flake inputs 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # Run this Action every month at 0:00pm UTC 7 | - cron: "0 0 1 * *" 8 | 9 | env: 10 | MAINTAINERS: piyoki 11 | TARGETS: | 12 | nixpkgs 13 | pre-commit-hooks 14 | PR_LABELS: | 15 | flake-lock 16 | automated-pr 17 | GIT_COMMIT_MESSAGE: "chore(flake) update flake.lock for the selected upstream inputs" 18 | 19 | jobs: 20 | update-flake: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Generate GitHub auth token 24 | # https://github.com/tibdex/github-app-token 25 | id: generate_token 26 | uses: tibdex/github-app-token@v2.1.0 27 | with: 28 | app_id: ${{ secrets.GH_APP_ID }} 29 | private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} 30 | 31 | - uses: actions/checkout@main 32 | with: 33 | token: ${{ steps.generate_token.outputs.token }} 34 | 35 | - uses: DeterminateSystems/nix-installer-action@main 36 | - uses: DeterminateSystems/magic-nix-cache-action@main 37 | - uses: DeterminateSystems/flake-checker-action@main 38 | 39 | - name: Update flake.lock 40 | id: update-flake-lock 41 | uses: DeterminateSystems/update-flake-lock@main 42 | with: 43 | pr-title: ${{ env.GIT_COMMIT_MESSAGE }} 44 | pr-labels: ${{ env.PR_LABELS }} 45 | inputs: ${{ env.TARGETS }} 46 | token: ${{ steps.generate_token.outputs.token }} 47 | pr-assignees: ${{ env.MAINTAINERS }} 48 | 49 | - name: Print PR number 50 | run: | 51 | echo Pull request number is ${{ steps.update.outputs.pull-request-number }}. 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

❄️ NixOS on Proxmox

2 |

3 | Deploy a minimum NixOS VM on Proxmox at speed 4 |

5 |

6 | License 7 | 8 | lastcommit 9 |

10 | 11 | --- 12 | 13 | > [!NOTE] 14 | > The image can be build locally, or by using the included GitHub action. 15 | 16 | Credit to [@Mayniklas](https://github.com/Mayniklas), [@NixOS-Pilots](https://github.com/NixOS-Pilots) and inspired by [Mayniklas/nixos-proxmox](https://github.com/Mayniklas/nixos-proxmox). 17 | 18 | ## Table of Contents 19 | 20 | 21 | 22 | * [Prerequisites](#prerequisites) 23 | * [Update user ssh-keys](#update-user-ssh-keys) 24 | * [Deployment](#deployment) 25 | * [Build the image](#build-the-image) 26 | * [Upload the image to Proxmox](#upload-the-image-to-proxmox) 27 | * [Apply config changes](#apply-config-changes) 28 | * [Contribution](#contribution) 29 | * [References](#references) 30 | 31 | 32 | 33 | ## Prerequisites 34 | 35 | ### Update user ssh-keys 36 | 37 | In `./system/users.nix`, update the `keyFiles` attribute with your own ssh key. 38 | 39 | ```nix 40 | { 41 | keyFiles = [ 42 | (pkgs.fetchurl { 43 | # replace with your own ssh key! 44 | # command to generate the hash: 45 | # nix-prefetch-url --type sha256 'https://github.com/.gpg' | xargs nix hash to-sri --type sha256 46 | url = "https://github.com/.keys"; 47 | hash = "sha256-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 48 | }) 49 | ]; 50 | } 51 | ``` 52 | 53 | ## Deployment 54 | 55 | ### Build the image 56 | 57 | ```bash 58 | # build VMA image 59 | nix build .#proxmox-image 60 | ``` 61 | 62 | ### Upload the image to Proxmox 63 | 64 | Upload the image to a location, that is accessible by Proxmox. 65 | 66 | ```bash 67 | # scp build artifact to Proxmox 68 | scp ./result/*.vma.zst root@:/root/ 69 | ``` 70 | 71 | Create a VM using `qmrestore` 72 | 73 | ```bash 74 | # import the VM from VMA image 75 | # unique is required to randomize the MAC address of the network interface 76 | # storage is the name of the storage, where we create the VM 77 | qmrestore ./vzdump-qemu-nixos-*.vma.zst 999 --unique true --storage 78 | ``` 79 | 80 | ## Apply config changes 81 | 82 | ```bash 83 | nixos-rebuild switch --flake .#proxmox-host 84 | ``` 85 | 86 | ## Contribution 87 | 88 | ## References 89 | 90 | - [github.com/MayNiklas/nixos-proxmox](https://github.com/MayNiklas/nixos-proxmox/) 91 | - [github.com/NixOS/nixpkgs/.../proxmox-image.nix](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/proxmox-image.nix#L272-L274) 92 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1761588595, 7 | "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "gitignore": { 20 | "inputs": { 21 | "nixpkgs": [ 22 | "pre-commit-hooks", 23 | "nixpkgs" 24 | ] 25 | }, 26 | "locked": { 27 | "lastModified": 1709087332, 28 | "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", 29 | "owner": "hercules-ci", 30 | "repo": "gitignore.nix", 31 | "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "hercules-ci", 36 | "repo": "gitignore.nix", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1764316264, 43 | "narHash": "sha256-82L+EJU+40+FIdeG4gmUlOF1jeSwlf2AwMarrpdHF6o=", 44 | "owner": "nixos", 45 | "repo": "nixpkgs", 46 | "rev": "9a7b80b6f82a71ea04270d7ba11b48855681c4b0", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "nixos", 51 | "ref": "nixos-25.05", 52 | "repo": "nixpkgs", 53 | "type": "github" 54 | } 55 | }, 56 | "nixpkgs_2": { 57 | "locked": { 58 | "lastModified": 1759417375, 59 | "narHash": "sha256-O7eHcgkQXJNygY6AypkF9tFhsoDQjpNEojw3eFs73Ow=", 60 | "owner": "NixOS", 61 | "repo": "nixpkgs", 62 | "rev": "dc704e6102e76aad573f63b74c742cd96f8f1e6c", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "owner": "NixOS", 67 | "ref": "nixpkgs-unstable", 68 | "repo": "nixpkgs", 69 | "type": "github" 70 | } 71 | }, 72 | "pre-commit-hooks": { 73 | "inputs": { 74 | "flake-compat": "flake-compat", 75 | "gitignore": "gitignore", 76 | "nixpkgs": "nixpkgs_2" 77 | }, 78 | "locked": { 79 | "lastModified": 1763988335, 80 | "narHash": "sha256-QlcnByMc8KBjpU37rbq5iP7Cp97HvjRP0ucfdh+M4Qc=", 81 | "owner": "cachix", 82 | "repo": "pre-commit-hooks.nix", 83 | "rev": "50b9238891e388c9fdc6a5c49e49c42533a1b5ce", 84 | "type": "github" 85 | }, 86 | "original": { 87 | "owner": "cachix", 88 | "repo": "pre-commit-hooks.nix", 89 | "type": "github" 90 | } 91 | }, 92 | "root": { 93 | "inputs": { 94 | "nixpkgs": "nixpkgs", 95 | "pre-commit-hooks": "pre-commit-hooks" 96 | } 97 | } 98 | }, 99 | "root": "root", 100 | "version": 7 101 | } 102 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A flake to bootstrap a KVM template for Proxmox"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05"; 6 | pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, ... }@inputs: with inputs; 10 | let 11 | system = "x86_64-linux"; 12 | inherit (import ./vars.nix) user; 13 | pkgs = (import nixpkgs) { inherit system; }; 14 | inherit (nixpkgs) lib; 15 | specialArgs = { inherit inputs pkgs system user; }; 16 | # function to generate pre-commit-checks 17 | genChecks = system: (pre-commit-hooks.lib.${system}.run { 18 | src = ./.; 19 | hooks = { 20 | nixpkgs-fmt.enable = true; # formatter 21 | statix.enable = true; # linter 22 | deadnix.enable = true; # linter 23 | }; 24 | }); 25 | in 26 | { 27 | # checks 28 | checks.${system}.pre-commit-check = genChecks system; 29 | 30 | # See for further options: 31 | # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/proxmox-image.nix 32 | nixosConfigurations.proxmox-host = lib.nixosSystem { 33 | inherit specialArgs; 34 | modules = [ 35 | "${nixpkgs}/nixos/modules/virtualisation/proxmox-image.nix" 36 | ./host/configuration.nix 37 | { 38 | proxmox = { 39 | # Reference: https://pve.proxmox.com/wiki/Qemu/KVM_Virtual_Machines#qm_virtual_machines_settings 40 | qemuConf = { 41 | # EFI support 42 | bios = "ovmf"; 43 | cores = 4; 44 | memory = 1024; 45 | net0 = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1"; 46 | diskSize = "20480"; # 20g 47 | }; 48 | qemuExtraConf = { 49 | # start the VM automatically on boot 50 | # onboot = "1"; 51 | cpu = "host"; 52 | tags = "nixos"; 53 | }; 54 | }; 55 | nix.nixPath = [ "nixpkgs=${nixpkgs}" ]; 56 | nix.registry.nixpkgs.flake = nixpkgs; 57 | } 58 | ]; 59 | }; 60 | 61 | packages.${system} = { 62 | # nix build .#proxmox-image 63 | proxmox-image = self.nixosConfigurations.proxmox-host.config.system.build.VMA; 64 | 65 | # nix build .#proxmox-image-uncompressed 66 | proxmox-image-uncompressed = pkgs.stdenv.mkDerivation { 67 | name = "proxmox-image-uncompressed"; 68 | dontUnpack = true; 69 | installPhase = '' 70 | # create output directory 71 | mkdir -p $out/ 72 | 73 | # basename of the vma file (without .zst) 74 | export filename=$(basename ${self.packages.${system}.proxmox-image}/vzdump-qemu-nixos-*.vma.zst .zst) 75 | 76 | # decompress the vma file and write it to the output directory 77 | ${pkgs.zstd}/bin/zstd -d ${self.packages.${system}.proxmox-image}/vzdump-qemu-nixos-*.vma.zst -o $out/$filename 78 | ''; 79 | }; 80 | }; 81 | }; 82 | } 83 | --------------------------------------------------------------------------------