├── LICENSE ├── README.md ├── bin ├── helper │ ├── 8447.patch │ ├── getIp.nix │ ├── hasIp.nix │ ├── lxc-container.nix │ ├── nixcloud-container-create.sh │ ├── nixcloud-container-exists.sh │ ├── nixcloud-container-start.sh │ ├── nixcloud-container-update.sh │ └── patches.nix ├── nixcloud-container └── nixcloud-container-man ├── default.nix ├── example ├── autostartExample.nix ├── basicExample.nix ├── fullExample.nix └── networkExample.nix ├── logo ├── nixcloud.container.png └── nixcloud.container.svg ├── package.nix └── test.nix /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Joachim Schiele, Paul Seitz and the Nixcloud contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![nixcloud-container](logo/nixcloud.container.png) 2 | 3 | # What is nixcloud-container? 4 | 5 | `nixcloud-container` is a Nix based wrapper around LXC, mainly to manage unprivileged LXC containers within NixOS. The implementation shares the /nix/store between host and guest. 6 | 7 | `nixcloud-container` is inspired by [nixos-container](https://nixos.org/nixos/manual/#ch-containers) which are based on systemd-nspawn. We chose LXC over systemd-nspawn because of unprivileged users support among other security features. One day systemd-nspawn might be as good as LXC but until that day we'll support LXC. 8 | 9 | # Requirements 10 | 11 | It requires NixOS as OS and nixpkgs in version: 12 | 13 | * 18.03 14 | * or newer 15 | 16 | The systemd of the LXC guest requires a patch, so all software in the container requires to be deployed from source. We might bring the patch into https://github.com/nixos/systemd but after the release of `nixcloud-container`. 17 | 18 | # nixcloud-container features 19 | 20 | * Easy to install 21 | 22 | To set up your NixOS to run LXC-containers using `nixcloud-container` you simply have to set integrate 'nixcloud-containers' and configure it by adding `nixcloud.container.enable = true;` 23 | 24 | * Shared /nix/store 25 | 26 | To speed up the generation of new containers the /nix/store is shared with the host. 27 | 28 | * Dropping privileges 29 | 30 | This abstraction automatically configures your root user as well as LXC in a way that container will drop privileges and will be run with the user 100000+. 31 | 32 | * Easy networking 33 | 34 | We support `hostonly` networking, so you can access the guest from the host and `internet` networking with IPv4 NAT and IPv6 via routable prefix. 35 | 36 | # Installation 37 | 38 | The easiest way to install `nixcloud-container` is by: 39 | 40 | 1. including the `nixcloud-webservices` repository https://github.com/nixcloud/nixcloud-webservices. Follow the README.md of `nixcloud-webservices` on how to include `nixcloud-webservices` into your configuration.nix. 41 | 42 | 2. After you added `nixcloud-webservices` you can add the following line to your configuration.nix and rebuild your system. 43 | 44 | nixcloud.container.enable = true; 45 | 46 | This will automatically prepare your users for unprivileged LXC containers. It will add a subGid and a subUid range for a `root` that will be used for the unprivileged container. It will also create two additional bridges for the container networks to be used. 47 | 48 | 49 | 50 | ## Commands 51 | 52 | `nixcloud-container` uses LXC tools and Nix tools internaly. You can pass parameters from the nixcloud-container abstraction to those wrapped commands individually. 53 | 54 | nixcloud-container create [-n ] [-l ] 55 | nixcloud-container update [-n ] [-l ] 56 | nixcloud-container destroy [-l ] 57 | 58 | nixcloud-container list-generations 59 | nixcloud-container delete-generations 60 | nixcloud-container switch-generation 61 | nixcloud-container rollback 62 | 63 | nixcloud-container start [-l ] 64 | nixcloud-container login [-l ] 65 | nixcloud-container stop [-l ] 66 | nixcloud-container terminate [-l ] 67 | 68 | nixcloud-container list [-l ] 69 | nixcloud-container show-ip 70 | nixcloud-container exists 71 | nixcloud-container state 72 | nixcloud-container help 73 | 74 | 75 | A more in detailed description can be found via `nixcloud-container help`. 76 | 77 | # Examples on usage 78 | 79 | ## Creating a LXC container 80 | 81 | 1. First create a simple configuration `example.nix`: 82 | 83 | ``` 84 | $ cat example.nix 85 | {pkgs, ip, name, ...}: 86 | { 87 | environment.systemPackages = [ pkgs.vim ]; 88 | } 89 | ``` 90 | This configuration creates a container with `vim` installed. 91 | 92 | **Note:** Run all following command(s) on the host as root! 93 | 94 | 2. to create a container with the above configuration: 95 | 96 | nixcloud-container create example example.nix 97 | 98 | 3. start the newly created container: 99 | 100 | nixcloud-container start example 101 | 102 | 4. log into the container: 103 | 104 | nixcloud-container login example 105 | 106 | ## Updating a LXC container 107 | 108 | To update the container, simply run: 109 | 110 | nixcloud-container update example example.nix 111 | 112 | **Note:** The container can either be stopped or running. If it was running during the update, it will change the state as it would with 'nixos-rebuild switch' on the host system. 113 | 114 | ## LXC container with custom nixpkgs 115 | 116 | Creating a LXC container from scratch: 117 | 118 | nixcloud-container create test ./containerConfig.nix -n "-I nixpkgs=/nixpkgs" 119 | 120 | Updating an existing LXC container: 121 | 122 | nixcloud-container update test ./containerConfig.nix -n "-I nixpkgs=/nixpkgs" 123 | 124 | ## Start an LXC container with shell 125 | 126 | If you want to see the boot process in detail: 127 | 128 | nixcloud-container start test -l "-F" 129 | 130 | ## Starting LXC container(s) automatically 131 | 132 | You can start/stop your containers manually. But if you want to start them after the host system has booted, you can add `autostart = true;` in your container configuration. Afterwards either update or create the container with that configuration. 133 | 134 | $ cat autostartExample.nix 135 | { 136 | autostart = true; # starts this container after host has booted 137 | 138 | configuration = {pkgs, ip, name, ...}: 139 | { 140 | services.openssh.enable = true; 141 | services.openssh.ports = [ 22 ]; 142 | networking.hostName = "autostartExample"; 143 | environment.systemPackages = [ pkgs.dfc pkgs.vim ]; 144 | }; 145 | } 146 | 147 | **Note:** This is implemented using a lxc-autostart.service systemd job which starts after `network.target` is reached. 148 | 149 | ## Host/Client implementation 150 | 151 | The host system and the guest system can be modified using these files: 152 | 153 | * The container is defined from [bin/helper/lxc-container.nix](bin/helper/lxc-container.nix) 154 | * The host extension can be found in [./modules/virtualisation/container.nix](https://github.com/nixcloud/nixcloud-webservices/blob/master/modules/virtualisation/container.nix) 155 | 156 | ## Declarative vs. imperative container(s) 157 | 158 | `nixcloud.container` as of now only supports stateful container management (no declarative interface). 159 | 160 | Our main use-case is to be able to rebuild individual deployments without having to run a global nixos-rebuild switch so these systems can fail individually without interference. 161 | 162 | We might implement a declarative interface later on. 163 | 164 | ## LXC container networking 165 | 166 | The following configuration generates a container with a separate bridge and a fixed IPv4 address. 167 | 168 | **Note:** You can also use fixed IPv4 addresses with the standard bridge interface, but better don't mix interfaces with static and dynamically (automatically) generated IPv4 addresses. 169 | 170 | `nixcloud-container` does not protect from collisions between automatically generated IPv4 addresses and static ones. 171 | 172 | $ cat networkExample.nix 173 | { 174 | network = { 175 | #replaces brNC with your own bridge 176 | bridge = "myOwnBridge"; 177 | #sets an ip 178 | ip = "10.10.1.2"; 179 | #disables the additional NAT interface (default) 180 | enableNat = false; 181 | }; 182 | configuration = {pkgs, ip, name, ...}: 183 | { 184 | services.openssh.enable = true; 185 | services.openssh.ports = [ 22 ]; 186 | networking.hostName = ip; 187 | users.extraUsers.root.openssh.authorizedKeys.keys = [ (builtins.readFile ./id_rsa.pub) ]; 188 | }; 189 | } 190 | 191 | **Note:** The above container configuration needs a bridge setup in the host system, which can be done like this: 192 | 193 | networking.interfaces.myOwnBridge = { 194 | ipv4.addresses = [ { address = "10.10.0.1"; prefixLength = 16; } ]; 195 | useDHCP = false; 196 | }; 197 | 198 | More example configurations can be found in the `/examples` folder. 199 | 200 | # Networking 201 | `nixcloud-container` creates two network interfaces for the communication with the containers: `brNC-hostonly` and `brNC-internet`. 202 | 203 | ## brNC-hostonly 204 | This bridge is using the IPv4 network `10.101.0.0/16`. IPv4(s) are generated idiomatically by `nixcloud-container` and are fixed during the lifetime of the container. 205 | As the name `brNC-hostonly` implies, this bridge is not forwarded to the internet (no NAT). Instead it is intended to be used for bringing webservices into the internet using `nixcloud.reverse-proxy` on the host. 206 | 207 | By setting `network.bridge` in the container config, the container will no longer be connected to `brNC-hostonly` but instead to the new bridge specified. See also `networkExample.nix`. 208 | 209 | ## brNC-internet 210 | 211 | If you want to have IPv4 NATed internet in the container, then: 212 | 213 | 1. Add this to your host configuration 214 | 215 | nixcloud.container = { 216 | enable = true; 217 | internetInterface = "enp0s3"; 218 | }; 219 | 220 | 2. Set `network.enableNat = true;` in the container config, then the container will be connected to the `brNC-internet` interface (default is false). Inside the container the interface will be called 'internet' and it will get an IPv4 address by `dhcpcd`. 221 | 222 | As said, by default containers are not connected to this bridge and there won't be a `internet` interface. 223 | 224 | 3. If you want an IPv6 address for your container, then use the ipv6 attribute set as below: 225 | 226 | nixcloud.container = { 227 | enable = true; 228 | internetInterface = "enp0s3"; 229 | ipv6 = { 230 | enable = true; 231 | ipv6InternetInterfaceAddress = "2a01:4f8:221:3744:4000::1"; 232 | ipv6Prefix = "2a01:4f8:221:3744:4000::"; 233 | ipv6PrefixLength = 66; 234 | ipv6NameServers = [ "2a01:4f8:0:1::add:1010" "2a01:4f8:0:1::add:9999" "2a01:4f8:0:1::add:9898" ]; 235 | }; 236 | }; 237 | 238 | **Note:** The `ipv6InternetInterfaceAddress` is assigned to the DHCPD6 interface and must be contained in the `ipv6Prefix` as DHCPD6 wouldn't work otherwise. 239 | 240 | **Note:** You have to fill in your own IPv6 addresses, prefix and nameservers and make sure that the host can actually be reached so the subnet correctly arrives at the host. Use `ping -6 youripv6address` and `tcpdump enp0s3 ip6 -n` to verify. 241 | 242 | # Security considerations 243 | 244 | * Apparmor LXC profiles are currently not supported 245 | * LXC Containers must be started on the host with the root user but they drop privileges to user 100000 246 | 247 | **Note:** Starting LXC containers as a non root user is currently not supported but would be nice to have but the most important thing is that this probably is not a security issue. 248 | 249 | * The host and all LXC containers share the same /nix/store so every container can read the whole store. This is a problem for https://github.com/NixOS/nixpkgs/issues/24288 related services! 250 | * The hostonly interface is shared among all containers, so using IPv4 each container can connect each other container! 251 | * The NixOS firewall inside the LXC containers work and is configured but you might have to check if it suits your needs 252 | * Currently there are no cgroup limitations on LXC containers or single processes, so you have to trust your users to not exceed the machine performance or implement them yourself 253 | 254 | # Tests 255 | 256 | The test implementation can be found in the [test.nix](test.nix) file. 257 | 258 | # License 259 | 260 | The license can be found in [LICENSE](LICENSE). 261 | 262 | For inquiries, please contact us at . 263 | -------------------------------------------------------------------------------- /bin/helper/8447.patch: -------------------------------------------------------------------------------- 1 | From 4c38195aa91166346ca0954cf138db0cbdf24894 Mon Sep 17 00:00:00 2001 2 | From: Dimitri John Ledkov 3 | Date: Tue, 13 Mar 2018 23:03:37 +0000 4 | Subject: [PATCH] core: use setreuid/setregid trick to create session keyring 5 | with right ownership 6 | 7 | Re-use the hacks used to link user keyring, when creating the session 8 | keyring. This way changing ownership of the keyring is not required, and thus 9 | incovation_id can be correctly created in restricted environments. 10 | 11 | Creating invocation_id with root permissions works and linking it into session 12 | keyring works, as at that point session keyring is possessed. 13 | 14 | Simple way to validate this is with following commands: 15 | 16 | $ journalctl -f & 17 | $ sudo systemd-run --uid 1000 /bin/sh -c 'keyctl describe @s; keyctl list @s; keyctl read `keyctl search @s user invocation_id`' 18 | 19 | which now works in LXD containers as well as on the host. 20 | 21 | Fixes: https://github.com/systemd/systemd/issues/7655 22 | --- 23 | src/core/execute.c | 95 ++++++++++++++++++++++++++---------------------------- 24 | 1 file changed, 46 insertions(+), 49 deletions(-) 25 | 26 | diff --git a/src/core/execute.c b/src/core/execute.c 27 | index 7292b815db3..fb4d09e2a4a 100644 28 | --- a/src/core/execute.c 29 | +++ b/src/core/execute.c 30 | @@ -2439,6 +2439,8 @@ static int setup_keyring( 31 | 32 | key_serial_t keyring; 33 | int r; 34 | + uid_t saved_uid; 35 | + gid_t saved_gid; 36 | 37 | assert(u); 38 | assert(context); 39 | @@ -2457,6 +2459,26 @@ static int setup_keyring( 40 | if (context->keyring_mode == EXEC_KEYRING_INHERIT) 41 | return 0; 42 | 43 | + /* Acquiring a reference to the user keyring is nasty. We briefly change identity in order to get things set up 44 | + * properly by the kernel. If we don't do that then we can't create it atomically, and that sucks for parallel 45 | + * execution. This mimics what pam_keyinit does, too. Setting up session keyring, to be owned by the right user 46 | + * & group is just as nasty as acquiring a reference to the user keyring. */ 47 | + 48 | + saved_uid = getuid(); 49 | + saved_gid = getgid(); 50 | + 51 | + if (gid_is_valid(gid) && gid != saved_gid) { 52 | + if (setregid(gid, -1) < 0) 53 | + return log_unit_error_errno(u, errno, "Failed to change GID for user keyring: %m"); 54 | + } 55 | + 56 | + if (uid_is_valid(uid) && uid != saved_uid) { 57 | + if (setreuid(uid, -1) < 0) { 58 | + (void) setregid(saved_gid, -1); 59 | + return log_unit_error_errno(u, errno, "Failed to change UID for user keyring: %m"); 60 | + } 61 | + } 62 | + 63 | keyring = keyctl(KEYCTL_JOIN_SESSION_KEYRING, 0, 0, 0, 0); 64 | if (keyring == -1) { 65 | if (errno == ENOSYS) 66 | @@ -2471,49 +2493,8 @@ static int setup_keyring( 67 | return 0; 68 | } 69 | 70 | - /* Populate they keyring with the invocation ID by default. */ 71 | - if (!sd_id128_is_null(u->invocation_id)) { 72 | - key_serial_t key; 73 | - 74 | - key = add_key("user", "invocation_id", &u->invocation_id, sizeof(u->invocation_id), KEY_SPEC_SESSION_KEYRING); 75 | - if (key == -1) 76 | - log_unit_debug_errno(u, errno, "Failed to add invocation ID to keyring, ignoring: %m"); 77 | - else { 78 | - if (keyctl(KEYCTL_SETPERM, key, 79 | - KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| 80 | - KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH, 0, 0) < 0) 81 | - return log_unit_error_errno(u, errno, "Failed to restrict invocation ID permission: %m"); 82 | - } 83 | - } 84 | - 85 | - /* And now, make the keyring owned by the service's user */ 86 | - if (uid_is_valid(uid) || gid_is_valid(gid)) 87 | - if (keyctl(KEYCTL_CHOWN, keyring, uid, gid, 0) < 0) 88 | - return log_unit_error_errno(u, errno, "Failed to change ownership of session keyring: %m"); 89 | - 90 | /* When requested link the user keyring into the session keyring. */ 91 | if (context->keyring_mode == EXEC_KEYRING_SHARED) { 92 | - uid_t saved_uid; 93 | - gid_t saved_gid; 94 | - 95 | - /* Acquiring a reference to the user keyring is nasty. We briefly change identity in order to get things 96 | - * set up properly by the kernel. If we don't do that then we can't create it atomically, and that 97 | - * sucks for parallel execution. This mimics what pam_keyinit does, too.*/ 98 | - 99 | - saved_uid = getuid(); 100 | - saved_gid = getgid(); 101 | - 102 | - if (gid_is_valid(gid) && gid != saved_gid) { 103 | - if (setregid(gid, -1) < 0) 104 | - return log_unit_error_errno(u, errno, "Failed to change GID for user keyring: %m"); 105 | - } 106 | - 107 | - if (uid_is_valid(uid) && uid != saved_uid) { 108 | - if (setreuid(uid, -1) < 0) { 109 | - (void) setregid(saved_gid, -1); 110 | - return log_unit_error_errno(u, errno, "Failed to change UID for user keyring: %m"); 111 | - } 112 | - } 113 | 114 | if (keyctl(KEYCTL_LINK, 115 | KEY_SPEC_USER_KEYRING, 116 | @@ -2526,17 +2507,33 @@ static int setup_keyring( 117 | 118 | return log_unit_error_errno(u, r, "Failed to link user keyring into session keyring: %m"); 119 | } 120 | + } 121 | 122 | - if (uid_is_valid(uid) && uid != saved_uid) { 123 | - if (setreuid(saved_uid, -1) < 0) { 124 | - (void) setregid(saved_gid, -1); 125 | - return log_unit_error_errno(u, errno, "Failed to change UID back for user keyring: %m"); 126 | - } 127 | + /* Restore uid/gid back */ 128 | + if (uid_is_valid(uid) && uid != saved_uid) { 129 | + if (setreuid(saved_uid, -1) < 0) { 130 | + (void) setregid(saved_gid, -1); 131 | + return log_unit_error_errno(u, errno, "Failed to change UID back for user keyring: %m"); 132 | } 133 | + } 134 | + 135 | + if (gid_is_valid(gid) && gid != saved_gid) { 136 | + if (setregid(saved_gid, -1) < 0) 137 | + return log_unit_error_errno(u, errno, "Failed to change GID back for user keyring: %m"); 138 | + } 139 | 140 | - if (gid_is_valid(gid) && gid != saved_gid) { 141 | - if (setregid(saved_gid, -1) < 0) 142 | - return log_unit_error_errno(u, errno, "Failed to change GID back for user keyring: %m"); 143 | + /* Populate they keyring with the invocation ID by default, as original saved_uid. */ 144 | + if (!sd_id128_is_null(u->invocation_id)) { 145 | + key_serial_t key; 146 | + 147 | + key = add_key("user", "invocation_id", &u->invocation_id, sizeof(u->invocation_id), KEY_SPEC_SESSION_KEYRING); 148 | + if (key == -1) 149 | + log_unit_debug_errno(u, errno, "Failed to add invocation ID to keyring, ignoring: %m"); 150 | + else { 151 | + if (keyctl(KEYCTL_SETPERM, key, 152 | + KEY_POS_VIEW|KEY_POS_READ|KEY_POS_SEARCH| 153 | + KEY_USR_VIEW|KEY_USR_READ|KEY_USR_SEARCH, 0, 0) < 0) 154 | + return log_unit_error_errno(u, errno, "Failed to restrict invocation ID permission: %m"); 155 | } 156 | } 157 | 158 | -------------------------------------------------------------------------------- /bin/helper/getIp.nix: -------------------------------------------------------------------------------- 1 | {config}: 2 | config.network.ip 3 | -------------------------------------------------------------------------------- /bin/helper/hasIp.nix: -------------------------------------------------------------------------------- 1 | {config}: 2 | config ? network && config.network ? ip 3 | -------------------------------------------------------------------------------- /bin/helper/lxc-container.nix: -------------------------------------------------------------------------------- 1 | let 2 | # XXX: This is needed so that hybrid cgroup layouts are properly working in 3 | # containers. Remove this as soon as lxc >= 2.0.9 has landed in . 4 | overlays = [ 5 | (self: super: (import ./patches.nix { inherit super;})) 6 | ]; 7 | in 8 | 9 | { system ? builtins.currentSystem 10 | , pkgs ? import { inherit system overlays; } 11 | , lib ? pkgs.stdenv.lib 12 | , lxcExtraConfig ? "" 13 | , name ? "" 14 | , ip ? "" 15 | , container 16 | }: 17 | 18 | with pkgs; 19 | with lib; 20 | 21 | #assert ip != "" && container_ ? network && container.network ? ip; 22 | 23 | let 24 | 25 | container_ = if container ? configuration then container else { configuration = container; }; 26 | 27 | configuration = container_.configuration; 28 | containerName = if name == "" then container.name else name; 29 | 30 | containerIp = if container ? network && container.network ? ip then container.network.ip else ip; 31 | gateway = if container_ ? network && container.network ? gateway then 32 | container.network.gateway else "10.101.0.1"; 33 | bridge = if container_ ? network && container.network ? bridge then 34 | container.network.bridge else "brNC-hostonly"; 35 | 36 | addBridgeInet = (container ? network && container.network ? enableNat && container.network.enableNat); 37 | bridgeInet = "brNC-internet"; 38 | 39 | autostart = if container_ ? autostart && container.autostart then "1" else "0"; 40 | 41 | containerConfig = (import { 42 | modules = 43 | let 44 | extraConfig = { 45 | nixpkgs.system = system; 46 | boot.isContainer = true; 47 | environment.systemPackages = with pkgs; [ dfc htop iptables ]; 48 | networking = { 49 | hostName = mkDefault containerName; 50 | interfaces.hostonly = { 51 | useDHCP = false; 52 | ipv4.addresses = [ { address = ip; prefixLength = 16; } ]; 53 | }; 54 | 55 | }; 56 | nixpkgs.overlays = overlays; 57 | services.mingetty.autologinUser = mkDefault "root"; 58 | }; 59 | networkInetBridge = if addBridgeInet then { 60 | networking = { 61 | dhcpcd = { 62 | allowInterfaces = [ "internet" ]; 63 | extraConfig = '' 64 | ipv6 65 | noipv4ll 66 | # we are using radvd, so we disable router solicitation in dhcpd6 67 | noipv6rs 68 | waitip 4 69 | interface internet 70 | # request a normal (IA_NA) IPv6 address with IAID 1 71 | ia_na 1 72 | waitip 4 73 | waitip 6 74 | ''; 75 | }; 76 | interfaces = { 77 | internet.useDHCP = true; 78 | }; 79 | }; 80 | } else {}; 81 | in [ extraConfig networkInetBridge configuration ]; 82 | prefix = [ "systemd" "containers" containerName ]; 83 | extraArgs = { inherit ip; name = containerName; }; 84 | }).config.system.build.toplevel; 85 | 86 | lxcConfigWrapper = pkgs.writeText "configWrapper" '' 87 | lxc.include = /nix/var/nix/profiles/nixcloud-container/${containerName}/profile/config 88 | ''; 89 | 90 | lxcConfig = pkgs.writeText "config" '' 91 | lxc.uts.name = ${containerName} 92 | 93 | # Fixme also support other architectures? 94 | lxc.arch = ${if system == "x86_64-linux" then "x86_64" else "i686"} 95 | # Not needed, just makes spares a few cpu cycles as LXC doesn't have 96 | # to detect the backend. 97 | #lxc.rootfs.backend = dir 98 | lxc.rootfs.path = /var/lib/lxc/${containerName}/rootfs 99 | lxc.init.cmd = /init/profile/container/init 100 | #lxc.rootfs = /var/lib/lxc/${containerName}/rootfs 101 | 102 | # Ensures correct functionality with user namespaces. Since mknod is not possible stuff like 103 | # /dev/console, /dev/tty, /dev/urandom, etc. need to be bind mounted. Note the order 104 | # of the file inclusion here is important. 105 | lxc.include = ${pkgs.lxc}/share/lxc/config/common.conf 106 | lxc.include = ${pkgs.lxc}/share/lxc/config/userns.conf 107 | 108 | ## Network 109 | # see also https://wiki.archlinux.org/index.php/Linux_Containers 110 | lxc.net.0.type = veth 111 | lxc.net.0.name = hostonly 112 | #lxc.net.0.ipv4.address = ${containerIp} (we assign this using nix, not from lxc) 113 | lxc.net.0.flags = up 114 | lxc.net.0.link = ${bridge} 115 | 116 | ${optionalString addBridgeInet '' 117 | lxc.net.1.type = veth 118 | lxc.net.1.name = internet 119 | lxc.net.1.flags = up 120 | lxc.net.1.link = ${bridgeInet} 121 | '' 122 | } 123 | 124 | # Specifiy {u,g}id mapping. 125 | lxc.idmap = u 0 100000 65536 126 | lxc.idmap = g 0 100000 65536 127 | 128 | # FIXME apparmor support 129 | # Nixos does not provide AppArmor support. 130 | #lxc.aa_profile = unconfined 131 | #lxc.aa_allow_incomplete = 1 132 | lxc.apparmor.profile = unconfined 133 | lxc.apparmor.allow_incomplete = 1 134 | 135 | # Tweaks for systemd. 136 | lxc.autodev = 1 137 | 138 | # Additional mount entries. 139 | lxc.mount.entry = /nix/store nix/store none defaults,bind.ro 0.0 140 | lxc.mount.entry = /nix/var/nix/profiles/nixcloud-container/${containerName}/ init none defaults,bind.ro 0 0 141 | 142 | # Mount entries that lead to a cleaner boot experience. 143 | lxc.mount.entry = /sys/kernel/debug sys/kernel/debug none bind,optional 0 0 144 | lxc.mount.entry = /sys/kernel/security sys/kernel/security none bind,optional 0 0 145 | lxc.mount.entry = /sys/fs/pstore sys/fs/pstore none bind,optional 0 0 146 | lxc.mount.entry = mqueue dev/mqueue mqueue rw,relatime,create=dir,optional 0 0 147 | 148 | # LXC autostart 149 | lxc.start.auto = ${autostart} 150 | 151 | ${lxcExtraConfig} 152 | ''; 153 | 154 | in pkgs.stdenv.mkDerivation { 155 | name = "nixcloudContainer-${name}"; 156 | phases = "installPhase"; 157 | installPhase = '' 158 | mkdir $out 159 | ln -s ${containerConfig} $out/container 160 | ln -s ${lxcConfig} $out/config 161 | ln -s ${lxcConfigWrapper} $out/configWrapper 162 | ''; 163 | } 164 | -------------------------------------------------------------------------------- /bin/helper/nixcloud-container-create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #check for arguments 3 | set -e 4 | if [ $# -le 1 ] 5 | then 6 | echo "No container name and/or configurationFile specified " 7 | exit 0 8 | fi 9 | 10 | #init variables 11 | NAME=$1 12 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 13 | CONFIGURATION="$(realpath $2)" 14 | 15 | LXCPATH="${ROOT_FS_HOME}/${NAME}" 16 | ROOT_FS="${ROOT_FS_HOME}/${NAME}/rootfs" 17 | 18 | IPGENERATED=false 19 | 20 | #check if container already exists 21 | if [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 22 | echo "container with name ${NAME} already exists. use 'nixcloud-container update' instead." 23 | exit 1 24 | fi 25 | 26 | shift 2 27 | getLxcNixParameter $@ 28 | 29 | #check if an ip needs to be generated 30 | set +e 31 | ipNotNeeded=$(nix-instantiate $NIXARGUMENTS --eval $SCRIPTDIR/hasIp.nix --arg config "import $CONFIGURATION") 32 | err=$? 33 | set -e 34 | if [ $err -ne 0 ]; then 35 | echo "could validate if an ip is given or needs to be generated." 36 | exit $err 37 | fi 38 | if [ "$ipNotNeeded" == "true" ]; then 39 | echo "container ip specified in configurationFile" 40 | else 41 | getIp $NAME 42 | echo "Ip is 10.101.$(($ip/256)).$(($ip%256))" 43 | fi 44 | 45 | #create rootfs 46 | workdir=$(pwd) 47 | mkdir -p $ROOT_FS 48 | mkdir -p $ROOT_FS/{proc,sys,dev,nix/store,etc,init} 49 | chown 100000:100000 $ROOT_FS 50 | chown -R 100000:100000 $ROOT_FS/* 51 | chmod 0755 $ROOT_FS/etc 52 | if ! [ -e $ROOT_FS/etc/os-release ]; then 53 | touch $ROOT_FS/etc/os-release 54 | fi 55 | 56 | #save config path 57 | echo "${CONFIGURATION}" >> "${LXCPATH}/nixConfigPath" 58 | 59 | #create init and container configuration inside the rootfs 60 | set +e 61 | profilePath="/nix/var/nix/profiles/nixcloud-container/${NAME}/profile" 62 | mkdir -p /nix/var/nix/profiles/nixcloud-container/${NAME} 63 | if [ "$ipNotNeeded" == "true" ]; then 64 | ip=$(nix-instantiate $NIXARGUMENTS --eval $SCRIPTDIR/getIp.nix --arg config "import $CONFIGURATION") 65 | setFixedIp $NAME $ip 66 | nix-env -i $NIXARGUMENTS -f $SCRIPTDIR/lxc-container.nix -p ${profilePath} --argstr name "${NAME}" --arg container "import ${CONFIGURATION}" 67 | err=$? 68 | else 69 | nix-env -i $NIXARGUMENTS -f $SCRIPTDIR/lxc-container.nix -p ${profilePath} --argstr name "${NAME}" --arg container "import ${CONFIGURATION}" --argstr ip "10.101.$(($ip/256)).$(($ip%256))" 70 | err=$? 71 | fi 72 | if [ $err -ne 0 ]; then 73 | echo "could not evaluate container config" 74 | freeIp $NAME 75 | exit $err 76 | fi 77 | #create lxc container 78 | lxc-create -n "${NAME}" -f "${profilePath}/configWrapper" -t none $LXCARGUMENTS 79 | err=$? 80 | if [ $err -ne 0 ]; then 81 | echo "could not create lxc container" 82 | freeIp $NAME 83 | exit $err 84 | fi 85 | echo "successfully created container ${NAME}" 86 | -------------------------------------------------------------------------------- /bin/helper/nixcloud-container-exists.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | #tests if a container name is already in use 4 | if [ $# -le 1 ] 5 | then 6 | echo "No container name specified " 7 | exit 1 8 | fi 9 | NAME=$1 10 | #check if container already exists 11 | if ! [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 12 | echo "container with name ${NAME} does not exist." 13 | exit 1 14 | fi 15 | echo "container with name ${NAME} does exist." 16 | exit 0 17 | -------------------------------------------------------------------------------- /bin/helper/nixcloud-container-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | NAME=$1 4 | shift 1 5 | #ROOT_FS="${ROOT_FS_HOME}/${NAME}/rootfs" 6 | getLxcNixParameter $@ 7 | #echo "${ROOT_FS}" 8 | #export CONTAINER_ROOT="${ROOT_FS}" 9 | set +e 10 | #lxc-start -n "${NAME}" -s "lxc.rootfs=${ROOT_FS}" /result/container/init $LXCARGUMENTS 11 | #ResultNumber=$(ls -rt $ROOT_FS/result |tail -1) 12 | #lxc-start -n "${NAME}" "/result/container/init" $LXCARGUMENTS 13 | lxc-start -n "${NAME}" $LXCARGUMENTS 14 | err=$? 15 | if [ $err -ne 0 ]; then 16 | echo "could not start container" 17 | exit $err 18 | fi 19 | -------------------------------------------------------------------------------- /bin/helper/nixcloud-container-update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | #check for arguments 4 | if [ $# -le 0 ]; then 5 | echo "No container name specified " 6 | exit 0 7 | fi 8 | 9 | declare NAME=$1 10 | LXCPATH="${ROOT_FS_HOME}/${NAME}" 11 | CONFIGURATIONPATH="${LXCPATH}/nixConfigPath" 12 | if [ $# -le 1 ]; then 13 | CONFIGURATION=$(cat "${CONFIGURATIONPATH}") 14 | echo "No container config specified only updating using ${CONFIGURATIONPATH}." 15 | else 16 | declare -r CONFIGURATION="$(realpath $2)" 17 | mkdir -p ${LXCPATH} 18 | touch "${CONFIGURATIONPATH}" 19 | echo "${CONFIGURATION}" >> "${CONFIGURATIONPATH}" 20 | fi 21 | #init constants 22 | declare SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 23 | declare ROOT_FS="${ROOT_FS_HOME}/${NAME}/rootfs" 24 | 25 | #check if container already exists 26 | set +e 27 | status=$(lxc-ls --line | grep ^${NAME}\$) 28 | echo $status 29 | if [ "$status" == "" ]; then 30 | echo "WARNING: container with name ${NAME} does not exist. Container will be created." 31 | source $BASEDIR/helper/nixcloud-container-create.sh "${@}" 32 | exit 0 33 | fi 34 | set -e 35 | shift 2 36 | getLxcNixParameter $@ 37 | 38 | #check if an ip needs to be generated 39 | set +e 40 | declare -r ipNotNeeded=$(nix-instantiate $NIXARGUMENTS --eval $SCRIPTDIR/hasIp.nix --arg config "import $CONFIGURATION") 41 | err=$? 42 | set -e 43 | if [ $err -ne 0 ]; then 44 | echo "could validate if an ip is given or needs to be generated." 45 | exit $err 46 | fi 47 | if [ "$ipNotNeeded" == "true" ]; then 48 | echo "container ip specified in configurationFile" 49 | else 50 | getIp $NAME 51 | echo "Ip is 10.101.$(($ip/256)).$(($ip%256))" 52 | fi 53 | 54 | #create rootfs 55 | if ! [ -d $ROOT_FS ]; then 56 | mkdir -p $ROOT_FS 57 | mkdir -p $ROOT_FS/{proc,sys,dev,nix/store,etc} 58 | chown 100000:100000 $ROOT_FS 59 | chown -R 100000:100000 $ROOT_FS/* 60 | chmod 0755 $ROOT_FS/etc 61 | if ! [ -e $ROOT_FS/etc/os-release ]; then 62 | touch $ROOT_FS/etc/os-release 63 | fi 64 | fi 65 | 66 | #create init and container configuration inside the rootfs 67 | cd $ROOT_FS 68 | set +e 69 | 70 | #ResultNumber=$(ls -rt $ROOT_FS/result |tail -1) 71 | #ResultNumber=$((ResultNumber + 1)) 72 | profilePath="/nix/var/nix/profiles/nixcloud-container/${NAME}/profile" 73 | if [ "$ipNotNeeded" == "true" ]; then 74 | # nix-build $NIXARGUMENTS $SCRIPTDIR/lxc-container.nix -o $ROOT_FS/result/$ResultNumber --arg name "\"${NAME}\"" --arg container "import ${CONFIGURATION}" 75 | nix-env -i $NIXARGUMENTS -f $SCRIPTDIR/lxc-container.nix -p ${profilePath} --argstr name "${NAME}" --arg container "import ${CONFIGURATION}" 76 | else 77 | # nix-build $NIXARGUMENTS $SCRIPTDIR/lxc-container.nix -o $ROOT_FS/result/$ResultNumber --arg name "\"${NAME}\"" --arg container "import ${CONFIGURATION}" --arg ip "\"10.101.$(($ip/256)).$(($ip%256))\"" 78 | nix-env -i $NIXARGUMENTS -f $SCRIPTDIR/lxc-container.nix -p ${profilePath} --argstr name "${NAME}" --arg container "import ${CONFIGURATION}" --argstr ip "10.101.$(($ip/256)).$(($ip%256))" 79 | fi 80 | err=$? 81 | if [ $err -ne 0 ]; then 82 | echo "could not evaluate container config" 83 | exit $err 84 | fi 85 | 86 | lxc-info -n "${NAME}" | grep "doesn't exist" &> /dev/null 87 | if [ $? == 0 ]; then 88 | #create lxc container 89 | echo "WARNING: container did not exist, rebuilding container" 90 | lxc-create -n "${NAME}" -f $ROOT_FS/result/config -t none $LXCARGUMENTS 91 | exit $? 92 | fi 93 | 94 | updateContainer $NAME 95 | -------------------------------------------------------------------------------- /bin/helper/patches.nix: -------------------------------------------------------------------------------- 1 | { super }: 2 | let 3 | lib = super.stdenv.lib; 4 | in 5 | { 6 | systemd = if ((builtins.compareVersions super.systemd.version "239") == -1) then 7 | super.systemd.overrideAttrs (drv: { 8 | # https://github.com/lxc/lxc/issues/2226 9 | patches = if super.systemd ? patches then super.systemd.patches ++ [ ./8447.patch ] 10 | else [ ./8447.patch ]; 11 | }) else super.systemd; 12 | } 13 | -------------------------------------------------------------------------------- /bin/nixcloud-container: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | #check if a command is given else print the help 4 | if [ $# -le 0 ] 5 | then 6 | STATE="help" 7 | else 8 | STATE=$1 9 | shift 1 10 | fi 11 | #export LXC_UPDATE_CONFIG_FORMAT=0 12 | #-------------some constants--------------------------- 13 | #path where ips and names of containers are saved 14 | declare -r -x SAVE="/var/lib/nixcloud/container/" 15 | #the directory that containes all rootFS of all LXC containers 16 | declare -r -x ROOT_FS_HOME="/var/lib/lxc" 17 | 18 | #create the files which containes all used ips and names 19 | if [ ! -d $SAVE ]; then 20 | mkdir -p $SAVE 21 | fi 22 | if [ ! -f "$SAVE/ip" ]; then 23 | touch "$SAVE/ip" 24 | fi 25 | if [ ! -f "$SAVE/name" ]; then 26 | touch "$SAVE/name" 27 | fi 28 | if [ ! -f "$SAVE/fixedIp" ]; then 29 | touch "$SAVE/fixedIp" 30 | fi 31 | if [ ! -f "$SAVE/.container.exclusivelock" ]; then 32 | touch "$SAVE/.container.exclusivelock" 33 | fi 34 | 35 | #------------begin function declaration--------------------------------- 36 | #prints the help/small documentation 37 | function printHelp { 38 | cat <<- EOF 39 | Usage: nixcloud-container create [-n ] [-l ] 40 | nixcloud-container update [-n ] [-l ] 41 | nixcloud-container destroy [-l ] 42 | 43 | nixcloud-container list-generations 44 | nixcloud-container delete-generations 45 | nixcloud-container switch-generation 46 | nixcloud-container rollback 47 | 48 | nixcloud-container start [-l ] 49 | nixcloud-container login [-l ] 50 | nixcloud-container stop [-l ] 51 | nixcloud-container terminate [-l ] 52 | nixcloud-container run 53 | 54 | nixcloud-container list [-l ] 55 | nixcloud-container show-ip 56 | nixcloud-container exists 57 | nixcloud-container state 58 | nixcloud-container help 59 | 60 | EOF 61 | } 62 | export -f printHelp 63 | 64 | #runs the update command inside a container 65 | function updateContainer { 66 | if [ $# -le 0 ] 67 | then 68 | echo "error occured while updating container. This is a bug please report it." 69 | exit 1 70 | fi 71 | NAME=$1 72 | set +e 73 | status=$(lxc-info -s -n "${NAME}" | grep RUNNING) 74 | 75 | if [ "$status" != "" ]; then 76 | echo "updating runnning container" 77 | lxc-attach -n "${NAME}" -- /init/profile/container/bin/switch-to-configuration switch 78 | err=$? 79 | #currently this returns with exit code 2 for some mounting problems within lxc 80 | #however those errors won't interfere with the update process. Hence it should 81 | #be ok to return 0 instead. 82 | if [ $err -eq 2 ]; then 83 | exit 0 84 | fi 85 | exit $err 86 | fi 87 | set -e 88 | } 89 | export -f updateContainer 90 | #gets all the passthrugh arguments 91 | function getLxcNixParameter { 92 | declare -g NIXCARGUMENTS="" 93 | declare -g LXCARGUMENTS="" 94 | while getopts ":n:l:" opt; do 95 | case $opt in 96 | n) 97 | NIXCARGUMENTS="$NIXCARGUMENTS $OPTARG" 98 | ;; 99 | l) 100 | LXCARGUMENTS="$LXCARGUMENTS $OPTARG" 101 | ;; 102 | \?) 103 | echo "Invalid option: -$OPTARG" >&2 104 | exit 1 105 | ;; 106 | :) 107 | echo "Option -$OPTARG requires an argument." >&2 108 | exit 1 109 | ;; 110 | esac 111 | done 112 | if [ "$LXCARGUMENTS" != "" ]; then 113 | echo "lxc options ${LXCARGUMENTS}" 114 | fi 115 | if [ "$NIXCARGUMENTS" != "" ]; then 116 | echo "nix options ${NIXCARGUMENTS}" 117 | fi 118 | } 119 | export -f getLxcNixParameter 120 | 121 | #frees a container name and the ip the container has. Requires a Name as an argument 122 | function freeIp { 123 | if [ $# -le 0 ] 124 | then 125 | echo "error occured while freeing the a new IP. This is a bug please report it." 126 | exit 1 127 | fi 128 | declare -r CONTAINER_NAME=$1 129 | declare -r fileIp="$SAVE/ip" 130 | declare -r fileName="$SAVE/name" 131 | ( 132 | /run/current-system/sw/bin/flock -n 200 133 | 134 | declare -A ips 135 | source -- "$fileIp" || exit 136 | declare -A names 137 | source -- "$fileName" || exit 138 | 139 | declare -r ip=${names[$CONTAINER_NAME]} 140 | 141 | if [[ ! -z "$ip" ]]; then 142 | unset ips[$ip] 143 | unset names[$CONTAINER_NAME] 144 | 145 | touch "${fileIp}_" 146 | touch "${fileName}_" 147 | 148 | declare -p ips > "${fileIp}_" 149 | declare -p names > "${fileName}_" 150 | 151 | mv "${fileIp}_" $fileIp 152 | mv "${fileName}_" $fileName 153 | fi 154 | ) 200>$SAVE/.container.exclusivelock 155 | } 156 | export -f freeIp 157 | 158 | #gets a new ip for the container 159 | function getIp { 160 | if [ $# -le 0 ] 161 | then 162 | echo "error occured while calculating the a new IP. This is a bug please report it." 163 | exit 1 164 | fi 165 | declare -r CONTAINER_NAME=$1 166 | 167 | declare -g -i ip=2 168 | fileIp="$SAVE/ip" 169 | fileName="$SAVE/name" 170 | 171 | exec {lock_fd}>$SAVE/.container.exclusivelock || exit 1 172 | /run/current-system/sw/bin/flock -n "$lock_fd" || { echo "ERROR: flock() failed." >&2; exit 1; } 173 | 174 | 175 | declare -A ips 176 | source -- "$fileIp" || exit 177 | declare -A names 178 | source -- "$fileName" || exit 179 | 180 | if [ ${names[$CONTAINER_NAME]} ]; then 181 | ip=${names[$CONTAINER_NAME]} 182 | echo "container ip found." 183 | else 184 | 185 | while [ -n "${ips[$ip]}" ] 186 | do 187 | ip=$(($ip + 1)) 188 | 189 | if [ $ip -ge 65535 ] 190 | then 191 | echo "all ip's are already in use" 192 | exit 1; 193 | fi 194 | done 195 | 196 | ips[$ip]=$CONTAINER_NAME 197 | names[$CONTAINER_NAME]="$ip" 198 | 199 | declare -p ips > "${fileIp}_" 200 | declare -p names > "${fileName}_" 201 | 202 | mv "${fileIp}_" $fileIp 203 | mv "${fileName}_" $fileName 204 | 205 | echo "container ip generated." 206 | fi 207 | /run/current-system/sw/bin/flock -u "$lock_fd" 208 | } 209 | export -f getIp 210 | 211 | function setFixedIp { 212 | if [ $# -le 1 ] 213 | then 214 | echo "error occured while setting the fixed IP. This is a bug please report it." 215 | exit 1 216 | fi 217 | declare -r CONTAINER_NAME=$1 218 | declare -r IP=$2 219 | declare -r fileFixedIp="$SAVE/fixedIp" 220 | ( 221 | /run/current-system/sw/bin/flock -n 200 222 | 223 | declare -A fixedIps 224 | source -- "$fileFixedIp" || exit 225 | 226 | fixedIps[$CONTAINER_NAME]=$IP 227 | 228 | touch "${fileFixedIp}_" 229 | 230 | declare -p fixedIps > "${fileFixedIp}_" 231 | 232 | mv "${fileFixedIp}_" $fileFixedIp 233 | ) 200>$SAVE/.container.exclusivelock 234 | } 235 | export -f setFixedIp 236 | 237 | 238 | function unsetFixedIp { 239 | if [ $# -le 0 ] 240 | then 241 | echo "error occured while unsetting the fixed IP. This is a bug please report it." 242 | exit 1 243 | fi 244 | declare -r CONTAINER_NAME=$1 245 | declare -r fileFixedIp="$SAVE/fixedIp" 246 | ( 247 | /run/current-system/sw/bin/flock -n 200 248 | 249 | declare -A fixedIps 250 | source -- "$fileFixedIp" || exit 251 | 252 | unset fixedIps[$CONTAINER_NAME] 253 | 254 | touch "${fileFixedIp}_" 255 | 256 | declare -p fixedIps > "${fileFixedIp}_" 257 | 258 | mv "${fileFixedIp}_" $fileFixedIp 259 | ) 200>$SAVE/.container.exclusivelock 260 | } 261 | export -f unsetFixedIp 262 | #--------------------end function declaration---------------------------- 263 | 264 | declare -r BASEDIR=$(dirname "$0") 265 | case $STATE in 266 | --list|list) 267 | getLxcNixParameter $@ 268 | lxc-ls "$LXCARGUMENTS" 269 | exit 0 270 | ;; 271 | --stop|stop) 272 | if [ $# -le 0 ] 273 | then 274 | echo "no container name specified" 275 | exit 1 276 | fi 277 | NAME=$1 278 | shift 1 279 | if ! [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 280 | echo "container with name ${NAME} does not exist." 281 | exit 1 282 | fi 283 | getLxcNixParameter $@ 284 | lxc-stop --name ${NAME} "$LXCARGUMENTS" 285 | exit $? 286 | ;; 287 | --exists|exists) 288 | if [ $# -le 0 ] 289 | then 290 | echo "no container name specified" 291 | exit 1 292 | fi 293 | NAME=$1 294 | shift 1 295 | if ! [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 296 | echo "container with name ${NAME} does not exist." 297 | exit 1 298 | fi 299 | echo "container with name ${NAME} does exist." 300 | exit 0 301 | ;; 302 | --state|state|status|--status) 303 | if [ $# -le 0 ] 304 | then 305 | echo "no container name specified" 306 | exit 1 307 | fi 308 | NAME=$1 309 | shift 1 310 | if ! [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 311 | echo "container with name ${NAME} does not exist." 312 | exit 1 313 | fi 314 | lxc-info -s -n ${NAME} 315 | exit $? 316 | ;; 317 | --terminate|terminate) 318 | if [ $# -le 0 ] 319 | then 320 | echo "no container name specified" 321 | exit 1 322 | fi 323 | NAME=$1 324 | shift 1 325 | if ! [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 326 | echo "container with name ${NAME} does not exist." 327 | exit 1 328 | fi 329 | getLxcNixParameter $@ 330 | lxc-stop --name ${NAME} -k "$LXCARGUMENTS" 331 | exit $? 332 | ;; 333 | --create|create) 334 | source $BASEDIR/helper/nixcloud-container-create.sh "${@}" 335 | exit 0 336 | ;; 337 | --start|start) 338 | source $BASEDIR/helper/nixcloud-container-start.sh "${@}" 339 | exit 0 340 | ;; 341 | --update|update) 342 | source $BASEDIR/helper/nixcloud-container-update.sh "${@}" 343 | exit 0 344 | ;; 345 | --login|login|attach|--attach) 346 | if [ $# -le 0 ] 347 | then 348 | echo "no container name specified" 349 | echo "" 350 | printHelp 351 | exit 1 352 | fi 353 | NAME=$1 354 | shift 1 355 | getLxcNixParameter $@ 356 | lxc-attach -n ${NAME} 357 | exit 0 358 | ;; 359 | --run|run) 360 | if [ $# -le 0 ] 361 | then 362 | echo "no container name specified" 363 | exit 1 364 | fi 365 | NAME=$1 366 | if ! [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 367 | echo "container with name ${NAME} does not exist." 368 | exit 1 369 | fi 370 | shift 1 371 | if [ $# -le 0 ] 372 | then 373 | echo "no command specified" 374 | exit 1 375 | fi 376 | lxc-attach --name $NAME -- "${@}" 377 | exit 0 378 | ;; 379 | --destroy|destroy) 380 | if [ $# -le 0 ] 381 | then 382 | echo "no container name specified" 383 | echo "" 384 | printHelp 385 | exit 1 386 | fi 387 | NAME=$1 388 | shift 1 389 | getLxcNixParameter $@ 390 | 391 | #check if container is running if so abort 392 | set +e 393 | status=$(lxc-info -s -n "${NAME}" | grep RUNNING) 394 | if [ "$status" != "" ]; then 395 | echo "container $NAME is running" 396 | exit 1 397 | fi 398 | 399 | lxc-destroy --name $NAME "$LXCARGUMENTS" 400 | err=$? 401 | 402 | rm -f "/nix/var/nix/profiles/nixcloud-container/${NAME}/profile" 403 | set -e 404 | 405 | freeIp $NAME 406 | unsetFixedIp $NAME 407 | exit $err 408 | ;; 409 | --show-ip|show-ip) 410 | if [ $# -le 0 ] 411 | then 412 | echo "no container name specified" 413 | echo "" 414 | printHelp 415 | exit 1 416 | fi 417 | 418 | NAME=$1 419 | if ! [ -n "$(lxc-ls --line | grep ^${NAME}\$)" ]; then 420 | echo "container with name ${NAME} does not exist." 421 | exit 1 422 | fi 423 | 424 | NameMap="$SAVE/name" 425 | declare -A names 426 | source -- "$NameMap" || exit 427 | ip="${names[$1]}" 428 | if [ "$ip" == "" ] 429 | then 430 | fileFixedIp="$SAVE/fixedIp" 431 | declare -A fixedIps 432 | source -- "$fileFixedIp" || exit 433 | 434 | ip="${fixedIps[$1]}" 435 | if [ "$ip" = "" ] 436 | then 437 | echo "ip couldn't be found" >&2; exit 1 438 | else 439 | echo "$ip" 440 | exit 0 441 | fi 442 | fi 443 | #check if $ip is an integer 444 | # re='^[0-9]+([.][0-9]+)?$' 445 | # if ! [[ $ip =~ $re ]] ; then 446 | # echo "ip couldn't be found" >&2; exit 1 447 | # fi 448 | echo "10.101.$(($ip/256)).$(($ip%256))" 449 | exit 0 450 | ;; 451 | --help|help) 452 | man $BASEDIR/nixcloud-container-man 453 | exit 0 454 | ;; 455 | --list-generations|list-generations) 456 | if [ $# -le 0 ] 457 | then 458 | echo "no container name specified" 459 | echo "" 460 | printHelp 461 | exit 1 462 | fi 463 | declare -r NAME=$1 464 | 465 | profilePath="/nix/var/nix/profiles/nixcloud-container/${NAME}/profile" 466 | nix-env -p "${profilePath}" "--list-generations" 467 | exit $? 468 | ;; 469 | --switch-generation|switch-generation) 470 | if [ $# -le 0 ] 471 | then 472 | echo "no container name specified" 473 | echo "" 474 | printHelp 475 | exit 1 476 | fi 477 | if [ $# -le 1 ] 478 | then 479 | echo "no generation specified" 480 | echo "" 481 | printHelp 482 | exit 1 483 | fi 484 | NAME=$1 485 | shift 1 486 | profilePath="/nix/var/nix/profiles/nixcloud-container/${NAME}/profile" 487 | nix-env -p "${profilePath}" "--switch-generation" $@ 488 | updateContainer $NAME 489 | exit $? 490 | ;; 491 | --delete-generations|delete-generations) 492 | if [ $# -le 0 ] 493 | then 494 | echo "no container name specified" 495 | echo "" 496 | printHelp 497 | exit 1 498 | fi 499 | if [ $# -le 1 ] 500 | then 501 | echo "no generation specified" 502 | echo "" 503 | printHelp 504 | exit 1 505 | fi 506 | NAME=$1 507 | shift 1 508 | profilePath="/nix/var/nix/profiles/nixcloud-container/${NAME}/profile" 509 | nix-env -p "${profilePath}" "--delete-generations" $@ 510 | updateContainer $NAME 511 | exit $? 512 | ;; 513 | --rollback|rollback) 514 | if [ $# -le 0 ] 515 | then 516 | echo "no container name specified" 517 | echo "" 518 | printHelp 519 | exit 1 520 | fi 521 | NAME=$1 522 | profilePath="/nix/var/nix/profiles/nixcloud-container/${NAME}/profile" 523 | nix-env -p "${profilePath}" "--rollback" 524 | if [ $? -ne 0 ]; then 525 | exit $? 526 | fi 527 | updateContainer $NAME 528 | exit $? 529 | ;; 530 | esac 531 | #command not found --> display help 532 | echo "command $STATE not found" 533 | echo "" 534 | printHelp 535 | -------------------------------------------------------------------------------- /bin/nixcloud-container-man: -------------------------------------------------------------------------------- 1 | .\" Manpage for nixcloud-container. 2 | .\" Visit https://github.com/nixcloud/nixcloud-container to correct errors or typos. 3 | .TH man 1 "25 JAN 2018" "1.0" "nixcloud-container man page" 4 | .SH NAME nixcloud-container – manages lxc container with a shared store between container and host system. 5 | 6 | .SH SYNOPSIS 7 | .BR nixcloud-container " { " create " | " update " | " destroy " | " list-generations " | " delete-generations " | " switch-generation " | " rollback " | " start " | " login " | " stop " | " terminate " | " list " | " show-ip " | " exists " | " state " | " help " } [ 8 | .IR " container-name " " ]" 9 | 10 | .SH DESCRIPTION 11 | This command manages lxc container with a shared store between container and host system. If this is used in combination with nixcloud-webservices all needed lxc configurations can be anbaled in the configuration.nix of the hostsystem with the option ‘nixcloud.container.enable = true;’. 12 | 13 | .SH COMMANDS 14 | 15 | .TP 16 | .BR "create container-name" " config-path [ " "-n" " nix-parameters ] [ " " -l" " lxc-parameters ]" 17 | 18 | creates a new LXC container. is the name of the new created container. It has to be unique and is used to address the container. is the path to the ‘configuration.nix’ of the container. The rootfs of the container will be created in the path /var/lib/lxc/container-name/rootfs. 19 | 20 | .B Example 21 | $ echo “{pkgs, ...}: 22 | { 23 | q services.openssh.enable = true; 24 | services.openssh.ports = [ 22 ]; 25 | environment.systemPackages = [ pkgs.dfc]; 26 | }” >> container_configuration.nix 27 | $ nixcloud-container create foobar container_configuration.nix 28 | Creates a new generator with the name foobar with automatic generated ip. 29 | 30 | .TP 31 | .BR "update " "container-name config-path [ " "-n" " nix-parameters ] [ " " -l" " lxc parameters ]" 32 | updates an existing container with the given name. If no configuration is specified the configuration /var/lib/lxc/container-name/config is used instead. Every update generates a new generation of the container. If the container is already running the container will automatically switch to the new generation. If the container isn’t the container will boot into the new generation once he is started. 33 | 34 | 35 | .TP 36 | .BR "destroy " "container-name [ " " -l" " lxc-parameters ]" 37 | destroys a container. 38 | 39 | .TP 40 | .BR list-generations " container-name" 41 | 42 | lists all generations of this container. Use 43 | .B switch-generation 44 | to switch to one of the listed generations. 45 | 46 | .B Example 47 | $ nixcloud-container list-generations myContainer 48 | 10 2018-01-17 14:41:55 49 | 11 2018-01-19 13:28:02 (current) 50 | 12 2018-01-20 10:22:45 51 | 52 | 53 | 54 | .TP 55 | .BR delete-generations " container-name generations" 56 | 57 | deletes a single or multiple generations of this container. 58 | 59 | .B Examples 60 | $ nixcloud-container delete-generations myContainer 3 4 7 61 | $ nixcloud-container delete-generations myContainer 30d 62 | 63 | .TP 64 | .BR switch-generation " container-name generation-id" 65 | 66 | switches the container to another generation. If the container is already running the container will automatically switch to the new generation. If the container isn’t the container will boot into the new generation once he is started. 67 | 68 | .TP 69 | .BR rollback " container-name" 70 | 71 | Switches the container to the “previous”generation of the container. 72 | 73 | 74 | .TP 75 | .BR start " container-name> [ " "-l" " lxc-parameters]" 76 | 77 | starts the container. 78 | 79 | .B Example 80 | $ nixcloud-container foobar -l -F 81 | starts the container and logs into the shell. “-l -F” passes the “-F” flag thruegh to the lxc command which causes the shell to get attached to the container. 82 | 83 | 84 | .TP 85 | .BR login " container-name [ " -l " lxc-parameters ]" 86 | 87 | opens a shell inside of the container. 88 | 89 | .TP 90 | .BR stop " container-name [ " -l " lxc-parameters ]" 91 | 92 | shuts the container down. 93 | 94 | .TP 95 | .BR terminate " container-name [ " -l " lxc-parameters ]" 96 | 97 | immediately shuts the container down not waiting for services to close themselves. 98 | 99 | .TP 100 | .BR run " container-name command" 101 | 102 | runs a command within the container 103 | 104 | .B Example 105 | $ nixcloud-container foobar run mkdir /tmp/bar 106 | creates the folder /tmp/bar within the container foobar 107 | 108 | .TP 109 | .BR list " [ " -l " lxc-parameters ]" 110 | 111 | lists all containers 112 | 113 | .TP 114 | .BR show-ip " container-name" 115 | 116 | showes the ip of the container 117 | 118 | .TP 119 | .BR exists " container-name" 120 | 121 | shows if a container with a given name exists. 122 | 123 | .TP 124 | .BR state " container-name" 125 | 126 | prints the current state of a container. 127 | 128 | .B Example 129 | $ nixcloud-container state foobar 130 | State: STOPPED 131 | 132 | .TP 133 | .B help 134 | print this message. 135 | 136 | .SH AUTHOR 137 | .B Paul Seitz & Joachim Schiele 138 | 139 | Nixcloud GmbH 140 | 141 | .SH COPYRIGHT 142 | .TP 143 | Copyright © 2018 Paul Seitz & Joachim Schiele 144 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: pkgs.callPackage ./package.nix {} 2 | -------------------------------------------------------------------------------- /example/autostartExample.nix: -------------------------------------------------------------------------------- 1 | { 2 | #starts this container automatically if the host ist started 3 | autostart = true; 4 | 5 | #system configuration for the server 6 | #ip is the ip for the container 7 | #name is the name of the container 8 | #those can be used even if not set above 9 | configuration = {pkgs, ip, name, ...}: 10 | { 11 | services.openssh.enable = true; 12 | services.openssh.ports = [ 22 ]; 13 | networking.hostName = ip; 14 | environment.systemPackages = [ pkgs.dfc pkgs.vim ]; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /example/basicExample.nix: -------------------------------------------------------------------------------- 1 | #a container can be generated out of a typical system description: 2 | {pkgs, ip, name, ...}: 3 | # ip is the ip for the container that will be automatically generated by nixcloud-container 4 | # name is the name of the container 5 | { 6 | services.openssh.enable = true; 7 | services.openssh.ports = [ 22 ]; 8 | environment.systemPackages = [ pkgs.dfc pkgs.vim pkgs.dfc]; 9 | } 10 | -------------------------------------------------------------------------------- /example/fullExample.nix: -------------------------------------------------------------------------------- 1 | ## WARNING: this configuration also requires to configure 2 | # an additional bridge on the host. 3 | { 4 | network = { 5 | #replaces brNC with your own bridge 6 | bridge = "myOwnBridge"; 7 | #sets an ip 8 | ip = "10.10.1.2"; 9 | #disables the additional NAT interface (default) 10 | enableNat = false; 11 | }; 12 | #starts this container automatically if the host ist started 13 | autostart = true; 14 | #additional lxc settings 15 | lxcExtraConfig = ""; 16 | 17 | #name of the container 18 | name = "foobar"; 19 | 20 | #system configuration for the server 21 | #ip is the ip for the container 22 | #name is the name of the container 23 | #those can be used even if not set above 24 | configuration = {pkgs, ip, name, ...}: 25 | { 26 | services.openssh.enable = true; 27 | services.openssh.ports = [ 22 ]; 28 | networking.hostName = ip; 29 | environment.systemPackages = [ pkgs.dfc pkgs.vim ]; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /example/networkExample.nix: -------------------------------------------------------------------------------- 1 | { 2 | network = { 3 | #replaces brNC-hostonly with your own bridge 4 | bridge = "myOwnBridge"; 5 | #sets an ip 6 | ip = "10.10.1.2"; 7 | #disables the additional NAT interface (default) 8 | enableNat = false; 9 | }; 10 | 11 | #system configuration for the server 12 | #ip is the ip for the container 13 | #name is the name of the container 14 | #those can be used even if not set above 15 | configuration = {pkgs, ip, name, ...}: 16 | { 17 | services.openssh.enable = true; 18 | services.openssh.ports = [ 22 ]; 19 | networking.hostName = ip; 20 | environment.systemPackages = [ pkgs.dfc pkgs.vim ]; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /logo/nixcloud.container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixcloud/nixcloud-container/fd95f6b53a44dc76b6cbbed5c0299b9b900ed72c/logo/nixcloud.container.png -------------------------------------------------------------------------------- /logo/nixcloud.container.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 61 | 109 | 116 | 123 | 129 | 135 | 141 | 147 | 153 | 158 | 163 | 166 | 169 | 171 | 173 | 177 | 183 | 190 | 195 | 201 | 207 | 213 | 218 | 223 | 226 | 228 | 231 | 234 | 236 | 239 | 243 | 244 | 245 | 246 | -------------------------------------------------------------------------------- /package.nix: -------------------------------------------------------------------------------- 1 | { pkgs, stdenv, makeWrapper, lxc, nix }: 2 | 3 | stdenv.mkDerivation rec{ 4 | name = "nixcloud-container-${version}"; 5 | version = "0.0.1"; 6 | 7 | nativeBuildInputs = [ makeWrapper ]; 8 | 9 | buildCommand = '' 10 | mkdir -p $out/bin 11 | cp -r ${./bin}/* $out/bin 12 | 13 | # FIXME/HACK nixos-container should probably be run from a 'interactive shell' which already contains a valid 14 | # NIX_PATH set (note: the NIX_PATH currently set points to a none-existing path, WTH?) 15 | # or 16 | # we should abstract over 'nix-channel' and for instance also push updates when they are avialbel ASAP 17 | wrapProgram $out/bin/nixcloud-container \ 18 | --prefix PATH : "${stdenv.lib.makeBinPath [ lxc nix pkgs.eject ]}" # eject because of util-linux and flock 19 | ''; 20 | } 21 | -------------------------------------------------------------------------------- /test.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | #servicescript that creates a file 5 | makeScript = scriptName: pkgs.writeText "serviceScript" '' 6 | #!${pkgs.bash}/bin/bash 7 | while true; do touch /${scriptName}; sleep 1; done 8 | ''; 9 | #derviation for aboves script 10 | makeScriptDerivation = script: (pkgs.stdenv.mkDerivation { 11 | name = "testscript"; 12 | phases = [ "installPhase" ]; 13 | installPhase = '' 14 | mkdir -p $out/bin 15 | cp ${script} $out/bin/yesscript 16 | chmod u+x $out/bin/* 17 | ''; 18 | }); 19 | #container configuration with that script 20 | makeConfig = scriptName: pkgs.writeText "testScript" ''{ 21 | autostart = true; 22 | configuration = { 23 | networking.firewall.allowPing = true; 24 | systemd.services.bar = { 25 | description = "YesItWorks ${scriptName} Daemon"; 26 | 27 | wantedBy = [ "multi-user.target" ]; 28 | after = [ "network.target" ]; 29 | serviceConfig = { 30 | ExecStart = "${makeScriptDerivation (makeScript scriptName)}/bin/yesscript"; 31 | Restart = "always"; 32 | Type="simple"; 33 | }; 34 | }; 35 | }; 36 | 37 | } ''; 38 | 39 | containerConfig = makeConfig "yesitworks"; 40 | containerConfigRollback = makeConfig "yesitrollsback"; 41 | containerConfigUpdate = makeConfig "yesitupdates"; 42 | 43 | #empty container 44 | containerTestTwo = pkgs.writeText "test2" '' 45 | { 46 | configuration = { }; 47 | }''; 48 | 49 | #container with fixed ip 50 | containerTestThree = pkgs.writeText "test3" '' 51 | { 52 | network = { 53 | ip = "10.10.10.10"; 54 | }; 55 | configuration = { }; 56 | }''; 57 | 58 | containerTest4 = pkgs.writeText "test4" '' 59 | { 60 | configuration = { }; 61 | }''; 62 | 63 | containerPreBuild = [ 64 | (import ./bin/helper/lxc-container.nix { 65 | name="test"; 66 | ip="10.101.0.2"; 67 | container = import containerConfig; 68 | }) 69 | (import ./bin/helper/lxc-container.nix { 70 | name="empty"; 71 | ip="10.101.0.2"; 72 | container = import containerTestTwo; 73 | }) 74 | ]; 75 | 76 | in { 77 | name = "nixcloud-container"; 78 | 79 | machine = { pkgs, lib, ... }: { 80 | nix.nixPath = [ "nixpkgs=${pkgs.path}" ]; 81 | nix.binaryCaches = lib.mkForce []; 82 | nixcloud.container.enable = true; 83 | networking.firewall.enable = false; 84 | 85 | virtualisation.memorySize = 2048; 86 | # Needed so that we have all dependencies available for building the 87 | # container config within the VM. 88 | virtualisation.pathsInNixDB = [ pkgs.stdenv ] ++ containerPreBuild; 89 | }; 90 | 91 | testScript = '' 92 | $machine->waitForUnit('multi-user.target'); 93 | 94 | $machine->nest('list all containers, should be empty', sub { 95 | $machine->succeed(' 96 | if [[ $(nixcloud-container list) ]]; then 97 | echo "Output should be empty." 98 | exit 1 99 | else 100 | exit 0 101 | fi 102 | '); 103 | }); 104 | 105 | $machine->succeed(' 106 | #make sure the profile directory exists 107 | mkdir -p /nix/var/nix/profiles 108 | '); 109 | 110 | $machine->succeed(' 111 | #create a new container 112 | nixcloud-container create test ${containerConfig} >&2 113 | '); 114 | 115 | $machine->nest('list all containers, should not be empty', sub { 116 | $machine->succeed(' 117 | if [[ $(nixcloud-container list) ]]; then 118 | exit 0 119 | else 120 | echo "Output should not be empty." 121 | exit 1 122 | fi 123 | '); 124 | }); 125 | 126 | $machine->nest('create a container that already exists', sub { 127 | $machine->fail('nixcloud-container create test ${containerConfig} >&2'); 128 | }); 129 | 130 | $machine->nest('start the container', sub { 131 | $machine->succeed('nixcloud-container start test >&2'); 132 | $machine->waitUntilSucceeds('test -f /var/lib/lxc/test/rootfs/yesitworks'); 133 | }); 134 | 135 | $machine->nest('ping the container', sub{ 136 | #ping the container 137 | $machine->succeed('ping -c 1 10.101.0.2'); 138 | #ping the host 139 | $machine->succeed('lxc-attach -n test -- ping -c 1 10.101.0.1'); 140 | }); 141 | 142 | $machine->nest('test if login into the container works', sub{ 143 | #create a file in the container rootFS 144 | $machine->succeed('touch /var/lib/lxc/test/rootfs/logintest'); 145 | #login to the container and check if the file exists 146 | $machine->succeed('lxc-attach -n test -- test -f /logintest'); 147 | }); 148 | 149 | $machine->nest('try to start container while it is still running', sub { 150 | $machine->succeed(' 151 | if [[ $(nixcloud-container start test) != "*Container is already running.*" ]]; then 152 | exit 0 153 | else 154 | echo "Container should already be running." 155 | exit 1 156 | fi 157 | '); 158 | }); 159 | 160 | $machine->nest('check if container runs with non-root privileges', sub { 161 | $machine->succeed('ps aux | grep "100000.*systemd"'); 162 | }); 163 | 164 | $machine->nest('terminate a machine that does not exist', sub { 165 | $machine->fail('nixcloud-container terminate testbar'); 166 | }); 167 | 168 | $machine->nest('terminate a machine that does exist', sub { 169 | $machine->succeed('nixcloud-container terminate test'); 170 | }); 171 | 172 | $machine->nest('update the existing container', sub { 173 | $machine->succeed( 174 | 'nixcloud-container update test ${containerConfigRollback}' 175 | ); 176 | $machine->succeed( 177 | 'nixcloud-container update test ${containerConfigUpdate}' 178 | ); 179 | }); 180 | 181 | $machine->nest('restart the container', sub { 182 | $machine->succeed('nixcloud-container start test'); 183 | $machine->waitUntilSucceeds('test -f /var/lib/lxc/test/rootfs/yesitupdates'); 184 | }); 185 | 186 | $machine->nest('switch to previous profile', sub{ 187 | #TODO FIXME currently when switching the profile inside a running container_ 188 | # the nixos script returns with exit 2 even if the configuration was 189 | # successfully switched because it does not manage to remount some directorys 190 | # once this bug is fixed this test should be changed back to 'succeed' 191 | $machine->fail('nixcloud-container rollback test >&2'); 192 | $machine->waitUntilSucceeds('test -f /var/lib/lxc/test/rootfs/yesitrollsback'); 193 | $machine->succeed('nixcloud-container list-generations test >&2'); 194 | }); 195 | 196 | $machine->succeed(' 197 | if [[ $(nixcloud-container show-ip test) == "10.101.0.2" ]]; then 198 | exit 0 199 | else 200 | echo "Container should have a valid ip." 201 | exit 1 202 | fi 203 | '); 204 | 205 | $machine->succeed('nixcloud-container list >&2'); 206 | $machine->nest('list all containers, should not be empty', sub { 207 | $machine->succeed(' 208 | if [[ $(nixcloud-container list) ]]; then 209 | exit 0 210 | else 211 | echo "Output should not be empty." 212 | exit 1 213 | fi 214 | '); 215 | }); 216 | 217 | $machine->succeed('rm /var/lib/lxc/test/rootfs/yesitrollsback'); 218 | 219 | 220 | #restart the lxc-autostart service this must restart the test container 221 | $machine->nest('testing autostart of the container', sub{ 222 | $machine->succeed('systemctl restart lxc-autostart.service'); 223 | $machine->waitUntilSucceeds(' 224 | nixcloud-container state test | grep "RUNNING" 225 | '); 226 | $machine->waitUntilSucceeds('test -f /var/lib/lxc/test/rootfs/yesitrollsback'); 227 | }); 228 | 229 | $machine->nest('terminate a machine that does exist', sub { 230 | $machine->succeed('nixcloud-container terminate test'); 231 | }); 232 | 233 | $machine->nest('delete a machine that does exist', sub { 234 | $machine->succeed('nixcloud-container destroy test'); 235 | }); 236 | 237 | $machine->nest('list all containers, should be empty', sub { 238 | $machine->succeed(' 239 | if [[ $(nixcloud-container list) ]]; then 240 | echo "Output should be empty." 241 | exit 1 242 | else 243 | exit 0 244 | fi 245 | '); 246 | }); 247 | 248 | #test if ip is freed 249 | $machine->succeed('nixcloud-container create empty ${containerTestTwo} >&2'); 250 | $machine->succeed(' 251 | if [[ $(nixcloud-container show-ip empty) == "10.101.0.2" ]]; then 252 | exit 0 253 | else 254 | echo "Container should have the ip 10.101.0.2." 255 | exit 1 256 | fi 257 | '); 258 | #test fixed ip 259 | $machine->succeed('nixcloud-container create fixed ${containerTestThree} >&2'); 260 | $machine->succeed(' 261 | if [[ $(nixcloud-container show-ip fixed) == "\"10.10.10.10\"" ]]; then 262 | exit 0 263 | else 264 | echo "Container should have the ip 10.10.10.10." 265 | exit 1 266 | fi 267 | '); 268 | #test fixed ip 269 | $machine->succeed('nixcloud-container create test4 ${containerTest4} >&2'); 270 | $machine->succeed(' 271 | if [[ $(nixcloud-container show-ip test4) == "10.101.0.3" ]]; then 272 | exit 0 273 | else 274 | echo "Container should have the ip 10.101.0.3." 275 | exit 1 276 | fi 277 | '); 278 | 279 | 280 | 281 | ''; 282 | } 283 | --------------------------------------------------------------------------------