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:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
444
nixos/modules/virtualisation/amazon-ec2-amis.nix
Normal file
444
nixos/modules/virtualisation/amazon-ec2-amis.nix
Normal 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
|
||||
178
nixos/modules/virtualisation/amazon-image.nix
Normal file
178
nixos/modules/virtualisation/amazon-image.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
87
nixos/modules/virtualisation/amazon-init.nix
Normal file
87
nixos/modules/virtualisation/amazon-init.nix
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
74
nixos/modules/virtualisation/amazon-options.nix
Normal file
74
nixos/modules/virtualisation/amazon-options.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
135
nixos/modules/virtualisation/anbox.nix
Normal file
135
nixos/modules/virtualisation/anbox.nix
Normal 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
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
49
nixos/modules/virtualisation/appvm.nix
Normal file
49
nixos/modules/virtualisation/appvm.nix
Normal 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" ];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
17
nixos/modules/virtualisation/azure-agent-entropy.patch
Normal file
17
nixos/modules/virtualisation/azure-agent-entropy.patch
Normal 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()
|
||||
194
nixos/modules/virtualisation/azure-agent.nix
Normal file
194
nixos/modules/virtualisation/azure-agent.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
3
nixos/modules/virtualisation/azure-bootstrap-blobs.nix
Normal file
3
nixos/modules/virtualisation/azure-bootstrap-blobs.nix
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"16.03" = "https://nixos.blob.core.windows.net/images/nixos-image-16.03.847.8688c17-x86_64-linux.vhd";
|
||||
}
|
||||
70
nixos/modules/virtualisation/azure-common.nix
Normal file
70
nixos/modules/virtualisation/azure-common.nix
Normal 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"
|
||||
|
||||
'';
|
||||
|
||||
}
|
||||
12
nixos/modules/virtualisation/azure-config-user.nix
Normal file
12
nixos/modules/virtualisation/azure-config-user.nix
Normal 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" ];
|
||||
}
|
||||
5
nixos/modules/virtualisation/azure-config.nix
Normal file
5
nixos/modules/virtualisation/azure-config.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{ modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [ "${modulesPath}/virtualisation/azure-image.nix" ];
|
||||
}
|
||||
71
nixos/modules/virtualisation/azure-image.nix
Normal file
71
nixos/modules/virtualisation/azure-image.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
}
|
||||
5
nixos/modules/virtualisation/azure-images.nix
Normal file
5
nixos/modules/virtualisation/azure-images.nix
Normal 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
|
||||
5
nixos/modules/virtualisation/brightbox-config.nix
Normal file
5
nixos/modules/virtualisation/brightbox-config.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{ modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [ "${modulesPath}/virtualisation/brightbox-image.nix" ];
|
||||
}
|
||||
166
nixos/modules/virtualisation/brightbox-image.nix
Normal file
166
nixos/modules/virtualisation/brightbox-image.nix
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
58
nixos/modules/virtualisation/build-vm.nix
Normal file
58
nixos/modules/virtualisation/build-vm.nix
Normal 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;
|
||||
}
|
||||
40
nixos/modules/virtualisation/cloudstack-config.nix
Normal file
40
nixos/modules/virtualisation/cloudstack-config.nix
Normal 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"]
|
||||
'';
|
||||
};
|
||||
}
|
||||
31
nixos/modules/virtualisation/container-config.nix
Normal file
31
nixos/modules/virtualisation/container-config.nix
Normal 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";
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
101
nixos/modules/virtualisation/containerd.nix
Normal file
101
nixos/modules/virtualisation/containerd.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
156
nixos/modules/virtualisation/containers.nix
Normal file
156
nixos/modules/virtualisation/containers.nix
Normal 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";
|
||||
};
|
||||
|
||||
}
|
||||
163
nixos/modules/virtualisation/cri-o.nix
Normal file
163
nixos/modules/virtualisation/cri-o.nix
Normal 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 ];
|
||||
};
|
||||
};
|
||||
}
|
||||
197
nixos/modules/virtualisation/digital-ocean-config.nix
Normal file
197
nixos/modules/virtualisation/digital-ocean-config.nix
Normal 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 ];
|
||||
}
|
||||
|
||||
70
nixos/modules/virtualisation/digital-ocean-image.nix
Normal file
70
nixos/modules/virtualisation/digital-ocean-image.nix
Normal 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 ];
|
||||
|
||||
}
|
||||
95
nixos/modules/virtualisation/digital-ocean-init.nix
Normal file
95
nixos/modules/virtualisation/digital-ocean-init.nix
Normal 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 ];
|
||||
}
|
||||
57
nixos/modules/virtualisation/docker-image.nix
Normal file
57
nixos/modules/virtualisation/docker-image.nix
Normal 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
|
||||
101
nixos/modules/virtualisation/docker-rootless.nix
Normal file
101
nixos/modules/virtualisation/docker-rootless.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
251
nixos/modules/virtualisation/docker.nix
Normal file
251
nixos/modules/virtualisation/docker.nix
Normal 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")
|
||||
];
|
||||
|
||||
}
|
||||
9
nixos/modules/virtualisation/ec2-amis.nix
Normal file
9
nixos/modules/virtualisation/ec2-amis.nix
Normal 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
|
||||
91
nixos/modules/virtualisation/ec2-data.nix
Normal file
91
nixos/modules/virtualisation/ec2-data.nix
Normal 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;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
77
nixos/modules/virtualisation/ec2-metadata-fetcher.nix
Normal file
77
nixos/modules/virtualisation/ec2-metadata-fetcher.nix
Normal 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
|
||||
''
|
||||
45
nixos/modules/virtualisation/ecs-agent.nix
Normal file
45
nixos/modules/virtualisation/ecs-agent.nix
Normal 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
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
17
nixos/modules/virtualisation/gce-images.nix
Normal file
17
nixos/modules/virtualisation/gce-images.nix
Normal 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
|
||||
102
nixos/modules/virtualisation/google-compute-config.nix
Normal file
102
nixos/modules/virtualisation/google-compute-config.nix
Normal 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
|
||||
'';
|
||||
}
|
||||
71
nixos/modules/virtualisation/google-compute-image.nix
Normal file
71
nixos/modules/virtualisation/google-compute-image.nix
Normal 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;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
3
nixos/modules/virtualisation/grow-partition.nix
Normal file
3
nixos/modules/virtualisation/grow-partition.nix
Normal 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"
|
||||
{ }
|
||||
66
nixos/modules/virtualisation/hyperv-guest.nix
Normal file
66
nixos/modules/virtualisation/hyperv-guest.nix
Normal 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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
71
nixos/modules/virtualisation/hyperv-image.nix
Normal file
71
nixos/modules/virtualisation/hyperv-image.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
30
nixos/modules/virtualisation/kubevirt.nix
Normal file
30
nixos/modules/virtualisation/kubevirt.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
}
|
||||
86
nixos/modules/virtualisation/kvmgt.nix
Normal file
86
nixos/modules/virtualisation/kvmgt.nix
Normal 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.<yourusername>.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 ];
|
||||
}
|
||||
413
nixos/modules/virtualisation/libvirtd.nix
Normal file
413
nixos/modules/virtualisation/libvirtd.nix
Normal 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;
|
||||
}
|
||||
});
|
||||
'';
|
||||
};
|
||||
}
|
||||
174
nixos/modules/virtualisation/lxc-container.nix
Normal file
174
nixos/modules/virtualisation/lxc-container.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
86
nixos/modules/virtualisation/lxc.nix
Normal file
86
nixos/modules/virtualisation/lxc.nix
Normal 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
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
45
nixos/modules/virtualisation/lxcfs.nix
Normal file
45
nixos/modules/virtualisation/lxcfs.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
182
nixos/modules/virtualisation/lxd.nix
Normal file
182
nixos/modules/virtualisation/lxd.nix
Normal 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" ];
|
||||
};
|
||||
}
|
||||
881
nixos/modules/virtualisation/nixos-containers.nix
Normal file
881
nixos/modules/virtualisation/nixos-containers.nix
Normal 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"
|
||||
];
|
||||
});
|
||||
}
|
||||
365
nixos/modules/virtualisation/oci-containers.nix
Normal file
365
nixos/modules/virtualisation/oci-containers.nix
Normal 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><ip>:<hostPort>:<containerPort></literal>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal><ip>::<containerPort></literal>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal><hostPort>:<containerPort></literal>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal><containerPort></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;
|
||||
})
|
||||
]);
|
||||
|
||||
}
|
||||
90
nixos/modules/virtualisation/openstack-config.nix
Normal file
90
nixos/modules/virtualisation/openstack-config.nix
Normal 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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
22
nixos/modules/virtualisation/openstack-metadata-fetcher.nix
Normal file
22
nixos/modules/virtualisation/openstack-metadata-fetcher.nix
Normal 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
|
||||
''
|
||||
71
nixos/modules/virtualisation/openstack-options.nix
Normal file
71
nixos/modules/virtualisation/openstack-options.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
145
nixos/modules/virtualisation/openvswitch.nix
Normal file
145
nixos/modules/virtualisation/openvswitch.nix
Normal 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 ];
|
||||
|
||||
}
|
||||
155
nixos/modules/virtualisation/parallels-guest.nix
Normal file
155
nixos/modules/virtualisation/parallels-guest.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
187
nixos/modules/virtualisation/podman/default.nix
Normal file
187
nixos/modules/virtualisation/podman/default.nix
Normal 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.
|
||||
'';
|
||||
}
|
||||
];
|
||||
}
|
||||
]);
|
||||
}
|
||||
36
nixos/modules/virtualisation/podman/dnsname.nix
Normal file
36
nixos/modules/virtualisation/podman/dnsname.nix
Normal 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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -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 ];
|
||||
}
|
||||
95
nixos/modules/virtualisation/podman/network-socket.nix
Normal file
95
nixos/modules/virtualisation/podman/network-socket.nix
Normal 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 ];
|
||||
}
|
||||
169
nixos/modules/virtualisation/proxmox-image.nix
Normal file
169
nixos/modules/virtualisation/proxmox-image.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
75
nixos/modules/virtualisation/proxmox-lxc.nix
Normal file
75
nixos/modules/virtualisation/proxmox-lxc.nix
Normal 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; }];
|
||||
|
||||
};
|
||||
}
|
||||
45
nixos/modules/virtualisation/qemu-guest-agent.nix
Normal file
45
nixos/modules/virtualisation/qemu-guest-agent.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
1052
nixos/modules/virtualisation/qemu-vm.nix
Normal file
1052
nixos/modules/virtualisation/qemu-vm.nix
Normal file
File diff suppressed because it is too large
Load diff
124
nixos/modules/virtualisation/railcar.nix
Normal file
124
nixos/modules/virtualisation/railcar.nix
Normal 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
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
26
nixos/modules/virtualisation/spice-usb-redirection.nix
Normal file
26
nixos/modules/virtualisation/spice-usb-redirection.nix
Normal 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 ];
|
||||
}
|
||||
58
nixos/modules/virtualisation/vagrant-guest.nix
Normal file
58
nixos/modules/virtualisation/vagrant-guest.nix
Normal 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;
|
||||
}
|
||||
60
nixos/modules/virtualisation/vagrant-virtualbox-image.nix
Normal file
60
nixos/modules/virtualisation/vagrant-virtualbox-image.nix
Normal 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 .
|
||||
'';
|
||||
}
|
||||
93
nixos/modules/virtualisation/virtualbox-guest.nix
Normal file
93
nixos/modules/virtualisation/virtualbox-guest.nix
Normal 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
|
||||
'';
|
||||
})]);
|
||||
|
||||
}
|
||||
168
nixos/modules/virtualisation/virtualbox-host.nix
Normal file
168
nixos/modules/virtualisation/virtualbox-host.nix
Normal 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
|
||||
'';
|
||||
})
|
||||
|
||||
]);
|
||||
}
|
||||
215
nixos/modules/virtualisation/virtualbox-image.nix
Normal file
215
nixos/modules/virtualisation/virtualbox-image.nix
Normal 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;
|
||||
|
||||
};
|
||||
}
|
||||
85
nixos/modules/virtualisation/vmware-guest.nix
Normal file
85
nixos/modules/virtualisation/vmware-guest.nix
Normal 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 ];
|
||||
};
|
||||
}
|
||||
166
nixos/modules/virtualisation/vmware-host.nix
Normal file
166
nixos/modules/virtualisation/vmware-host.nix
Normal 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" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
91
nixos/modules/virtualisation/vmware-image.nix
Normal file
91
nixos/modules/virtualisation/vmware-image.nix
Normal 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;
|
||||
};
|
||||
}
|
||||
75
nixos/modules/virtualisation/waydroid.nix
Normal file
75
nixos/modules/virtualisation/waydroid.nix
Normal 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
|
||||
];
|
||||
};
|
||||
|
||||
}
|
||||
52
nixos/modules/virtualisation/xe-guest-utilities.nix
Normal file
52
nixos/modules/virtualisation/xe-guest-utilities.nix
Normal 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";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
453
nixos/modules/virtualisation/xen-dom0.nix
Normal file
453
nixos/modules/virtualisation/xen-dom0.nix
Normal 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";
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
19
nixos/modules/virtualisation/xen-domU.nix
Normal file
19
nixos/modules/virtualisation/xen-domU.nix
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue