uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead

https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948
this can do it nicely.

Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
Anton Arapov 2021-04-03 12:58:10 +02:00 committed by Alan Daniels
commit 56de2bcd43
30691 changed files with 3076956 additions and 0 deletions

View file

@ -0,0 +1,444 @@
let self = {
"14.04".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-71c6f470";
"14.04".ap-northeast-1.x86_64-linux.pv-ebs = "ami-4dcbf84c";
"14.04".ap-northeast-1.x86_64-linux.pv-s3 = "ami-8fc4f68e";
"14.04".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-da280888";
"14.04".ap-southeast-1.x86_64-linux.pv-ebs = "ami-7a9dbc28";
"14.04".ap-southeast-1.x86_64-linux.pv-s3 = "ami-c4290996";
"14.04".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-ab523e91";
"14.04".ap-southeast-2.x86_64-linux.pv-ebs = "ami-6769055d";
"14.04".ap-southeast-2.x86_64-linux.pv-s3 = "ami-15533f2f";
"14.04".eu-central-1.x86_64-linux.hvm-ebs = "ami-ba0234a7";
"14.04".eu-west-1.x86_64-linux.hvm-ebs = "ami-96cb63e1";
"14.04".eu-west-1.x86_64-linux.pv-ebs = "ami-b48c25c3";
"14.04".eu-west-1.x86_64-linux.pv-s3 = "ami-06cd6571";
"14.04".sa-east-1.x86_64-linux.hvm-ebs = "ami-01b90e1c";
"14.04".sa-east-1.x86_64-linux.pv-ebs = "ami-69e35474";
"14.04".sa-east-1.x86_64-linux.pv-s3 = "ami-61b90e7c";
"14.04".us-east-1.x86_64-linux.hvm-ebs = "ami-58ba3a30";
"14.04".us-east-1.x86_64-linux.pv-ebs = "ami-9e0583f6";
"14.04".us-east-1.x86_64-linux.pv-s3 = "ami-9cbe3ef4";
"14.04".us-west-1.x86_64-linux.hvm-ebs = "ami-0bc3d74e";
"14.04".us-west-1.x86_64-linux.pv-ebs = "ami-8b1703ce";
"14.04".us-west-1.x86_64-linux.pv-s3 = "ami-27ccd862";
"14.04".us-west-2.x86_64-linux.hvm-ebs = "ami-3bf1bf0b";
"14.04".us-west-2.x86_64-linux.pv-ebs = "ami-259bd515";
"14.04".us-west-2.x86_64-linux.pv-s3 = "ami-07094037";
"14.12".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-24435f25";
"14.12".ap-northeast-1.x86_64-linux.pv-ebs = "ami-b0425eb1";
"14.12".ap-northeast-1.x86_64-linux.pv-s3 = "ami-fed3c6ff";
"14.12".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-6c765d3e";
"14.12".ap-southeast-1.x86_64-linux.pv-ebs = "ami-6a765d38";
"14.12".ap-southeast-1.x86_64-linux.pv-s3 = "ami-d1bf9183";
"14.12".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-af86f395";
"14.12".ap-southeast-2.x86_64-linux.pv-ebs = "ami-b386f389";
"14.12".ap-southeast-2.x86_64-linux.pv-s3 = "ami-69c5ae53";
"14.12".eu-central-1.x86_64-linux.hvm-ebs = "ami-4a497a57";
"14.12".eu-central-1.x86_64-linux.pv-ebs = "ami-4c497a51";
"14.12".eu-central-1.x86_64-linux.pv-s3 = "ami-60f2c27d";
"14.12".eu-west-1.x86_64-linux.hvm-ebs = "ami-d126a5a6";
"14.12".eu-west-1.x86_64-linux.pv-ebs = "ami-0126a576";
"14.12".eu-west-1.x86_64-linux.pv-s3 = "ami-deda5fa9";
"14.12".sa-east-1.x86_64-linux.hvm-ebs = "ami-2d239e30";
"14.12".sa-east-1.x86_64-linux.pv-ebs = "ami-35239e28";
"14.12".sa-east-1.x86_64-linux.pv-s3 = "ami-81e3519c";
"14.12".us-east-1.x86_64-linux.hvm-ebs = "ami-0c463a64";
"14.12".us-east-1.x86_64-linux.pv-ebs = "ami-ac473bc4";
"14.12".us-east-1.x86_64-linux.pv-s3 = "ami-00e18a68";
"14.12".us-west-1.x86_64-linux.hvm-ebs = "ami-ca534a8f";
"14.12".us-west-1.x86_64-linux.pv-ebs = "ami-3e534a7b";
"14.12".us-west-1.x86_64-linux.pv-s3 = "ami-2905196c";
"14.12".us-west-2.x86_64-linux.hvm-ebs = "ami-fb9dc3cb";
"14.12".us-west-2.x86_64-linux.pv-ebs = "ami-899dc3b9";
"14.12".us-west-2.x86_64-linux.pv-s3 = "ami-cb7f2dfb";
"15.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-58cac236";
"15.09".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-39c8c057";
"15.09".ap-northeast-1.x86_64-linux.pv-ebs = "ami-5ac9c134";
"15.09".ap-northeast-1.x86_64-linux.pv-s3 = "ami-03cec66d";
"15.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-2fc2094c";
"15.09".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-9ec308fd";
"15.09".ap-southeast-1.x86_64-linux.pv-ebs = "ami-95c00bf6";
"15.09".ap-southeast-1.x86_64-linux.pv-s3 = "ami-bfc00bdc";
"15.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-996c4cfa";
"15.09".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-3f6e4e5c";
"15.09".ap-southeast-2.x86_64-linux.pv-ebs = "ami-066d4d65";
"15.09".ap-southeast-2.x86_64-linux.pv-s3 = "ami-cc6e4eaf";
"15.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-3f8c6b50";
"15.09".eu-central-1.x86_64-linux.hvm-s3 = "ami-5b836434";
"15.09".eu-central-1.x86_64-linux.pv-ebs = "ami-118c6b7e";
"15.09".eu-central-1.x86_64-linux.pv-s3 = "ami-2c977043";
"15.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-9cf04aef";
"15.09".eu-west-1.x86_64-linux.hvm-s3 = "ami-2bea5058";
"15.09".eu-west-1.x86_64-linux.pv-ebs = "ami-c9e852ba";
"15.09".eu-west-1.x86_64-linux.pv-s3 = "ami-c6f64cb5";
"15.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-6e52df02";
"15.09".sa-east-1.x86_64-linux.hvm-s3 = "ami-1852df74";
"15.09".sa-east-1.x86_64-linux.pv-ebs = "ami-4368e52f";
"15.09".sa-east-1.x86_64-linux.pv-s3 = "ami-f15ad79d";
"15.09".us-east-1.x86_64-linux.hvm-ebs = "ami-84a6a0ee";
"15.09".us-east-1.x86_64-linux.hvm-s3 = "ami-06a7a16c";
"15.09".us-east-1.x86_64-linux.pv-ebs = "ami-a4a1a7ce";
"15.09".us-east-1.x86_64-linux.pv-s3 = "ami-5ba8ae31";
"15.09".us-west-1.x86_64-linux.hvm-ebs = "ami-22c8bb42";
"15.09".us-west-1.x86_64-linux.hvm-s3 = "ami-a2ccbfc2";
"15.09".us-west-1.x86_64-linux.pv-ebs = "ami-10cebd70";
"15.09".us-west-1.x86_64-linux.pv-s3 = "ami-fa30429a";
"15.09".us-west-2.x86_64-linux.hvm-ebs = "ami-ce57b9ae";
"15.09".us-west-2.x86_64-linux.hvm-s3 = "ami-2956b849";
"15.09".us-west-2.x86_64-linux.pv-ebs = "ami-005fb160";
"15.09".us-west-2.x86_64-linux.pv-s3 = "ami-cd55bbad";
"16.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-40619d21";
"16.03".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-ce629eaf";
"16.03".ap-northeast-1.x86_64-linux.pv-ebs = "ami-ef639f8e";
"16.03".ap-northeast-1.x86_64-linux.pv-s3 = "ami-a1609cc0";
"16.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-deca00b0";
"16.03".ap-northeast-2.x86_64-linux.hvm-s3 = "ami-a3b77dcd";
"16.03".ap-northeast-2.x86_64-linux.pv-ebs = "ami-7bcb0115";
"16.03".ap-northeast-2.x86_64-linux.pv-s3 = "ami-a2b77dcc";
"16.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-0dff9562";
"16.03".ap-south-1.x86_64-linux.hvm-s3 = "ami-13f69c7c";
"16.03".ap-south-1.x86_64-linux.pv-ebs = "ami-0ef39961";
"16.03".ap-south-1.x86_64-linux.pv-s3 = "ami-e0c8a28f";
"16.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-5e964a3d";
"16.03".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-4d964a2e";
"16.03".ap-southeast-1.x86_64-linux.pv-ebs = "ami-ec9b478f";
"16.03".ap-southeast-1.x86_64-linux.pv-s3 = "ami-999b47fa";
"16.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-9f7359fc";
"16.03".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-987359fb";
"16.03".ap-southeast-2.x86_64-linux.pv-ebs = "ami-a2705ac1";
"16.03".ap-southeast-2.x86_64-linux.pv-s3 = "ami-a3705ac0";
"16.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-17a45178";
"16.03".eu-central-1.x86_64-linux.hvm-s3 = "ami-f9a55096";
"16.03".eu-central-1.x86_64-linux.pv-ebs = "ami-c8a550a7";
"16.03".eu-central-1.x86_64-linux.pv-s3 = "ami-6ea45101";
"16.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-b5b3d5c6";
"16.03".eu-west-1.x86_64-linux.hvm-s3 = "ami-c986e0ba";
"16.03".eu-west-1.x86_64-linux.pv-ebs = "ami-b083e5c3";
"16.03".eu-west-1.x86_64-linux.pv-s3 = "ami-3c83e54f";
"16.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-f6eb7f9a";
"16.03".sa-east-1.x86_64-linux.hvm-s3 = "ami-93e773ff";
"16.03".sa-east-1.x86_64-linux.pv-ebs = "ami-cbb82ca7";
"16.03".sa-east-1.x86_64-linux.pv-s3 = "ami-abb82cc7";
"16.03".us-east-1.x86_64-linux.hvm-ebs = "ami-c123a3d6";
"16.03".us-east-1.x86_64-linux.hvm-s3 = "ami-bc25a5ab";
"16.03".us-east-1.x86_64-linux.pv-ebs = "ami-bd25a5aa";
"16.03".us-east-1.x86_64-linux.pv-s3 = "ami-a325a5b4";
"16.03".us-west-1.x86_64-linux.hvm-ebs = "ami-748bcd14";
"16.03".us-west-1.x86_64-linux.hvm-s3 = "ami-a68dcbc6";
"16.03".us-west-1.x86_64-linux.pv-ebs = "ami-048acc64";
"16.03".us-west-1.x86_64-linux.pv-s3 = "ami-208dcb40";
"16.03".us-west-2.x86_64-linux.hvm-ebs = "ami-8263a0e2";
"16.03".us-west-2.x86_64-linux.hvm-s3 = "ami-925c9ff2";
"16.03".us-west-2.x86_64-linux.pv-ebs = "ami-5e61a23e";
"16.03".us-west-2.x86_64-linux.pv-s3 = "ami-734c8f13";
# 16.09.1508.3909827
"16.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-68453b0f";
"16.09".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-f9bec09e";
"16.09".ap-northeast-1.x86_64-linux.pv-ebs = "ami-254a3442";
"16.09".ap-northeast-1.x86_64-linux.pv-s3 = "ami-ef473988";
"16.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-18ae7f76";
"16.09".ap-northeast-2.x86_64-linux.hvm-s3 = "ami-9eac7df0";
"16.09".ap-northeast-2.x86_64-linux.pv-ebs = "ami-57aa7b39";
"16.09".ap-northeast-2.x86_64-linux.pv-s3 = "ami-5cae7f32";
"16.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-b3f98fdc";
"16.09".ap-south-1.x86_64-linux.hvm-s3 = "ami-98e690f7";
"16.09".ap-south-1.x86_64-linux.pv-ebs = "ami-aef98fc1";
"16.09".ap-south-1.x86_64-linux.pv-s3 = "ami-caf88ea5";
"16.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-80fb51e3";
"16.09".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-2df3594e";
"16.09".ap-southeast-1.x86_64-linux.pv-ebs = "ami-37f05a54";
"16.09".ap-southeast-1.x86_64-linux.pv-s3 = "ami-27f35944";
"16.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-57ece834";
"16.09".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-87f4f0e4";
"16.09".ap-southeast-2.x86_64-linux.pv-ebs = "ami-d8ede9bb";
"16.09".ap-southeast-2.x86_64-linux.pv-s3 = "ami-a6ebefc5";
"16.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-9f863bfb";
"16.09".ca-central-1.x86_64-linux.hvm-s3 = "ami-ea85388e";
"16.09".ca-central-1.x86_64-linux.pv-ebs = "ami-ce8a37aa";
"16.09".ca-central-1.x86_64-linux.pv-s3 = "ami-448a3720";
"16.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-1b884774";
"16.09".eu-central-1.x86_64-linux.hvm-s3 = "ami-b08c43df";
"16.09".eu-central-1.x86_64-linux.pv-ebs = "ami-888946e7";
"16.09".eu-central-1.x86_64-linux.pv-s3 = "ami-06874869";
"16.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-1ed3e76d";
"16.09".eu-west-1.x86_64-linux.hvm-s3 = "ami-73d1e500";
"16.09".eu-west-1.x86_64-linux.pv-ebs = "ami-44c0f437";
"16.09".eu-west-1.x86_64-linux.pv-s3 = "ami-f3d8ec80";
"16.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-2c9c9648";
"16.09".eu-west-2.x86_64-linux.hvm-s3 = "ami-6b9e940f";
"16.09".eu-west-2.x86_64-linux.pv-ebs = "ami-f1999395";
"16.09".eu-west-2.x86_64-linux.pv-s3 = "ami-bb9f95df";
"16.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-a11882cd";
"16.09".sa-east-1.x86_64-linux.hvm-s3 = "ami-7726bc1b";
"16.09".sa-east-1.x86_64-linux.pv-ebs = "ami-9725bffb";
"16.09".sa-east-1.x86_64-linux.pv-s3 = "ami-b027bddc";
"16.09".us-east-1.x86_64-linux.hvm-ebs = "ami-854ca593";
"16.09".us-east-1.x86_64-linux.hvm-s3 = "ami-2241a834";
"16.09".us-east-1.x86_64-linux.pv-ebs = "ami-a441a8b2";
"16.09".us-east-1.x86_64-linux.pv-s3 = "ami-e841a8fe";
"16.09".us-east-2.x86_64-linux.hvm-ebs = "ami-3f41645a";
"16.09".us-east-2.x86_64-linux.hvm-s3 = "ami-804065e5";
"16.09".us-east-2.x86_64-linux.pv-ebs = "ami-f1466394";
"16.09".us-east-2.x86_64-linux.pv-s3 = "ami-05426760";
"16.09".us-west-1.x86_64-linux.hvm-ebs = "ami-c2efbca2";
"16.09".us-west-1.x86_64-linux.hvm-s3 = "ami-d71042b7";
"16.09".us-west-1.x86_64-linux.pv-ebs = "ami-04e8bb64";
"16.09".us-west-1.x86_64-linux.pv-s3 = "ami-31e9ba51";
"16.09".us-west-2.x86_64-linux.hvm-ebs = "ami-6449f504";
"16.09".us-west-2.x86_64-linux.hvm-s3 = "ami-344af654";
"16.09".us-west-2.x86_64-linux.pv-ebs = "ami-6d4af60d";
"16.09".us-west-2.x86_64-linux.pv-s3 = "ami-de48f4be";
# 17.03.885.6024dd4067
"17.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-dbd0f7bc";
"17.03".ap-northeast-1.x86_64-linux.hvm-s3 = "ami-7cdff81b";
"17.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-c59a48ab";
"17.03".ap-northeast-2.x86_64-linux.hvm-s3 = "ami-0b944665";
"17.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-4f413220";
"17.03".ap-south-1.x86_64-linux.hvm-s3 = "ami-864033e9";
"17.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-e08c3383";
"17.03".ap-southeast-1.x86_64-linux.hvm-s3 = "ami-c28f30a1";
"17.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-fca9a69f";
"17.03".ap-southeast-2.x86_64-linux.hvm-s3 = "ami-3daaa55e";
"17.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-9b00bdff";
"17.03".ca-central-1.x86_64-linux.hvm-s3 = "ami-e800bd8c";
"17.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-5450803b";
"17.03".eu-central-1.x86_64-linux.hvm-s3 = "ami-6e2efe01";
"17.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-10754c76";
"17.03".eu-west-1.x86_64-linux.hvm-s3 = "ami-11734a77";
"17.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-ff1d099b";
"17.03".eu-west-2.x86_64-linux.hvm-s3 = "ami-fe1d099a";
"17.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-d95d3eb5";
"17.03".sa-east-1.x86_64-linux.hvm-s3 = "ami-fca2c190";
"17.03".us-east-1.x86_64-linux.hvm-ebs = "ami-0940c61f";
"17.03".us-east-1.x86_64-linux.hvm-s3 = "ami-674fc971";
"17.03".us-east-2.x86_64-linux.hvm-ebs = "ami-afc2e6ca";
"17.03".us-east-2.x86_64-linux.hvm-s3 = "ami-a1cde9c4";
"17.03".us-west-1.x86_64-linux.hvm-ebs = "ami-587b2138";
"17.03".us-west-1.x86_64-linux.hvm-s3 = "ami-70411b10";
"17.03".us-west-2.x86_64-linux.hvm-ebs = "ami-a93daac9";
"17.03".us-west-2.x86_64-linux.hvm-s3 = "ami-5139ae31";
# 17.09.2681.59661f21be6
"17.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-a30192da";
"17.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-295a414d";
"17.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-8c0eb9f1";
"17.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-266cfe49";
"17.09".us-east-1.x86_64-linux.hvm-ebs = "ami-40bee63a";
"17.09".us-east-2.x86_64-linux.hvm-ebs = "ami-9d84aff8";
"17.09".us-west-1.x86_64-linux.hvm-ebs = "ami-d14142b1";
"17.09".us-west-2.x86_64-linux.hvm-ebs = "ami-3eb40346";
"17.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-ca8207ae";
"17.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-84bccff8";
"17.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0dc5386f";
"17.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-89b921ef";
"17.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-179b3b79";
"17.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-4762202b";
"17.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-4e376021";
# 18.03.132946.1caae7247b8
"18.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-065c46ec";
"18.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-64f31903";
"18.03".eu-west-3.x86_64-linux.hvm-ebs = "ami-5a8d3d27";
"18.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-09faf9e2";
"18.03".us-east-1.x86_64-linux.hvm-ebs = "ami-8b3538f4";
"18.03".us-east-2.x86_64-linux.hvm-ebs = "ami-150b3170";
"18.03".us-west-1.x86_64-linux.hvm-ebs = "ami-ce06ebad";
"18.03".us-west-2.x86_64-linux.hvm-ebs = "ami-586c3520";
"18.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-aca72ac8";
"18.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-aa0b4d40";
"18.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-d0f254b2";
"18.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-456511a8";
"18.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-3366d15d";
"18.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-163e1f7a";
"18.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-6a390b05";
# 18.09.910.c15e342304a
"18.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-0f412186fb8a0ec97";
"18.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-0dada3805ce43c55e";
"18.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-074df85565f2e02e2";
"18.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-07c9b884e679df4f8";
"18.09".us-east-1.x86_64-linux.hvm-ebs = "ami-009c9c3f1af480ff3";
"18.09".us-east-2.x86_64-linux.hvm-ebs = "ami-08199961085ea8bc6";
"18.09".us-west-1.x86_64-linux.hvm-ebs = "ami-07aa7f56d612ddd38";
"18.09".us-west-2.x86_64-linux.hvm-ebs = "ami-01c84b7c368ac24d1";
"18.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-04f66113f76198f6c";
"18.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0892c7e24ebf2194f";
"18.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-010730f36424b0a2c";
"18.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-0cdba8e998f076547";
"18.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0400a698e6a9f4a15";
"18.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-0e4a8a47fd6db6112";
"18.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-0880a678d3f555313";
# 19.03.172286.8ea36d73256
"19.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-0fe40176548ff0940";
"19.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-03a40fd3a02fe95ba";
"19.03".eu-west-3.x86_64-linux.hvm-ebs = "ami-0436f9da0f20a638e";
"19.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-0022b8ea9efde5de4";
"19.03".us-east-1.x86_64-linux.hvm-ebs = "ami-0efc58fb70ae9a217";
"19.03".us-east-2.x86_64-linux.hvm-ebs = "ami-0abf711b1b34da1af";
"19.03".us-west-1.x86_64-linux.hvm-ebs = "ami-07d126e8838c40ec5";
"19.03".us-west-2.x86_64-linux.hvm-ebs = "ami-03f8a737546e47fb0";
"19.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-03f9fd0ef2e035ede";
"19.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0cff66114c652c262";
"19.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-054c73a7f8d773ea9";
"19.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-00db62688900456a4";
"19.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0485cdd1a5fdd2117";
"19.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-0c6a43c6e0ad1f4e2";
"19.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-0303deb1b5890f878";
# 19.09.2243.84af403f54f
"19.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-071082f0fa035374f";
"19.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-0d9dc33c54d1dc4c3";
"19.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-09566799591d1bfed";
"19.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-015f8efc2be419b79";
"19.09".eu-north-1.x86_64-linux.hvm-ebs = "ami-07fc0a32d885e01ed";
"19.09".us-east-1.x86_64-linux.hvm-ebs = "ami-03330d8b51287412f";
"19.09".us-east-2.x86_64-linux.hvm-ebs = "ami-0518b4c84972e967f";
"19.09".us-west-1.x86_64-linux.hvm-ebs = "ami-06ad07e61a353b4a6";
"19.09".us-west-2.x86_64-linux.hvm-ebs = "ami-0e31e30925cf3ce4e";
"19.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-07df50fc76702a36d";
"19.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0f71ae5d4b0b78d95";
"19.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-057bbf2b4bd62d210";
"19.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-02a62555ca182fb5b";
"19.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0219dde0e6b7b7b93";
"19.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-066f7f2a895c821a1";
"19.09".ap-east-1.x86_64-linux.hvm-ebs = "ami-055b2348db2827ff1";
"19.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-018aab68377227e06";
# 20.03.1554.94e39623a49
"20.03".eu-west-1.x86_64-linux.hvm-ebs = "ami-02c34db5766cc7013";
"20.03".eu-west-2.x86_64-linux.hvm-ebs = "ami-0e32bd8c7853883f1";
"20.03".eu-west-3.x86_64-linux.hvm-ebs = "ami-061edb1356c1d69fd";
"20.03".eu-central-1.x86_64-linux.hvm-ebs = "ami-0a1a94722dcbff94c";
"20.03".eu-north-1.x86_64-linux.hvm-ebs = "ami-02699abfacbb6464b";
"20.03".us-east-1.x86_64-linux.hvm-ebs = "ami-0c5e7760748b74e85";
"20.03".us-east-2.x86_64-linux.hvm-ebs = "ami-030296bb256764655";
"20.03".us-west-1.x86_64-linux.hvm-ebs = "ami-050be818e0266b741";
"20.03".us-west-2.x86_64-linux.hvm-ebs = "ami-06562f78dca68eda2";
"20.03".ca-central-1.x86_64-linux.hvm-ebs = "ami-02365684a173255c7";
"20.03".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0dbf353e168d155f7";
"20.03".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-04c0f3a75f63daddd";
"20.03".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-093d9cc49c191eb6c";
"20.03".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0087df91a7b6ebd45";
"20.03".ap-south-1.x86_64-linux.hvm-ebs = "ami-0a1a6b569af04af9d";
"20.03".ap-east-1.x86_64-linux.hvm-ebs = "ami-0d18fdd309cdefa86";
"20.03".sa-east-1.x86_64-linux.hvm-ebs = "ami-09859378158ae971d";
# 20.03.2351.f8248ab6d9e-aarch64-linux
"20.03".eu-west-1.aarch64-linux.hvm-ebs = "ami-0a4c46dfdfe921aab";
"20.03".eu-west-2.aarch64-linux.hvm-ebs = "ami-0b47871912b7d36f9";
"20.03".eu-west-3.aarch64-linux.hvm-ebs = "ami-01031e1aa505b8935";
"20.03".eu-central-1.aarch64-linux.hvm-ebs = "ami-0bb4669de1f477fd1";
# missing "20.03".eu-north-1.aarch64-linux.hvm-ebs = "ami-";
"20.03".us-east-1.aarch64-linux.hvm-ebs = "ami-01d2de16a1878271c";
"20.03".us-east-2.aarch64-linux.hvm-ebs = "ami-0eade0158b1ff49c0";
"20.03".us-west-1.aarch64-linux.hvm-ebs = "ami-0913bf30cb9a764a4";
"20.03".us-west-2.aarch64-linux.hvm-ebs = "ami-073449580ff8e82b5";
"20.03".ca-central-1.aarch64-linux.hvm-ebs = "ami-050f2e923c4d703c0";
"20.03".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-0d11ef6705a9a11a7";
"20.03".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-05446a2f818cd3263";
"20.03".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-0c057f010065d2453";
"20.03".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-0e90eda7f24eb33ab";
"20.03".ap-south-1.aarch64-linux.hvm-ebs = "ami-03ba7e9f093f568bc";
"20.03".sa-east-1.aarch64-linux.hvm-ebs = "ami-0a8344c6ce6d0c902";
# 20.09.2016.19db3e5ea27
"20.09".eu-west-1.x86_64-linux.hvm-ebs = "ami-0057cb7d614329fa2";
"20.09".eu-west-2.x86_64-linux.hvm-ebs = "ami-0d46f16e0bb0ec8fd";
"20.09".eu-west-3.x86_64-linux.hvm-ebs = "ami-0e8985c3ea42f87fe";
"20.09".eu-central-1.x86_64-linux.hvm-ebs = "ami-0eed77c38432886d2";
"20.09".eu-north-1.x86_64-linux.hvm-ebs = "ami-0be5bcadd632bea14";
"20.09".us-east-1.x86_64-linux.hvm-ebs = "ami-0a2cce52b42daccc8";
"20.09".us-east-2.x86_64-linux.hvm-ebs = "ami-09378bf487b07a4d8";
"20.09".us-west-1.x86_64-linux.hvm-ebs = "ami-09b4337b2a9e77485";
"20.09".us-west-2.x86_64-linux.hvm-ebs = "ami-081d3bb5fbee0a1ac";
"20.09".ca-central-1.x86_64-linux.hvm-ebs = "ami-020c24c6c607e7ac7";
"20.09".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-08f648d5db009e67d";
"20.09".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0be390efaccbd40f9";
"20.09".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-0c3311601cbe8f927";
"20.09".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0020146701f4d56cf";
"20.09".ap-south-1.x86_64-linux.hvm-ebs = "ami-0117e2bd876bb40d1";
"20.09".ap-east-1.x86_64-linux.hvm-ebs = "ami-0c42f97e5b1fda92f";
"20.09".sa-east-1.x86_64-linux.hvm-ebs = "ami-021637976b094959d";
# 20.09.2016.19db3e5ea27-aarch64-linux
"20.09".eu-west-1.aarch64-linux.hvm-ebs = "ami-00a02608ff45ff8f9";
"20.09".eu-west-2.aarch64-linux.hvm-ebs = "ami-0e991d0f8dca21e20";
"20.09".eu-west-3.aarch64-linux.hvm-ebs = "ami-0d18eec4dc48c6f3b";
"20.09".eu-central-1.aarch64-linux.hvm-ebs = "ami-01691f25d08f48c9e";
"20.09".eu-north-1.aarch64-linux.hvm-ebs = "ami-09bb5aabe567ec6f4";
"20.09".us-east-1.aarch64-linux.hvm-ebs = "ami-0504bd006f9eaae42";
"20.09".us-east-2.aarch64-linux.hvm-ebs = "ami-00f0f8f2ab2d695ad";
"20.09".us-west-1.aarch64-linux.hvm-ebs = "ami-02d147d2cb992f878";
"20.09".us-west-2.aarch64-linux.hvm-ebs = "ami-07f40006cf4d4820e";
"20.09".ca-central-1.aarch64-linux.hvm-ebs = "ami-0e5f563919a987894";
"20.09".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-083e35d1acecae5c1";
"20.09".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-052cdc008b245b067";
"20.09".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-05e137f373bd72c0c";
"20.09".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-020791fe4c32f851a";
"20.09".ap-south-1.aarch64-linux.hvm-ebs = "ami-0285bb96a0f2c3955";
"20.09".sa-east-1.aarch64-linux.hvm-ebs = "ami-0a55ab650c32be058";
# 21.05.740.aa576357673
"21.05".eu-west-1.x86_64-linux.hvm-ebs = "ami-048dbc738074a3083";
"21.05".eu-west-2.x86_64-linux.hvm-ebs = "ami-0234cf81fec68315d";
"21.05".eu-west-3.x86_64-linux.hvm-ebs = "ami-020e459baf709107d";
"21.05".eu-central-1.x86_64-linux.hvm-ebs = "ami-0857d5d1309ab8b77";
"21.05".eu-north-1.x86_64-linux.hvm-ebs = "ami-05403e3ae53d3716f";
"21.05".us-east-1.x86_64-linux.hvm-ebs = "ami-0d3002ba40b5b9897";
"21.05".us-east-2.x86_64-linux.hvm-ebs = "ami-069a0ca1bde6dea52";
"21.05".us-west-1.x86_64-linux.hvm-ebs = "ami-0b415460a84bcf9bc";
"21.05".us-west-2.x86_64-linux.hvm-ebs = "ami-093cba49754abd7f8";
"21.05".ca-central-1.x86_64-linux.hvm-ebs = "ami-065c13e1d52d60b33";
"21.05".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-04f570c70ff9b665e";
"21.05".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-02a3d1df595df5ef6";
"21.05".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-027836fddb5c56012";
"21.05".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-0edacd41dc7700c39";
"21.05".ap-south-1.x86_64-linux.hvm-ebs = "ami-0b279b5bb55288059";
"21.05".ap-east-1.x86_64-linux.hvm-ebs = "ami-06dc98082bc55c1fc";
"21.05".sa-east-1.x86_64-linux.hvm-ebs = "ami-04737dd49b98936c6";
# 21.11.333823.96b4157790f-x86_64-linux
"21.11".eu-west-1.x86_64-linux.hvm-ebs = "ami-01d0304a712f2f3f0";
"21.11".eu-west-2.x86_64-linux.hvm-ebs = "ami-00e828bfc1e5d09ac";
"21.11".eu-west-3.x86_64-linux.hvm-ebs = "ami-0e1ea64430d8103f2";
"21.11".eu-central-1.x86_64-linux.hvm-ebs = "ami-0fcf28c07e86142c5";
"21.11".eu-north-1.x86_64-linux.hvm-ebs = "ami-0ee83a3c6590fd6b1";
"21.11".us-east-1.x86_64-linux.hvm-ebs = "ami-099756bfda4540da0";
"21.11".us-east-2.x86_64-linux.hvm-ebs = "ami-0b20a80b82052d23f";
"21.11".us-west-1.x86_64-linux.hvm-ebs = "ami-088ea590004b01752";
"21.11".us-west-2.x86_64-linux.hvm-ebs = "ami-0025b9d4831b911a7";
"21.11".ca-central-1.x86_64-linux.hvm-ebs = "ami-0e67089f898e74443";
"21.11".ap-southeast-1.x86_64-linux.hvm-ebs = "ami-0dc8d718279d3402d";
"21.11".ap-southeast-2.x86_64-linux.hvm-ebs = "ami-0155e842329970187";
"21.11".ap-northeast-1.x86_64-linux.hvm-ebs = "ami-07c95eda953bf5435";
"21.11".ap-northeast-2.x86_64-linux.hvm-ebs = "ami-04167df3cd952b3bd";
"21.11".ap-south-1.x86_64-linux.hvm-ebs = "ami-0680e05531b3db677";
"21.11".ap-east-1.x86_64-linux.hvm-ebs = "ami-0835a3e481dc240f9";
"21.11".sa-east-1.x86_64-linux.hvm-ebs = "ami-0f7c354c421348e51";
# 21.11.333823.96b4157790f-aarch64-linux
"21.11".eu-west-1.aarch64-linux.hvm-ebs = "ami-048f3eea6a12c4b3b";
"21.11".eu-west-2.aarch64-linux.hvm-ebs = "ami-0e6f18f2009806add";
"21.11".eu-west-3.aarch64-linux.hvm-ebs = "ami-0a28d593f5e938d80";
"21.11".eu-central-1.aarch64-linux.hvm-ebs = "ami-0b9c95d926ab9474c";
"21.11".eu-north-1.aarch64-linux.hvm-ebs = "ami-0f2d400b4a2368a1a";
"21.11".us-east-1.aarch64-linux.hvm-ebs = "ami-05afb75585567d386";
"21.11".us-east-2.aarch64-linux.hvm-ebs = "ami-07f360673c2fccf8d";
"21.11".us-west-1.aarch64-linux.hvm-ebs = "ami-0a6892c61d85774db";
"21.11".us-west-2.aarch64-linux.hvm-ebs = "ami-04eaf20283432e852";
"21.11".ca-central-1.aarch64-linux.hvm-ebs = "ami-036b69828502e7fdf";
"21.11".ap-southeast-1.aarch64-linux.hvm-ebs = "ami-0d52e51e68b6954ef";
"21.11".ap-southeast-2.aarch64-linux.hvm-ebs = "ami-000a3019e003f4fb9";
"21.11".ap-northeast-1.aarch64-linux.hvm-ebs = "ami-09b0c7928780e25b6";
"21.11".ap-northeast-2.aarch64-linux.hvm-ebs = "ami-05f80f3c83083ff62";
"21.11".ap-south-1.aarch64-linux.hvm-ebs = "ami-05b2a3ff8489c3f59";
"21.11".ap-east-1.aarch64-linux.hvm-ebs = "ami-0aa3b50a4f2822a00";
"21.11".sa-east-1.aarch64-linux.hvm-ebs = "ami-00f68eff453d3fe69";
latest = self."21.11";
}; in self

View file

@ -0,0 +1,178 @@
# Configuration for Amazon EC2 instances. (Note that this file is a
# misnomer - it should be "amazon-config.nix" or so, not
# "amazon-image.nix", since it's used not only to build images but
# also to reconfigure instances. However, we can't rename it because
# existing "configuration.nix" files on EC2 instances refer to it.)
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.ec2;
metadataFetcher = import ./ec2-metadata-fetcher.nix {
inherit (pkgs) curl;
targetRoot = "$targetRoot/";
wgetExtraOptions = "-q";
};
in
{
imports = [
../profiles/headless.nix
# Note: While we do use the headless profile, we also explicitly
# turn on the serial console on ttyS0 below. This is because
# AWS does support accessing the serial console:
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configure-access-to-serial-console.html
./ec2-data.nix
./amazon-init.nix
];
config = {
assertions = [
{ assertion = cfg.hvm;
message = "Paravirtualized EC2 instances are no longer supported.";
}
{ assertion = cfg.efi -> cfg.hvm;
message = "EC2 instances using EFI must be HVM instances.";
}
{ assertion = versionOlder config.boot.kernelPackages.kernel.version "5.17";
message = "ENA driver fails to build with kernel >= 5.17";
}
];
boot.growPartition = cfg.hvm;
fileSystems."/" = mkIf (!cfg.zfs.enable) {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
autoResize = true;
};
fileSystems."/boot" = mkIf (cfg.efi || cfg.zfs.enable) {
# The ZFS image uses a partition labeled ESP whether or not we're
# booting with EFI.
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
services.zfs.expandOnBoot = mkIf cfg.zfs.enable "all";
boot.zfs.devNodes = mkIf cfg.zfs.enable "/dev/";
boot.extraModulePackages = [
config.boot.kernelPackages.ena
];
boot.initrd.kernelModules = [ "xen-blkfront" "xen-netfront" ];
boot.initrd.availableKernelModules = [ "ixgbevf" "ena" "nvme" ];
boot.kernelParams = mkIf cfg.hvm [ "console=ttyS0,115200n8" "random.trust_cpu=on" ];
# Prevent the nouveau kernel module from being loaded, as it
# interferes with the nvidia/nvidia-uvm modules needed for CUDA.
# Also blacklist xen_fbfront to prevent a 30 second delay during
# boot.
boot.blacklistedKernelModules = [ "nouveau" "xen_fbfront" ];
# Generate a GRUB menu. Amazon's pv-grub uses this to boot our kernel/initrd.
boot.loader.grub.version = if cfg.hvm then 2 else 1;
boot.loader.grub.device = if (cfg.hvm && !cfg.efi) then "/dev/xvda" else "nodev";
boot.loader.grub.extraPerEntryConfig = mkIf (!cfg.hvm) "root (hd0)";
boot.loader.grub.efiSupport = cfg.efi;
boot.loader.grub.efiInstallAsRemovable = cfg.efi;
boot.loader.timeout = 1;
boot.loader.grub.extraConfig = ''
serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
terminal_output console serial
terminal_input console serial
'';
boot.initrd.network.enable = true;
# Mount all formatted ephemeral disks and activate all swap devices.
# We cannot do this with the fileSystems and swapDevices options
# because the set of devices is dependent on the instance type
# (e.g. "m1.small" has one ephemeral filesystem and one swap device,
# while "m1.large" has two ephemeral filesystems and no swap
# devices). Also, put /tmp and /var on /disk0, since it has a lot
# more space than the root device. Similarly, "move" /nix to /disk0
# by layering a unionfs-fuse mount on top of it so we have a lot more space for
# Nix operations.
boot.initrd.postMountCommands =
''
${metadataFetcher}
diskNr=0
diskForUnionfs=
for device in /dev/xvd[abcde]*; do
if [ "$device" = /dev/xvda -o "$device" = /dev/xvda1 ]; then continue; fi
fsType=$(blkid -o value -s TYPE "$device" || true)
if [ "$fsType" = swap ]; then
echo "activating swap device $device..."
swapon "$device" || true
elif [ "$fsType" = ext3 ]; then
mp="/disk$diskNr"
diskNr=$((diskNr + 1))
if mountFS "$device" "$mp" "" ext3; then
if [ -z "$diskForUnionfs" ]; then diskForUnionfs="$mp"; fi
fi
else
echo "skipping unknown device type $device"
fi
done
if [ -n "$diskForUnionfs" ]; then
mkdir -m 755 -p $targetRoot/$diskForUnionfs/root
mkdir -m 1777 -p $targetRoot/$diskForUnionfs/root/tmp $targetRoot/tmp
mount --bind $targetRoot/$diskForUnionfs/root/tmp $targetRoot/tmp
if [ "$(cat "$metaDir/ami-manifest-path")" != "(unknown)" ]; then
mkdir -m 755 -p $targetRoot/$diskForUnionfs/root/var $targetRoot/var
mount --bind $targetRoot/$diskForUnionfs/root/var $targetRoot/var
mkdir -p /unionfs-chroot/ro-nix
mount --rbind $targetRoot/nix /unionfs-chroot/ro-nix
mkdir -m 755 -p $targetRoot/$diskForUnionfs/root/nix
mkdir -p /unionfs-chroot/rw-nix
mount --rbind $targetRoot/$diskForUnionfs/root/nix /unionfs-chroot/rw-nix
unionfs -o allow_other,cow,nonempty,chroot=/unionfs-chroot,max_files=32768 /rw-nix=RW:/ro-nix=RO $targetRoot/nix
fi
fi
'';
boot.initrd.extraUtilsCommands =
''
# We need swapon in the initrd.
copy_bin_and_libs ${pkgs.util-linux}/sbin/swapon
'';
# Allow root logins only using the SSH key that the user specified
# at instance creation time.
services.openssh.enable = true;
services.openssh.permitRootLogin = "prohibit-password";
# Enable the serial console on ttyS0
systemd.services."serial-getty@ttyS0".enable = true;
# Creates symlinks for block device names.
services.udev.packages = [ pkgs.amazon-ec2-utils ];
# Force getting the hostname from EC2.
networking.hostName = mkDefault "";
# Always include cryptsetup so that Charon can use it.
environment.systemPackages = [ pkgs.cryptsetup ];
boot.initrd.supportedFilesystems = [ "unionfs-fuse" ];
# EC2 has its own NTP server provided by the hypervisor
networking.timeServers = [ "169.254.169.123" ];
# udisks has become too bloated to have in a headless system
# (e.g. it depends on GTK).
services.udisks2.enable = false;
};
}

View file

@ -0,0 +1,87 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.amazon-init;
script = ''
#!${pkgs.runtimeShell} -eu
echo "attempting to fetch configuration from EC2 user data..."
export HOME=/root
export PATH=${pkgs.lib.makeBinPath [ config.nix.package config.systemd.package pkgs.gnugrep pkgs.git pkgs.gnutar pkgs.gzip pkgs.gnused pkgs.xz config.system.build.nixos-rebuild]}:$PATH
export NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
userData=/etc/ec2-metadata/user-data
# Check if user-data looks like a shell script and execute it with the
# runtime shell if it does. Otherwise treat it as a nixos configuration
# expression
if IFS= LC_ALL=C read -rN2 shebang < $userData && [ "$shebang" = '#!' ]; then
# NB: we cannot chmod the $userData file, this is why we execute it via
# `pkgs.runtimeShell`. This means we have only limited support for shell
# scripts compatible with the `pkgs.runtimeShell`.
exec ${pkgs.runtimeShell} $userData
fi
if [ -s "$userData" ]; then
# If the user-data looks like it could be a nix expression,
# copy it over. Also, look for a magic three-hash comment and set
# that as the channel.
if sed '/^\(#\|SSH_HOST_.*\)/d' < "$userData" | grep -q '\S'; then
channels="$(grep '^###' "$userData" | sed 's|###\s*||')"
while IFS= read -r channel; do
echo "writing channel: $channel"
done < <(printf "%s\n" "$channels")
if [[ -n "$channels" ]]; then
printf "%s" "$channels" > /root/.nix-channels
nix-channel --update
fi
echo "setting configuration from EC2 user data"
cp "$userData" /etc/nixos/configuration.nix
else
echo "user data does not appear to be a Nix expression; ignoring"
exit
fi
else
echo "no user data is available"
exit
fi
nixos-rebuild switch
'';
in {
options.virtualisation.amazon-init = {
enable = mkOption {
default = true;
type = types.bool;
description = ''
Enable or disable the amazon-init service.
'';
};
};
config = mkIf cfg.enable {
systemd.services.amazon-init = {
inherit script;
description = "Reconfigure the system from EC2 userdata on startup";
wantedBy = [ "multi-user.target" ];
after = [ "multi-user.target" ];
requires = [ "network-online.target" ];
restartIfChanged = false;
unitConfig.X-StopOnRemoval = false;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
};
};
}

View file

@ -0,0 +1,74 @@
{ config, lib, pkgs, ... }:
let
inherit (lib) literalExpression types;
in {
options = {
ec2 = {
zfs = {
enable = lib.mkOption {
default = false;
internal = true;
description = ''
Whether the EC2 instance uses a ZFS root.
'';
};
datasets = lib.mkOption {
description = ''
Datasets to create under the `tank` and `boot` zpools.
**NOTE:** This option is used only at image creation time, and
does not attempt to declaratively create or manage datasets
on an existing system.
'';
default = {};
type = types.attrsOf (types.submodule {
options = {
mount = lib.mkOption {
description = "Where to mount this dataset.";
type = types.nullOr types.string;
default = null;
};
properties = lib.mkOption {
description = "Properties to set on this dataset.";
type = types.attrsOf types.string;
default = {};
};
};
});
};
};
hvm = lib.mkOption {
default = lib.versionAtLeast config.system.stateVersion "17.03";
internal = true;
description = ''
Whether the EC2 instance is a HVM instance.
'';
};
efi = lib.mkOption {
default = pkgs.stdenv.hostPlatform.isAarch64;
defaultText = literalExpression "pkgs.stdenv.hostPlatform.isAarch64";
internal = true;
description = ''
Whether the EC2 instance is using EFI.
'';
};
};
};
config = lib.mkIf config.ec2.zfs.enable {
networking.hostId = lib.mkDefault "00000000";
fileSystems = let
mountable = lib.filterAttrs (_: value: ((value.mount or null) != null)) config.ec2.zfs.datasets;
in lib.mapAttrs'
(dataset: opts: lib.nameValuePair opts.mount {
device = dataset;
fsType = "zfs";
})
mountable;
};
}

View file

@ -0,0 +1,135 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.anbox;
kernelPackages = config.boot.kernelPackages;
addrOpts = v: addr: pref: name: {
address = mkOption {
default = addr;
type = types.str;
description = ''
IPv${toString v} ${name} address.
'';
};
prefixLength = mkOption {
default = pref;
type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
description = ''
Subnet mask of the ${name} address, specified as the number of
bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
'';
};
};
in
{
options.virtualisation.anbox = {
enable = mkEnableOption "Anbox";
image = mkOption {
default = pkgs.anbox.image;
defaultText = literalExpression "pkgs.anbox.image";
type = types.package;
description = ''
Base android image for Anbox.
'';
};
extraInit = mkOption {
type = types.lines;
default = "";
description = ''
Extra shell commands to be run inside the container image during init.
'';
};
ipv4 = {
container = addrOpts 4 "192.168.250.2" 24 "Container";
gateway = addrOpts 4 "192.168.250.1" 24 "Host";
dns = mkOption {
default = "1.1.1.1";
type = types.str;
description = ''
Container DNS server.
'';
};
};
};
config = mkIf cfg.enable {
assertions = singleton {
assertion = versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.18";
message = "Anbox needs user namespace support to work properly";
};
environment.systemPackages = with pkgs; [ anbox ];
services.udev.extraRules = ''
KERNEL=="ashmem", NAME="%k", MODE="0666"
KERNEL=="binder*", NAME="%k", MODE="0666"
'';
virtualisation.lxc.enable = true;
networking.bridges.anbox0.interfaces = [];
networking.interfaces.anbox0.ipv4.addresses = [ cfg.ipv4.gateway ];
networking.nat = {
enable = true;
internalInterfaces = [ "anbox0" ];
};
systemd.services.anbox-container-manager = let
anboxloc = "/var/lib/anbox";
in {
description = "Anbox Container Management Daemon";
environment.XDG_RUNTIME_DIR="${anboxloc}";
wantedBy = [ "multi-user.target" ];
preStart = let
initsh = pkgs.writeText "nixos-init" (''
#!/system/bin/sh
setprop nixos.version ${config.system.nixos.version}
# we don't have radio
setprop ro.radio.noril yes
stop ril-daemon
# speed up boot
setprop debug.sf.nobootanimation 1
'' + cfg.extraInit);
initshloc = "${anboxloc}/rootfs-overlay/system/etc/init.goldfish.sh";
in ''
mkdir -p ${anboxloc}
mkdir -p $(dirname ${initshloc})
[ -f ${initshloc} ] && rm ${initshloc}
cp ${initsh} ${initshloc}
chown 100000:100000 ${initshloc}
chmod +x ${initshloc}
'';
serviceConfig = {
ExecStart = ''
${pkgs.anbox}/bin/anbox container-manager \
--data-path=${anboxloc} \
--android-image=${cfg.image} \
--container-network-address=${cfg.ipv4.container.address} \
--container-network-gateway=${cfg.ipv4.gateway.address} \
--container-network-dns-servers=${cfg.ipv4.dns} \
--use-rootfs-overlay \
--privileged
'';
};
};
};
}

View file

@ -0,0 +1,49 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.appvm;
in {
options = {
virtualisation.appvm = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
This enables AppVMs and related virtualisation settings.
'';
};
user = mkOption {
type = types.str;
description = ''
AppVM user login. Currenly only AppVMs are supported for a single user only.
'';
};
};
};
config = mkIf cfg.enable {
virtualisation.libvirtd = {
enable = true;
qemu.verbatimConfig = ''
namespaces = []
user = "${cfg.user}"
group = "users"
remember_owner = 0
'';
};
users.users."${cfg.user}" = {
packages = [ pkgs.appvm ];
extraGroups = [ "libvirtd" ];
};
};
}

View file

@ -0,0 +1,17 @@
--- a/waagent 2016-03-12 09:58:15.728088851 +0200
+++ a/waagent 2016-03-12 09:58:43.572680025 +0200
@@ -6173,10 +6173,10 @@
Log("MAC address: " + ":".join(["%02X" % Ord(a) for a in mac]))
# Consume Entropy in ACPI table provided by Hyper-V
- try:
- SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
- except:
- pass
+ #try:
+ # SetFileContents("/dev/random", GetFileContents("/sys/firmware/acpi/tables/OEM0"))
+ #except:
+ # pass
Log("Probing for Azure environment.")
self.Endpoint = self.DoDhcpWork()

View file

@ -0,0 +1,194 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.azure.agent;
waagent = with pkgs; stdenv.mkDerivation rec {
name = "waagent-2.0";
src = pkgs.fetchFromGitHub {
owner = "Azure";
repo = "WALinuxAgent";
rev = "1b3a8407a95344d9d12a2a377f64140975f1e8e4";
sha256 = "10byzvmpgrmr4d5mdn2kq04aapqb3sgr1admk13wjmy5cd6bwd2x";
};
patches = [ ./azure-agent-entropy.patch ];
buildInputs = [ makeWrapper python pythonPackages.wrapPython ];
runtimeDeps = [ findutils gnugrep gawk coreutils openssl openssh
nettools # for hostname
procps # for pidof
shadow # for useradd, usermod
util-linux # for (u)mount, fdisk, sfdisk, mkswap
parted
];
pythonPath = [ pythonPackages.pyasn1 ];
configurePhase = false;
buildPhase = false;
installPhase = ''
substituteInPlace config/99-azure-product-uuid.rules \
--replace /bin/chmod "${coreutils}/bin/chmod"
mkdir -p $out/lib/udev/rules.d
cp config/*.rules $out/lib/udev/rules.d
mkdir -p $out/bin
cp waagent $out/bin/
chmod +x $out/bin/waagent
wrapProgram "$out/bin/waagent" \
--prefix PYTHONPATH : $PYTHONPATH \
--prefix PATH : "${makeBinPath runtimeDeps}"
'';
};
provisionedHook = pkgs.writeScript "provisioned-hook" ''
#!${pkgs.runtimeShell}
/run/current-system/systemd/bin/systemctl start provisioned.target
'';
in
{
###### interface
options.virtualisation.azure.agent = {
enable = mkOption {
default = false;
description = "Whether to enable the Windows Azure Linux Agent.";
};
verboseLogging = mkOption {
default = false;
description = "Whether to enable verbose logging.";
};
mountResourceDisk = mkOption {
default = true;
description = "Whether the agent should format (ext4) and mount the resource disk to /mnt/resource.";
};
};
###### implementation
config = mkIf cfg.enable {
assertions = [ {
assertion = pkgs.stdenv.hostPlatform.isx86;
message = "Azure not currently supported on ${pkgs.stdenv.hostPlatform.system}";
} {
assertion = config.networking.networkmanager.enable == false;
message = "Windows Azure Linux Agent is not compatible with NetworkManager";
} ];
boot.initrd.kernelModules = [ "ata_piix" ];
networking.firewall.allowedUDPPorts = [ 68 ];
environment.etc."waagent.conf".text = ''
#
# Windows Azure Linux Agent Configuration
#
Role.StateConsumer=${provisionedHook}
# Enable instance creation
Provisioning.Enabled=y
# Password authentication for root account will be unavailable.
Provisioning.DeleteRootPassword=n
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=n
# Supported values are "rsa", "dsa" and "ecdsa".
Provisioning.SshHostKeyPairType=ed25519
# Monitor host name changes and publish changes via DHCP requests.
Provisioning.MonitorHostName=y
# Decode CustomData from Base64.
Provisioning.DecodeCustomData=n
# Execute CustomData after provisioning.
Provisioning.ExecuteCustomData=n
# Format if unformatted. If 'n', resource disk will not be mounted.
ResourceDisk.Format=${if cfg.mountResourceDisk then "y" else "n"}
# File system on the resource disk
# Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
ResourceDisk.Filesystem=ext4
# Mount point for the resource disk
ResourceDisk.MountPoint=/mnt/resource
# Respond to load balancer probes if requested by Windows Azure.
LBProbeResponder=y
# Enable logging to serial console (y|n)
# When stdout is not enough...
# 'y' if not set
Logs.Console=y
# Enable verbose logging (y|n)
Logs.Verbose=${if cfg.verboseLogging then "y" else "n"}
# Root device timeout in seconds.
OS.RootDeviceScsiTimeout=300
'';
services.udev.packages = [ waagent ];
networking.dhcpcd.persistent = true;
services.logrotate = {
enable = true;
settings."/var/log/waagent.log" = {
compress = true;
frequency = "monthly";
rotate = 6;
};
};
systemd.targets.provisioned = {
description = "Services Requiring Azure VM provisioning to have finished";
};
systemd.services.consume-hypervisor-entropy =
{ description = "Consume entropy in ACPI table provided by Hyper-V";
wantedBy = [ "sshd.service" "waagent.service" ];
before = [ "sshd.service" "waagent.service" ];
path = [ pkgs.coreutils ];
script =
''
echo "Fetching entropy..."
cat /sys/firmware/acpi/tables/OEM0 > /dev/random
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
serviceConfig.StandardError = "journal+console";
serviceConfig.StandardOutput = "journal+console";
};
systemd.services.waagent = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" "sshd.service" ];
wants = [ "network-online.target" ];
path = [ pkgs.e2fsprogs pkgs.bash ];
description = "Windows Azure Agent Service";
unitConfig.ConditionPathExists = "/etc/waagent.conf";
serviceConfig = {
ExecStart = "${waagent}/bin/waagent -daemon";
Type = "simple";
};
};
};
}

View file

@ -0,0 +1,3 @@
{
"16.03" = "https://nixos.blob.core.windows.net/images/nixos-image-16.03.847.8688c17-x86_64-linux.vhd";
}

View file

@ -0,0 +1,70 @@
{ lib, pkgs, ... }:
with lib;
{
imports = [ ../profiles/headless.nix ];
require = [ ./azure-agent.nix ];
virtualisation.azure.agent.enable = true;
boot.kernelParams = [ "console=ttyS0" "earlyprintk=ttyS0" "rootdelay=300" "panic=1" "boot.panic_on_fail" ];
boot.initrd.kernelModules = [ "hv_vmbus" "hv_netvsc" "hv_utils" "hv_storvsc" ];
# Generate a GRUB menu.
boot.loader.grub.device = "/dev/sda";
boot.loader.grub.version = 2;
boot.loader.timeout = 0;
boot.growPartition = true;
# Don't put old configurations in the GRUB menu. The user has no
# way to select them anyway.
boot.loader.grub.configurationLimit = 0;
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
autoResize = true;
};
# Allow root logins only using the SSH key that the user specified
# at instance creation time, ping client connections to avoid timeouts
services.openssh.enable = true;
services.openssh.permitRootLogin = "prohibit-password";
services.openssh.extraConfig = ''
ClientAliveInterval 180
'';
# Force getting the hostname from Azure
networking.hostName = mkDefault "";
# Always include cryptsetup so that NixOps can use it.
# sg_scan is needed to finalize disk removal on older kernels
environment.systemPackages = [ pkgs.cryptsetup pkgs.sg3_utils ];
networking.usePredictableInterfaceNames = false;
services.udev.extraRules = ''
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:0", ATTR{removable}=="0", SYMLINK+="disk/by-lun/0",
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:1", ATTR{removable}=="0", SYMLINK+="disk/by-lun/1",
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:2", ATTR{removable}=="0", SYMLINK+="disk/by-lun/2"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:3", ATTR{removable}=="0", SYMLINK+="disk/by-lun/3"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:4", ATTR{removable}=="0", SYMLINK+="disk/by-lun/4"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:5", ATTR{removable}=="0", SYMLINK+="disk/by-lun/5"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:6", ATTR{removable}=="0", SYMLINK+="disk/by-lun/6"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:7", ATTR{removable}=="0", SYMLINK+="disk/by-lun/7"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:8", ATTR{removable}=="0", SYMLINK+="disk/by-lun/8"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:9", ATTR{removable}=="0", SYMLINK+="disk/by-lun/9"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:10", ATTR{removable}=="0", SYMLINK+="disk/by-lun/10"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:11", ATTR{removable}=="0", SYMLINK+="disk/by-lun/11"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:12", ATTR{removable}=="0", SYMLINK+="disk/by-lun/12"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:13", ATTR{removable}=="0", SYMLINK+="disk/by-lun/13"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:14", ATTR{removable}=="0", SYMLINK+="disk/by-lun/14"
ENV{DEVTYPE}=="disk", KERNEL!="sda" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNELS=="?:0:0:15", ATTR{removable}=="0", SYMLINK+="disk/by-lun/15"
'';
}

View file

@ -0,0 +1,12 @@
{ modulesPath, ... }:
{
# To build the configuration or use nix-env, you need to run
# either nixos-rebuild --upgrade or nix-channel --update
# to fetch the nixos channel.
# This configures everything but bootstrap services,
# which only need to be run once and have already finished
# if you are able to see this comment.
imports = [ "${modulesPath}/virtualisation/azure-common.nix" ];
}

View file

@ -0,0 +1,5 @@
{ modulesPath, ... }:
{
imports = [ "${modulesPath}/virtualisation/azure-image.nix" ];
}

View file

@ -0,0 +1,71 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.azureImage;
in
{
imports = [ ./azure-common.nix ];
options = {
virtualisation.azureImage.diskSize = mkOption {
type = with types; either (enum [ "auto" ]) int;
default = "auto";
example = 2048;
description = ''
Size of disk image. Unit is MB.
'';
};
};
config = {
system.build.azureImage = import ../../lib/make-disk-image.nix {
name = "azure-image";
postVM = ''
${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -o subformat=fixed,force_size -O vpc $diskImage $out/disk.vhd
rm $diskImage
'';
configFile = ./azure-config-user.nix;
format = "raw";
inherit (cfg) diskSize;
inherit config lib pkgs;
};
# Azure metadata is available as a CD-ROM drive.
fileSystems."/metadata".device = "/dev/sr0";
systemd.services.fetch-ssh-keys = {
description = "Fetch host keys and authorized_keys for root user";
wantedBy = [ "sshd.service" "waagent.service" ];
before = [ "sshd.service" "waagent.service" ];
path = [ pkgs.coreutils ];
script =
''
eval "$(cat /metadata/CustomData.bin)"
if ! [ -z "$ssh_host_ecdsa_key" ]; then
echo "downloaded ssh_host_ecdsa_key"
echo "$ssh_host_ecdsa_key" > /etc/ssh/ssh_host_ed25519_key
chmod 600 /etc/ssh/ssh_host_ed25519_key
fi
if ! [ -z "$ssh_host_ecdsa_key_pub" ]; then
echo "downloaded ssh_host_ecdsa_key_pub"
echo "$ssh_host_ecdsa_key_pub" > /etc/ssh/ssh_host_ed25519_key.pub
chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
fi
if ! [ -z "$ssh_root_auth_key" ]; then
echo "downloaded ssh_root_auth_key"
mkdir -m 0700 -p /root/.ssh
echo "$ssh_root_auth_key" > /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
fi
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
serviceConfig.StandardError = "journal+console";
serviceConfig.StandardOutput = "journal+console";
};
};
}

View file

@ -0,0 +1,5 @@
let self = {
"16.09" = "https://nixos.blob.core.windows.net/images/nixos-image-16.09.1694.019dcc3-x86_64-linux.vhd";
latest = self."16.09";
}; in self

View file

@ -0,0 +1,5 @@
{ modulesPath, ... }:
{
imports = [ "${modulesPath}/virtualisation/brightbox-image.nix" ];
}

View file

@ -0,0 +1,166 @@
{ config, lib, pkgs, ... }:
with lib;
let
diskSize = "20G";
in
{
imports = [ ../profiles/headless.nix ../profiles/qemu-guest.nix ];
system.build.brightboxImage =
pkgs.vmTools.runInLinuxVM (
pkgs.runCommand "brightbox-image"
{ preVM =
''
mkdir $out
diskImage=$out/$diskImageBase
truncate $diskImage --size ${diskSize}
mv closure xchg/
'';
postVM =
''
PATH=$PATH:${lib.makeBinPath [ pkgs.gnutar pkgs.gzip ]}
pushd $out
${pkgs.qemu_kvm}/bin/qemu-img convert -c -O qcow2 $diskImageBase nixos.qcow2
rm $diskImageBase
popd
'';
diskImageBase = "nixos-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.raw";
buildInputs = [ pkgs.util-linux pkgs.perl ];
exportReferencesGraph =
[ "closure" config.system.build.toplevel ];
}
''
# Create partition table
${pkgs.parted}/sbin/parted --script /dev/vda mklabel msdos
${pkgs.parted}/sbin/parted --script /dev/vda mkpart primary ext4 1 ${diskSize}
${pkgs.parted}/sbin/parted --script /dev/vda print
. /sys/class/block/vda1/uevent
mknod /dev/vda1 b $MAJOR $MINOR
# Create an empty filesystem and mount it.
${pkgs.e2fsprogs}/sbin/mkfs.ext4 -L nixos /dev/vda1
${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1
mkdir /mnt
mount /dev/vda1 /mnt
# The initrd expects these directories to exist.
mkdir /mnt/dev /mnt/proc /mnt/sys
mount --bind /proc /mnt/proc
mount --bind /dev /mnt/dev
mount --bind /sys /mnt/sys
# Copy all paths in the closure to the filesystem.
storePaths=$(perl ${pkgs.pathsFromGraph} /tmp/xchg/closure)
mkdir -p /mnt/nix/store
echo "copying everything (will take a while)..."
cp -prd $storePaths /mnt/nix/store/
# Register the paths in the Nix database.
printRegistration=1 perl ${pkgs.pathsFromGraph} /tmp/xchg/closure | \
chroot /mnt ${config.nix.package.out}/bin/nix-store --load-db --option build-users-group ""
# Create the system profile to allow nixos-rebuild to work.
chroot /mnt ${config.nix.package.out}/bin/nix-env \
-p /nix/var/nix/profiles/system --set ${config.system.build.toplevel} \
--option build-users-group ""
# `nixos-rebuild' requires an /etc/NIXOS.
mkdir -p /mnt/etc
touch /mnt/etc/NIXOS
# `switch-to-configuration' requires a /bin/sh
mkdir -p /mnt/bin
ln -s ${config.system.build.binsh}/bin/sh /mnt/bin/sh
# Install a configuration.nix.
mkdir -p /mnt/etc/nixos /mnt/boot/grub
cp ${./brightbox-config.nix} /mnt/etc/nixos/configuration.nix
# Generate the GRUB menu.
ln -s vda /dev/sda
chroot /mnt ${config.system.build.toplevel}/bin/switch-to-configuration boot
umount /mnt/proc /mnt/dev /mnt/sys
umount /mnt
''
);
fileSystems."/".label = "nixos";
# Generate a GRUB menu. Amazon's pv-grub uses this to boot our kernel/initrd.
boot.loader.grub.device = "/dev/vda";
boot.loader.timeout = 0;
# Don't put old configurations in the GRUB menu. The user has no
# way to select them anyway.
boot.loader.grub.configurationLimit = 0;
# Allow root logins only using the SSH key that the user specified
# at instance creation time.
services.openssh.enable = true;
services.openssh.permitRootLogin = "prohibit-password";
# Force getting the hostname from Google Compute.
networking.hostName = mkDefault "";
# Always include cryptsetup so that NixOps can use it.
environment.systemPackages = [ pkgs.cryptsetup ];
systemd.services.fetch-ec2-data =
{ description = "Fetch EC2 Data";
wantedBy = [ "multi-user.target" "sshd.service" ];
before = [ "sshd.service" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
path = [ pkgs.wget pkgs.iproute2 ];
script =
''
wget="wget -q --retry-connrefused -O -"
${optionalString (config.networking.hostName == "") ''
echo "setting host name..."
${pkgs.nettools}/bin/hostname $($wget http://169.254.169.254/latest/meta-data/hostname)
''}
# Don't download the SSH key if it has already been injected
# into the image (a Nova feature).
if ! [ -e /root/.ssh/authorized_keys ]; then
echo "obtaining SSH key..."
mkdir -m 0700 -p /root/.ssh
$wget http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key > /root/key.pub
if [ $? -eq 0 -a -e /root/key.pub ]; then
if ! grep -q -f /root/key.pub /root/.ssh/authorized_keys; then
cat /root/key.pub >> /root/.ssh/authorized_keys
echo "new key added to authorized_keys"
fi
chmod 600 /root/.ssh/authorized_keys
rm -f /root/key.pub
fi
fi
# Extract the intended SSH host key for this machine from
# the supplied user data, if available. Otherwise sshd will
# generate one normally.
$wget http://169.254.169.254/2011-01-01/user-data > /root/user-data || true
key="$(sed 's/|/\n/g; s/SSH_HOST_DSA_KEY://; t; d' /root/user-data)"
key_pub="$(sed 's/SSH_HOST_DSA_KEY_PUB://; t; d' /root/user-data)"
if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_dsa_key ]; then
mkdir -m 0755 -p /etc/ssh
(umask 077; echo "$key" > /etc/ssh/ssh_host_dsa_key)
echo "$key_pub" > /etc/ssh/ssh_host_dsa_key.pub
fi
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
};
}

View file

@ -0,0 +1,58 @@
{ config, extendModules, lib, ... }:
let
inherit (lib)
mkOption
;
vmVariant = extendModules {
modules = [ ./qemu-vm.nix ];
};
vmVariantWithBootLoader = vmVariant.extendModules {
modules = [
({ config, ... }: {
_file = "nixos/default.nix##vmWithBootLoader";
virtualisation.useBootLoader = true;
virtualisation.useEFIBoot =
config.boot.loader.systemd-boot.enable ||
config.boot.loader.efi.canTouchEfiVariables;
})
];
};
in
{
options = {
virtualisation.vmVariant = mkOption {
description = ''
Machine configuration to be added for the vm script produced by <literal>nixos-rebuild build-vm</literal>.
'';
inherit (vmVariant) type;
default = {};
visible = "shallow";
};
virtualisation.vmVariantWithBootLoader = mkOption {
description = ''
Machine configuration to be added for the vm script produced by <literal>nixos-rebuild build-vm-with-bootloader</literal>.
'';
inherit (vmVariantWithBootLoader) type;
default = {};
visible = "shallow";
};
};
config = {
system.build = {
vm = lib.mkDefault config.virtualisation.vmVariant.system.build.vm;
vmWithBootLoader = lib.mkDefault config.virtualisation.vmVariantWithBootLoader.system.build.vm;
};
};
# uses extendModules
meta.buildDocsInSandbox = false;
}

View file

@ -0,0 +1,40 @@
{ lib, pkgs, ... }:
with lib;
{
imports = [
../profiles/qemu-guest.nix
];
config = {
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
};
boot.growPartition = true;
boot.kernelParams = [ "console=tty0" ];
boot.loader.grub.device = "/dev/vda";
boot.loader.timeout = 0;
# Allow root logins
services.openssh = {
enable = true;
permitRootLogin = "prohibit-password";
};
# Cloud-init configuration.
services.cloud-init.enable = true;
# Wget is needed for setting password. This is of little use as
# root password login is disabled above.
environment.systemPackages = [ pkgs.wget ];
# Only enable CloudStack datasource for faster boot speed.
environment.etc."cloud/cloud.cfg.d/99_cloudstack.cfg".text = ''
datasource:
CloudStack: {}
None: {}
datasource_list: ["CloudStack"]
'';
};
}

View file

@ -0,0 +1,31 @@
{ config, pkgs, lib, ... }:
with lib;
{
config = mkIf config.boot.isContainer {
# Disable some features that are not useful in a container.
nix.optimise.automatic = mkDefault false; # the store is host managed
services.udisks2.enable = mkDefault false;
powerManagement.enable = mkDefault false;
documentation.nixos.enable = mkDefault false;
networking.useHostResolvConf = mkDefault true;
# Containers should be light-weight, so start sshd on demand.
services.openssh.startWhenNeeded = mkDefault true;
# Shut up warnings about not having a boot loader.
system.build.installBootLoader = lib.mkDefault "${pkgs.coreutils}/bin/true";
# Not supported in systemd-nspawn containers.
security.audit.enable = false;
# Use the host's nix-daemon.
environment.variables.NIX_REMOTE = "daemon";
};
}

View file

@ -0,0 +1,101 @@
{ pkgs, lib, config, ... }:
let
cfg = config.virtualisation.containerd;
configFile = if cfg.configFile == null then
settingsFormat.generate "containerd.toml" cfg.settings
else
cfg.configFile;
containerdConfigChecked = pkgs.runCommand "containerd-config-checked.toml" {
nativeBuildInputs = [ pkgs.containerd ];
} ''
containerd -c ${configFile} config dump >/dev/null
ln -s ${configFile} $out
'';
settingsFormat = pkgs.formats.toml {};
in
{
options.virtualisation.containerd = with lib.types; {
enable = lib.mkEnableOption "containerd container runtime";
configFile = lib.mkOption {
default = null;
description = ''
Path to containerd config file.
Setting this option will override any configuration applied by the settings option.
'';
type = nullOr path;
};
settings = lib.mkOption {
type = settingsFormat.type;
default = {};
description = ''
Verbatim lines to add to containerd.toml
'';
};
args = lib.mkOption {
default = {};
description = "extra args to append to the containerd cmdline";
type = attrsOf str;
};
};
config = lib.mkIf cfg.enable {
warnings = lib.optional (cfg.configFile != null) ''
`virtualisation.containerd.configFile` is deprecated. use `virtualisation.containerd.settings` instead.
'';
virtualisation.containerd = {
args.config = toString containerdConfigChecked;
settings = {
version = 2;
plugins."io.containerd.grpc.v1.cri" = {
containerd.snapshotter =
lib.mkIf config.boot.zfs.enabled (lib.mkOptionDefault "zfs");
cni.bin_dir = lib.mkOptionDefault "${pkgs.cni-plugins}/bin";
};
};
};
environment.systemPackages = [ pkgs.containerd ];
systemd.services.containerd = {
description = "containerd - container runtime";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = with pkgs; [
containerd
runc
iptables
] ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package;
serviceConfig = {
ExecStart = ''${pkgs.containerd}/bin/containerd ${lib.concatStringsSep " " (lib.cli.toGNUCommandLine {} cfg.args)}'';
Delegate = "yes";
KillMode = "process";
Type = "notify";
Restart = "always";
RestartSec = "10";
# "limits" defined below are adopted from upstream: https://github.com/containerd/containerd/blob/master/containerd.service
LimitNPROC = "infinity";
LimitCORE = "infinity";
LimitNOFILE = "infinity";
TasksMax = "infinity";
OOMScoreAdjust = "-999";
StateDirectory = "containerd";
RuntimeDirectory = "containerd";
RuntimeDirectoryPreserve = "yes";
};
unitConfig = {
StartLimitBurst = "16";
StartLimitIntervalSec = "120s";
};
};
};
}

View file

@ -0,0 +1,156 @@
{ config, lib, pkgs, utils, ... }:
let
cfg = config.virtualisation.containers;
inherit (lib) literalExpression mkOption types;
toml = pkgs.formats.toml { };
in
{
meta = {
maintainers = [] ++ lib.teams.podman.members;
};
imports = [
(
lib.mkRemovedOptionModule
[ "virtualisation" "containers" "users" ]
"All users with `isNormalUser = true` set now get appropriate subuid/subgid mappings."
)
(
lib.mkRemovedOptionModule
[ "virtualisation" "containers" "containersConf" "extraConfig" ]
"Use virtualisation.containers.containersConf.settings instead."
)
];
options.virtualisation.containers = {
enable =
mkOption {
type = types.bool;
default = false;
description = ''
This option enables the common /etc/containers configuration module.
'';
};
ociSeccompBpfHook.enable = mkOption {
type = types.bool;
default = false;
description = "Enable the OCI seccomp BPF hook";
};
containersConf.settings = mkOption {
type = toml.type;
default = { };
description = "containers.conf configuration";
};
containersConf.cniPlugins = mkOption {
type = types.listOf types.package;
defaultText = literalExpression ''
[
pkgs.cni-plugins
]
'';
example = literalExpression ''
[
pkgs.cniPlugins.dnsname
]
'';
description = ''
CNI plugins to install on the system.
'';
};
storage.settings = mkOption {
type = toml.type;
default = {
storage = {
driver = "overlay";
graphroot = "/var/lib/containers/storage";
runroot = "/run/containers/storage";
};
};
description = "storage.conf configuration";
};
registries = {
search = mkOption {
type = types.listOf types.str;
default = [ "docker.io" "quay.io" ];
description = ''
List of repositories to search.
'';
};
insecure = mkOption {
default = [];
type = types.listOf types.str;
description = ''
List of insecure repositories.
'';
};
block = mkOption {
default = [];
type = types.listOf types.str;
description = ''
List of blocked repositories.
'';
};
};
policy = mkOption {
default = {};
type = types.attrs;
example = literalExpression ''
{
default = [ { type = "insecureAcceptAnything"; } ];
transports = {
docker-daemon = {
"" = [ { type = "insecureAcceptAnything"; } ];
};
};
}
'';
description = ''
Signature verification policy file.
If this option is empty the default policy file from
<literal>skopeo</literal> will be used.
'';
};
};
config = lib.mkIf cfg.enable {
virtualisation.containers.containersConf.cniPlugins = [ pkgs.cni-plugins ];
virtualisation.containers.containersConf.settings = {
network.cni_plugin_dirs = map (p: "${lib.getBin p}/bin") cfg.containersConf.cniPlugins;
engine = {
init_path = "${pkgs.catatonit}/bin/catatonit";
} // lib.optionalAttrs cfg.ociSeccompBpfHook.enable {
hooks_dir = [ config.boot.kernelPackages.oci-seccomp-bpf-hook ];
};
};
environment.etc."containers/containers.conf".source =
toml.generate "containers.conf" cfg.containersConf.settings;
environment.etc."containers/storage.conf".source =
toml.generate "storage.conf" cfg.storage.settings;
environment.etc."containers/registries.conf".source = toml.generate "registries.conf" {
registries = lib.mapAttrs (n: v: { registries = v; }) cfg.registries;
};
environment.etc."containers/policy.json".source =
if cfg.policy != {} then pkgs.writeText "policy.json" (builtins.toJSON cfg.policy)
else utils.copyFile "${pkgs.skopeo.src}/default-policy.json";
};
}

View file

@ -0,0 +1,163 @@
{ config, lib, pkgs, utils, ... }:
with lib;
let
cfg = config.virtualisation.cri-o;
crioPackage = (pkgs.cri-o.override { inherit (cfg) extraPackages; });
format = pkgs.formats.toml { };
cfgFile = format.generate "00-default.conf" cfg.settings;
in
{
imports = [
(mkRenamedOptionModule [ "virtualisation" "cri-o" "registries" ] [ "virtualisation" "containers" "registries" "search" ])
];
meta = {
maintainers = teams.podman.members;
};
options.virtualisation.cri-o = {
enable = mkEnableOption "Container Runtime Interface for OCI (CRI-O)";
storageDriver = mkOption {
type = types.enum [ "btrfs" "overlay" "vfs" ];
default = "overlay";
description = "Storage driver to be used";
};
logLevel = mkOption {
type = types.enum [ "trace" "debug" "info" "warn" "error" "fatal" ];
default = "info";
description = "Log level to be used";
};
pauseImage = mkOption {
type = types.nullOr types.str;
default = null;
description = "Override the default pause image for pod sandboxes";
example = "k8s.gcr.io/pause:3.2";
};
pauseCommand = mkOption {
type = types.nullOr types.str;
default = null;
description = "Override the default pause command";
example = "/pause";
};
runtime = mkOption {
type = types.nullOr types.str;
default = null;
description = "Override the default runtime";
example = "crun";
};
extraPackages = mkOption {
type = with types; listOf package;
default = [ ];
example = literalExpression ''
[
pkgs.gvisor
]
'';
description = ''
Extra packages to be installed in the CRI-O wrapper.
'';
};
package = mkOption {
type = types.package;
default = crioPackage;
defaultText = literalDocBook ''
<literal>pkgs.cri-o</literal> built with
<literal>config.${opt.extraPackages}</literal>.
'';
internal = true;
description = ''
The final CRI-O package (including extra packages).
'';
};
networkDir = mkOption {
type = types.nullOr types.path;
default = null;
description = "Override the network_dir option.";
internal = true;
};
settings = mkOption {
type = format.type;
default = { };
description = ''
Configuration for cri-o, see
<link xlink:href="https://github.com/cri-o/cri-o/blob/master/docs/crio.conf.5.md"/>.
'';
};
};
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package pkgs.cri-tools ];
environment.etc."crictl.yaml".source = utils.copyFile "${pkgs.cri-o-unwrapped.src}/crictl.yaml";
virtualisation.cri-o.settings.crio = {
storage_driver = cfg.storageDriver;
image = {
pause_image = mkIf (cfg.pauseImage != null) cfg.pauseImage;
pause_command = mkIf (cfg.pauseCommand != null) cfg.pauseCommand;
};
network = {
plugin_dirs = [ "${pkgs.cni-plugins}/bin" ];
network_dir = mkIf (cfg.networkDir != null) cfg.networkDir;
};
runtime = {
cgroup_manager = "systemd";
log_level = cfg.logLevel;
manage_ns_lifecycle = true;
pinns_path = "${cfg.package}/bin/pinns";
hooks_dir =
optional (config.virtualisation.containers.ociSeccompBpfHook.enable)
config.boot.kernelPackages.oci-seccomp-bpf-hook;
default_runtime = mkIf (cfg.runtime != null) cfg.runtime;
runtimes = mkIf (cfg.runtime != null) {
"${cfg.runtime}" = { };
};
};
};
environment.etc."cni/net.d/10-crio-bridge.conf".source = utils.copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/10-crio-bridge.conf";
environment.etc."cni/net.d/99-loopback.conf".source = utils.copyFile "${pkgs.cri-o-unwrapped.src}/contrib/cni/99-loopback.conf";
environment.etc."crio/crio.conf.d/00-default.conf".source = cfgFile;
# Enable common /etc/containers configuration
virtualisation.containers.enable = true;
systemd.services.crio = {
description = "Container Runtime Interface for OCI (CRI-O)";
documentation = [ "https://github.com/cri-o/cri-o" ];
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
path = [ cfg.package ];
serviceConfig = {
Type = "notify";
ExecStart = "${cfg.package}/bin/crio";
ExecReload = "/bin/kill -s HUP $MAINPID";
TasksMax = "infinity";
LimitNOFILE = "1048576";
LimitNPROC = "1048576";
LimitCORE = "infinity";
OOMScoreAdjust = "-999";
TimeoutStartSec = "0";
Restart = "on-abnormal";
};
restartTriggers = [ cfgFile ];
};
};
}

View file

@ -0,0 +1,197 @@
{ config, pkgs, lib, modulesPath, ... }:
with lib;
{
imports = [
(modulesPath + "/profiles/qemu-guest.nix")
(modulesPath + "/virtualisation/digital-ocean-init.nix")
];
options.virtualisation.digitalOcean = with types; {
setRootPassword = mkOption {
type = bool;
default = false;
example = true;
description = "Whether to set the root password from the Digital Ocean metadata";
};
setSshKeys = mkOption {
type = bool;
default = true;
example = true;
description = "Whether to fetch ssh keys from Digital Ocean";
};
seedEntropy = mkOption {
type = bool;
default = true;
example = true;
description = "Whether to run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
};
};
config =
let
cfg = config.virtualisation.digitalOcean;
hostName = config.networking.hostName;
doMetadataFile = "/run/do-metadata/v1.json";
in mkMerge [{
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
boot = {
growPartition = true;
kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ];
initrd.kernelModules = [ "virtio_scsi" ];
kernelModules = [ "virtio_pci" "virtio_net" ];
loader = {
grub.device = "/dev/vda";
timeout = 0;
grub.configurationLimit = 0;
};
};
services.openssh = {
enable = mkDefault true;
passwordAuthentication = mkDefault false;
};
services.do-agent.enable = mkDefault true;
networking = {
hostName = mkDefault ""; # use Digital Ocean metadata server
};
/* Check for and wait for the metadata server to become reachable.
* This serves as a dependency for all the other metadata services. */
systemd.services.digitalocean-metadata = {
path = [ pkgs.curl ];
description = "Get host metadata provided by Digitalocean";
script = ''
set -eu
DO_DELAY_ATTEMPTS=0
while ! curl -fsSL -o $RUNTIME_DIRECTORY/v1.json http://169.254.169.254/metadata/v1.json; do
DO_DELAY_ATTEMPTS=$((DO_DELAY_ATTEMPTS + 1))
if (( $DO_DELAY_ATTEMPTS >= $DO_DELAY_ATTEMPTS_MAX )); then
echo "giving up"
exit 1
fi
echo "metadata unavailable, trying again in 1s..."
sleep 1
done
chmod 600 $RUNTIME_DIRECTORY/v1.json
'';
environment = {
DO_DELAY_ATTEMPTS_MAX = "10";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "do-metadata";
RuntimeDirectoryPreserve = "yes";
};
unitConfig = {
ConditionPathExists = "!${doMetadataFile}";
After = [ "network-pre.target" ] ++
optional config.networking.dhcpcd.enable "dhcpcd.service" ++
optional config.systemd.network.enable "systemd-networkd.service";
};
};
/* Fetch the root password from the digital ocean metadata.
* There is no specific route for this, so we use jq to get
* it from the One Big JSON metadata blob */
systemd.services.digitalocean-set-root-password = mkIf cfg.setRootPassword {
path = [ pkgs.shadow pkgs.jq ];
description = "Set root password provided by Digitalocean";
wantedBy = [ "multi-user.target" ];
script = ''
set -eo pipefail
ROOT_PASSWORD=$(jq -er '.auth_key' ${doMetadataFile})
echo "root:$ROOT_PASSWORD" | chpasswd
mkdir -p /etc/do-metadata/set-root-password
'';
unitConfig = {
ConditionPathExists = "!/etc/do-metadata/set-root-password";
Before = optional config.services.openssh.enable "sshd.service";
After = [ "digitalocean-metadata.service" ];
Requires = [ "digitalocean-metadata.service" ];
};
serviceConfig = {
Type = "oneshot";
};
};
/* Set the hostname from Digital Ocean, unless the user configured it in
* the NixOS configuration. The cached metadata file isn't used here
* because the hostname is a mutable part of the droplet. */
systemd.services.digitalocean-set-hostname = mkIf (hostName == "") {
path = [ pkgs.curl pkgs.nettools ];
description = "Set hostname provided by Digitalocean";
wantedBy = [ "network.target" ];
script = ''
set -e
DIGITALOCEAN_HOSTNAME=$(curl -fsSL http://169.254.169.254/metadata/v1/hostname)
hostname "$DIGITALOCEAN_HOSTNAME"
if [[ ! -e /etc/hostname || -w /etc/hostname ]]; then
printf "%s\n" "$DIGITALOCEAN_HOSTNAME" > /etc/hostname
fi
'';
unitConfig = {
Before = [ "network.target" ];
After = [ "digitalocean-metadata.service" ];
Wants = [ "digitalocean-metadata.service" ];
};
serviceConfig = {
Type = "oneshot";
};
};
/* Fetch the ssh keys for root from Digital Ocean */
systemd.services.digitalocean-ssh-keys = mkIf cfg.setSshKeys {
description = "Set root ssh keys provided by Digital Ocean";
wantedBy = [ "multi-user.target" ];
path = [ pkgs.jq ];
script = ''
set -e
mkdir -m 0700 -p /root/.ssh
jq -er '.public_keys[]' ${doMetadataFile} > /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
unitConfig = {
ConditionPathExists = "!/root/.ssh/authorized_keys";
Before = optional config.services.openssh.enable "sshd.service";
After = [ "digitalocean-metadata.service" ];
Requires = [ "digitalocean-metadata.service" ];
};
};
/* Initialize the RNG by running the entropy-seed script from the
* Digital Ocean metadata
*/
systemd.services.digitalocean-entropy-seed = mkIf cfg.seedEntropy {
description = "Run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
wantedBy = [ "network.target" ];
path = [ pkgs.jq pkgs.mpack ];
script = ''
set -eo pipefail
TEMPDIR=$(mktemp -d)
jq -er '.vendor_data' ${doMetadataFile} | munpack -tC $TEMPDIR
ENTROPY_SEED=$(grep -rl "DigitalOcean Entropy Seed script" $TEMPDIR)
${pkgs.runtimeShell} $ENTROPY_SEED
rm -rf $TEMPDIR
'';
unitConfig = {
Before = [ "network.target" ];
After = [ "digitalocean-metadata.service" ];
Requires = [ "digitalocean-metadata.service" ];
};
serviceConfig = {
Type = "oneshot";
};
};
}
];
meta.maintainers = with maintainers; [ arianvp eamsden ];
}

View file

@ -0,0 +1,70 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.digitalOceanImage;
in
{
imports = [ ./digital-ocean-config.nix ];
options = {
virtualisation.digitalOceanImage.diskSize = mkOption {
type = with types; either (enum [ "auto" ]) int;
default = "auto";
example = 4096;
description = ''
Size of disk image. Unit is MB.
'';
};
virtualisation.digitalOceanImage.configFile = mkOption {
type = with types; nullOr path;
default = null;
description = ''
A path to a configuration file which will be placed at
<literal>/etc/nixos/configuration.nix</literal> and be used when switching
to a new configuration. If set to <literal>null</literal>, a default
configuration is used that imports
<literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
'';
};
virtualisation.digitalOceanImage.compressionMethod = mkOption {
type = types.enum [ "gzip" "bzip2" ];
default = "gzip";
example = "bzip2";
description = ''
Disk image compression method. Choose bzip2 to generate smaller images that
take longer to generate but will consume less metered storage space on your
Digital Ocean account.
'';
};
};
#### implementation
config = {
system.build.digitalOceanImage = import ../../lib/make-disk-image.nix {
name = "digital-ocean-image";
format = "qcow2";
postVM = let
compress = {
"gzip" = "${pkgs.gzip}/bin/gzip";
"bzip2" = "${pkgs.bzip2}/bin/bzip2";
}.${cfg.compressionMethod};
in ''
${compress} $diskImage
'';
configFile = if cfg.configFile == null
then config.virtualisation.digitalOcean.defaultConfigFile
else cfg.configFile;
inherit (cfg) diskSize;
inherit config lib pkgs;
};
};
meta.maintainers = with maintainers; [ arianvp eamsden ];
}

View file

@ -0,0 +1,95 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.virtualisation.digitalOcean;
defaultConfigFile = pkgs.writeText "digitalocean-configuration.nix" ''
{ modulesPath, lib, ... }:
{
imports = lib.optional (builtins.pathExists ./do-userdata.nix) ./do-userdata.nix ++ [
(modulesPath + "/virtualisation/digital-ocean-config.nix")
];
}
'';
in {
options.virtualisation.digitalOcean.rebuildFromUserData = mkOption {
type = types.bool;
default = true;
example = true;
description = "Whether to reconfigure the system from Digital Ocean user data";
};
options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
type = types.path;
default = defaultConfigFile;
defaultText = literalDocBook ''
The default configuration imports user-data if applicable and
<literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
'';
description = ''
A path to a configuration file which will be placed at
<literal>/etc/nixos/configuration.nix</literal> and be used when switching to
a new configuration.
'';
};
config = {
systemd.services.digitalocean-init = mkIf cfg.rebuildFromUserData {
description = "Reconfigure the system from Digital Ocean userdata on startup";
wantedBy = [ "network-online.target" ];
unitConfig = {
ConditionPathExists = "!/etc/nixos/do-userdata.nix";
After = [ "digitalocean-metadata.service" "network-online.target" ];
Requires = [ "digitalocean-metadata.service" ];
X-StopOnRemoval = false;
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
restartIfChanged = false;
path = [ pkgs.jq pkgs.gnused pkgs.gnugrep config.systemd.package config.nix.package config.system.build.nixos-rebuild ];
environment = {
HOME = "/root";
NIX_PATH = concatStringsSep ":" [
"/nix/var/nix/profiles/per-user/root/channels/nixos"
"nixos-config=/etc/nixos/configuration.nix"
"/nix/var/nix/profiles/per-user/root/channels"
];
};
script = ''
set -e
echo "attempting to fetch configuration from Digital Ocean user data..."
userData=$(mktemp)
if jq -er '.user_data' /run/do-metadata/v1.json > $userData; then
# If the user-data looks like it could be a nix expression,
# copy it over. Also, look for a magic three-hash comment and set
# that as the channel.
if nix-instantiate --parse $userData > /dev/null; then
channels="$(grep '^###' "$userData" | sed 's|###\s*||')"
printf "%s" "$channels" | while read channel; do
echo "writing channel: $channel"
done
if [[ -n "$channels" ]]; then
printf "%s" "$channels" > /root/.nix-channels
nix-channel --update
fi
echo "setting configuration from Digital Ocean user data"
cp "$userData" /etc/nixos/do-userdata.nix
if [[ ! -e /etc/nixos/configuration.nix ]]; then
install -m0644 ${cfg.defaultConfigFile} /etc/nixos/configuration.nix
fi
else
echo "user data does not appear to be a Nix expression; ignoring"
exit
fi
nixos-rebuild switch
else
echo "no user data is available"
fi
'';
};
};
meta.maintainers = with maintainers; [ arianvp eamsden ];
}

View file

@ -0,0 +1,57 @@
{ ... }:
{
imports = [
../profiles/docker-container.nix # FIXME, shouldn't include something from profiles/
];
boot.postBootCommands =
''
# Set virtualisation to docker
echo "docker" > /run/systemd/container
'';
# Iptables do not work in Docker.
networking.firewall.enable = false;
# Socket activated ssh presents problem in Docker.
services.openssh.startWhenNeeded = false;
}
# Example usage:
#
## default.nix
# let
# nixos = import <nixpkgs/nixos> {
# configuration = ./configuration.nix;
# system = "x86_64-linux";
# };
# in
# nixos.config.system.build.tarball
#
## configuration.nix
# { pkgs, config, lib, ... }:
# {
# imports = [
# <nixpkgs/nixos/modules/virtualisation/docker-image.nix>
# <nixpkgs/nixos/modules/installer/cd-dvd/channel.nix>
# ];
#
# documentation.doc.enable = false;
#
# environment.systemPackages = with pkgs; [
# bashInteractive
# cacert
# nix
# ];
# }
#
## Run
# Build the tarball:
# $ nix-build default.nix
# Load into docker:
# $ docker import result/tarball/nixos-system-*.tar.xz nixos-docker
# Boots into systemd
# $ docker run --privileged -it nixos-docker /init
# Log into the container
# $ docker exec -it <container-name> /run/current-system/sw/bin/bash

View file

@ -0,0 +1,101 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.docker.rootless;
proxy_env = config.networking.proxy.envVars;
settingsFormat = pkgs.formats.json {};
daemonSettingsFile = settingsFormat.generate "daemon.json" cfg.daemon.settings;
in
{
###### interface
options.virtualisation.docker.rootless = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
This option enables docker in a rootless mode, a daemon that manages
linux containers. To interact with the daemon, one needs to set
<command>DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock</command>.
'';
};
setSocketVariable = mkOption {
type = types.bool;
default = false;
description = ''
Point <command>DOCKER_HOST</command> to rootless Docker instance for
normal users by default.
'';
};
daemon.settings = mkOption {
type = settingsFormat.type;
default = { };
example = {
ipv6 = true;
"fixed-cidr-v6" = "fd00::/80";
};
description = ''
Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
'';
};
package = mkOption {
default = pkgs.docker;
defaultText = literalExpression "pkgs.docker";
type = types.package;
description = ''
Docker package to be used in the module.
'';
};
};
###### implementation
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
environment.extraInit = optionalString cfg.setSocketVariable ''
if [ -z "$DOCKER_HOST" -a -n "$XDG_RUNTIME_DIR" ]; then
export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"
fi
'';
# Taken from https://github.com/moby/moby/blob/master/contrib/dockerd-rootless-setuptool.sh
systemd.user.services.docker = {
wantedBy = [ "default.target" ];
description = "Docker Application Container Engine (Rootless)";
# needs newuidmap from pkgs.shadow
path = [ "/run/wrappers" ];
environment = proxy_env;
unitConfig = {
# docker-rootless doesn't support running as root.
ConditionUser = "!root";
StartLimitInterval = "60s";
};
serviceConfig = {
Type = "notify";
ExecStart = "${cfg.package}/bin/dockerd-rootless --config-file=${daemonSettingsFile}";
ExecReload = "${pkgs.procps}/bin/kill -s HUP $MAINPID";
TimeoutSec = 0;
RestartSec = 2;
Restart = "always";
StartLimitBurst = 3;
LimitNOFILE = "infinity";
LimitNPROC = "infinity";
LimitCORE = "infinity";
Delegate = true;
NotifyAccess = "all";
KillMode = "mixed";
};
};
};
}

View file

@ -0,0 +1,251 @@
# Systemd services for docker.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.docker;
proxy_env = config.networking.proxy.envVars;
settingsFormat = pkgs.formats.json {};
daemonSettingsFile = settingsFormat.generate "daemon.json" cfg.daemon.settings;
in
{
###### interface
options.virtualisation.docker = {
enable =
mkOption {
type = types.bool;
default = false;
description =
''
This option enables docker, a daemon that manages
linux containers. Users in the "docker" group can interact with
the daemon (e.g. to start or stop containers) using the
<command>docker</command> command line tool.
'';
};
listenOptions =
mkOption {
type = types.listOf types.str;
default = ["/run/docker.sock"];
description =
''
A list of unix and tcp docker should listen to. The format follows
ListenStream as described in systemd.socket(5).
'';
};
enableOnBoot =
mkOption {
type = types.bool;
default = true;
description =
''
When enabled dockerd is started on boot. This is required for
containers which are created with the
<literal>--restart=always</literal> flag to work. If this option is
disabled, docker might be started on demand by socket activation.
'';
};
daemon.settings =
mkOption {
type = settingsFormat.type;
default = { };
example = {
ipv6 = true;
"fixed-cidr-v6" = "fd00::/80";
};
description = ''
Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
'';
};
enableNvidia =
mkOption {
type = types.bool;
default = false;
description = ''
Enable nvidia-docker wrapper, supporting NVIDIA GPUs inside docker containers.
'';
};
liveRestore =
mkOption {
type = types.bool;
default = true;
description =
''
Allow dockerd to be restarted without affecting running container.
This option is incompatible with docker swarm.
'';
};
storageDriver =
mkOption {
type = types.nullOr (types.enum ["aufs" "btrfs" "devicemapper" "overlay" "overlay2" "zfs"]);
default = null;
description =
''
This option determines which Docker storage driver to use. By default
it let's docker automatically choose preferred storage driver.
'';
};
logDriver =
mkOption {
type = types.enum ["none" "json-file" "syslog" "journald" "gelf" "fluentd" "awslogs" "splunk" "etwlogs" "gcplogs"];
default = "journald";
description =
''
This option determines which Docker log driver to use.
'';
};
extraOptions =
mkOption {
type = types.separatedString " ";
default = "";
description =
''
The extra command-line options to pass to
<command>docker</command> daemon.
'';
};
autoPrune = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to periodically prune Docker resources. If enabled, a
systemd timer will run <literal>docker system prune -f</literal>
as specified by the <literal>dates</literal> option.
'';
};
flags = mkOption {
type = types.listOf types.str;
default = [];
example = [ "--all" ];
description = ''
Any additional flags passed to <command>docker system prune</command>.
'';
};
dates = mkOption {
default = "weekly";
type = types.str;
description = ''
Specification (in the format described by
<citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>) of the time at
which the prune will occur.
'';
};
};
package = mkOption {
default = pkgs.docker;
defaultText = literalExpression "pkgs.docker";
type = types.package;
description = ''
Docker package to be used in the module.
'';
};
};
###### implementation
config = mkIf cfg.enable (mkMerge [{
boot.kernelModules = [ "bridge" "veth" ];
boot.kernel.sysctl = {
"net.ipv4.conf.all.forwarding" = mkOverride 98 true;
"net.ipv4.conf.default.forwarding" = mkOverride 98 true;
};
environment.systemPackages = [ cfg.package ]
++ optional cfg.enableNvidia pkgs.nvidia-docker;
users.groups.docker.gid = config.ids.gids.docker;
systemd.packages = [ cfg.package ];
systemd.services.docker = {
wantedBy = optional cfg.enableOnBoot "multi-user.target";
after = [ "network.target" "docker.socket" ];
requires = [ "docker.socket" ];
environment = proxy_env;
serviceConfig = {
Type = "notify";
ExecStart = [
""
''
${cfg.package}/bin/dockerd \
--config-file=${daemonSettingsFile} \
${cfg.extraOptions}
''];
ExecReload=[
""
"${pkgs.procps}/bin/kill -s HUP $MAINPID"
];
};
path = [ pkgs.kmod ] ++ optional (cfg.storageDriver == "zfs") pkgs.zfs
++ optional cfg.enableNvidia pkgs.nvidia-docker;
};
systemd.sockets.docker = {
description = "Docker Socket for the API";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = cfg.listenOptions;
SocketMode = "0660";
SocketUser = "root";
SocketGroup = "docker";
};
};
systemd.services.docker-prune = {
description = "Prune docker resources";
restartIfChanged = false;
unitConfig.X-StopOnRemoval = false;
serviceConfig.Type = "oneshot";
script = ''
${cfg.package}/bin/docker system prune -f ${toString cfg.autoPrune.flags}
'';
startAt = optional cfg.autoPrune.enable cfg.autoPrune.dates;
};
assertions = [
{ assertion = cfg.enableNvidia -> config.hardware.opengl.driSupport32Bit or false;
message = "Option enableNvidia requires 32bit support libraries";
}];
virtualisation.docker.daemon.settings = {
group = "docker";
hosts = [ "fd://" ];
log-driver = mkDefault cfg.logDriver;
storage-driver = mkIf (cfg.storageDriver != null) (mkDefault cfg.storageDriver);
live-restore = mkDefault cfg.liveRestore;
runtimes = mkIf cfg.enableNvidia {
nvidia = {
path = "${pkgs.nvidia-docker}/bin/nvidia-container-runtime";
};
};
};
}
]);
imports = [
(mkRemovedOptionModule ["virtualisation" "docker" "socketActivation"] "This option was removed and socket activation is now always active")
];
}

View file

@ -0,0 +1,9 @@
# Compatibility shim
let
lib = import ../../../lib;
inherit (lib) mapAttrs;
everything = import ./amazon-ec2-amis.nix;
doAllVersions = mapAttrs (versionName: doRegion);
doRegion = mapAttrs (regionName: systems: systems.x86_64-linux);
in
doAllVersions everything

View file

@ -0,0 +1,91 @@
# This module defines a systemd service that sets the SSH host key and
# authorized client key and host name of virtual machines running on
# Amazon EC2, Eucalyptus and OpenStack Compute (Nova).
{ config, lib, pkgs, ... }:
with lib;
{
imports = [
(mkRemovedOptionModule [ "ec2" "metadata" ] "")
];
config = {
systemd.services.apply-ec2-data =
{ description = "Apply EC2 Data";
wantedBy = [ "multi-user.target" "sshd.service" ];
before = [ "sshd.service" ];
path = [ pkgs.iproute2 ];
script =
''
${optionalString (config.networking.hostName == "") ''
echo "setting host name..."
if [ -s /etc/ec2-metadata/hostname ]; then
${pkgs.nettools}/bin/hostname $(cat /etc/ec2-metadata/hostname)
fi
''}
if ! [ -e /root/.ssh/authorized_keys ]; then
echo "obtaining SSH key..."
mkdir -m 0700 -p /root/.ssh
if [ -s /etc/ec2-metadata/public-keys-0-openssh-key ]; then
cat /etc/ec2-metadata/public-keys-0-openssh-key >> /root/.ssh/authorized_keys
echo "new key added to authorized_keys"
chmod 600 /root/.ssh/authorized_keys
fi
fi
# Extract the intended SSH host key for this machine from
# the supplied user data, if available. Otherwise sshd will
# generate one normally.
userData=/etc/ec2-metadata/user-data
mkdir -m 0755 -p /etc/ssh
if [ -s "$userData" ]; then
key="$(sed 's/|/\n/g; s/SSH_HOST_DSA_KEY://; t; d' $userData)"
key_pub="$(sed 's/SSH_HOST_DSA_KEY_PUB://; t; d' $userData)"
if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_dsa_key ]; then
(umask 077; echo "$key" > /etc/ssh/ssh_host_dsa_key)
echo "$key_pub" > /etc/ssh/ssh_host_dsa_key.pub
fi
key="$(sed 's/|/\n/g; s/SSH_HOST_ED25519_KEY://; t; d' $userData)"
key_pub="$(sed 's/SSH_HOST_ED25519_KEY_PUB://; t; d' $userData)"
if [ -n "$key" -a -n "$key_pub" -a ! -e /etc/ssh/ssh_host_ed25519_key ]; then
(umask 077; echo "$key" > /etc/ssh/ssh_host_ed25519_key)
echo "$key_pub" > /etc/ssh/ssh_host_ed25519_key.pub
fi
fi
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
};
systemd.services.print-host-key =
{ description = "Print SSH Host Key";
wantedBy = [ "multi-user.target" ];
after = [ "sshd.service" ];
script =
''
# Print the host public key on the console so that the user
# can obtain it securely by parsing the output of
# ec2-get-console-output.
echo "-----BEGIN SSH HOST KEY FINGERPRINTS-----" > /dev/console
for i in /etc/ssh/ssh_host_*_key.pub; do
${config.programs.ssh.package}/bin/ssh-keygen -l -f $i > /dev/console
done
echo "-----END SSH HOST KEY FINGERPRINTS-----" > /dev/console
'';
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
};
};
}

View file

@ -0,0 +1,77 @@
{ curl, targetRoot, wgetExtraOptions }:
# Note: be very cautious about dependencies, each dependency grows
# the closure of the initrd. Ideally we would not even require curl,
# but there is no reasonable way to send an HTTP PUT request without
# it. Note: do not be fooled: the wget referenced in this script
# is busybox's wget, not the fully featured one with --method support.
#
# Make sure that every package you depend on here is already listed as
# a channel blocker for both the full-sized and small channels.
# Otherwise, we risk breaking user deploys in released channels.
#
# Also note: OpenStack's metadata service for its instances aims to be
# compatible with the EC2 IMDS. Where possible, try to keep the set of
# fetched metadata in sync with ./openstack-metadata-fetcher.nix .
''
metaDir=${targetRoot}etc/ec2-metadata
mkdir -m 0755 -p "$metaDir"
rm -f "$metaDir/*"
get_imds_token() {
# retry-delay of 1 selected to give the system a second to get going,
# but not add a lot to the bootup time
${curl}/bin/curl \
-v \
--retry 3 \
--retry-delay 1 \
--fail \
-X PUT \
--connect-timeout 1 \
-H "X-aws-ec2-metadata-token-ttl-seconds: 600" \
http://169.254.169.254/latest/api/token
}
preflight_imds_token() {
# retry-delay of 1 selected to give the system a second to get going,
# but not add a lot to the bootup time
${curl}/bin/curl \
-v \
--retry 3 \
--retry-delay 1 \
--fail \
--connect-timeout 1 \
-H "X-aws-ec2-metadata-token: $IMDS_TOKEN" \
http://169.254.169.254/1.0/meta-data/instance-id
}
try=1
while [ $try -le 3 ]; do
echo "(attempt $try/3) getting an EC2 instance metadata service v2 token..."
IMDS_TOKEN=$(get_imds_token) && break
try=$((try + 1))
sleep 1
done
if [ "x$IMDS_TOKEN" == "x" ]; then
echo "failed to fetch an IMDS2v token."
fi
try=1
while [ $try -le 10 ]; do
echo "(attempt $try/10) validating the EC2 instance metadata service v2 token..."
preflight_imds_token && break
try=$((try + 1))
sleep 1
done
echo "getting EC2 instance metadata..."
wget_imds() {
wget ${wgetExtraOptions} --header "X-aws-ec2-metadata-token: $IMDS_TOKEN" "$@";
}
wget_imds -O "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
(umask 077 && wget_imds -O "$metaDir/user-data" http://169.254.169.254/1.0/user-data)
wget_imds -O "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname
wget_imds -O "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key
''

View file

@ -0,0 +1,45 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.services.ecs-agent;
in {
options.services.ecs-agent = {
enable = mkEnableOption "Amazon ECS agent";
package = mkOption {
type = types.path;
description = "The ECS agent package to use";
default = pkgs.ecs-agent;
defaultText = literalExpression "pkgs.ecs-agent";
};
extra-environment = mkOption {
type = types.attrsOf types.str;
description = "The environment the ECS agent should run with. See the ECS agent documentation for keys that work here.";
default = {};
};
};
config = lib.mkIf cfg.enable {
# This service doesn't run if docker isn't running, and unlike potentially remote services like e.g., postgresql, docker has
# to be running locally so `docker.enable` will always be set if the ECS agent is enabled.
virtualisation.docker.enable = true;
systemd.services.ecs-agent = {
inherit (cfg.package.meta) description;
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = cfg.extra-environment;
script = ''
if [ ! -z "$ECS_DATADIR" ]; then
mkdir -p "$ECS_DATADIR"
fi
${cfg.package}/bin/agent
'';
};
};
}

View file

@ -0,0 +1,17 @@
let self = {
"14.12" = "gs://nixos-cloud-images/nixos-14.12.471.1f09b77-x86_64-linux.raw.tar.gz";
"15.09" = "gs://nixos-cloud-images/nixos-15.09.425.7870f20-x86_64-linux.raw.tar.gz";
"16.03" = "gs://nixos-cloud-images/nixos-image-16.03.847.8688c17-x86_64-linux.raw.tar.gz";
"17.03" = "gs://nixos-cloud-images/nixos-image-17.03.1082.4aab5c5798-x86_64-linux.raw.tar.gz";
"18.03" = "gs://nixos-cloud-images/nixos-image-18.03.132536.fdb5ba4cdf9-x86_64-linux.raw.tar.gz";
"18.09" = "gs://nixos-cloud-images/nixos-image-18.09.1228.a4c4cbb613c-x86_64-linux.raw.tar.gz";
# This format will be handled by the upcoming NixOPS 2.0 release.
# The old images based on a GS object are deprecated.
"20.09" = {
project = "nixos-cloud";
name = "nixos-image-20-09-3531-3858fbc08e6-x86-64-linux";
};
latest = self."20.09";
}; in self

View file

@ -0,0 +1,102 @@
{ config, lib, pkgs, ... }:
with lib;
{
imports = [
../profiles/headless.nix
../profiles/qemu-guest.nix
];
fileSystems."/" = {
fsType = "ext4";
device = "/dev/disk/by-label/nixos";
autoResize = true;
};
boot.growPartition = true;
boot.kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ];
boot.initrd.kernelModules = [ "virtio_scsi" ];
boot.kernelModules = [ "virtio_pci" "virtio_net" ];
# Generate a GRUB menu.
boot.loader.grub.device = "/dev/sda";
boot.loader.timeout = 0;
# Don't put old configurations in the GRUB menu. The user has no
# way to select them anyway.
boot.loader.grub.configurationLimit = 0;
# Allow root logins only using SSH keys
# and disable password authentication in general
services.openssh.enable = true;
services.openssh.permitRootLogin = "prohibit-password";
services.openssh.passwordAuthentication = mkDefault false;
# enable OS Login. This also requires setting enable-oslogin=TRUE metadata on
# instance or project level
security.googleOsLogin.enable = true;
# Use GCE udev rules for dynamic disk volumes
services.udev.packages = [ pkgs.google-guest-configs ];
services.udev.path = [ pkgs.google-guest-configs ];
# Force getting the hostname from Google Compute.
networking.hostName = mkDefault "";
# Always include cryptsetup so that NixOps can use it.
environment.systemPackages = [ pkgs.cryptsetup ];
# Rely on GCP's firewall instead
networking.firewall.enable = mkDefault false;
# Configure default metadata hostnames
networking.extraHosts = ''
169.254.169.254 metadata.google.internal metadata
'';
networking.timeServers = [ "metadata.google.internal" ];
networking.usePredictableInterfaceNames = false;
# GC has 1460 MTU
networking.interfaces.eth0.mtu = 1460;
systemd.packages = [ pkgs.google-guest-agent ];
systemd.services.google-guest-agent = {
wantedBy = [ "multi-user.target" ];
restartTriggers = [ config.environment.etc."default/instance_configs.cfg".source ];
path = lib.optional config.users.mutableUsers pkgs.shadow;
};
systemd.services.google-startup-scripts.wantedBy = [ "multi-user.target" ];
systemd.services.google-shutdown-scripts.wantedBy = [ "multi-user.target" ];
security.sudo.extraRules = mkIf config.users.mutableUsers [
{ groups = [ "google-sudoers" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
];
users.groups.google-sudoers = mkIf config.users.mutableUsers { };
boot.extraModprobeConfig = lib.readFile "${pkgs.google-guest-configs}/etc/modprobe.d/gce-blacklist.conf";
environment.etc."sysctl.d/60-gce-network-security.conf".source = "${pkgs.google-guest-configs}/etc/sysctl.d/60-gce-network-security.conf";
environment.etc."default/instance_configs.cfg".text = ''
[Accounts]
useradd_cmd = useradd -m -s /run/current-system/sw/bin/bash -p * {user}
[Daemons]
accounts_daemon = ${boolToString config.users.mutableUsers}
[InstanceSetup]
# Make sure GCE image does not replace host key that NixOps sets.
set_host_keys = false
[MetadataScripts]
default_shell = ${pkgs.stdenv.shell}
[NetworkInterfaces]
dhclient_script = ${pkgs.google-guest-configs}/bin/google-dhclient-script
# We set up network interfaces declaratively.
setup = false
'';
}

View file

@ -0,0 +1,71 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.googleComputeImage;
defaultConfigFile = pkgs.writeText "configuration.nix" ''
{ ... }:
{
imports = [
<nixpkgs/nixos/modules/virtualisation/google-compute-image.nix>
];
}
'';
in
{
imports = [ ./google-compute-config.nix ];
options = {
virtualisation.googleComputeImage.diskSize = mkOption {
type = with types; either (enum [ "auto" ]) int;
default = "auto";
example = 1536;
description = ''
Size of disk image. Unit is MB.
'';
};
virtualisation.googleComputeImage.configFile = mkOption {
type = with types; nullOr str;
default = null;
description = ''
A path to a configuration file which will be placed at `/etc/nixos/configuration.nix`
and be used when switching to a new configuration.
If set to `null`, a default configuration is used, where the only import is
`<nixpkgs/nixos/modules/virtualisation/google-compute-image.nix>`.
'';
};
virtualisation.googleComputeImage.compressionLevel = mkOption {
type = types.int;
default = 6;
description = ''
GZIP compression level of the resulting disk image (1-9).
'';
};
};
#### implementation
config = {
system.build.googleComputeImage = import ../../lib/make-disk-image.nix {
name = "google-compute-image";
postVM = ''
PATH=$PATH:${with pkgs; lib.makeBinPath [ gnutar gzip ]}
pushd $out
mv $diskImage disk.raw
tar -Sc disk.raw | gzip -${toString cfg.compressionLevel} > \
nixos-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.raw.tar.gz
rm $out/disk.raw
popd
'';
format = "raw";
configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile;
inherit (cfg) diskSize;
inherit config lib pkgs;
};
};
}

View file

@ -0,0 +1,3 @@
# This profile is deprecated, use boot.growPartition directly.
builtins.trace "the profile <nixos/modules/virtualisation/grow-partition.nix> is deprecated, use boot.growPartition instead"
{ }

View file

@ -0,0 +1,66 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.hypervGuest;
in {
options = {
virtualisation.hypervGuest = {
enable = mkEnableOption "Hyper-V Guest Support";
videoMode = mkOption {
type = types.str;
default = "1152x864";
example = "1024x768";
description = ''
Resolution at which to initialize the video adapter.
Supports screen resolution up to Full HD 1920x1080 with 32 bit color
on Windows Server 2012, and 1600x1200 with 16 bit color on Windows
Server 2008 R2 or earlier.
'';
};
};
};
config = mkIf cfg.enable {
boot = {
initrd.kernelModules = [
"hv_balloon" "hv_netvsc" "hv_storvsc" "hv_utils" "hv_vmbus"
];
initrd.availableKernelModules = [ "hyperv_keyboard" ];
kernelParams = [
"video=hyperv_fb:${cfg.videoMode}" "elevator=noop"
];
};
environment.systemPackages = [ config.boot.kernelPackages.hyperv-daemons.bin ];
# enable hotadding cpu/memory
services.udev.packages = lib.singleton (pkgs.writeTextFile {
name = "hyperv-cpu-and-memory-hotadd-udev-rules";
destination = "/etc/udev/rules.d/99-hyperv-cpu-and-memory-hotadd.rules";
text = ''
# Memory hotadd
SUBSYSTEM=="memory", ACTION=="add", DEVPATH=="/devices/system/memory/memory[0-9]*", TEST=="state", ATTR{state}="online"
# CPU hotadd
SUBSYSTEM=="cpu", ACTION=="add", DEVPATH=="/devices/system/cpu/cpu[0-9]*", TEST=="online", ATTR{online}="1"
'';
});
systemd = {
packages = [ config.boot.kernelPackages.hyperv-daemons.lib ];
services.hv-vss.unitConfig.ConditionPathExists = [ "/dev/vmbus/hv_vss" ];
targets.hyperv-daemons = {
wantedBy = [ "multi-user.target" ];
};
};
};
}

View file

@ -0,0 +1,71 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.hyperv;
in {
options = {
hyperv = {
baseImageSize = mkOption {
type = with types; either (enum [ "auto" ]) int;
default = "auto";
example = 2048;
description = ''
The size of the hyper-v base image in MiB.
'';
};
vmDerivationName = mkOption {
type = types.str;
default = "nixos-hyperv-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
description = ''
The name of the derivation for the hyper-v appliance.
'';
};
vmFileName = mkOption {
type = types.str;
default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.vhdx";
description = ''
The file name of the hyper-v appliance.
'';
};
};
};
config = {
system.build.hypervImage = import ../../lib/make-disk-image.nix {
name = cfg.vmDerivationName;
postVM = ''
${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -o subformat=dynamic -O vhdx $diskImage $out/${cfg.vmFileName}
rm $diskImage
'';
format = "raw";
diskSize = cfg.baseImageSize;
partitionTableType = "efi";
inherit config lib pkgs;
};
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
boot.growPartition = true;
boot.loader.grub = {
version = 2;
device = "nodev";
efiSupport = true;
efiInstallAsRemovable = true;
};
virtualisation.hypervGuest.enable = true;
};
}

View file

@ -0,0 +1,30 @@
{ config, lib, pkgs, ... }:
{
imports = [
../profiles/qemu-guest.nix
];
config = {
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
autoResize = true;
};
boot.growPartition = true;
boot.kernelParams = [ "console=ttyS0" ];
boot.loader.grub.device = "/dev/vda";
boot.loader.timeout = 0;
services.qemuGuest.enable = true;
services.openssh.enable = true;
services.cloud-init.enable = true;
systemd.services."serial-getty@ttyS0".enable = true;
system.build.kubevirtImage = import ../../lib/make-disk-image.nix {
inherit lib config pkgs;
format = "qcow2";
};
};
}

View file

@ -0,0 +1,86 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.kvmgt;
kernelPackages = config.boot.kernelPackages;
vgpuOptions = {
uuid = mkOption {
type = with types; listOf str;
description = "UUID(s) of VGPU device. You can generate one with <package>libossp_uuid</package>.";
};
};
in {
options = {
virtualisation.kvmgt = {
enable = mkEnableOption ''
KVMGT (iGVT-g) VGPU support. Allows Qemu/KVM guests to share host's Intel integrated graphics card.
Currently only one graphical device can be shared. To allow users to access the device without root add them
to the kvm group: <literal>users.extraUsers.&lt;yourusername&gt;.extraGroups = [ "kvm" ];</literal>
'';
# multi GPU support is under the question
device = mkOption {
type = types.str;
default = "0000:00:02.0";
description = "PCI ID of graphics card. You can figure it with <command>ls /sys/class/mdev_bus</command>.";
};
vgpus = mkOption {
default = {};
type = with types; attrsOf (submodule [ { options = vgpuOptions; } ]);
description = ''
Virtual GPUs to be used in Qemu. You can find devices via <command>ls /sys/bus/pci/devices/*/mdev_supported_types</command>
and find info about device via <command>cat /sys/bus/pci/devices/*/mdev_supported_types/i915-GVTg_V5_4/description</command>
'';
example = {
i915-GVTg_V5_8.uuid = [ "a297db4a-f4c2-11e6-90f6-d3b88d6c9525" ];
};
};
};
};
config = mkIf cfg.enable {
assertions = singleton {
assertion = versionAtLeast kernelPackages.kernel.version "4.16";
message = "KVMGT is not properly supported for kernels older than 4.16";
};
boot.kernelModules = [ "kvmgt" ];
boot.kernelParams = [ "i915.enable_gvt=1" ];
services.udev.extraRules = ''
SUBSYSTEM=="vfio", OWNER="root", GROUP="kvm"
'';
systemd = let
vgpus = listToAttrs (flatten (mapAttrsToList
(mdev: opt: map (id: nameValuePair "kvmgt-${id}" { inherit mdev; uuid = id; }) opt.uuid)
cfg.vgpus));
in {
paths = mapAttrs (_: opt:
{
description = "KVMGT VGPU ${opt.uuid} path";
wantedBy = [ "multi-user.target" ];
pathConfig = {
PathExists = "/sys/bus/pci/devices/${cfg.device}/mdev_supported_types/${opt.mdev}/create";
};
}) vgpus;
services = mapAttrs (_: opt:
{
description = "KVMGT VGPU ${opt.uuid}";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = "${pkgs.runtimeShell} -c 'echo ${opt.uuid} > /sys/bus/pci/devices/${cfg.device}/mdev_supported_types/${opt.mdev}/create'";
ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/bus/pci/devices/${cfg.device}/${opt.uuid}/remove'";
};
}) vgpus;
};
};
meta.maintainers = with maintainers; [ patryk27 ];
}

View file

@ -0,0 +1,413 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.libvirtd;
vswitch = config.virtualisation.vswitch;
configFile = pkgs.writeText "libvirtd.conf" ''
auth_unix_ro = "polkit"
auth_unix_rw = "polkit"
${cfg.extraConfig}
'';
qemuConfigFile = pkgs.writeText "qemu.conf" ''
${optionalString cfg.qemu.ovmf.enable ''
nvram = [ "/run/libvirt/nix-ovmf/AAVMF_CODE.fd:/run/libvirt/nix-ovmf/AAVMF_VARS.fd", "/run/libvirt/nix-ovmf/OVMF_CODE.fd:/run/libvirt/nix-ovmf/OVMF_VARS.fd" ]
''}
${optionalString (!cfg.qemu.runAsRoot) ''
user = "qemu-libvirtd"
group = "qemu-libvirtd"
''}
${cfg.qemu.verbatimConfig}
'';
dirName = "libvirt";
subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
ovmfModule = types.submodule {
options = {
enable = mkOption {
type = types.bool;
default = true;
description = ''
Allows libvirtd to take advantage of OVMF when creating new
QEMU VMs with UEFI boot.
'';
};
# mkRemovedOptionModule does not work in submodules, do it manually
package = mkOption {
type = types.nullOr types.package;
default = null;
internal = true;
};
packages = mkOption {
type = types.listOf types.package;
default = [ pkgs.OVMF.fd ];
defaultText = literalExpression "[ pkgs.OVMF.fd ]";
example = literalExpression "[ pkgs.OVMFFull.fd pkgs.pkgsCross.aarch64-multiplatform.OVMF.fd ]";
description = ''
List of OVMF packages to use. Each listed package must contain files names FV/OVMF_CODE.fd and FV/OVMF_VARS.fd or FV/AAVMF_CODE.fd and FV/AAVMF_VARS.fd
'';
};
};
};
swtpmModule = types.submodule {
options = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Allows libvirtd to use swtpm to create an emulated TPM.
'';
};
package = mkOption {
type = types.package;
default = pkgs.swtpm;
defaultText = literalExpression "pkgs.swtpm";
description = ''
swtpm package to use.
'';
};
};
};
qemuModule = types.submodule {
options = {
package = mkOption {
type = types.package;
default = pkgs.qemu;
defaultText = literalExpression "pkgs.qemu";
description = ''
Qemu package to use with libvirt.
`pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
`pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
'';
};
runAsRoot = mkOption {
type = types.bool;
default = true;
description = ''
If true, libvirtd runs qemu as root.
If false, libvirtd runs qemu as unprivileged user qemu-libvirtd.
Changing this option to false may cause file permission issues
for existing guests. To fix these, manually change ownership
of affected files in /var/lib/libvirt/qemu to qemu-libvirtd.
'';
};
verbatimConfig = mkOption {
type = types.lines;
default = ''
namespaces = []
'';
description = ''
Contents written to the qemu configuration file, qemu.conf.
Make sure to include a proper namespace configuration when
supplying custom configuration.
'';
};
ovmf = mkOption {
type = ovmfModule;
default = { };
description = ''
QEMU's OVMF options.
'';
};
swtpm = mkOption {
type = swtpmModule;
default = { };
description = ''
QEMU's swtpm options.
'';
};
};
};
in
{
imports = [
(mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ]
"Set the option `virtualisation.libvirtd.qemu.package' instead.")
(mkRenamedOptionModule
[ "virtualisation" "libvirtd" "qemuPackage" ]
[ "virtualisation" "libvirtd" "qemu" "package" ])
(mkRenamedOptionModule
[ "virtualisation" "libvirtd" "qemuRunAsRoot" ]
[ "virtualisation" "libvirtd" "qemu" "runAsRoot" ])
(mkRenamedOptionModule
[ "virtualisation" "libvirtd" "qemuVerbatimConfig" ]
[ "virtualisation" "libvirtd" "qemu" "verbatimConfig" ])
(mkRenamedOptionModule
[ "virtualisation" "libvirtd" "qemuOvmf" ]
[ "virtualisation" "libvirtd" "qemu" "ovmf" "enable" ])
(mkRemovedOptionModule
[ "virtualisation" "libvirtd" "qemuOvmfPackage" ]
"If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.")
(mkRenamedOptionModule
[ "virtualisation" "libvirtd" "qemuSwtpm" ]
[ "virtualisation" "libvirtd" "qemu" "swtpm" "enable" ])
];
###### interface
options.virtualisation.libvirtd = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
This option enables libvirtd, a daemon that manages
virtual machines. Users in the "libvirtd" group can interact with
the daemon (e.g. to start or stop VMs) using the
<command>virsh</command> command line tool, among others.
'';
};
package = mkOption {
type = types.package;
default = pkgs.libvirt;
defaultText = literalExpression "pkgs.libvirt";
description = ''
libvirt package to use.
'';
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra contents appended to the libvirtd configuration file,
libvirtd.conf.
'';
};
extraOptions = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "--verbose" ];
description = ''
Extra command line arguments passed to libvirtd on startup.
'';
};
onBoot = mkOption {
type = types.enum [ "start" "ignore" ];
default = "start";
description = ''
Specifies the action to be done to / on the guests when the host boots.
The "start" option starts all guests that were running prior to shutdown
regardless of their autostart settings. The "ignore" option will not
start the formerly running guest on boot. However, any guest marked as
autostart will still be automatically started by libvirtd.
'';
};
onShutdown = mkOption {
type = types.enum [ "shutdown" "suspend" ];
default = "suspend";
description = ''
When shutting down / restarting the host what method should
be used to gracefully halt the guests. Setting to "shutdown"
will cause an ACPI shutdown of each guest. "suspend" will
attempt to save the state of the guests ready to restore on boot.
'';
};
allowedBridges = mkOption {
type = types.listOf types.str;
default = [ "virbr0" ];
description = ''
List of bridge devices that can be used by qemu:///session
'';
};
qemu = mkOption {
type = qemuModule;
default = { };
description = ''
QEMU related options.
'';
};
};
###### implementation
config = mkIf cfg.enable {
assertions = [
{
assertion = config.virtualisation.libvirtd.qemu.ovmf.package == null;
message = ''
The option virtualisation.libvirtd.qemu.ovmf.package is superseded by virtualisation.libvirtd.qemu.ovmf.packages.
If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.
'';
}
{
assertion = config.security.polkit.enable;
message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true').";
}
];
environment = {
# this file is expected in /etc/qemu and not sysconfdir (/var/lib)
etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n"
(e:
"allow ${e}")
cfg.allowedBridges;
systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ];
etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes";
};
boot.kernelModules = [ "tun" ];
users.groups.libvirtd.gid = config.ids.gids.libvirtd;
# libvirtd runs qemu as this user and group by default
users.extraGroups.qemu-libvirtd.gid = config.ids.gids.qemu-libvirtd;
users.extraUsers.qemu-libvirtd = {
uid = config.ids.uids.qemu-libvirtd;
isNormalUser = false;
group = "qemu-libvirtd";
};
security.wrappers.qemu-bridge-helper = {
setuid = true;
owner = "root";
group = "root";
source = "/run/${dirName}/nix-helpers/qemu-bridge-helper";
};
systemd.packages = [ cfg.package ];
systemd.services.libvirtd-config = {
description = "Libvirt Virtual Machine Management Daemon - configuration";
script = ''
# Copy default libvirt network config .xml files to /var/lib
# Files modified by the user will not be overwritten
for i in $(cd ${cfg.package}/var/lib && echo \
libvirt/qemu/networks/*.xml libvirt/qemu/networks/autostart/*.xml \
libvirt/nwfilter/*.xml );
do
mkdir -p /var/lib/$(dirname $i) -m 755
cp -npd ${cfg.package}/var/lib/$i /var/lib/$i
done
# Copy generated qemu config to libvirt directory
cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf
# stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs
for emulator in ${cfg.package}/libexec/libvirt_lxc ${cfg.qemu.package}/bin/qemu-kvm ${cfg.qemu.package}/bin/qemu-system-*; do
ln -s --force "$emulator" /run/${dirName}/nix-emulators/
done
for helper in libexec/qemu-bridge-helper bin/qemu-pr-helper; do
ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/
done
${optionalString cfg.qemu.ovmf.enable (let
ovmfpackage = pkgs.buildEnv {
name = "qemu-ovmf";
paths = cfg.qemu.ovmf.packages;
};
in
''
ln -s --force ${ovmfpackage}/FV/AAVMF_CODE.fd /run/${dirName}/nix-ovmf/
ln -s --force ${ovmfpackage}/FV/OVMF_CODE.fd /run/${dirName}/nix-ovmf/
ln -s --force ${ovmfpackage}/FV/AAVMF_VARS.fd /run/${dirName}/nix-ovmf/
ln -s --force ${ovmfpackage}/FV/OVMF_VARS.fd /run/${dirName}/nix-ovmf/
'')}
'';
serviceConfig = {
Type = "oneshot";
RuntimeDirectoryPreserve = "yes";
LogsDirectory = subDirs [ "qemu" ];
RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ];
StateDirectory = subDirs [ "dnsmasq" ];
};
};
systemd.services.libvirtd = {
requires = [ "libvirtd-config.service" ];
after = [ "libvirtd-config.service" ]
++ optional vswitch.enable "ovs-vswitchd.service";
environment.LIBVIRTD_ARGS = escapeShellArgs (
[
"--config"
configFile
"--timeout"
"120" # from ${libvirt}/var/lib/sysconfig/libvirtd
] ++ cfg.extraOptions
);
path = [ cfg.qemu.package ] # libvirtd requires qemu-img to manage disk images
++ optional vswitch.enable vswitch.package
++ optional cfg.qemu.swtpm.enable cfg.qemu.swtpm.package;
serviceConfig = {
Type = "notify";
KillMode = "process"; # when stopping, leave the VMs alone
Restart = "no";
};
restartIfChanged = false;
};
systemd.services.virtchd = {
path = [ pkgs.cloud-hypervisor ];
};
systemd.services.libvirt-guests = {
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ coreutils gawk cfg.package ];
restartIfChanged = false;
environment.ON_BOOT = "${cfg.onBoot}";
environment.ON_SHUTDOWN = "${cfg.onShutdown}";
};
systemd.sockets.virtlogd = {
description = "Virtual machine log manager socket";
wantedBy = [ "sockets.target" ];
listenStreams = [ "/run/${dirName}/virtlogd-sock" ];
};
systemd.services.virtlogd = {
description = "Virtual machine log manager";
serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlogd virtlogd";
restartIfChanged = false;
};
systemd.sockets.virtlockd = {
description = "Virtual machine lock manager socket";
wantedBy = [ "sockets.target" ];
listenStreams = [ "/run/${dirName}/virtlockd-sock" ];
};
systemd.services.virtlockd = {
description = "Virtual machine lock manager";
serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlockd virtlockd";
restartIfChanged = false;
};
# https://libvirt.org/daemons.html#monolithic-systemd-integration
systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ];
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (action.id == "org.libvirt.unix.manage" &&
subject.isInGroup("libvirtd")) {
return polkit.Result.YES;
}
});
'';
};
}

View file

@ -0,0 +1,174 @@
{ lib, config, pkgs, ... }:
with lib;
let
templateSubmodule = { ... }: {
options = {
enable = mkEnableOption "this template";
target = mkOption {
description = "Path in the container";
type = types.path;
};
template = mkOption {
description = ".tpl file for rendering the target";
type = types.path;
};
when = mkOption {
description = "Events which trigger a rewrite (create, copy)";
type = types.listOf (types.str);
};
properties = mkOption {
description = "Additional properties";
type = types.attrs;
default = {};
};
};
};
toYAML = name: data: pkgs.writeText name (generators.toYAML {} data);
cfg = config.virtualisation.lxc;
templates = if cfg.templates != {} then let
list = mapAttrsToList (name: value: { inherit name; } // value)
(filterAttrs (name: value: value.enable) cfg.templates);
in
{
files = map (tpl: {
source = tpl.template;
target = "/templates/${tpl.name}.tpl";
}) list;
properties = listToAttrs (map (tpl: nameValuePair tpl.target {
when = tpl.when;
template = "${tpl.name}.tpl";
properties = tpl.properties;
}) list);
}
else { files = []; properties = {}; };
in
{
imports = [
../installer/cd-dvd/channel.nix
../profiles/minimal.nix
../profiles/clone-config.nix
];
options = {
virtualisation.lxc = {
templates = mkOption {
description = "Templates for LXD";
type = types.attrsOf (types.submodule (templateSubmodule));
default = {};
example = literalExpression ''
{
# create /etc/hostname on container creation
"hostname" = {
enable = true;
target = "/etc/hostname";
template = builtins.writeFile "hostname.tpl" "{{ container.name }}";
when = [ "create" ];
};
# create /etc/nixos/hostname.nix with a configuration for keeping the hostname applied
"hostname-nix" = {
enable = true;
target = "/etc/nixos/hostname.nix";
template = builtins.writeFile "hostname-nix.tpl" "{ ... }: { networking.hostName = "{{ container.name }}"; }";
# copy keeps the file updated when the container is changed
when = [ "create" "copy" ];
};
# copy allow the user to specify a custom configuration.nix
"configuration-nix" = {
enable = true;
target = "/etc/nixos/configuration.nix";
template = builtins.writeFile "configuration-nix" "{{ config_get(\"user.user-data\", properties.default) }}";
when = [ "create" ];
};
};
'';
};
};
};
config = {
boot.isContainer = true;
boot.postBootCommands =
''
# After booting, register the contents of the Nix store in the Nix
# database.
if [ -f /nix-path-registration ]; then
${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration &&
rm /nix-path-registration
fi
# nixos-rebuild also requires a "system" profile
${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
'';
system.build.metadata = pkgs.callPackage ../../lib/make-system-tarball.nix {
contents = [
{
source = toYAML "metadata.yaml" {
architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.system)) 0;
creation_date = 1;
properties = {
description = "NixOS ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.system}";
os = "nixos";
release = "${config.system.nixos.codeName}";
};
templates = templates.properties;
};
target = "/metadata.yaml";
}
] ++ templates.files;
};
# TODO: build rootfs as squashfs for faster unpack
system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
extraArgs = "--owner=0";
storeContents = [
{
object = config.system.build.toplevel;
symlink = "none";
}
];
contents = [
{
source = config.system.build.toplevel + "/init";
target = "/sbin/init";
}
];
extraCommands = "mkdir -p proc sys dev";
};
# Add the overrides from lxd distrobuilder
systemd.extraConfig = ''
[Service]
ProtectProc=default
ProtectControlGroups=no
ProtectKernelTunables=no
'';
# Allow the user to login as root without password.
users.users.root.initialHashedPassword = mkOverride 150 "";
system.activationScripts.installInitScript = mkForce ''
ln -fs $systemConfig/init /sbin/init
'';
# Some more help text.
services.getty.helpLine =
''
Log in as "root" with an empty password.
'';
# Containers should be light-weight, so start sshd on demand.
services.openssh.enable = mkDefault true;
services.openssh.startWhenNeeded = mkDefault true;
};
}

View file

@ -0,0 +1,86 @@
# LXC Configuration
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.lxc;
in
{
###### interface
options.virtualisation.lxc = {
enable =
mkOption {
type = types.bool;
default = false;
description =
''
This enables Linux Containers (LXC), which provides tools
for creating and managing system or application containers
on Linux.
'';
};
systemConfig =
mkOption {
type = types.lines;
default = "";
description =
''
This is the system-wide LXC config. See
<citerefentry><refentrytitle>lxc.system.conf</refentrytitle>
<manvolnum>5</manvolnum></citerefentry>.
'';
};
defaultConfig =
mkOption {
type = types.lines;
default = "";
description =
''
Default config (default.conf) for new containers, i.e. for
network config. See <citerefentry><refentrytitle>lxc.container.conf
</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
'';
};
usernetConfig =
mkOption {
type = types.lines;
default = "";
description =
''
This is the config file for managing unprivileged user network
administration access in LXC. See <citerefentry>
<refentrytitle>lxc-usernet</refentrytitle><manvolnum>5</manvolnum>
</citerefentry>.
'';
};
};
###### implementation
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.lxc ];
environment.etc."lxc/lxc.conf".text = cfg.systemConfig;
environment.etc."lxc/lxc-usernet".text = cfg.usernetConfig;
environment.etc."lxc/default.conf".text = cfg.defaultConfig;
systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
security.apparmor.packages = [ pkgs.lxc ];
security.apparmor.policies = {
"bin.lxc-start".profile = ''
include ${pkgs.lxc}/etc/apparmor.d/usr.bin.lxc-start
'';
"lxc-containers".profile = ''
include ${pkgs.lxc}/etc/apparmor.d/lxc-containers
'';
};
};
}

View file

@ -0,0 +1,45 @@
# LXC Configuration
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.lxc.lxcfs;
in {
meta.maintainers = [ maintainers.mic92 ];
###### interface
options.virtualisation.lxc.lxcfs = {
enable =
mkOption {
type = types.bool;
default = false;
description = ''
This enables LXCFS, a FUSE filesystem for LXC.
To use lxcfs in include the following configuration in your
container configuration:
<code>
virtualisation.lxc.defaultConfig = "lxc.include = ''${pkgs.lxcfs}/share/lxc/config/common.conf.d/00-lxcfs.conf";
</code>
'';
};
};
###### implementation
config = mkIf cfg.enable {
systemd.services.lxcfs = {
description = "FUSE filesystem for LXC";
wantedBy = [ "multi-user.target" ];
before = [ "lxc.service" ];
restartIfChanged = false;
serviceConfig = {
ExecStartPre="${pkgs.coreutils}/bin/mkdir -p /var/lib/lxcfs";
ExecStart="${pkgs.lxcfs}/bin/lxcfs /var/lib/lxcfs";
ExecStopPost="-${pkgs.fuse}/bin/fusermount -u /var/lib/lxcfs";
KillMode="process";
Restart="on-failure";
};
};
};
}

View file

@ -0,0 +1,182 @@
# Systemd services for lxd.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.lxd;
in {
imports = [
(mkRemovedOptionModule [ "virtualisation" "lxd" "zfsPackage" ] "Override zfs in an overlay instead to override it globally")
];
###### interface
options = {
virtualisation.lxd = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
This option enables lxd, a daemon that manages
containers. Users in the "lxd" group can interact with
the daemon (e.g. to start or stop containers) using the
<command>lxc</command> command line tool, among others.
Most of the time, you'll also want to start lxcfs, so
that containers can "see" the limits:
<code>
virtualisation.lxc.lxcfs.enable = true;
</code>
'';
};
package = mkOption {
type = types.package;
default = pkgs.lxd;
defaultText = literalExpression "pkgs.lxd";
description = ''
The LXD package to use.
'';
};
lxcPackage = mkOption {
type = types.package;
default = pkgs.lxc;
defaultText = literalExpression "pkgs.lxc";
description = ''
The LXC package to use with LXD (required for AppArmor profiles).
'';
};
zfsSupport = mkOption {
type = types.bool;
default = config.boot.zfs.enabled;
defaultText = literalExpression "config.boot.zfs.enabled";
description = ''
Enables lxd to use zfs as a storage for containers.
This option is enabled by default if a zfs pool is configured
with nixos.
'';
};
recommendedSysctlSettings = mkOption {
type = types.bool;
default = false;
description = ''
Enables various settings to avoid common pitfalls when
running containers requiring many file operations.
Fixes errors like "Too many open files" or
"neighbour: ndisc_cache: neighbor table overflow!".
See https://lxd.readthedocs.io/en/latest/production-setup/
for details.
'';
};
startTimeout = mkOption {
type = types.int;
default = 600;
apply = toString;
description = ''
Time to wait (in seconds) for LXD to become ready to process requests.
If LXD does not reply within the configured time, lxd.service will be
considered failed and systemd will attempt to restart it.
'';
};
};
};
###### implementation
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
# Note: the following options are also declared in virtualisation.lxc, but
# the latter can't be simply enabled to reuse the formers, because it
# does a bunch of unrelated things.
systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
security.apparmor = {
packages = [ cfg.lxcPackage ];
policies = {
"bin.lxc-start".profile = ''
include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
'';
"lxc-containers".profile = ''
include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
'';
};
};
# TODO: remove once LXD gets proper support for cgroupsv2
# (currently most of the e.g. CPU accounting stuff doesn't work)
systemd.enableUnifiedCgroupHierarchy = false;
systemd.sockets.lxd = {
description = "LXD UNIX socket";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = "/var/lib/lxd/unix.socket";
SocketMode = "0660";
SocketGroup = "lxd";
Service = "lxd.service";
};
};
systemd.services.lxd = {
description = "LXD Container Management Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" "lxcfs.service" ];
requires = [ "network-online.target" "lxd.socket" "lxcfs.service" ];
documentation = [ "man:lxd(1)" ];
path = optional cfg.zfsSupport config.boot.zfs.package;
serviceConfig = {
ExecStart = "@${cfg.package}/bin/lxd lxd --group lxd";
ExecStartPost = "${cfg.package}/bin/lxd waitready --timeout=${cfg.startTimeout}";
ExecStop = "${cfg.package}/bin/lxd shutdown";
KillMode = "process"; # when stopping, leave the containers alone
LimitMEMLOCK = "infinity";
LimitNOFILE = "1048576";
LimitNPROC = "infinity";
TasksMax = "infinity";
Restart = "on-failure";
TimeoutStartSec = "${cfg.startTimeout}s";
TimeoutStopSec = "30s";
# By default, `lxd` loads configuration files from hard-coded
# `/usr/share/lxc/config` - since this is a no-go for us, we have to
# explicitly tell it where the actual configuration files are
Environment = mkIf (config.virtualisation.lxc.lxcfs.enable)
"LXD_LXC_TEMPLATE_CONFIG=${pkgs.lxcfs}/share/lxc/config";
};
};
users.groups.lxd = {};
users.users.root = {
subUidRanges = [ { startUid = 1000000; count = 65536; } ];
subGidRanges = [ { startGid = 1000000; count = 65536; } ];
};
boot.kernel.sysctl = mkIf cfg.recommendedSysctlSettings {
"fs.inotify.max_queued_events" = 1048576;
"fs.inotify.max_user_instances" = 1048576;
"fs.inotify.max_user_watches" = 1048576;
"vm.max_map_count" = 262144;
"kernel.dmesg_restrict" = 1;
"net.ipv4.neigh.default.gc_thresh3" = 8192;
"net.ipv6.neigh.default.gc_thresh3" = 8192;
"kernel.keys.maxkeys" = 2000;
};
boot.kernelModules = [ "veth" "xt_comment" "xt_CHECKSUM" "xt_MASQUERADE" ]
++ optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
};
}

View file

@ -0,0 +1,881 @@
{ config, lib, pkgs, ... }:
with lib;
let
configurationPrefix = optionalString (versionAtLeast config.system.stateVersion "22.05") "nixos-";
configurationDirectoryName = "${configurationPrefix}containers";
configurationDirectory = "/etc/${configurationDirectoryName}";
stateDirectory = "/var/lib/${configurationPrefix}containers";
# The container's init script, a small wrapper around the regular
# NixOS stage-2 init script.
containerInit = (cfg:
let
renderExtraVeth = (name: cfg:
''
echo "Bringing ${name} up"
ip link set dev ${name} up
${optionalString (cfg.localAddress != null) ''
echo "Setting ip for ${name}"
ip addr add ${cfg.localAddress} dev ${name}
''}
${optionalString (cfg.localAddress6 != null) ''
echo "Setting ip6 for ${name}"
ip -6 addr add ${cfg.localAddress6} dev ${name}
''}
${optionalString (cfg.hostAddress != null) ''
echo "Setting route to host for ${name}"
ip route add ${cfg.hostAddress} dev ${name}
''}
${optionalString (cfg.hostAddress6 != null) ''
echo "Setting route6 to host for ${name}"
ip -6 route add ${cfg.hostAddress6} dev ${name}
''}
''
);
in
pkgs.writeScript "container-init"
''
#! ${pkgs.runtimeShell} -e
# Exit early if we're asked to shut down.
trap "exit 0" SIGRTMIN+3
# Initialise the container side of the veth pair.
if [ -n "$HOST_ADDRESS" ] || [ -n "$HOST_ADDRESS6" ] ||
[ -n "$LOCAL_ADDRESS" ] || [ -n "$LOCAL_ADDRESS6" ] ||
[ -n "$HOST_BRIDGE" ]; then
ip link set host0 name eth0
ip link set dev eth0 up
if [ -n "$LOCAL_ADDRESS" ]; then
ip addr add $LOCAL_ADDRESS dev eth0
fi
if [ -n "$LOCAL_ADDRESS6" ]; then
ip -6 addr add $LOCAL_ADDRESS6 dev eth0
fi
if [ -n "$HOST_ADDRESS" ]; then
ip route add $HOST_ADDRESS dev eth0
ip route add default via $HOST_ADDRESS
fi
if [ -n "$HOST_ADDRESS6" ]; then
ip -6 route add $HOST_ADDRESS6 dev eth0
ip -6 route add default via $HOST_ADDRESS6
fi
fi
${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
# Start the regular stage 2 script.
# We source instead of exec to not lose an early stop signal, which is
# also the only _reliable_ shutdown signal we have since early stop
# does not execute ExecStop* commands.
set +e
. "$1"
''
);
nspawnExtraVethArgs = (name: cfg: "--network-veth-extra=${name}");
startScript = cfg:
''
mkdir -p -m 0755 "$root/etc" "$root/var/lib"
mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/nixos-containers
if ! [ -e "$root/etc/os-release" ]; then
touch "$root/etc/os-release"
fi
if ! [ -e "$root/etc/machine-id" ]; then
touch "$root/etc/machine-id"
fi
mkdir -p -m 0755 \
"/nix/var/nix/profiles/per-container/$INSTANCE" \
"/nix/var/nix/gcroots/per-container/$INSTANCE"
cp --remove-destination /etc/resolv.conf "$root/etc/resolv.conf"
if [ "$PRIVATE_NETWORK" = 1 ]; then
extraFlags+=" --private-network"
fi
if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] ||
[ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then
extraFlags+=" --network-veth"
fi
if [ -n "$HOST_PORT" ]; then
OIFS=$IFS
IFS=","
for i in $HOST_PORT
do
extraFlags+=" --port=$i"
done
IFS=$OIFS
fi
if [ -n "$HOST_BRIDGE" ]; then
extraFlags+=" --network-bridge=$HOST_BRIDGE"
fi
extraFlags+=" ${concatStringsSep " " (mapAttrsToList nspawnExtraVethArgs cfg.extraVeths)}"
for iface in $INTERFACES; do
extraFlags+=" --network-interface=$iface"
done
for iface in $MACVLANS; do
extraFlags+=" --network-macvlan=$iface"
done
# If the host is 64-bit and the container is 32-bit, add a
# --personality flag.
${optionalString (config.nixpkgs.localSystem.system == "x86_64-linux") ''
if [ "$(< ''${SYSTEM_PATH:-/nix/var/nix/profiles/per-container/$INSTANCE/system}/system)" = i686-linux ]; then
extraFlags+=" --personality=x86"
fi
''}
# Run systemd-nspawn without startup notification (we'll
# wait for the container systemd to signal readiness)
# Kill signal handling means systemd-nspawn will pass a system-halt signal
# to the container systemd when it receives SIGTERM for container shutdown;
# containerInit and stage2 have to handle this as well.
exec ${config.systemd.package}/bin/systemd-nspawn \
--keep-unit \
-M "$INSTANCE" -D "$root" $extraFlags \
$EXTRA_NSPAWN_FLAGS \
--notify-ready=yes \
--kill-signal=SIGRTMIN+3 \
--bind-ro=/nix/store \
--bind-ro=/nix/var/nix/db \
--bind-ro=/nix/var/nix/daemon-socket \
--bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \
--bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \
${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \
--setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
--setenv HOST_BRIDGE="$HOST_BRIDGE" \
--setenv HOST_ADDRESS="$HOST_ADDRESS" \
--setenv LOCAL_ADDRESS="$LOCAL_ADDRESS" \
--setenv HOST_ADDRESS6="$HOST_ADDRESS6" \
--setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \
--setenv HOST_PORT="$HOST_PORT" \
--setenv PATH="$PATH" \
${optionalString cfg.ephemeral "--ephemeral"} \
${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then
''--capability="${concatStringsSep "," cfg.additionalCapabilities}"'' else ""
} \
${if cfg.tmpfs != null && cfg.tmpfs != [] then
''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}'' else ""
} \
${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init"
'';
preStartScript = cfg:
''
# Clean up existing machined registration and interfaces.
machinectl terminate "$INSTANCE" 2> /dev/null || true
if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] ||
[ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then
ip link del dev "ve-$INSTANCE" 2> /dev/null || true
ip link del dev "vb-$INSTANCE" 2> /dev/null || true
fi
${concatStringsSep "\n" (
mapAttrsToList (name: cfg:
"ip link del dev ${name} 2> /dev/null || true "
) cfg.extraVeths
)}
'';
postStartScript = (cfg:
let
ipcall = cfg: ipcmd: variable: attribute:
if cfg.${attribute} == null then
''
if [ -n "${variable}" ]; then
${ipcmd} add ${variable} dev $ifaceHost
fi
''
else
"${ipcmd} add ${cfg.${attribute}} dev $ifaceHost";
renderExtraVeth = name: cfg:
if cfg.hostBridge != null then
''
# Add ${name} to bridge ${cfg.hostBridge}
ip link set dev ${name} master ${cfg.hostBridge} up
''
else
''
echo "Bring ${name} up"
ip link set dev ${name} up
# Set IPs and routes for ${name}
${optionalString (cfg.hostAddress != null) ''
ip addr add ${cfg.hostAddress} dev ${name}
''}
${optionalString (cfg.hostAddress6 != null) ''
ip -6 addr add ${cfg.hostAddress6} dev ${name}
''}
${optionalString (cfg.localAddress != null) ''
ip route add ${cfg.localAddress} dev ${name}
''}
${optionalString (cfg.localAddress6 != null) ''
ip -6 route add ${cfg.localAddress6} dev ${name}
''}
'';
in
''
if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] ||
[ -n "$HOST_ADDRESS6" ] || [ -n "$LOCAL_ADDRESS6" ]; then
if [ -z "$HOST_BRIDGE" ]; then
ifaceHost=ve-$INSTANCE
ip link set dev $ifaceHost up
${ipcall cfg "ip addr" "$HOST_ADDRESS" "hostAddress"}
${ipcall cfg "ip -6 addr" "$HOST_ADDRESS6" "hostAddress6"}
${ipcall cfg "ip route" "$LOCAL_ADDRESS" "localAddress"}
${ipcall cfg "ip -6 route" "$LOCAL_ADDRESS6" "localAddress6"}
fi
fi
${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
''
);
serviceDirectives = cfg: {
ExecReload = pkgs.writeScript "reload-container"
''
#! ${pkgs.runtimeShell} -e
${pkgs.nixos-container}/bin/nixos-container run "$INSTANCE" -- \
bash --login -c "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/bin/switch-to-configuration test"
'';
SyslogIdentifier = "container %i";
EnvironmentFile = "-${configurationDirectory}/%i.conf";
Type = "notify";
RuntimeDirectory = lib.optional cfg.ephemeral "${configurationDirectoryName}/%i";
# Note that on reboot, systemd-nspawn returns 133, so this
# unit will be restarted. On poweroff, it returns 0, so the
# unit won't be restarted.
RestartForceExitStatus = "133";
SuccessExitStatus = "133";
# Some containers take long to start
# especially when you automatically start many at once
TimeoutStartSec = cfg.timeoutStartSec;
Restart = "on-failure";
Slice = "machine.slice";
Delegate = true;
# We rely on systemd-nspawn turning a SIGTERM to itself into a shutdown
# signal (SIGRTMIN+3) for the inner container.
KillMode = "mixed";
KillSignal = "TERM";
DevicePolicy = "closed";
DeviceAllow = map (d: "${d.node} ${d.modifier}") cfg.allowedDevices;
};
inherit (config.nixpkgs) localSystem;
kernelVersion = config.boot.kernelPackages.kernel.version;
bindMountOpts = { name, ... }: {
options = {
mountPoint = mkOption {
example = "/mnt/usb";
type = types.str;
description = "Mount point on the container file system.";
};
hostPath = mkOption {
default = null;
example = "/home/alice";
type = types.nullOr types.str;
description = "Location of the host path to be mounted.";
};
isReadOnly = mkOption {
default = true;
type = types.bool;
description = "Determine whether the mounted path will be accessed in read-only mode.";
};
};
config = {
mountPoint = mkDefault name;
};
};
allowedDeviceOpts = { ... }: {
options = {
node = mkOption {
example = "/dev/net/tun";
type = types.str;
description = "Path to device node";
};
modifier = mkOption {
example = "rw";
type = types.str;
description = ''
Device node access modifier. Takes a combination
<literal>r</literal> (read), <literal>w</literal> (write), and
<literal>m</literal> (mknod). See the
<literal>systemd.resource-control(5)</literal> man page for more
information.'';
};
};
};
mkBindFlag = d:
let flagPrefix = if d.isReadOnly then " --bind-ro=" else " --bind=";
mountstr = if d.hostPath != null then "${d.hostPath}:${d.mountPoint}" else "${d.mountPoint}";
in flagPrefix + mountstr ;
mkBindFlags = bs: concatMapStrings mkBindFlag (lib.attrValues bs);
networkOptions = {
hostBridge = mkOption {
type = types.nullOr types.str;
default = null;
example = "br0";
description = ''
Put the host-side of the veth-pair into the named bridge.
Only one of hostAddress* or hostBridge can be given.
'';
};
forwardPorts = mkOption {
type = types.listOf (types.submodule {
options = {
protocol = mkOption {
type = types.str;
default = "tcp";
description = "The protocol specifier for port forwarding between host and container";
};
hostPort = mkOption {
type = types.int;
description = "Source port of the external interface on host";
};
containerPort = mkOption {
type = types.nullOr types.int;
default = null;
description = "Target port of container";
};
};
});
default = [];
example = [ { protocol = "tcp"; hostPort = 8080; containerPort = 80; } ];
description = ''
List of forwarded ports from host to container. Each forwarded port
is specified by protocol, hostPort and containerPort. By default,
protocol is tcp and hostPort and containerPort are assumed to be
the same if containerPort is not explicitly given.
'';
};
hostAddress = mkOption {
type = types.nullOr types.str;
default = null;
example = "10.231.136.1";
description = ''
The IPv4 address assigned to the host interface.
(Not used when hostBridge is set.)
'';
};
hostAddress6 = mkOption {
type = types.nullOr types.str;
default = null;
example = "fc00::1";
description = ''
The IPv6 address assigned to the host interface.
(Not used when hostBridge is set.)
'';
};
localAddress = mkOption {
type = types.nullOr types.str;
default = null;
example = "10.231.136.2";
description = ''
The IPv4 address assigned to the interface in the container.
If a hostBridge is used, this should be given with netmask to access
the whole network. Otherwise the default netmask is /32 and routing is
set up from localAddress to hostAddress and back.
'';
};
localAddress6 = mkOption {
type = types.nullOr types.str;
default = null;
example = "fc00::2";
description = ''
The IPv6 address assigned to the interface in the container.
If a hostBridge is used, this should be given with netmask to access
the whole network. Otherwise the default netmask is /128 and routing is
set up from localAddress6 to hostAddress6 and back.
'';
};
};
dummyConfig =
{
extraVeths = {};
additionalCapabilities = [];
ephemeral = false;
timeoutStartSec = "1min";
allowedDevices = [];
hostAddress = null;
hostAddress6 = null;
localAddress = null;
localAddress6 = null;
tmpfs = null;
};
in
{
options = {
boot.isContainer = mkOption {
type = types.bool;
default = false;
description = ''
Whether this NixOS machine is a lightweight container running
in another NixOS system.
'';
};
boot.enableContainers = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable support for NixOS containers. Defaults to true
(at no cost if containers are not actually used).
'';
};
containers = mkOption {
type = types.attrsOf (types.submodule (
{ config, options, name, ... }:
{
options = {
config = mkOption {
description = ''
A specification of the desired configuration of this
container, as a NixOS module.
'';
type = lib.mkOptionType {
name = "Toplevel NixOS config";
merge = loc: defs: (import "${toString config.nixpkgs}/nixos/lib/eval-config.nix" {
modules =
let
extraConfig = {
_file = "module at ${__curPos.file}:${toString __curPos.line}";
config = {
nixpkgs = { inherit localSystem; };
boot.isContainer = true;
networking.hostName = mkDefault name;
networking.useDHCP = false;
assertions = [
{
assertion =
(builtins.compareVersions kernelVersion "5.8" <= 0)
-> config.privateNetwork
-> stringLength name <= 11;
message = ''
Container name `${name}` is too long: When `privateNetwork` is enabled, container names can
not be longer than 11 characters, because the container's interface name is derived from it.
You should either make the container name shorter or upgrade to a more recent kernel that
supports interface altnames (i.e. at least Linux 5.8 - please see https://github.com/NixOS/nixpkgs/issues/38509
for details).
'';
}
];
};
};
in [ extraConfig ] ++ (map (x: x.value) defs);
prefix = [ "containers" name ];
}).config;
};
};
path = mkOption {
type = types.path;
example = "/nix/var/nix/profiles/per-container/webserver";
description = ''
As an alternative to specifying
<option>config</option>, you can specify the path to
the evaluated NixOS system configuration, typically a
symlink to a system profile.
'';
};
additionalCapabilities = mkOption {
type = types.listOf types.str;
default = [];
example = [ "CAP_NET_ADMIN" "CAP_MKNOD" ];
description = ''
Grant additional capabilities to the container. See the
capabilities(7) and systemd-nspawn(1) man pages for more
information.
'';
};
nixpkgs = mkOption {
type = types.path;
default = pkgs.path;
defaultText = literalExpression "pkgs.path";
description = ''
A path to the nixpkgs that provide the modules, pkgs and lib for evaluating the container.
To only change the <literal>pkgs</literal> argument used inside the container modules,
set the <literal>nixpkgs.*</literal> options in the container <option>config</option>.
Setting <literal>config.nixpkgs.pkgs = pkgs</literal> speeds up the container evaluation
by reusing the system pkgs, but the <literal>nixpkgs.config</literal> option in the
container config is ignored in this case.
'';
};
ephemeral = mkOption {
type = types.bool;
default = false;
description = ''
Runs container in ephemeral mode with the empty root filesystem at boot.
This way container will be bootstrapped from scratch on each boot
and will be cleaned up on shutdown leaving no traces behind.
Useful for completely stateless, reproducible containers.
Note that this option might require to do some adjustments to the container configuration,
e.g. you might want to set
<varname>systemd.network.networks.$interface.dhcpV4Config.ClientIdentifier</varname> to "mac"
if you use <varname>macvlans</varname> option.
This way dhcp client identifier will be stable between the container restarts.
Note that the container journal will not be linked to the host if this option is enabled.
'';
};
enableTun = mkOption {
type = types.bool;
default = false;
description = ''
Allows the container to create and setup tunnel interfaces
by granting the <literal>NET_ADMIN</literal> capability and
enabling access to <literal>/dev/net/tun</literal>.
'';
};
privateNetwork = mkOption {
type = types.bool;
default = false;
description = ''
Whether to give the container its own private virtual
Ethernet interface. The interface is called
<literal>eth0</literal>, and is hooked up to the interface
<literal>ve-<replaceable>container-name</replaceable></literal>
on the host. If this option is not set, then the
container shares the network interfaces of the host,
and can bind to any port on any interface.
'';
};
interfaces = mkOption {
type = types.listOf types.str;
default = [];
example = [ "eth1" "eth2" ];
description = ''
The list of interfaces to be moved into the container.
'';
};
macvlans = mkOption {
type = types.listOf types.str;
default = [];
example = [ "eth1" "eth2" ];
description = ''
The list of host interfaces from which macvlans will be
created. For each interface specified, a macvlan interface
will be created and moved to the container.
'';
};
extraVeths = mkOption {
type = with types; attrsOf (submodule { options = networkOptions; });
default = {};
description = ''
Extra veth-pairs to be created for the container.
'';
};
autoStart = mkOption {
type = types.bool;
default = false;
description = ''
Whether the container is automatically started at boot-time.
'';
};
timeoutStartSec = mkOption {
type = types.str;
default = "1min";
description = ''
Time for the container to start. In case of a timeout,
the container processes get killed.
See <citerefentry><refentrytitle>systemd.time</refentrytitle>
<manvolnum>7</manvolnum></citerefentry>
for more information about the format.
'';
};
bindMounts = mkOption {
type = with types; attrsOf (submodule bindMountOpts);
default = {};
example = literalExpression ''
{ "/home" = { hostPath = "/home/alice";
isReadOnly = false; };
}
'';
description =
''
An extra list of directories that is bound to the container.
'';
};
allowedDevices = mkOption {
type = with types; listOf (submodule allowedDeviceOpts);
default = [];
example = [ { node = "/dev/net/tun"; modifier = "rw"; } ];
description = ''
A list of device nodes to which the containers has access to.
'';
};
tmpfs = mkOption {
type = types.listOf types.str;
default = [];
example = [ "/var" ];
description = ''
Mounts a set of tmpfs file systems into the container.
Multiple paths can be specified.
Valid items must conform to the --tmpfs argument
of systemd-nspawn. See systemd-nspawn(1) for details.
'';
};
extraFlags = mkOption {
type = types.listOf types.str;
default = [];
example = [ "--drop-capability=CAP_SYS_CHROOT" ];
description = ''
Extra flags passed to the systemd-nspawn command.
See systemd-nspawn(1) for details.
'';
};
# Removed option. See `checkAssertion` below for the accompanying error message.
pkgs = mkOption { visible = false; };
} // networkOptions;
config = let
# Throw an error when removed option `pkgs` is used.
# Because this is a submodule we cannot use `mkRemovedOptionModule` or option `assertions`.
optionPath = "containers.${name}.pkgs";
files = showFiles options.pkgs.files;
checkAssertion = if options.pkgs.isDefined then throw ''
The option definition `${optionPath}' in ${files} no longer has any effect; please remove it.
Alternatively, you can use the following options:
- containers.${name}.nixpkgs
This sets the nixpkgs (and thereby the modules, pkgs and lib) that
are used for evaluating the container.
- containers.${name}.config.nixpkgs.pkgs
This only sets the `pkgs` argument used inside the container modules.
''
else null;
in {
path = builtins.seq checkAssertion
mkIf options.config.isDefined config.config.system.build.toplevel;
};
}));
default = {};
example = literalExpression
''
{ webserver =
{ path = "/nix/var/nix/profiles/webserver";
};
database =
{ config =
{ config, pkgs, ... }:
{ services.postgresql.enable = true;
services.postgresql.package = pkgs.postgresql_10;
system.stateVersion = "21.05";
};
};
}
'';
description = ''
A set of NixOS system configurations to be run as lightweight
containers. Each container appears as a service
<literal>container-<replaceable>name</replaceable></literal>
on the host system, allowing it to be started and stopped via
<command>systemctl</command>.
'';
};
};
config = mkIf (config.boot.enableContainers) (let
warnings = flatten [
(optional (config.virtualisation.containers.enable && versionOlder config.system.stateVersion "22.05") ''
Enabling both boot.enableContainers & virtualisation.containers on system.stateVersion < 22.05 is unsupported.
'')
];
unit = {
description = "Container '%i'";
unitConfig.RequiresMountsFor = "${stateDirectory}/%i";
path = [ pkgs.iproute2 ];
environment = {
root = "${stateDirectory}/%i";
INSTANCE = "%i";
};
preStart = preStartScript dummyConfig;
script = startScript dummyConfig;
postStart = postStartScript dummyConfig;
restartIfChanged = false;
serviceConfig = serviceDirectives dummyConfig;
};
in {
systemd.targets.multi-user.wants = [ "machines.target" ];
systemd.services = listToAttrs (filter (x: x.value != null) (
# The generic container template used by imperative containers
[{ name = "container@"; value = unit; }]
# declarative containers
++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let
containerConfig = cfg // (
if cfg.enableTun then
{
allowedDevices = cfg.allowedDevices
++ [ { node = "/dev/net/tun"; modifier = "rw"; } ];
additionalCapabilities = cfg.additionalCapabilities
++ [ "CAP_NET_ADMIN" ];
}
else {});
in
recursiveUpdate unit {
preStart = preStartScript containerConfig;
script = startScript containerConfig;
postStart = postStartScript containerConfig;
serviceConfig = serviceDirectives containerConfig;
unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "${stateDirectory}/%i";
environment.root = if containerConfig.ephemeral then "/run/nixos-containers/%i" else "${stateDirectory}/%i";
} // (
if containerConfig.autoStart then
{
wantedBy = [ "machines.target" ];
wants = [ "network.target" ];
after = [ "network.target" ];
restartTriggers = [
containerConfig.path
config.environment.etc."${configurationDirectoryName}/${name}.conf".source
];
restartIfChanged = true;
}
else {})
)) config.containers)
));
# Generate a configuration file in /etc/nixos-containers for each
# container so that container@.target can get the container
# configuration.
environment.etc =
let mkPortStr = p: p.protocol + ":" + (toString p.hostPort) + ":" + (if p.containerPort == null then toString p.hostPort else toString p.containerPort);
in mapAttrs' (name: cfg: nameValuePair "${configurationDirectoryName}/${name}.conf"
{ text =
''
SYSTEM_PATH=${cfg.path}
${optionalString cfg.privateNetwork ''
PRIVATE_NETWORK=1
${optionalString (cfg.hostBridge != null) ''
HOST_BRIDGE=${cfg.hostBridge}
''}
${optionalString (length cfg.forwardPorts > 0) ''
HOST_PORT=${concatStringsSep "," (map mkPortStr cfg.forwardPorts)}
''}
${optionalString (cfg.hostAddress != null) ''
HOST_ADDRESS=${cfg.hostAddress}
''}
${optionalString (cfg.hostAddress6 != null) ''
HOST_ADDRESS6=${cfg.hostAddress6}
''}
${optionalString (cfg.localAddress != null) ''
LOCAL_ADDRESS=${cfg.localAddress}
''}
${optionalString (cfg.localAddress6 != null) ''
LOCAL_ADDRESS6=${cfg.localAddress6}
''}
''}
INTERFACES="${toString cfg.interfaces}"
MACVLANS="${toString cfg.macvlans}"
${optionalString cfg.autoStart ''
AUTO_START=1
''}
EXTRA_NSPAWN_FLAGS="${mkBindFlags cfg.bindMounts +
optionalString (cfg.extraFlags != [])
(" " + concatStringsSep " " cfg.extraFlags)}"
'';
}) config.containers;
# Generate /etc/hosts entries for the containers.
networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null)
''
${head (splitString "/" cfg.localAddress)} ${name}.containers
'') config.containers);
networking.dhcpcd.denyInterfaces = [ "ve-*" "vb-*" ];
services.udev.extraRules = optionalString config.networking.networkmanager.enable ''
# Don't manage interfaces created by nixos-container.
ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1"
'';
environment.systemPackages = [
(pkgs.nixos-container.override {
inherit stateDirectory configurationDirectory;
})
];
boot.kernelModules = [
"bridge"
"macvlan"
"tap"
"tun"
];
});
}

View file

@ -0,0 +1,365 @@
{ config, options, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.oci-containers;
proxy_env = config.networking.proxy.envVars;
defaultBackend = options.virtualisation.oci-containers.backend.default;
containerOptions =
{ ... }: {
options = {
image = mkOption {
type = with types; str;
description = "OCI image to run.";
example = "library/hello-world";
};
imageFile = mkOption {
type = with types; nullOr package;
default = null;
description = ''
Path to an image file to load before running the image. This can
be used to bypass pulling the image from the registry.
The <literal>image</literal> attribute must match the name and
tag of the image contained in this file, as they will be used to
run the container with that image. If they do not match, the
image will be pulled from the registry as usual.
'';
example = literalExpression "pkgs.dockerTools.buildImage {...};";
};
login = {
username = mkOption {
type = with types; nullOr str;
default = null;
description = "Username for login.";
};
passwordFile = mkOption {
type = with types; nullOr str;
default = null;
description = "Path to file containing password.";
example = "/etc/nixos/dockerhub-password.txt";
};
registry = mkOption {
type = with types; nullOr str;
default = null;
description = "Registry where to login to.";
example = "https://docker.pkg.github.com";
};
};
cmd = mkOption {
type = with types; listOf str;
default = [];
description = "Commandline arguments to pass to the image's entrypoint.";
example = literalExpression ''
["--port=9000"]
'';
};
entrypoint = mkOption {
type = with types; nullOr str;
description = "Override the default entrypoint of the image.";
default = null;
example = "/bin/my-app";
};
environment = mkOption {
type = with types; attrsOf str;
default = {};
description = "Environment variables to set for this container.";
example = literalExpression ''
{
DATABASE_HOST = "db.example.com";
DATABASE_PORT = "3306";
}
'';
};
environmentFiles = mkOption {
type = with types; listOf path;
default = [];
description = "Environment files for this container.";
example = literalExpression ''
[
/path/to/.env
/path/to/.env.secret
]
'';
};
log-driver = mkOption {
type = types.str;
default = "journald";
description = ''
Logging driver for the container. The default of
<literal>"journald"</literal> means that the container's logs will be
handled as part of the systemd unit.
For more details and a full list of logging drivers, refer to respective backends documentation.
For Docker:
<link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">Docker engine documentation</link>
For Podman:
Refer to the docker-run(1) man page.
'';
};
ports = mkOption {
type = with types; listOf str;
default = [];
description = ''
Network ports to publish from the container to the outer host.
Valid formats:
<itemizedlist>
<listitem>
<para>
<literal>&lt;ip&gt;:&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
</para>
</listitem>
<listitem>
<para>
<literal>&lt;ip&gt;::&lt;containerPort&gt;</literal>
</para>
</listitem>
<listitem>
<para>
<literal>&lt;hostPort&gt;:&lt;containerPort&gt;</literal>
</para>
</listitem>
<listitem>
<para>
<literal>&lt;containerPort&gt;</literal>
</para>
</listitem>
</itemizedlist>
Both <literal>hostPort</literal> and
<literal>containerPort</literal> can be specified as a range of
ports. When specifying ranges for both, the number of container
ports in the range must match the number of host ports in the
range. Example: <literal>1234-1236:1234-1236/tcp</literal>
When specifying a range for <literal>hostPort</literal> only, the
<literal>containerPort</literal> must <emphasis>not</emphasis> be a
range. In this case, the container port is published somewhere
within the specified <literal>hostPort</literal> range. Example:
<literal>1234-1236:1234/tcp</literal>
Refer to the
<link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
Docker engine documentation</link> for full details.
'';
example = literalExpression ''
[
"8080:9000"
]
'';
};
user = mkOption {
type = with types; nullOr str;
default = null;
description = ''
Override the username or UID (and optionally groupname or GID) used
in the container.
'';
example = "nobody:nogroup";
};
volumes = mkOption {
type = with types; listOf str;
default = [];
description = ''
List of volumes to attach to this container.
Note that this is a list of <literal>"src:dst"</literal> strings to
allow for <literal>src</literal> to refer to
<literal>/nix/store</literal> paths, which would be difficult with an
attribute set. There are also a variety of mount options available
as a third field; please refer to the
<link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems">
docker engine documentation</link> for details.
'';
example = literalExpression ''
[
"volume_name:/path/inside/container"
"/path/on/host:/path/inside/container"
]
'';
};
workdir = mkOption {
type = with types; nullOr str;
default = null;
description = "Override the default working directory for the container.";
example = "/var/lib/hello_world";
};
dependsOn = mkOption {
type = with types; listOf str;
default = [];
description = ''
Define which other containers this one depends on. They will be added to both After and Requires for the unit.
Use the same name as the attribute under <literal>virtualisation.oci-containers.containers</literal>.
'';
example = literalExpression ''
virtualisation.oci-containers.containers = {
node1 = {};
node2 = {
dependsOn = [ "node1" ];
}
}
'';
};
extraOptions = mkOption {
type = with types; listOf str;
default = [];
description = "Extra options for <command>${defaultBackend} run</command>.";
example = literalExpression ''
["--network=host"]
'';
};
autoStart = mkOption {
type = types.bool;
default = true;
description = ''
When enabled, the container is automatically started on boot.
If this option is set to false, the container has to be started on-demand via its service.
'';
};
};
};
isValidLogin = login: login.username != null && login.passwordFile != null && login.registry != null;
mkService = name: container: let
dependsOn = map (x: "${cfg.backend}-${x}.service") container.dependsOn;
in {
wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ] ++ dependsOn;
requires = dependsOn;
environment = proxy_env;
path =
if cfg.backend == "docker" then [ config.virtualisation.docker.package ]
else if cfg.backend == "podman" then [ config.virtualisation.podman.package ]
else throw "Unhandled backend: ${cfg.backend}";
preStart = ''
${cfg.backend} rm -f ${name} || true
${optionalString (isValidLogin container.login) ''
cat ${container.login.passwordFile} | \
${cfg.backend} login \
${container.login.registry} \
--username ${container.login.username} \
--password-stdin
''}
${optionalString (container.imageFile != null) ''
${cfg.backend} load -i ${container.imageFile}
''}
'';
script = concatStringsSep " \\\n " ([
"exec ${cfg.backend} run"
"--rm"
"--name=${escapeShellArg name}"
"--log-driver=${container.log-driver}"
] ++ optional (container.entrypoint != null)
"--entrypoint=${escapeShellArg container.entrypoint}"
++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
++ map (f: "--env-file ${escapeShellArg f}") container.environmentFiles
++ map (p: "-p ${escapeShellArg p}") container.ports
++ optional (container.user != null) "-u ${escapeShellArg container.user}"
++ map (v: "-v ${escapeShellArg v}") container.volumes
++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
++ map escapeShellArg container.extraOptions
++ [container.image]
++ map escapeShellArg container.cmd
);
preStop = "[ $SERVICE_RESULT = success ] || ${cfg.backend} stop ${name}";
postStop = "${cfg.backend} rm -f ${name} || true";
serviceConfig = {
### There is no generalized way of supporting `reload` for docker
### containers. Some containers may respond well to SIGHUP sent to their
### init process, but it is not guaranteed; some apps have other reload
### mechanisms, some don't have a reload signal at all, and some docker
### images just have broken signal handling. The best compromise in this
### case is probably to leave ExecReload undefined, so `systemctl reload`
### will at least result in an error instead of potentially undefined
### behaviour.
###
### Advanced users can still override this part of the unit to implement
### a custom reload handler, since the result of all this is a normal
### systemd service from the perspective of the NixOS module system.
###
# ExecReload = ...;
###
TimeoutStartSec = 0;
TimeoutStopSec = 120;
Restart = "always";
};
};
in {
imports = [
(
lib.mkChangedOptionModule
[ "docker-containers" ]
[ "virtualisation" "oci-containers" ]
(oldcfg: {
backend = "docker";
containers = lib.mapAttrs (n: v: builtins.removeAttrs (v // {
extraOptions = v.extraDockerOptions or [];
}) [ "extraDockerOptions" ]) oldcfg.docker-containers;
})
)
];
options.virtualisation.oci-containers = {
backend = mkOption {
type = types.enum [ "podman" "docker" ];
default = if versionAtLeast config.system.stateVersion "22.05" then "podman" else "docker";
description = "The underlying Docker implementation to use.";
};
containers = mkOption {
default = {};
type = types.attrsOf (types.submodule containerOptions);
description = "OCI (Docker) containers to run as systemd services.";
};
};
config = lib.mkIf (cfg.containers != {}) (lib.mkMerge [
{
systemd.services = mapAttrs' (n: v: nameValuePair "${cfg.backend}-${n}" (mkService n v)) cfg.containers;
}
(lib.mkIf (cfg.backend == "podman") {
virtualisation.podman.enable = true;
})
(lib.mkIf (cfg.backend == "docker") {
virtualisation.docker.enable = true;
})
]);
}

View file

@ -0,0 +1,90 @@
{ config, pkgs, lib, ... }:
# image metadata:
# hw_firmware_type=uefi
let
inherit (lib) mkIf mkDefault;
cfg = config.openstack;
metadataFetcher = import ./openstack-metadata-fetcher.nix {
targetRoot = "/";
wgetExtraOptions = "--retry-connrefused";
};
in
{
imports = [
../profiles/qemu-guest.nix
# Note: While we do use the headless profile, we also explicitly
# turn on the serial console on tty1 below.
# Note that I could not find any documentation indicating tty1 was
# the correct choice. I picked tty1 because that is what one
# particular host was using.
../profiles/headless.nix
# The Openstack Metadata service exposes data on an EC2 API also.
./ec2-data.nix
./amazon-init.nix
];
config = {
fileSystems."/" = mkIf (!cfg.zfs.enable) {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
autoResize = true;
};
fileSystems."/boot" = mkIf (cfg.efi || cfg.zfs.enable) {
# The ZFS image uses a partition labeled ESP whether or not we're
# booting with EFI.
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
boot.growPartition = true;
boot.kernelParams = [ "console=tty1" ];
boot.loader.grub.device = if (!cfg.efi) then "/dev/vda" else "nodev";
boot.loader.grub.efiSupport = cfg.efi;
boot.loader.grub.efiInstallAsRemovable = cfg.efi;
boot.loader.timeout = 1;
boot.loader.grub.extraConfig = ''
serial --unit=1 --speed=115200 --word=8 --parity=no --stop=1
terminal_output console serial
terminal_input console serial
'';
services.zfs.expandOnBoot = mkIf cfg.zfs.enable (lib.mkDefault "all");
boot.zfs.devNodes = mkIf cfg.zfs.enable "/dev/";
# Allow root logins
services.openssh = {
enable = true;
permitRootLogin = "prohibit-password";
passwordAuthentication = mkDefault false;
};
users.users.root.initialPassword = "foobar";
# Enable the serial console on tty1
systemd.services."serial-getty@tty1".enable = true;
# Force getting the hostname from Openstack metadata.
networking.hostName = mkDefault "";
systemd.services.openstack-init = {
path = [ pkgs.wget ];
description = "Fetch Metadata on startup";
wantedBy = [ "multi-user.target" ];
before = [ "apply-ec2-data.service" "amazon-init.service" ];
wants = [ "network-online.target" ];
after = [ "network-online.target" ];
script = metadataFetcher;
restartIfChanged = false;
unitConfig.X-StopOnRemoval = false;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
};
};
}

View file

@ -0,0 +1,22 @@
{ targetRoot, wgetExtraOptions }:
# OpenStack's metadata service aims to be EC2-compatible. Where
# possible, try to keep the set of fetched metadata in sync with
# ./ec2-metadata-fetcher.nix .
''
metaDir=${targetRoot}etc/ec2-metadata
mkdir -m 0755 -p "$metaDir"
rm -f "$metaDir/*"
echo "getting instance metadata..."
wget_imds() {
wget ${wgetExtraOptions} "$@"
}
wget_imds -O "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path || true
# When no user-data is provided, the OpenStack metadata server doesn't expose the user-data route.
(umask 077 && wget_imds -O "$metaDir/user-data" http://169.254.169.254/1.0/user-data || rm -f "$metaDir/user-data")
wget_imds -O "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname || true
wget_imds -O "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key || true
''

View file

@ -0,0 +1,71 @@
{ config, lib, pkgs, ... }:
let
inherit (lib) literalExpression types;
in
{
options = {
openstack = {
zfs = {
enable = lib.mkOption {
default = false;
internal = true;
description = ''
Whether the OpenStack instance uses a ZFS root.
'';
};
datasets = lib.mkOption {
description = ''
Datasets to create under the `tank` and `boot` zpools.
**NOTE:** This option is used only at image creation time, and
does not attempt to declaratively create or manage datasets
on an existing system.
'';
default = { };
type = types.attrsOf (types.submodule {
options = {
mount = lib.mkOption {
description = "Where to mount this dataset.";
type = types.nullOr types.string;
default = null;
};
properties = lib.mkOption {
description = "Properties to set on this dataset.";
type = types.attrsOf types.string;
default = { };
};
};
});
};
};
efi = lib.mkOption {
default = pkgs.stdenv.hostPlatform.isAarch64;
defaultText = literalExpression "pkgs.stdenv.hostPlatform.isAarch64";
internal = true;
description = ''
Whether the instance is using EFI.
'';
};
};
};
config = lib.mkIf config.openstack.zfs.enable {
networking.hostId = lib.mkDefault "00000000";
fileSystems =
let
mountable = lib.filterAttrs (_: value: ((value.mount or null) != null)) config.openstack.zfs.datasets;
in
lib.mapAttrs'
(dataset: opts: lib.nameValuePair opts.mount {
device = dataset;
fsType = "zfs";
})
mountable;
};
}

View file

@ -0,0 +1,145 @@
# Systemd services for openvswitch
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.vswitch;
in {
options.virtualisation.vswitch = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable Open vSwitch. A configuration daemon (ovs-server)
will be started.
'';
};
resetOnStart = mkOption {
type = types.bool;
default = false;
description = ''
Whether to reset the Open vSwitch configuration database to a default
configuration on every start of the systemd <literal>ovsdb.service</literal>.
'';
};
package = mkOption {
type = types.package;
default = pkgs.openvswitch;
defaultText = literalExpression "pkgs.openvswitch";
description = ''
Open vSwitch package to use.
'';
};
};
config = mkIf cfg.enable (let
# Where the communication sockets live
runDir = "/run/openvswitch";
# The path to the an initialized version of the database
db = pkgs.stdenv.mkDerivation {
name = "vswitch.db";
dontUnpack = true;
buildPhase = "true";
buildInputs = with pkgs; [
cfg.package
];
installPhase = "mkdir -p $out";
};
in {
environment.systemPackages = [ cfg.package ];
boot.kernelModules = [ "tun" "openvswitch" ];
boot.extraModulePackages = [ cfg.package ];
systemd.services.ovsdb = {
description = "Open_vSwitch Database Server";
wantedBy = [ "multi-user.target" ];
after = [ "systemd-udev-settle.service" ];
path = [ cfg.package ];
restartTriggers = [ db cfg.package ];
# Create the config database
preStart =
''
mkdir -p ${runDir}
mkdir -p /var/db/openvswitch
chmod +w /var/db/openvswitch
${optionalString cfg.resetOnStart "rm -f /var/db/openvswitch/conf.db"}
if [[ ! -e /var/db/openvswitch/conf.db ]]; then
${cfg.package}/bin/ovsdb-tool create \
"/var/db/openvswitch/conf.db" \
"${cfg.package}/share/openvswitch/vswitch.ovsschema"
fi
chmod -R +w /var/db/openvswitch
if ${cfg.package}/bin/ovsdb-tool needs-conversion /var/db/openvswitch/conf.db | grep -q "yes"
then
echo "Performing database upgrade"
${cfg.package}/bin/ovsdb-tool convert /var/db/openvswitch/conf.db
else
echo "Database already up to date"
fi
'';
serviceConfig = {
ExecStart =
''
${cfg.package}/bin/ovsdb-server \
--remote=punix:${runDir}/db.sock \
--private-key=db:Open_vSwitch,SSL,private_key \
--certificate=db:Open_vSwitch,SSL,certificate \
--bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \
--unixctl=ovsdb.ctl.sock \
--pidfile=/run/openvswitch/ovsdb.pid \
--detach \
/var/db/openvswitch/conf.db
'';
Restart = "always";
RestartSec = 3;
PIDFile = "/run/openvswitch/ovsdb.pid";
# Use service type 'forking' to correctly determine when ovsdb-server is ready.
Type = "forking";
};
postStart = ''
${cfg.package}/bin/ovs-vsctl --timeout 3 --retry --no-wait init
'';
};
systemd.services.ovs-vswitchd = {
description = "Open_vSwitch Daemon";
wantedBy = [ "multi-user.target" ];
bindsTo = [ "ovsdb.service" ];
after = [ "ovsdb.service" ];
path = [ cfg.package ];
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/ovs-vswitchd \
--pidfile=/run/openvswitch/ovs-vswitchd.pid \
--detach
'';
PIDFile = "/run/openvswitch/ovs-vswitchd.pid";
# Use service type 'forking' to correctly determine when vswitchd is ready.
Type = "forking";
Restart = "always";
RestartSec = 3;
};
};
});
imports = [
(mkRemovedOptionModule [ "virtualisation" "vswitch" "ipsec" ] ''
OpenVSwitch IPSec functionality has been removed, because it depended on racoon,
which was removed from nixpkgs, because it was abanoded upstream.
'')
];
meta.maintainers = with maintainers; [ netixx ];
}

View file

@ -0,0 +1,155 @@
{ config, lib, pkgs, ... }:
with lib;
let
prl-tools = config.hardware.parallels.package;
in
{
options = {
hardware.parallels = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
This enables Parallels Tools for Linux guests, along with provided
video, mouse and other hardware drivers.
'';
};
autoMountShares = mkOption {
type = types.bool;
default = true;
description = ''
Control prlfsmountd service. When this service is running, shares can not be manually
mounted through `mount -t prl_fs ...` as this service will remount and trample any set options.
Recommended to enable for simple file sharing, but extended share use such as for code should
disable this to manually mount shares.
'';
};
package = mkOption {
type = types.nullOr types.package;
default = config.boot.kernelPackages.prl-tools;
defaultText = literalExpression "config.boot.kernelPackages.prl-tools";
description = ''
Defines which package to use for prl-tools. Override to change the version.
'';
};
};
};
config = mkIf config.hardware.parallels.enable {
services.xserver = {
drivers = singleton
{ name = "prlvideo"; modules = [ prl-tools ]; };
screenSection = ''
Option "NoMTRR"
'';
config = ''
Section "InputClass"
Identifier "prlmouse"
MatchIsPointer "on"
MatchTag "prlmouse"
Driver "prlmouse"
EndSection
'';
};
hardware.opengl.package = prl-tools;
hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.prl-tools.override { libsOnly = true; kernel = null; };
hardware.opengl.setLdLibraryPath = true;
services.udev.packages = [ prl-tools ];
environment.systemPackages = [ prl-tools ];
boot.extraModulePackages = [ prl-tools ];
boot.kernelModules = [ "prl_tg" "prl_eth" "prl_fs" "prl_fs_freeze" ];
services.timesyncd.enable = false;
systemd.services.prltoolsd = {
description = "Parallels Tools' service";
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prltoolsd -f";
PIDFile = "/var/run/prltoolsd.pid";
};
};
systemd.services.prlfsmountd = mkIf config.hardware.parallels.autoMountShares {
description = "Parallels Shared Folders Daemon";
wantedBy = [ "multi-user.target" ];
serviceConfig = rec {
ExecStart = "${prl-tools}/sbin/prlfsmountd ${PIDFile}";
ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /media";
ExecStopPost = "${prl-tools}/sbin/prlfsmountd -u";
PIDFile = "/run/prlfsmountd.pid";
};
};
systemd.services.prlshprint = {
description = "Parallels Shared Printer Tool";
wantedBy = [ "multi-user.target" ];
bindsTo = [ "cups.service" ];
serviceConfig = {
Type = "forking";
ExecStart = "${prl-tools}/bin/prlshprint";
};
};
systemd.user.services = {
prlcc = {
description = "Parallels Control Center";
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlcc";
};
};
prldnd = {
description = "Parallels Control Center";
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prldnd";
};
};
prl_wmouse_d = {
description = "Parallels Walking Mouse Daemon";
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prl_wmouse_d";
};
};
prlcp = {
description = "Parallels CopyPaste Tool";
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlcp";
};
};
prlsga = {
description = "Parallels Shared Guest Applications Tool";
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlsga";
};
};
prlshprof = {
description = "Parallels Shared Profile Tool";
wantedBy = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = "${prl-tools}/bin/prlshprof";
};
};
};
};
}

View file

@ -0,0 +1,187 @@
{ config, lib, pkgs, ... }:
let
cfg = config.virtualisation.podman;
toml = pkgs.formats.toml { };
json = pkgs.formats.json { };
inherit (lib) mkOption types;
podmanPackage = (pkgs.podman.override {
extraPackages = cfg.extraPackages
++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
});
# Provides a fake "docker" binary mapping to podman
dockerCompat = pkgs.runCommand "${podmanPackage.pname}-docker-compat-${podmanPackage.version}" {
outputs = [ "out" "man" ];
inherit (podmanPackage) meta;
} ''
mkdir -p $out/bin
ln -s ${podmanPackage}/bin/podman $out/bin/docker
mkdir -p $man/share/man/man1
for f in ${podmanPackage.man}/share/man/man1/*; do
basename=$(basename $f | sed s/podman/docker/g)
ln -s $f $man/share/man/man1/$basename
done
'';
net-conflist = pkgs.runCommand "87-podman-bridge.conflist" {
nativeBuildInputs = [ pkgs.jq ];
extraPlugins = builtins.toJSON cfg.defaultNetwork.extraPlugins;
jqScript = ''
. + { "plugins": (.plugins + $extraPlugins) }
'';
} ''
jq <${cfg.package}/etc/cni/net.d/87-podman-bridge.conflist \
--argjson extraPlugins "$extraPlugins" \
"$jqScript" \
>$out
'';
in
{
imports = [
./dnsname.nix
./network-socket.nix
(lib.mkRenamedOptionModule [ "virtualisation" "podman" "libpod" ] [ "virtualisation" "containers" "containersConf" ])
];
meta = {
maintainers = lib.teams.podman.members;
};
options.virtualisation.podman = {
enable =
mkOption {
type = types.bool;
default = false;
description = ''
This option enables Podman, a daemonless container engine for
developing, managing, and running OCI Containers on your Linux System.
It is a drop-in replacement for the <command>docker</command> command.
'';
};
dockerSocket.enable = mkOption {
type = types.bool;
default = false;
description = ''
Make the Podman socket available in place of the Docker socket, so
Docker tools can find the Podman socket.
Podman implements the Docker API.
Users must be in the <code>podman</code> group in order to connect. As
with Docker, members of this group can gain root access.
'';
};
dockerCompat = mkOption {
type = types.bool;
default = false;
description = ''
Create an alias mapping <command>docker</command> to <command>podman</command>.
'';
};
enableNvidia = mkOption {
type = types.bool;
default = false;
description = ''
Enable use of NVidia GPUs from within podman containers.
'';
};
extraPackages = mkOption {
type = with types; listOf package;
default = [ ];
example = lib.literalExpression ''
[
pkgs.gvisor
]
'';
description = ''
Extra packages to be installed in the Podman wrapper.
'';
};
package = lib.mkOption {
type = types.package;
default = podmanPackage;
internal = true;
description = ''
The final Podman package (including extra packages).
'';
};
defaultNetwork.extraPlugins = lib.mkOption {
type = types.listOf json.type;
default = [];
description = ''
Extra CNI plugin configurations to add to podman's default network.
'';
};
};
config = lib.mkIf cfg.enable (lib.mkMerge [
{
environment.systemPackages = [ cfg.package ]
++ lib.optional cfg.dockerCompat dockerCompat;
environment.etc."cni/net.d/87-podman-bridge.conflist".source = net-conflist;
virtualisation.containers = {
enable = true; # Enable common /etc/containers configuration
containersConf.settings = lib.optionalAttrs cfg.enableNvidia {
engine = {
conmon_env_vars = [ "PATH=${lib.makeBinPath [ pkgs.nvidia-podman ]}" ];
runtimes.nvidia = [ "${pkgs.nvidia-podman}/bin/nvidia-container-runtime" ];
};
};
};
systemd.packages = [ cfg.package ];
systemd.services.podman.serviceConfig = {
ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
};
systemd.sockets.podman.wantedBy = [ "sockets.target" ];
systemd.sockets.podman.socketConfig.SocketGroup = "podman";
systemd.tmpfiles.packages = [
# The /run/podman rule interferes with our podman group, so we remove
# it and let the systemd socket logic take care of it.
(pkgs.runCommand "podman-tmpfiles-nixos" { package = cfg.package; } ''
mkdir -p $out/lib/tmpfiles.d/
grep -v 'D! /run/podman 0700 root root' \
<$package/lib/tmpfiles.d/podman.conf \
>$out/lib/tmpfiles.d/podman.conf
'') ];
systemd.tmpfiles.rules =
lib.optionals cfg.dockerSocket.enable [
"L! /run/docker.sock - - - - /run/podman/podman.sock"
];
users.groups.podman = {};
assertions = [
{
assertion = cfg.dockerCompat -> !config.virtualisation.docker.enable;
message = "Option dockerCompat conflicts with docker";
}
{
assertion = cfg.dockerSocket.enable -> !config.virtualisation.docker.enable;
message = ''
The options virtualisation.podman.dockerSocket.enable and virtualisation.docker.enable conflict, because only one can serve the socket.
'';
}
];
}
]);
}

View file

@ -0,0 +1,36 @@
{ config, lib, pkgs, ... }:
let
inherit (lib)
mkOption
mkIf
types
;
cfg = config.virtualisation.podman;
in
{
options = {
virtualisation.podman = {
defaultNetwork.dnsname.enable = mkOption {
type = types.bool;
default = false;
description = ''
Enable DNS resolution in the default podman network.
'';
};
};
};
config = {
virtualisation.containers.containersConf.cniPlugins = mkIf cfg.defaultNetwork.dnsname.enable [ pkgs.dnsname-cni ];
virtualisation.podman.defaultNetwork.extraPlugins =
lib.optional cfg.defaultNetwork.dnsname.enable {
type = "dnsname";
domainName = "dns.podman";
capabilities.aliases = true;
};
};
}

View file

@ -0,0 +1,34 @@
{ config, lib, pkg, ... }:
let
inherit (lib)
mkOption
types
;
cfg = config.virtualisation.podman.networkSocket;
in
{
options.virtualisation.podman.networkSocket = {
server = mkOption {
type = types.enum [ "ghostunnel" ];
};
};
config = lib.mkIf (cfg.enable && cfg.server == "ghostunnel") {
services.ghostunnel = {
enable = true;
servers."podman-socket" = {
inherit (cfg.tls) cert key cacert;
listen = "${cfg.listenAddress}:${toString cfg.port}";
target = "unix:/run/podman/podman.sock";
allowAll = lib.mkDefault true;
};
};
systemd.services.ghostunnel-server-podman-socket.serviceConfig.SupplementaryGroups = ["podman"];
};
meta.maintainers = lib.teams.podman.members ++ [ lib.maintainers.roberth ];
}

View file

@ -0,0 +1,95 @@
{ config, lib, pkg, ... }:
let
inherit (lib)
mkOption
types
;
cfg = config.virtualisation.podman.networkSocket;
in
{
imports = [
./network-socket-ghostunnel.nix
];
options.virtualisation.podman.networkSocket = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Make the Podman and Docker compatibility API available over the network
with TLS client certificate authentication.
This allows Docker clients to connect with the equivalents of the Docker
CLI <code>-H</code> and <code>--tls*</code> family of options.
For certificate setup, see https://docs.docker.com/engine/security/protect-access/
This option is independent of <xref linkend="opt-virtualisation.podman.dockerSocket.enable"/>.
'';
};
server = mkOption {
type = types.enum [];
description = ''
Choice of TLS proxy server.
'';
example = "ghostunnel";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Whether to open the port in the firewall.
'';
};
tls.cacert = mkOption {
type = types.path;
description = ''
Path to CA certificate to use for client authentication.
'';
};
tls.cert = mkOption {
type = types.path;
description = ''
Path to certificate describing the server.
'';
};
tls.key = mkOption {
type = types.path;
description = ''
Path to the private key corresponding to the server certificate.
Use a string for this setting. Otherwise it will be copied to the Nix
store first, where it is readable by any system process.
'';
};
port = mkOption {
type = types.port;
default = 2376;
description = ''
TCP port number for receiving TLS connections.
'';
};
listenAddress = mkOption {
type = types.str;
default = "0.0.0.0";
description = ''
Interface address for receiving TLS connections.
'';
};
};
config = {
networking.firewall.allowedTCPPorts =
lib.optional (cfg.enable && cfg.openFirewall) cfg.port;
};
meta.maintainers = lib.teams.podman.members ++ [ lib.maintainers.roberth ];
}

View file

@ -0,0 +1,169 @@
{ config, pkgs, lib, ... }:
with lib;
{
options.proxmox = {
qemuConf = {
# essential configs
boot = mkOption {
type = types.str;
default = "";
example = "order=scsi0;net0";
description = ''
Default boot device. PVE will try all devices in its default order if this value is empty.
'';
};
scsihw = mkOption {
type = types.str;
default = "virtio-scsi-pci";
example = "lsi";
description = ''
SCSI controller type. Must be one of the supported values given in
<link xlink:href="https://pve.proxmox.com/wiki/Qemu/KVM_Virtual_Machines"/>
'';
};
virtio0 = mkOption {
type = types.str;
default = "local-lvm:vm-9999-disk-0";
example = "ceph:vm-123-disk-0";
description = ''
Configuration for the default virtio disk. It can be used as a cue for PVE to autodetect the target sotrage.
This parameter is required by PVE even if it isn't used.
'';
};
ostype = mkOption {
type = types.str;
default = "l26";
description = ''
Guest OS type
'';
};
cores = mkOption {
type = types.ints.positive;
default = 1;
description = ''
Guest core count
'';
};
memory = mkOption {
type = types.ints.positive;
default = 1024;
description = ''
Guest memory in MB
'';
};
# optional configs
name = mkOption {
type = types.str;
default = "nixos-${config.system.nixos.label}";
description = ''
VM name
'';
};
net0 = mkOption {
type = types.commas;
default = "virtio=00:00:00:00:00:00,bridge=vmbr0,firewall=1";
description = ''
Configuration for the default interface. When restoring from VMA, check the
"unique" box to ensure device mac is randomized.
'';
};
serial0 = mkOption {
type = types.str;
default = "socket";
example = "/dev/ttyS0";
description = ''
Create a serial device inside the VM (n is 0 to 3), and pass through a host serial device (i.e. /dev/ttyS0),
or create a unix socket on the host side (use qm terminal to open a terminal connection).
'';
};
agent = mkOption {
type = types.bool;
apply = x: if x then "1" else "0";
default = true;
description = ''
Expect guest to have qemu agent running
'';
};
};
qemuExtraConf = mkOption {
type = with types; attrsOf (oneOf [ str int ]);
default = {};
example = literalExpression ''{
cpu = "host";
onboot = 1;
}'';
description = ''
Additional options appended to qemu-server.conf
'';
};
filenameSuffix = mkOption {
type = types.str;
default = config.proxmox.qemuConf.name;
example = "999-nixos_template";
description = ''
Filename of the image will be vzdump-qemu-''${filenameSuffix}.vma.zstd.
This will also determine the default name of the VM on restoring the VMA.
Start this value with a number if you want the VMA to be detected as a backup of
any specific VMID.
'';
};
};
config = let
cfg = config.proxmox;
cfgLine = name: value: ''
${name}: ${builtins.toString value}
'';
cfgFile = fileName: properties: pkgs.writeTextDir fileName ''
# generated by NixOS
${lib.concatStrings (lib.mapAttrsToList cfgLine properties)}
#qmdump#map:virtio0:drive-virtio0:local-lvm:raw:
'';
in {
system.build.VMA = import ../../lib/make-disk-image.nix {
name = "proxmox-${cfg.filenameSuffix}";
postVM = let
# Build qemu with PVE's patch that adds support for the VMA format
vma = pkgs.qemu_kvm.overrideAttrs ( super: {
patches = let
rev = "cc707c362ea5c8d832aac270d1ffa7ac66a8908f";
path = "debian/patches/pve/0025-PVE-Backup-add-vma-backup-format-code.patch";
vma-patch = pkgs.fetchpatch {
url = "https://git.proxmox.com/?p=pve-qemu.git;a=blob_plain;hb=${rev};f=${path}";
sha256 = "1z467xnmfmry3pjy7p34psd5xdil9x0apnbvfz8qbj0bf9fgc8zf";
};
in super.patches ++ [ vma-patch ];
buildInputs = super.buildInputs ++ [ pkgs.libuuid ];
});
in
''
${vma}/bin/vma create "vzdump-qemu-${cfg.filenameSuffix}.vma" \
-c ${cfgFile "qemu-server.conf" (cfg.qemuConf // cfg.qemuExtraConf)}/qemu-server.conf drive-virtio0=$diskImage
rm $diskImage
${pkgs.zstd}/bin/zstd "vzdump-qemu-${cfg.filenameSuffix}.vma"
mv "vzdump-qemu-${cfg.filenameSuffix}.vma.zst" $out/
'';
format = "raw";
inherit config lib pkgs;
};
boot = {
growPartition = true;
kernelParams = [ "console=ttyS0" ];
loader.grub.device = lib.mkDefault "/dev/vda";
loader.timeout = 0;
initrd.availableKernelModules = [ "uas" "virtio_blk" "virtio_pci" ];
};
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
services.qemuGuest.enable = lib.mkDefault true;
};
}

View file

@ -0,0 +1,75 @@
{ config, pkgs, lib, ... }:
with lib;
{
options.proxmoxLXC = {
privileged = mkOption {
type = types.bool;
default = false;
description = ''
Whether to enable privileged mounts
'';
};
manageNetwork = mkOption {
type = types.bool;
default = false;
description = ''
Whether to manage network interfaces through nix options
When false, systemd-networkd is enabled to accept network
configuration from proxmox.
'';
};
manageHostName = mkOption {
type = types.bool;
default = false;
description = ''
Whether to manage hostname through nix options
When false, the hostname is picked up from /etc/hostname
populated by proxmox.
'';
};
};
config =
let
cfg = config.proxmoxLXC;
in
{
system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
storeContents = [{
object = config.system.build.toplevel;
symlink = "none";
}];
contents = [{
source = config.system.build.toplevel + "/init";
target = "/sbin/init";
}];
extraCommands = "mkdir -p root etc/systemd/network";
};
boot = {
isContainer = true;
loader.initScript.enable = true;
};
networking = mkIf (!cfg.manageNetwork) {
useDHCP = false;
useHostResolvConf = false;
useNetworkd = true;
# pick up hostname from /etc/hostname generated by proxmox
hostName = mkIf (!cfg.manageHostName) (mkForce "");
};
services.openssh = {
enable = mkDefault true;
startWhenNeeded = mkDefault true;
};
systemd.mounts = mkIf (!cfg.privileged)
[{ where = "/sys/kernel/debug"; enable = false; }];
};
}

View file

@ -0,0 +1,45 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.qemuGuest;
in {
options.services.qemuGuest = {
enable = mkOption {
type = types.bool;
default = false;
description = "Whether to enable the qemu guest agent.";
};
package = mkOption {
type = types.package;
default = pkgs.qemu_kvm.ga;
defaultText = literalExpression "pkgs.qemu_kvm.ga";
description = "The QEMU guest agent package.";
};
};
config = mkIf cfg.enable (
mkMerge [
{
services.udev.extraRules = ''
SUBSYSTEM=="virtio-ports", ATTR{name}=="org.qemu.guest_agent.0", TAG+="systemd" ENV{SYSTEMD_WANTS}="qemu-guest-agent.service"
'';
systemd.services.qemu-guest-agent = {
description = "Run the QEMU Guest Agent";
serviceConfig = {
ExecStart = "${cfg.package}/bin/qemu-ga --statedir /run/qemu-ga";
Restart = "always";
RestartSec = 0;
# Runtime directory and mode
RuntimeDirectory = "qemu-ga";
RuntimeDirectoryMode = "0755";
};
};
}
]
);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,124 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.railcar;
generateUnit = name: containerConfig:
let
container = pkgs.ociTools.buildContainer {
args = [
(pkgs.writeShellScript "run.sh" containerConfig.cmd).outPath
];
};
in
nameValuePair "railcar-${name}" {
enable = true;
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/railcar -r ${cfg.stateDir} run ${name} -b ${container}
'';
Type = containerConfig.runType;
};
};
mount = with types; (submodule {
options = {
type = mkOption {
type = str;
default = "none";
description = ''
The type of the filesystem to be mounted.
Linux: filesystem types supported by the kernel as listed in
`/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs",
"reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts
(when options include either bind or rbind), the type is a dummy,
often "none" (not listed in /proc/filesystems).
'';
};
source = mkOption {
type = str;
description = "Source for the in-container mount";
};
options = mkOption {
type = listOf str;
default = [ "bind" ];
description = ''
Mount options of the filesystem to be used.
Support options are listed in the mount(8) man page. Note that
both filesystem-independent and filesystem-specific options
are listed.
'';
};
};
});
in
{
options.services.railcar = {
enable = mkEnableOption "railcar";
containers = mkOption {
default = {};
description = "Declarative container configuration";
type = with types; attrsOf (submodule ({ name, config, ... }: {
options = {
cmd = mkOption {
type = types.lines;
description = "Command or script to run inside the container";
};
mounts = mkOption {
type = with types; attrsOf mount;
default = {};
description = ''
A set of mounts inside the container.
The defaults have been chosen for simple bindmounts, meaning
that you only need to provide the "source" parameter.
'';
example = { "/data" = { source = "/var/lib/data"; }; };
};
runType = mkOption {
type = types.str;
default = "oneshot";
description = "The systemd service run type";
};
os = mkOption {
type = types.str;
default = "linux";
description = "OS type of the container";
};
arch = mkOption {
type = types.str;
default = "x86_64";
description = "Computer architecture type of the container";
};
};
}));
};
stateDir = mkOption {
type = types.path;
default = "/var/railcar";
description = "Railcar persistent state directory";
};
package = mkOption {
type = types.package;
default = pkgs.railcar;
defaultText = literalExpression "pkgs.railcar";
description = "Railcar package to use";
};
};
config = mkIf cfg.enable {
systemd.services = flip mapAttrs' cfg.containers (name: containerConfig:
generateUnit name containerConfig
);
};
}

View file

@ -0,0 +1,26 @@
{ config, pkgs, lib, ... }:
{
options.virtualisation.spiceUSBRedirection.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Install the SPICE USB redirection helper with setuid
privileges. This allows unprivileged users to pass USB devices
connected to this machine to libvirt VMs, both local and
remote. Note that this allows users arbitrary access to USB
devices.
'';
};
config = lib.mkIf config.virtualisation.spiceUSBRedirection.enable {
environment.systemPackages = [ pkgs.spice-gtk ]; # For polkit actions
security.wrappers.spice-client-glib-usb-acl-helper = {
owner = "root";
group = "root";
capabilities = "cap_fowner+ep";
source = "${pkgs.spice-gtk}/bin/spice-client-glib-usb-acl-helper";
};
};
meta.maintainers = [ lib.maintainers.lheckemann ];
}

View file

@ -0,0 +1,58 @@
# Minimal configuration that vagrant depends on
{ config, pkgs, ... }:
let
# Vagrant uses an insecure shared private key by default, but we
# don't use the authorizedKeys attribute under users because it should be
# removed on first boot and replaced with a random one. This script sets
# the correct permissions and installs the temporary key if no
# ~/.ssh/authorized_keys exists.
install-vagrant-ssh-key = pkgs.writeScriptBin "install-vagrant-ssh-key" ''
#!${pkgs.runtimeShell}
if [ ! -e ~/.ssh/authorized_keys ]; then
mkdir -m 0700 -p ~/.ssh
echo "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key" >> ~/.ssh/authorized_keys
chmod 0600 ~/.ssh/authorized_keys
fi
'';
in
{
# Enable the OpenSSH daemon.
services.openssh.enable = true;
# Packages used by Vagrant
environment.systemPackages = with pkgs; [
findutils
iputils
nettools
netcat
nfs-utils
rsync
];
users.extraUsers.vagrant = {
isNormalUser = true;
createHome = true;
description = "Vagrant user account";
extraGroups = [ "users" "wheel" ];
home = "/home/vagrant";
password = "vagrant";
useDefaultShell = true;
uid = 1000;
};
systemd.services.install-vagrant-ssh-key = {
description = "Vagrant SSH key install (if needed)";
after = [ "fs.target" ];
wants = [ "fs.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${install-vagrant-ssh-key}/bin/install-vagrant-ssh-key";
User = "vagrant";
# So it won't be (needlessly) restarted:
RemainAfterExit = true;
};
};
security.sudo.wheelNeedsPassword = false;
}

View file

@ -0,0 +1,60 @@
# Vagrant + VirtualBox
{ config, pkgs, ... }:
{
imports = [
./vagrant-guest.nix
./virtualbox-image.nix
];
virtualbox.params = {
audio = "none";
audioin = "off";
audioout = "off";
usb = "off";
usbehci = "off";
};
sound.enable = false;
documentation.man.enable = false;
documentation.nixos.enable = false;
users.extraUsers.vagrant.extraGroups = [ "vboxsf" ];
# generate the box v1 format which is much easier to generate
# https://www.vagrantup.com/docs/boxes/format.html
system.build.vagrantVirtualbox = pkgs.runCommand
"virtualbox-vagrant.box"
{}
''
mkdir workdir
cd workdir
# 1. create that metadata.json file
echo '{"provider":"virtualbox"}' > metadata.json
# 2. create a default Vagrantfile config
cat <<VAGRANTFILE > Vagrantfile
Vagrant.configure("2") do |config|
config.vm.base_mac = "0800275F0936"
end
VAGRANTFILE
# 3. add the exported VM files
tar xvf ${config.system.build.virtualBoxOVA}/*.ova
# 4. move the ovf to the fixed location
mv *.ovf box.ovf
# 5. generate OVF manifest file
rm *.mf
touch box.mf
for fname in *; do
checksum=$(sha256sum $fname | cut -d' ' -f 1)
echo "SHA256($fname)= $checksum" >> box.mf
done
# 6. compress everything back together
tar --owner=0 --group=0 --sort=name --numeric-owner -czf $out .
'';
}

View file

@ -0,0 +1,93 @@
# Module for VirtualBox guests.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.virtualbox.guest;
kernel = config.boot.kernelPackages;
in
{
###### interface
options.virtualisation.virtualbox.guest = {
enable = mkOption {
default = false;
type = types.bool;
description = "Whether to enable the VirtualBox service and other guest additions.";
};
x11 = mkOption {
default = true;
type = types.bool;
description = "Whether to enable x11 graphics";
};
};
###### implementation
config = mkIf cfg.enable (mkMerge [{
assertions = [{
assertion = pkgs.stdenv.hostPlatform.isx86;
message = "Virtualbox not currently supported on ${pkgs.stdenv.hostPlatform.system}";
}];
environment.systemPackages = [ kernel.virtualboxGuestAdditions ];
boot.extraModulePackages = [ kernel.virtualboxGuestAdditions ];
boot.supportedFilesystems = [ "vboxsf" ];
boot.initrd.supportedFilesystems = [ "vboxsf" ];
users.groups.vboxsf.gid = config.ids.gids.vboxsf;
systemd.services.virtualbox =
{ description = "VirtualBox Guest Services";
wantedBy = [ "multi-user.target" ];
requires = [ "dev-vboxguest.device" ];
after = [ "dev-vboxguest.device" ];
unitConfig.ConditionVirtualization = "oracle";
serviceConfig.ExecStart = "@${kernel.virtualboxGuestAdditions}/bin/VBoxService VBoxService --foreground";
};
services.udev.extraRules =
''
# /dev/vboxuser is necessary for VBoxClient to work. Maybe we
# should restrict this to logged-in users.
KERNEL=="vboxuser", OWNER="root", GROUP="root", MODE="0666"
# Allow systemd dependencies on vboxguest.
SUBSYSTEM=="misc", KERNEL=="vboxguest", TAG+="systemd"
'';
} (mkIf cfg.x11 {
services.xserver.videoDrivers = [ "vmware" "virtualbox" "modesetting" ];
services.xserver.config =
''
Section "InputDevice"
Identifier "VBoxMouse"
Driver "vboxmouse"
EndSection
'';
services.xserver.serverLayoutSection =
''
InputDevice "VBoxMouse"
'';
services.xserver.displayManager.sessionCommands =
''
PATH=${makeBinPath [ pkgs.gnugrep pkgs.which pkgs.xorg.xorgserver.out ]}:$PATH \
${kernel.virtualboxGuestAdditions}/bin/VBoxClient-all
'';
})]);
}

View file

@ -0,0 +1,168 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.virtualbox.host;
virtualbox = cfg.package.override {
inherit (cfg) enableHardening headless enableWebService;
extensionPack = if cfg.enableExtensionPack then pkgs.virtualboxExtpack else null;
};
kernelModules = config.boot.kernelPackages.virtualbox.override {
inherit virtualbox;
};
in
{
options.virtualisation.virtualbox.host = {
enable = mkEnableOption "VirtualBox" // {
description = ''
Whether to enable VirtualBox.
<note><para>
In order to pass USB devices from the host to the guests, the user
needs to be in the <literal>vboxusers</literal> group.
</para></note>
'';
};
enableExtensionPack = mkEnableOption "VirtualBox extension pack" // {
description = ''
Whether to install the Oracle Extension Pack for VirtualBox.
<important><para>
You must set <literal>nixpkgs.config.allowUnfree = true</literal> in
order to use this. This requires you accept the VirtualBox PUEL.
</para></important>
'';
};
package = mkOption {
type = types.package;
default = pkgs.virtualbox;
defaultText = literalExpression "pkgs.virtualbox";
description = ''
Which VirtualBox package to use.
'';
};
addNetworkInterface = mkOption {
type = types.bool;
default = true;
description = ''
Automatically set up a vboxnet0 host-only network interface.
'';
};
enableHardening = mkOption {
type = types.bool;
default = true;
description = ''
Enable hardened VirtualBox, which ensures that only the binaries in the
system path get access to the devices exposed by the kernel modules
instead of all users in the vboxusers group.
<important><para>
Disabling this can put your system's security at risk, as local users
in the vboxusers group can tamper with the VirtualBox device files.
</para></important>
'';
};
headless = mkOption {
type = types.bool;
default = false;
description = ''
Use VirtualBox installation without GUI and Qt dependency. Useful to enable on servers
and when virtual machines are controlled only via SSH.
'';
};
enableWebService = mkOption {
type = types.bool;
default = false;
description = ''
Build VirtualBox web service tool (vboxwebsrv) to allow managing VMs via other webpage frontend tools. Useful for headless servers.
'';
};
};
config = mkIf cfg.enable (mkMerge [{
warnings = mkIf (config.nixpkgs.config.virtualbox.enableExtensionPack or false)
["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"];
boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
boot.extraModulePackages = [ kernelModules ];
environment.systemPackages = [ virtualbox ];
security.wrappers = let
mkSuid = program: {
source = "${virtualbox}/libexec/virtualbox/${program}";
owner = "root";
group = "vboxusers";
setuid = true;
};
in mkIf cfg.enableHardening
(builtins.listToAttrs (map (x: { name = x; value = mkSuid x; }) [
"VBoxHeadless"
"VBoxNetAdpCtl"
"VBoxNetDHCP"
"VBoxNetNAT"
"VBoxSDL"
"VBoxVolInfo"
"VirtualBoxVM"
]));
users.groups.vboxusers.gid = config.ids.gids.vboxusers;
services.udev.extraRules =
''
KERNEL=="vboxdrv", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
KERNEL=="vboxdrvu", OWNER="root", GROUP="root", MODE="0666", TAG+="systemd"
KERNEL=="vboxnetctl", OWNER="root", GROUP="vboxusers", MODE="0660", TAG+="systemd"
SUBSYSTEM=="usb_device", ACTION=="add", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
SUBSYSTEM=="usb", ACTION=="add", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh $major $minor $attr{bDeviceClass}"
SUBSYSTEM=="usb_device", ACTION=="remove", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
SUBSYSTEM=="usb", ACTION=="remove", ENV{DEVTYPE}=="usb_device", RUN+="${virtualbox}/libexec/virtualbox/VBoxCreateUSBNode.sh --remove $major $minor"
'';
# Since we lack the right setuid/setcap binaries, set up a host-only network by default.
} (mkIf cfg.addNetworkInterface {
systemd.services.vboxnet0 =
{ description = "VirtualBox vboxnet0 Interface";
requires = [ "dev-vboxnetctl.device" ];
after = [ "dev-vboxnetctl.device" ];
wantedBy = [ "network.target" "sys-subsystem-net-devices-vboxnet0.device" ];
path = [ virtualbox ];
serviceConfig.RemainAfterExit = true;
serviceConfig.Type = "oneshot";
serviceConfig.PrivateTmp = true;
environment.VBOX_USER_HOME = "/tmp";
script =
''
if ! [ -e /sys/class/net/vboxnet0 ]; then
VBoxManage hostonlyif create
cat /tmp/VBoxSVC.log >&2
fi
'';
postStop =
''
VBoxManage hostonlyif remove vboxnet0
'';
};
networking.interfaces.vboxnet0.ipv4.addresses = [{ address = "192.168.56.1"; prefixLength = 24; }];
# Make sure NetworkManager won't assume this interface being up
# means we have internet access.
networking.networkmanager.unmanaged = ["vboxnet0"];
}) (mkIf config.networking.useNetworkd {
systemd.network.networks."40-vboxnet0".extraConfig = ''
[Link]
RequiredForOnline=no
'';
})
]);
}

View file

@ -0,0 +1,215 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualbox;
in {
options = {
virtualbox = {
baseImageSize = mkOption {
type = with types; either (enum [ "auto" ]) int;
default = "auto";
example = 50 * 1024;
description = ''
The size of the VirtualBox base image in MiB.
'';
};
baseImageFreeSpace = mkOption {
type = with types; int;
default = 30 * 1024;
description = ''
Free space in the VirtualBox base image in MiB.
'';
};
memorySize = mkOption {
type = types.int;
default = 1536;
description = ''
The amount of RAM the VirtualBox appliance can use in MiB.
'';
};
vmDerivationName = mkOption {
type = types.str;
default = "nixos-ova-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
description = ''
The name of the derivation for the VirtualBox appliance.
'';
};
vmName = mkOption {
type = types.str;
default = "NixOS ${config.system.nixos.label} (${pkgs.stdenv.hostPlatform.system})";
description = ''
The name of the VirtualBox appliance.
'';
};
vmFileName = mkOption {
type = types.str;
default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.ova";
description = ''
The file name of the VirtualBox appliance.
'';
};
params = mkOption {
type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
example = {
audio = "alsa";
rtcuseutc = "on";
usb = "off";
};
description = ''
Parameters passed to the Virtualbox appliance.
Run <literal>VBoxManage modifyvm --help</literal> to see more options.
'';
};
exportParams = mkOption {
type = with types; listOf (oneOf [ str int bool (listOf str) ]);
example = [
"--vsys" "0" "--vendor" "ACME Inc."
];
default = [];
description = ''
Parameters passed to the Virtualbox export command.
Run <literal>VBoxManage export --help</literal> to see more options.
'';
};
extraDisk = mkOption {
description = ''
Optional extra disk/hdd configuration.
The disk will be an 'ext4' partition on a separate VMDK file.
'';
default = null;
example = {
label = "storage";
mountPoint = "/home/demo/storage";
size = 100 * 1024;
};
type = types.nullOr (types.submodule {
options = {
size = mkOption {
type = types.int;
description = "Size in MiB";
};
label = mkOption {
type = types.str;
default = "vm-extra-storage";
description = "Label for the disk partition";
};
mountPoint = mkOption {
type = types.str;
description = "Path where to mount this disk.";
};
};
});
};
};
};
config = {
virtualbox.params = mkMerge [
(mapAttrs (name: mkDefault) {
acpi = "on";
vram = 32;
nictype1 = "virtio";
nic1 = "nat";
audiocontroller = "ac97";
audio = "alsa";
audioout = "on";
graphicscontroller = "vmsvga";
rtcuseutc = "on";
usb = "on";
usbehci = "on";
mouse = "usbtablet";
})
(mkIf (pkgs.stdenv.hostPlatform.system == "i686-linux") { pae = "on"; })
];
system.build.virtualBoxOVA = import ../../lib/make-disk-image.nix {
name = cfg.vmDerivationName;
inherit pkgs lib config;
partitionTableType = "legacy";
diskSize = cfg.baseImageSize;
additionalSpace = "${toString cfg.baseImageFreeSpace}M";
postVM =
''
export HOME=$PWD
export PATH=${pkgs.virtualbox}/bin:$PATH
echo "creating VirtualBox pass-through disk wrapper (no copying involved)..."
VBoxManage internalcommands createrawvmdk -filename disk.vmdk -rawdisk $diskImage
${optionalString (cfg.extraDisk != null) ''
echo "creating extra disk: data-disk.raw"
dataDiskImage=data-disk.raw
truncate -s ${toString cfg.extraDisk.size}M $dataDiskImage
parted --script $dataDiskImage -- \
mklabel msdos \
mkpart primary ext4 1MiB -1
eval $(partx $dataDiskImage -o START,SECTORS --nr 1 --pairs)
mkfs.ext4 -F -L ${cfg.extraDisk.label} $dataDiskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
echo "creating extra disk: data-disk.vmdk"
VBoxManage internalcommands createrawvmdk -filename data-disk.vmdk -rawdisk $dataDiskImage
''}
echo "creating VirtualBox VM..."
vmName="${cfg.vmName}";
VBoxManage createvm --name "$vmName" --register \
--ostype ${if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "Linux26_64" else "Linux26"}
VBoxManage modifyvm "$vmName" \
--memory ${toString cfg.memorySize} \
${lib.cli.toGNUCommandLineShell { } cfg.params}
VBoxManage storagectl "$vmName" --name SATA --add sata --portcount 4 --bootable on --hostiocache on
VBoxManage storageattach "$vmName" --storagectl SATA --port 0 --device 0 --type hdd \
--medium disk.vmdk
${optionalString (cfg.extraDisk != null) ''
VBoxManage storageattach "$vmName" --storagectl SATA --port 1 --device 0 --type hdd \
--medium data-disk.vmdk
''}
echo "exporting VirtualBox VM..."
mkdir -p $out
fn="$out/${cfg.vmFileName}"
VBoxManage export "$vmName" --output "$fn" --options manifest ${escapeShellArgs cfg.exportParams}
rm -v $diskImage
mkdir -p $out/nix-support
echo "file ova $fn" >> $out/nix-support/hydra-build-products
'';
};
fileSystems = {
"/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
} // (lib.optionalAttrs (cfg.extraDisk != null) {
${cfg.extraDisk.mountPoint} = {
device = "/dev/disk/by-label/" + cfg.extraDisk.label;
autoResize = true;
fsType = "ext4";
};
});
boot.growPartition = true;
boot.loader.grub.device = "/dev/sda";
swapDevices = [{
device = "/var/swap";
size = 2048;
}];
virtualisation.virtualbox.guest.enable = true;
};
}

View file

@ -0,0 +1,85 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.vmware.guest;
open-vm-tools = if cfg.headless then pkgs.open-vm-tools-headless else pkgs.open-vm-tools;
xf86inputvmmouse = pkgs.xorg.xf86inputvmmouse;
in
{
imports = [
(mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ])
];
options.virtualisation.vmware.guest = {
enable = mkEnableOption "VMWare Guest Support";
headless = mkOption {
type = types.bool;
default = false;
description = "Whether to disable X11-related features.";
};
};
config = mkIf cfg.enable {
assertions = [ {
assertion = pkgs.stdenv.hostPlatform.isx86;
message = "VMWare guest is not currently supported on ${pkgs.stdenv.hostPlatform.system}";
} ];
boot.initrd.availableKernelModules = [ "mptspi" ];
boot.initrd.kernelModules = [ "vmw_pvscsi" ];
environment.systemPackages = [ open-vm-tools ];
systemd.services.vmware =
{ description = "VMWare Guest Service";
wantedBy = [ "multi-user.target" ];
after = [ "display-manager.service" ];
unitConfig.ConditionVirtualization = "vmware";
serviceConfig.ExecStart = "${open-vm-tools}/bin/vmtoolsd";
};
# Mount the vmblock for drag-and-drop and copy-and-paste.
systemd.mounts = mkIf (!cfg.headless) [
{
description = "VMware vmblock fuse mount";
documentation = [ "https://github.com/vmware/open-vm-tools/blob/master/open-vm-tools/vmblock-fuse/design.txt" ];
unitConfig.ConditionVirtualization = "vmware";
what = "${open-vm-tools}/bin/vmware-vmblock-fuse";
where = "/run/vmblock-fuse";
type = "fuse";
options = "subtype=vmware-vmblock,default_permissions,allow_other";
wantedBy = [ "multi-user.target" ];
}
];
security.wrappers.vmware-user-suid-wrapper = mkIf (!cfg.headless) {
setuid = true;
owner = "root";
group = "root";
source = "${open-vm-tools}/bin/vmware-user-suid-wrapper";
};
environment.etc.vmware-tools.source = "${open-vm-tools}/etc/vmware-tools/*";
services.xserver = mkIf (!cfg.headless) {
modules = [ xf86inputvmmouse ];
config = ''
Section "InputClass"
Identifier "VMMouse"
MatchDevicePath "/dev/input/event*"
MatchProduct "ImPS/2 Generic Wheel Mouse"
Driver "vmmouse"
EndSection
'';
displayManager.sessionCommands = ''
${open-vm-tools}/bin/vmware-user-suid-wrapper
'';
};
services.udev.packages = [ open-vm-tools ];
};
}

View file

@ -0,0 +1,166 @@
{ config, pkgs, lib, ... }:
let
cfg = config.virtualisation.vmware.host;
wrapperDir = "/run/vmware/bin"; # Perfectly fits as /usr/local/bin
parentWrapperDir = dirOf wrapperDir;
vmwareWrappers = # Needed as hardcoded paths workaround
let mkVmwareSymlink =
program:
''
ln -s "${config.security.wrapperDir}/${program}" $wrapperDir/${program}
'';
in
[
(mkVmwareSymlink "pkexec")
(mkVmwareSymlink "mount")
(mkVmwareSymlink "umount")
];
in
{
options = with lib; {
virtualisation.vmware.host = {
enable = mkEnableOption "VMware" // {
description = ''
This enables VMware host virtualisation for running VMs.
<important><para>
<literal>vmware-vmx</literal> will cause kcompactd0 due to
<literal>Transparent Hugepages</literal> feature in kernel.
Apply <literal>[ "transparent_hugepage=never" ]</literal> in
option <option>boot.kernelParams</option> to disable them.
</para></important>
<note><para>
If that didn't work disable <literal>TRANSPARENT_HUGEPAGE</literal>,
<literal>COMPACTION</literal> configs and recompile kernel.
</para></note>
'';
};
package = mkOption {
type = types.package;
default = pkgs.vmware-workstation;
defaultText = literalExpression "pkgs.vmware-workstation";
description = "VMware host virtualisation package to use";
};
extraPackages = mkOption {
type = with types; listOf package;
default = with pkgs; [ ];
description = "Extra packages to be used with VMware host.";
example = "with pkgs; [ ntfs3g ]";
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = "Add extra config to /etc/vmware/config";
example = ''
# Allow unsupported device's OpenGL and Vulkan acceleration for guest vGPU
mks.gl.allowUnsupportedDrivers = "TRUE"
mks.vk.allowUnsupportedDevices = "TRUE"
'';
};
};
};
config = lib.mkIf cfg.enable {
boot.extraModulePackages = [ config.boot.kernelPackages.vmware ];
boot.extraModprobeConfig = "alias char-major-10-229 fuse";
boot.kernelModules = [ "vmw_pvscsi" "vmw_vmci" "vmmon" "vmnet" "fuse" ];
environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
services.printing.drivers = [ cfg.package ];
environment.etc."vmware/config".text = ''
${builtins.readFile "${cfg.package}/etc/vmware/config"}
${cfg.extraConfig}
'';
environment.etc."vmware/bootstrap".source = "${cfg.package}/etc/vmware/bootstrap";
environment.etc."vmware/icu".source = "${cfg.package}/etc/vmware/icu";
environment.etc."vmware-installer".source = "${cfg.package}/etc/vmware-installer";
# SUID wrappers
security.wrappers = {
vmware-vmx = {
setuid = true;
owner = "root";
group = "root";
source = "${cfg.package}/lib/vmware/bin/.vmware-vmx-wrapped";
};
};
###### wrappers activation script
system.activationScripts.vmwareWrappers =
lib.stringAfter [ "specialfs" "users" ]
''
mkdir -p "${parentWrapperDir}"
chmod 755 "${parentWrapperDir}"
# We want to place the tmpdirs for the wrappers to the parent dir.
wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
chmod a+rx "$wrapperDir"
${lib.concatStringsSep "\n" (vmwareWrappers)}
if [ -L ${wrapperDir} ]; then
# Atomically replace the symlink
# See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
old=$(readlink -f ${wrapperDir})
if [ -e "${wrapperDir}-tmp" ]; then
rm --force --recursive "${wrapperDir}-tmp"
fi
ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
rm --force --recursive "$old"
else
# For initial setup
ln --symbolic "$wrapperDir" "${wrapperDir}"
fi
'';
# Services
systemd.services."vmware-authdlauncher" = {
description = "VMware Authentification Daemon";
serviceConfig = {
Type = "forking";
ExecStart = [ "${cfg.package}/bin/vmware-authdlauncher" ];
};
wantedBy = [ "multi-user.target" ];
};
systemd.services."vmware-networks-configuration" = {
description = "VMware Networks Configuration Generation";
unitConfig.ConditionPathExists = "!/etc/vmware/networking";
serviceConfig = {
UMask = "0077";
ExecStart = [
"${cfg.package}/bin/vmware-networks --postinstall vmware-player,0,1"
];
Type = "oneshot";
RemainAfterExit = "yes";
};
wantedBy = [ "multi-user.target" ];
};
systemd.services."vmware-networks" = {
description = "VMware Networks";
after = [ "vmware-networks-configuration.service" ];
requires = [ "vmware-networks-configuration.service" ];
serviceConfig = {
Type = "forking";
ExecCondition = [ "${pkgs.kmod}/bin/modprobe vmnet" ];
ExecStart = [ "${cfg.package}/bin/vmware-networks --start" ];
ExecStop = [ "${cfg.package}/bin/vmware-networks --stop" ];
};
wantedBy = [ "multi-user.target" ];
};
systemd.services."vmware-usbarbitrator" = {
description = "VMware USB Arbitrator";
serviceConfig = {
ExecStart = [ "${cfg.package}/bin/vmware-usbarbitrator -f" ];
};
wantedBy = [ "multi-user.target" ];
};
};
}

View file

@ -0,0 +1,91 @@
{ config, pkgs, lib, ... }:
with lib;
let
boolToStr = value: if value then "on" else "off";
cfg = config.vmware;
subformats = [
"monolithicSparse"
"monolithicFlat"
"twoGbMaxExtentSparse"
"twoGbMaxExtentFlat"
"streamOptimized"
];
in {
options = {
vmware = {
baseImageSize = mkOption {
type = with types; either (enum [ "auto" ]) int;
default = "auto";
example = 2048;
description = ''
The size of the VMWare base image in MiB.
'';
};
vmDerivationName = mkOption {
type = types.str;
default = "nixos-vmware-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
description = ''
The name of the derivation for the VMWare appliance.
'';
};
vmFileName = mkOption {
type = types.str;
default = "nixos-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.vmdk";
description = ''
The file name of the VMWare appliance.
'';
};
vmSubformat = mkOption {
type = types.enum subformats;
default = "monolithicSparse";
description = "Specifies which VMDK subformat to use.";
};
vmCompat6 = mkOption {
type = types.bool;
default = false;
example = true;
description = "Create a VMDK version 6 image (instead of version 4).";
};
};
};
config = {
system.build.vmwareImage = import ../../lib/make-disk-image.nix {
name = cfg.vmDerivationName;
postVM = ''
${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -o compat6=${boolToStr cfg.vmCompat6},subformat=${cfg.vmSubformat} -O vmdk $diskImage $out/${cfg.vmFileName}
rm $diskImage
'';
format = "raw";
diskSize = cfg.baseImageSize;
partitionTableType = "efi";
inherit config lib pkgs;
};
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
boot.growPartition = true;
boot.loader.grub = {
version = 2;
device = "nodev";
efiSupport = true;
efiInstallAsRemovable = true;
};
virtualisation.vmware.guest.enable = true;
};
}

View file

@ -0,0 +1,75 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.waydroid;
kernelPackages = config.boot.kernelPackages;
waydroidGbinderConf = pkgs.writeText "waydroid.conf" ''
[Protocol]
/dev/binder = aidl2
/dev/vndbinder = aidl2
/dev/hwbinder = hidl
[ServiceManager]
/dev/binder = aidl2
/dev/vndbinder = aidl2
/dev/hwbinder = hidl
'';
in
{
options.virtualisation.waydroid = {
enable = mkEnableOption "Waydroid";
};
config = mkIf cfg.enable {
assertions = singleton {
assertion = versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.18";
message = "Waydroid needs user namespace support to work properly";
};
system.requiredKernelConfig = with config.lib.kernelConfig; [
(isEnabled "ANDROID_BINDER_IPC")
(isEnabled "ANDROID_BINDERFS")
(isEnabled "ASHMEM")
];
/* NOTE: we always enable this flag even if CONFIG_PSI_DEFAULT_DISABLED is not on
as reading the kernel config is not always possible and on kernels where it's
already on it will be no-op
*/
boot.kernelParams = [ "psi=1" ];
environment.etc."gbinder.d/waydroid.conf".source = waydroidGbinderConf;
environment.systemPackages = with pkgs; [ waydroid ];
networking.firewall.trustedInterfaces = [ "waydroid0" ];
virtualisation.lxc.enable = true;
systemd.services.waydroid-container = {
description = "Waydroid Container";
wantedBy = [ "multi-user.target" ];
unitConfig = {
ConditionPathExists = "/var/lib/waydroid/lxc/waydroid";
};
serviceConfig = {
ExecStart = "${pkgs.waydroid}/bin/waydroid container start";
ExecStop = "${pkgs.waydroid}/bin/waydroid container stop";
ExecStopPost = "${pkgs.waydroid}/bin/waydroid session stop";
};
};
systemd.tmpfiles.rules = [
"d /var/lib/misc 0755 root root -" # for dnsmasq.leases
];
};
}

View file

@ -0,0 +1,52 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.xe-guest-utilities;
in {
options = {
services.xe-guest-utilities = {
enable = mkEnableOption "the Xen guest utilities daemon";
};
};
config = mkIf cfg.enable {
services.udev.packages = [ pkgs.xe-guest-utilities ];
systemd.tmpfiles.rules = [ "d /run/xenstored 0755 - - -" ];
systemd.services.xe-daemon = {
description = "xen daemon file";
wantedBy = [ "multi-user.target" ];
after = [ "xe-linux-distribution.service" ];
requires = [ "proc-xen.mount" ];
path = [ pkgs.coreutils pkgs.iproute2 ];
serviceConfig = {
PIDFile = "/run/xe-daemon.pid";
ExecStart = "${pkgs.xe-guest-utilities}/bin/xe-daemon -p /run/xe-daemon.pid";
ExecStop = "${pkgs.procps}/bin/pkill -TERM -F /run/xe-daemon.pid";
};
};
systemd.services.xe-linux-distribution = {
description = "xen linux distribution service";
wantedBy = [ "multi-user.target" ];
before = [ "xend.service" ];
path = [ pkgs.xe-guest-utilities pkgs.coreutils pkgs.gawk pkgs.gnused ];
serviceConfig = {
Type = "simple";
RemainAfterExit = "yes";
ExecStart = "${pkgs.xe-guest-utilities}/bin/xe-linux-distribution /var/cache/xe-linux-distribution";
};
};
systemd.mounts = [
{ description = "Mount /proc/xen files";
what = "xenfs";
where = "/proc/xen";
type = "xenfs";
unitConfig = {
ConditionPathExists = "/proc/xen";
RefuseManualStop = "true";
};
}
];
};
}

View file

@ -0,0 +1,453 @@
# Xen hypervisor (Dom0) support.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.virtualisation.xen;
in
{
imports = [
(mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.")
(mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ])
];
###### interface
options = {
virtualisation.xen.enable =
mkOption {
default = false;
type = types.bool;
description =
''
Setting this option enables the Xen hypervisor, a
virtualisation technology that allows multiple virtual
machines, known as <emphasis>domains</emphasis>, to run
concurrently on the physical machine. NixOS runs as the
privileged <emphasis>Domain 0</emphasis>. This option
requires a reboot to take effect.
'';
};
virtualisation.xen.package = mkOption {
type = types.package;
defaultText = literalExpression "pkgs.xen";
example = literalExpression "pkgs.xen-light";
description = ''
The package used for Xen binary.
'';
relatedPackages = [ "xen" "xen-light" ];
};
virtualisation.xen.package-qemu = mkOption {
type = types.package;
defaultText = literalExpression "pkgs.xen";
example = literalExpression "pkgs.qemu_xen-light";
description = ''
The package with qemu binaries for dom0 qemu and xendomains.
'';
relatedPackages = [ "xen"
{ name = "qemu_xen-light"; comment = "For use with pkgs.xen-light."; }
];
};
virtualisation.xen.bootParams =
mkOption {
default = [];
type = types.listOf types.str;
description =
''
Parameters passed to the Xen hypervisor at boot time.
'';
};
virtualisation.xen.domain0MemorySize =
mkOption {
default = 0;
example = 512;
type = types.addCheck types.int (n: n >= 0);
description =
''
Amount of memory (in MiB) allocated to Domain 0 on boot.
If set to 0, all memory is assigned to Domain 0.
'';
};
virtualisation.xen.bridge = {
name = mkOption {
default = "xenbr0";
type = types.str;
description = ''
Name of bridge the Xen domUs connect to.
'';
};
address = mkOption {
type = types.str;
default = "172.16.0.1";
description = ''
IPv4 address of the bridge.
'';
};
prefixLength = mkOption {
type = types.addCheck types.int (n: n >= 0 && n <= 32);
default = 16;
description = ''
Subnet mask of the bridge interface, specified as the number of
bits in the prefix (<literal>24</literal>).
A DHCP server will provide IP addresses for the whole, remaining
subnet.
'';
};
forwardDns = mkOption {
type = types.bool;
default = false;
description = ''
If set to <literal>true</literal>, the DNS queries from the
hosts connected to the bridge will be forwarded to the DNS
servers specified in /etc/resolv.conf .
'';
};
};
virtualisation.xen.stored =
mkOption {
type = types.path;
description =
''
Xen Store daemon to use. Defaults to oxenstored of the xen package.
'';
};
virtualisation.xen.domains = {
extraConfig = mkOption {
type = types.lines;
default = "";
description =
''
Options defined here will override the defaults for xendomains.
The default options can be seen in the file included from
/etc/default/xendomains.
'';
};
};
virtualisation.xen.trace = mkEnableOption "Xen tracing";
};
###### implementation
config = mkIf cfg.enable {
assertions = [ {
assertion = pkgs.stdenv.isx86_64;
message = "Xen currently not supported on ${pkgs.stdenv.hostPlatform.system}";
} {
assertion = config.boot.loader.grub.enable && (config.boot.loader.grub.efiSupport == false);
message = "Xen currently does not support EFI boot";
} ];
virtualisation.xen.package = mkDefault pkgs.xen;
virtualisation.xen.package-qemu = mkDefault pkgs.xen;
virtualisation.xen.stored = mkDefault "${cfg.package}/bin/oxenstored";
environment.systemPackages = [ cfg.package ];
boot.kernelModules =
[ "xen-evtchn" "xen-gntdev" "xen-gntalloc" "xen-blkback" "xen-netback"
"xen-pciback" "evtchn" "gntdev" "netbk" "blkbk" "xen-scsibk"
"usbbk" "pciback" "xen-acpi-processor" "blktap2" "tun" "netxen_nic"
"xen_wdt" "xen-acpi-processor" "xen-privcmd" "xen-scsiback"
"xenfs"
];
# The xenfs module is needed in system.activationScripts.xen, but
# the modprobe command there fails silently. Include xenfs in the
# initrd as a work around.
boot.initrd.kernelModules = [ "xenfs" ];
# The radeonfb kernel module causes the screen to go black as soon
# as it's loaded, so don't load it.
boot.blacklistedKernelModules = [ "radeonfb" ];
# Increase the number of loopback devices from the default (8),
# which is way too small because every VM virtual disk requires a
# loopback device.
boot.extraModprobeConfig =
''
options loop max_loop=64
'';
virtualisation.xen.bootParams = [] ++
optionals cfg.trace [ "loglvl=all" "guest_loglvl=all" ] ++
optional (cfg.domain0MemorySize != 0) "dom0_mem=${toString cfg.domain0MemorySize}M";
system.extraSystemBuilderCmds =
''
ln -s ${cfg.package}/boot/xen.gz $out/xen.gz
echo "${toString cfg.bootParams}" > $out/xen-params
'';
# Mount the /proc/xen pseudo-filesystem.
system.activationScripts.xen =
''
if [ -d /proc/xen ]; then
${pkgs.kmod}/bin/modprobe xenfs 2> /dev/null
${pkgs.util-linux}/bin/mountpoint -q /proc/xen || \
${pkgs.util-linux}/bin/mount -t xenfs none /proc/xen
fi
'';
# Domain 0 requires a pvops-enabled kernel.
system.requiredKernelConfig = with config.lib.kernelConfig;
[ (isYes "XEN")
(isYes "X86_IO_APIC")
(isYes "ACPI")
(isYes "XEN_DOM0")
(isYes "PCI_XEN")
(isYes "XEN_DEV_EVTCHN")
(isYes "XENFS")
(isYes "XEN_COMPAT_XENFS")
(isYes "XEN_SYS_HYPERVISOR")
(isYes "XEN_GNTDEV")
(isYes "XEN_BACKEND")
(isModule "XEN_NETDEV_BACKEND")
(isModule "XEN_BLKDEV_BACKEND")
(isModule "XEN_PCIDEV_BACKEND")
(isYes "XEN_BALLOON")
(isYes "XEN_SCRUB_PAGES")
];
environment.etc =
{
"xen/xl.conf".source = "${cfg.package}/etc/xen/xl.conf";
"xen/scripts".source = "${cfg.package}/etc/xen/scripts";
"default/xendomains".text = ''
source ${cfg.package}/etc/default/xendomains
${cfg.domains.extraConfig}
'';
}
// optionalAttrs (builtins.compareVersions cfg.package.version "4.10" >= 0) {
# in V 4.10 oxenstored requires /etc/xen/oxenstored.conf to start
"xen/oxenstored.conf".source = "${cfg.package}/etc/xen/oxenstored.conf";
};
# Xen provides udev rules.
services.udev.packages = [ cfg.package ];
services.udev.path = [ pkgs.bridge-utils pkgs.iproute2 ];
systemd.services.xen-store = {
description = "Xen Store Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "xen-store.socket" ];
requires = [ "xen-store.socket" ];
preStart = ''
export XENSTORED_ROOTDIR="/var/lib/xenstored"
rm -f "$XENSTORED_ROOTDIR"/tdb* &>/dev/null
mkdir -p /var/run
mkdir -p /var/log/xen # Running xl requires /var/log/xen and /var/lib/xen,
mkdir -p /var/lib/xen # so we create them here unconditionally.
grep -q control_d /proc/xen/capabilities
'';
serviceConfig = if (builtins.compareVersions cfg.package.version "4.8" < 0) then
{ ExecStart = ''
${cfg.stored}${optionalString cfg.trace " -T /var/log/xen/xenstored-trace.log"} --no-fork
'';
} else {
ExecStart = ''
${cfg.package}/etc/xen/scripts/launch-xenstore
'';
Type = "notify";
RemainAfterExit = true;
NotifyAccess = "all";
};
postStart = ''
${optionalString (builtins.compareVersions cfg.package.version "4.8" < 0) ''
time=0
timeout=30
# Wait for xenstored to actually come up, timing out after 30 seconds
while [ $time -lt $timeout ] && ! `${cfg.package}/bin/xenstore-read -s / >/dev/null 2>&1` ; do
time=$(($time+1))
sleep 1
done
# Exit if we timed out
if ! [ $time -lt $timeout ] ; then
echo "Could not start Xenstore Daemon"
exit 1
fi
''}
echo "executing xen-init-dom0"
${cfg.package}/lib/xen/bin/xen-init-dom0
'';
};
systemd.sockets.xen-store = {
description = "XenStore Socket for userspace API";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = [ "/var/run/xenstored/socket" "/var/run/xenstored/socket_ro" ];
SocketMode = "0660";
SocketUser = "root";
SocketGroup = "root";
};
};
systemd.services.xen-console = {
description = "Xen Console Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "xen-store.service" ];
requires = [ "xen-store.service" ];
preStart = ''
mkdir -p /var/run/xen
${optionalString cfg.trace "mkdir -p /var/log/xen"}
grep -q control_d /proc/xen/capabilities
'';
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/xenconsoled\
${optionalString ((builtins.compareVersions cfg.package.version "4.8" >= 0)) " -i"}\
${optionalString cfg.trace " --log=all --log-dir=/var/log/xen"}
'';
};
};
systemd.services.xen-qemu = {
description = "Xen Qemu Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "xen-console.service" ];
requires = [ "xen-store.service" ];
serviceConfig.ExecStart = ''
${cfg.package-qemu}/${cfg.package-qemu.qemu-system-i386} \
-xen-attach -xen-domid 0 -name dom0 -M xenpv \
-nographic -monitor /dev/null -serial /dev/null -parallel /dev/null
'';
};
systemd.services.xen-watchdog = {
description = "Xen Watchdog Daemon";
wantedBy = [ "multi-user.target" ];
after = [ "xen-qemu.service" "xen-domains.service" ];
serviceConfig.ExecStart = "${cfg.package}/bin/xenwatchdogd 30 15";
serviceConfig.Type = "forking";
serviceConfig.RestartSec = "1";
serviceConfig.Restart = "on-failure";
};
systemd.services.xen-bridge = {
description = "Xen bridge";
wantedBy = [ "multi-user.target" ];
before = [ "xen-domains.service" ];
preStart = ''
mkdir -p /var/run/xen
touch /var/run/xen/dnsmasq.pid
touch /var/run/xen/dnsmasq.etherfile
touch /var/run/xen/dnsmasq.leasefile
IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Usable\ range`
export XEN_BRIDGE_IP_RANGE_START="${"\${data[1]//[[:blank:]]/}"}"
export XEN_BRIDGE_IP_RANGE_END="${"\${data[2]//[[:blank:]]/}"}"
IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ mask`
export XEN_BRIDGE_NETMASK="${"\${data[1]//[[:blank:]]/}"}"
echo "${cfg.bridge.address} host gw dns" > /var/run/xen/dnsmasq.hostsfile
cat <<EOF > /var/run/xen/dnsmasq.conf
no-daemon
pid-file=/var/run/xen/dnsmasq.pid
interface=${cfg.bridge.name}
except-interface=lo
bind-interfaces
auth-zone=xen.local,$XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength}
domain=xen.local
addn-hosts=/var/run/xen/dnsmasq.hostsfile
expand-hosts
strict-order
no-hosts
bogus-priv
${optionalString (!cfg.bridge.forwardDns) ''
no-resolv
no-poll
auth-server=dns.xen.local,${cfg.bridge.name}
''}
filterwin2k
clear-on-reload
domain-needed
dhcp-hostsfile=/var/run/xen/dnsmasq.etherfile
dhcp-authoritative
dhcp-range=$XEN_BRIDGE_IP_RANGE_START,$XEN_BRIDGE_IP_RANGE_END
dhcp-no-override
no-ping
dhcp-leasefile=/var/run/xen/dnsmasq.leasefile
EOF
# DHCP
${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
# DNS
${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
${pkgs.iptables}/bin/iptables -w -I INPUT -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
${pkgs.bridge-utils}/bin/brctl addbr ${cfg.bridge.name}
${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} ${cfg.bridge.address}
${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} netmask $XEN_BRIDGE_NETMASK
${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} up
'';
serviceConfig.ExecStart = "${pkgs.dnsmasq}/bin/dnsmasq --conf-file=/var/run/xen/dnsmasq.conf";
postStop = ''
IFS='-' read -a data <<< `${pkgs.sipcalc}/bin/sipcalc ${cfg.bridge.address}/${toString cfg.bridge.prefixLength} | grep Network\ address`
export XEN_BRIDGE_NETWORK_ADDRESS="${"\${data[1]//[[:blank:]]/}"}"
${pkgs.inetutils}/bin/ifconfig ${cfg.bridge.name} down
${pkgs.bridge-utils}/bin/brctl delbr ${cfg.bridge.name}
# DNS
${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p udp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p tcp -d ${cfg.bridge.address} --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT
# DHCP
${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p udp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
${pkgs.iptables}/bin/iptables -w -D INPUT -i ${cfg.bridge.name} -p tcp -s $XEN_BRIDGE_NETWORK_ADDRESS/${toString cfg.bridge.prefixLength} --sport 68 --dport 67 -j ACCEPT
'';
};
systemd.services.xen-domains = {
description = "Xen domains - automatically starts, saves and restores Xen domains";
wantedBy = [ "multi-user.target" ];
after = [ "xen-bridge.service" "xen-qemu.service" ];
requires = [ "xen-bridge.service" "xen-qemu.service" ];
## To prevent a race between dhcpcd and xend's bridge setup script
## (which renames eth* to peth* and recreates eth* as a virtual
## device), start dhcpcd after xend.
before = [ "dhcpd.service" ];
restartIfChanged = false;
serviceConfig.RemainAfterExit = "yes";
path = [ cfg.package cfg.package-qemu ];
environment.XENDOM_CONFIG = "${cfg.package}/etc/sysconfig/xendomains";
preStart = "mkdir -p /var/lock/subsys -m 755";
serviceConfig.ExecStart = "${cfg.package}/etc/init.d/xendomains start";
serviceConfig.ExecStop = "${cfg.package}/etc/init.d/xendomains stop";
};
};
}

View file

@ -0,0 +1,19 @@
# Common configuration for Xen DomU NixOS virtual machines.
{ ... }:
{
boot.loader.grub.version = 2;
boot.loader.grub.device = "nodev";
boot.initrd.kernelModules =
[ "xen-blkfront" "xen-tpmfront" "xen-kbdfront" "xen-fbfront"
"xen-netfront" "xen-pcifront" "xen-scsifront"
];
# Send syslog messages to the Xen console.
services.syslogd.tty = "hvc0";
# Don't run ntpd, since we should get the correct time from Dom0.
services.timesyncd.enable = false;
}