Getting Started: Google Cloud Edition

Prerequisites

Your setup machine needs the following:

  • Nix on your Setup Machine (unless you're using NixOS)

  • An id_ed25519 keypair on your Setup Machine. (Link coming soon.)

  • Git (Optional). Clan uses Git internally, but you can optionally install it to make your own use of it. See the Git installation instructions.

1. Create a Server on Google Cloud

Danger

The steps in this document will erase all data on your Google Cloud server's hard drive.

If you already have a server on Google Cloud running, you can skip this step.

We recommend using gcloud command-line tool.

Launch a server:

gcloud compute instances create linux-server-01 \
    --machine-type=e2-medium \
    --image-family=ubuntu-2204-lts \
    --image-project=ubuntu-os-cloud \
    --boot-disk-size=20GB \
    --metadata="ssh-keys=$(whoami):$(cat ~/.ssh/id_ed25519.pub)" \
    --no-shielded-secure-boot \
    --no-shielded-vtpm \
    --no-shielded-integrity-monitoring

(Notice the whoami: We're creating a user on the remote machine with the same name as your local username.)

After this command runs, you can find the IP address from the command's output, under EXTERNAL_IP:

NAME             ZONE           MACHINE_TYPE  PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP  STATUS
linux-server-01  us-central1-a  e2-medium                  10.128.0.4   34.170.5.83  RUNNING

Verify that you can log in:

ssh <USERNAME>@<IP-ADDRESS>

replacing <USERNAME> with your local username and <IP-ADDRESS> with the IP address displayed after the server was provisioned. (Note, the server sometimes takes a moment to boot up, so you might need to try this command a couple of times before it lets you in.)

Next, enable root access. First, copy the authorized_keys file:

gcloud compute ssh linux-server-01 --command="sudo mkdir -p /root/.ssh && sudo cp ~/.ssh/authorized_keys /root/.ssh/authorized_keys"

Second, enable root login:

gcloud compute ssh linux-server-01 --command="sudo sed -i 's/PermitRootLogin no/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config"

Third, restart the ssh daemon:

gcloud compute ssh linux-server-01 --command="sudo systemctl restart ssh"

Now test out root access:

ssh root@<IP-ADDRESS>

replacing <IP-ADDRESS> with the ip address provided earlier.

Then exit:

exit

2. Run the Clan setup

Start by creating a new clan:

nix run "https://git.clan.lol/clan/clan-core/archive/main.tar.gz#clan-cli" --refresh -- init

and enter a name for it, e.g. MY-CLAN-1, followed by a domain, e.g. myclan1.lol. (This does not have to be an actual registered domain.)

Important

The first time you run this, Clan will automatically create an age key at ~/.config/sops/age/keys.txt. This key encrypts your secrets - back it up somewhere safe, and then type "y".

Important

If you've run this before, you'll also be asked to select admin keys; you'll most likely want to type "1" and press enter.

Change to the new folder:

cd MY-CLAN-1

You will see a message about direnv needing approval to run. Type:

direnv allow

3. Create a Machine Configuration

Next, create a machine configuration, which adds a description of a machine to your inventory. For this example, call it test-machine, by typing:

clan machines create test-machine

Open clan.nix, and find the inventory.machines line; add the following immediately after it; replace the IP address with your Hetzner server's IP address:

inventory.machines = { # FIND THIS LINE, ADD THE FOLLOWING
    test-machine = {
        deploy.targetHost = "root@<IP-ADDRESS>"; # REPLACE WITH YOUR MACHINE'S IP ADDRESS; keep "root@"
        tags = [ ];
    };

Test it out:

clan machines list

4. Add your allowed keys

Next, add your public key to the allowed keys. You can find it by running:

cat ~/.ssh/id_ed25519.pub

Open clan.nix, and replace PASTE_YOUR_KEY_HERE with the contents of the id_ed25519.pub file:

"admin-machine-1" = "PASTE_YOUR_KEY_HERE";

Verify that your configuration is valid:

clan show

5. Gather Hardware Configuration

Now gather the hardware configuration from the target machine:

clan machines init-hardware-config test-machine

You will be asked to enter "y" to proceed.

6. Add a Disk Configuration.

Next, configure a disk for the target machine. You'll run this command in two steps; first, type it like so:

clan templates apply disk ext4-single-disk test-machine --set mainDisk ""

This will generate an error; note the disk ID it prints out (typically /dev/disk/by-id/scsi-0Google_PersistentDisk_persistent-disk-0), and add it inside the quotes, e.g.:

clan templates apply disk ext4-single-disk test-machine --set mainDisk "/dev/disk/by-id/scsi-0Google_PersistentDisk_persistent-disk-0"

Next we need to deal with a situation specific to Google Cloud. Google Cloud does not expose the partition tables to the guest operating systsem. As such, we need to make some adjustments to the default configuration.nix and disko.nix files.

Switch to the directory holding these files:

cd machines/test-machine

Open configuration.nix and replace its contents with:

{ lib, modulesPath, ... }:
{
  imports = [
    (modulesPath + "/virtualisation/google-compute-image.nix")
  ];
  networking.hostName = lib.mkForce "test-machine";
  security.googleOsLogin.enable = lib.mkForce false;
}

The GCP module (google-compute-image.nix) provides essential drivers and services for running NixOS on Google Cloud, but it also enables Google OS Login by default, which is a GCP-specific SSH key management system that conflicts with clan's sshd service. We disable it with security.googleOsLogin.enable = lib.mkForce false so that clan's authorized_keys configuration works properly. The lib.mkForce is needed because we're overriding values that the GCP module already sets.

Next, open 'disko.nix" and add the highlighted lines:

# ---
# schema = "ext4-single-disk"
# [placeholders]
# mainDisk = "/dev/disk/by-id/scsi-0Google_PersistentDisk_persistent-disk-0" 
# ---
# This file was automatically generated!
# CHANGING this configuration requires wiping and reinstalling the machine
{ lib, ... }: # ADD THIS LINE
{
  boot.loader.grub = {
    efiInstallAsRemovable = true;
    efiSupport = true;
  };
  boot.loader.timeout = lib.mkForce 0; # ADD THIS LINE
  fileSystems."/".device = lib.mkForce "/dev/disk/by-label/nixos"; # ADD THIS LINE

  disko.devices = {
    disk = {
      main = {
        name = "main-49086db16eb74c23bed59fc2045fd513";
        device = "/dev/disk/by-id/scsi-0Google_PersistentDisk_persistent-disk-0";
        type = "disk";
        content = {
          type = "gpt";
          partitions = {
            "boot" = {
              size = "1M";
              type = "EF02"; # for grub MBR
              priority = 1;
            };
            ESP = {
              type = "EF00";
              size = "500M";
              content = {
                type = "filesystem";
                format = "vfat";
                mountpoint = "/boot";
                mountOptions = [ "umask=0077" ];
                extraArgs = [ "-n" "ESP" ]; # ADD THIS LINE
              };
            };
            root = {
              size = "100%";
              content = {
                type = "filesystem";
                format = "ext4";
                mountpoint = "/";
                extraArgs = [ "-L" "nixos" ]; # ADD THIS LINE
              };
            };
          };
        };
      };
    };
  };
}

Then return to the root of the clan:

cd ../..

7. Install NixOS

Install NixOS on the target machine by typing:

clan machines install test-machine

You will be asked whether you want to install — type y. You will also be prompted for a password; you can accept the defaults and press Enter.

You will then be asked for a password to assign to the root login for the machine. You can either create one, or let Clan assign a random one.

If you get an error about Sandboxing

If you get an error regarding sandboxing not being available, type the following to disable sandboxing, and then run the above command again:

clan vars generate test-machine --no-sandbox

8. Test the Connection

Now you can try connecting to the remote machine:

clan ssh test-machine

You'll quite likely get an error at first regarding the host identification. It should include a line to type to remove the old ID; paste the line you're shown, which will look similar to this:

  ssh-keygen -f '/home/user/.ssh/known_hosts' -R '<IP-ADDRESS>'

Then try again:

clan ssh test-machine

You should connect and see the prompt:

[root@test-machine:~]#

Practice: Install Some Packages

Now let's look at how you can use Clan to install and remove packages on a target machine.

For this demonstration we'll add three command-line packages: bat, btop, and tldr. In clan.nix, under inventory.instances, add the following lines:

  inventory.instances = {
    packages = {
      roles.default.machines."test-machine".settings = {
        packages = [ "bat" "btop" "tldr" ];
      };
    };
    # ... existing wifi service ...
  };

This declares that the three packages will be present on the machine. To install them, type:

clan machines update test-machine

Now ssh into the machine, and they should be present:

which bat
which btop
which tldr

Each will show a path to the binary file:

/run/current-system/sw/bin/bat
/run/current-system/sw/bin/btop
/run/current-system/sw/bin/tldr

Next, let's remove one of the three packages. The packages portion of clan.nix declares what additional packages should exist; by removing one, Nix will remove that package. Remove the "tldr" from the list:

        packages = [ "bat" "btop" ];

and run the update again:

clan machines update test-machine

Now when you check which tldr, it should show that it's not in the path:

which tldr
which: no tldr in (/run/wrappers/bin:/root/.nix-profile/bin:/nix/profile/bin:/root/.local/state/nix/profile/bin:/etc/profiles/per-user/root/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin)

Practice: Add a User

When you need to add a new user, you can do so right from within the clan.nix file, and then update the system.

Add a New User (no sudo access)

Let's add a user called Alice. Open clan.nix, and under inventory.instances, add the following:

  inventory.instances = { # Add the following under this line
    user-alice = {
      module.name = "users";
      roles.default.machines."test-machine" = {};
      roles.default.tags.all = {};
      roles.default.settings = {
        user = "alice";
      };
    };

Save the file. Now type the following to add a password for alice (include the no-sandbox if you needed no sandbox earlier):

clan vars generate test-machine --no-sandbox

You will be prompted for a password. Or you can press Enter to automatically generate one.

If you automatically generated one, to retrieve it type:

clan vars get test-machine user-password-alice/user-password
Note

On cloud machines, this password will be used for sudo access if you grant it. Typically password login is disabled on a cloud machine.

Next, let's add a key file so Alice can log in remotely. For this we'll use your own key file as before. Type:

cat ~/.ssh/id_ed25519.pub

Then open machines/test-machine/configuration.nix. Add the following, before the closing brace:

{
  imports = [

  ];

  # New machine!

  users.users.alice.openssh.authorizedKeys.keys = [
    "PASTE_YOUR_KEY_HERE"
  ];
}

and replace PASTE_YOUR_KEY_HERE with the contents of the file.

Now update the machine by typing:

clan machines update test-machine

Once complete, you can log in as alice:

ssh alice@<IP-ADDRESS>

replacing <IP-ADDRESS> with the Hetzner server's IP address.

Give that user sudo access

After you trust Alice, you can grant her sudo access. To do so, update the clan.nix file by adding her to the wheel group:

    user-alice = {
      module.name = "users";
      roles.default.machines."test-machine" = {};
      roles.default.tags.all = {};
      roles.default.settings = {
        user = "alice";
        groups = [ "wheel" ];  # Add this to allow sudo
      };
    };

Again type:

clan machines update test-machine

If you were already logged in as alice before running the update, you will need to log out and back in for the change to take.

Then after logged in as alice, try using sudo:

sudo echo "hello"

You will be prompted for the password and should see "hello" printed.

Revoke the sudo access

To revoke alice's sudo access, simply remove the line you added:

        groups = [ "wheel" ];

And once again run:

clan machines update test-machine

Log out, and log alice back in. Now try the same sudo command; you'll be prompted for password, but then shown:

alice is not in the sudoers file.