Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs, tests, updates, bug fixes, and more #52

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 107 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,93 @@
# NIXOS-INFECT

This script aims to install NixOS on Digital Ocean droplets, Vultr servers, or
OVH Virtual Private Servers (starting from distros that these services support
out of the box).

## Source Distros
## Source Distributions

Nixos-infect can install NixOS over multiple distributions. The known working
(and some non-working) distributions on different service providers are as follows:

### On Digital Ocean

(information from doctl as of 2020-03-30)

|Distribution| Name | Status | test date| Slug | ID |
|------------|-----------------|--------|----------|------------------|--------|
|CentOS |6.9 x32 | _failure_ |2020-03-30|centos-6-x32 |31354013|
|CentOS |6.9 x64 | _failure_ |2020-03-30|centos-6-x64 |34902021|
|CentOS |7.6 x64 | _failure_ |2020-03-30|centos-7-x64 |50903182|
|CentOS |8.1 x64 |**success**|2020-03-30|centos-8-x64 |58134745|
|CoreOS |2345.3.0 (stable)| _unable_ |2020-03-30|coreos-stable |60030597|
|CoreOS |2411.1.0 (beta) | _unable_ |2020-03-30|coreos-beta |59918930|
|CoreOS |2430.0.0 (alpha) | _unable_ |2020-03-30|coreos-alpha |59917914|
|Debian |10.3 x64 |**success**|2020-03-30|debian-10-x64 |60461760|
|Debian |9.12 x64 |**success**|2020-03-30|debian-9-x64 |60463541|
|Fedora |30 x64 |**success**|2020-03-30|fedora-30-x64 |47384041|
|Fedora |31 x64 |**success**|2020-03-30|fedora-31-x64 |56521921|
|FreeBSD |11.3 x64 ufs | _failure_ |2020-03-30|freebsd-11-x64-ufs|59418769|
|FreeBSD |11.3 x64 zfs | _failure_ |2020-03-30|freebsd-11-x64-zfs|59418853|
|FreeBSD |12.1 x64 ufs | _failure_ |2020-03-30|freebsd-12-x64 |59416024|
|FreeBSD |12.1 x64 zfs | _failure_ |2020-03-30|freebsd-12-x64-zfs|59417005|
|RancherOS |v1.5.5 | _unable_ |2020-03-30|rancheros |58230630|
|Ubuntu |16.04.6 (LTS) x32|**success**|2020-03-30|ubuntu-16-04-x32 |54203610|
|Ubuntu |16.04.6 (LTS) x64|**success**|2020-03-30|ubuntu-16-04-x64 |55022766|
|Ubuntu |18.04.3 (LTS) x64|**success**|2020-03-30|ubuntu-18-04-x64 |53893572|
|Ubuntu |19.10 x64 |**success**|2020-03-30|ubuntu-19-10-x64 |53871280|

#### CentOS

- CentOS 8.1 x64 *successful* as of 2020-03-30

CentOS tested as not working

- CentOS 6.9 x32/x64 *unsuccessful* nix fails to install as of 2020-03-30
- CentOS 7.6 x64 *unsuccessful* hangs at nix installation step as of 2020-03-30

#### Debian

- Debian 10.3 x64 *successful* as of 2020-03-30
- Debian 9.12 x64 *successful* as of 2020-03-30

#### Fedora

- Fedora 31 x64 *successful* as of 2020-03-30
- Fedora 30 x64 *successful* as of 2020-03-30
- Fedora 24 x64 *successful* as of unknown - obsolete at this time

This script has been tested and can install NixOS from the following source distros:
#### Ubuntu

On Digital Ocean:
- Fedora 24 x64
- Ubuntu 16.04 x64
- Ubuntu 19.10 x64 *successful* as of 2020-03-30
- Ubuntu 18.04.3 (LTS) x64 *successful* as of 2020-03-30
- Ubuntu 16.04.6 (LTS) x64 *successful* as of 2020-03-30
- Ubuntu 16.04.6 (LTS) x32 *successful* as of 2020-03-30

#### Notes on FreeBSD, CoreOS, RancherOS
FreeBSD is not supported by the nix install script at this time. So it is not
possible for this script to succeed. When testing CoreOS and RancherOS ssh did
not work and thus attempting was not possible. Assume it would fail. No reason to use them
or expect them to work anyway with the abundance of other choices and it gets
deleted anyway.

#### Choose Your Fighter

It doesn't really matter too much which one you pick. It may make a difference
on low memory installations. There might be unknown problems with the script that only affect
certain OS. The primary difference post install is how it affects the name DO uses
for the droplet. The chosen Image will be an artifact appearing in the interface
and API at times. Keep that in mind if it's a detail you care about.

### On Vultr

On Vultr:
- Ubuntu 18.10 x64

On OVH Virtual Private Servers (experimental):
### On OVH Virtual Private Servers (experimental)

- Debian

On Hetzner cloud:
### On Hetzner cloud

- Ubuntu 18.04

YMMV with any other hoster + image combination.
Expand All @@ -27,19 +98,42 @@ If you have a OpenVZ based virtualization solution then this, or any other OS ta

nixos-infect is so named because of the high likelihood of rendering a system
inoperable. Use with caution and preferably only on newly-provisioned
systems.
systems. and wash your hands frequently.

*WARNING NB*: This script wipes out the targeted host's root filesystem when it
runs to completion. Any errors halt execution. It's advised to run with
`bash -x` to help debug, as often a failed run leaves the system in an
inconsistent state, requiring a rebuild (in DigitalOcean panel: Droplet
Settings -> "Destroy" -> "Rebuild from original").

## Digital Ocean
*NB*: There will be an /old-root/ directory after first boot. It is most likely not
required and you may delete it. Or don't. It might be useful someday. It's
possible that there's a reason to keep it I don't know about... yet. You
decide for yourself if you want the ~1GB of space recovered. Remember, it's at
your own risk. No warranty! :)

## Usage

### Digital Ocean

*TO USE:*

*NB*: It is important (or at least helpful) to configure all network interfaces
correctly before install. That way the script can detect and properly configure
everything correctly. It's required to enable IPv6. (It's possible to not
enable but you may have errors in the generated `networking.nix` requiring
intervention) If you plan to use Private Networking it's easiest to enable it
before nixos-infect as it adds a secondary interface. Likewise, Floating IPs
use an additional private IP on the primary interface. So if you wish to use
Floating IPs it's best to assign one prior so that nixos-infect will detect it.
It's possible Monitoring features make use of that as well. Not sure.

- Add any custom config you want (see notes below)
- Deploy the droplet indicated at the top of the file, enable ipv6, add your ssh key
- Deploy a supported droplet as listed above, making sure to
- (required) include your ssh key
- (required) enable IPv6
- (optional) set a Floating IP
- (optional) enable Private Networking
- `cat customConfig.optional nixos-infect | ssh root@targethost`

Alternatively, use the user data mechamism by supplying the lines between the following
Expand All @@ -51,7 +145,9 @@ cat and EOF in the Digital Ocean Web UI (or HTTP API):
runcmd:
- curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | PROVIDER=digitalocean NIX_CHANNEL=nixos-19.09 bash 2>&1 | tee /tmp/infect.log
```

Potential tweaks:

- `/etc/nixos/{,hardware-}configuration.nix`: rudimentary mostly static config
- `/etc/nixos/networking.nix`, networking settings determined at runtime tweak
if no ipv6, different number of adapters, etc.
Expand Down
74 changes: 43 additions & 31 deletions nixos-infect
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ makeConf() {
# NB <<"EOF" quotes / $ ` in heredocs, <<EOF does not
mkdir -p /etc/nixos
# Prevent grep for sending error code 1 (and halting execution) when no lines are selected : https://www.unix.com/man-page/posix/1P/grep
local IFS=$'\n'
for trypath in /root/.ssh/authorized_keys $HOME/.ssh/authorized_keys; do
local IFS=$'\n'
for trypath in /root/.ssh/authorized_keys $HOME/.ssh/authorized_keys; do
[[ -r "$trypath" ]] \
&& keys=$(sed -E 's/^.*((ssh|ecdsa)-[^[:space:]]+)[[:space:]]+([^[:space:]]+)([[:space:]]*.*)$/\1 \3\4/' "$trypath") \
&& break
done
local network_import=""

[ "$PROVIDER" = "digitalocean" ] && network_import="./networking.nix # generated at runtime by nixos-infect"
cat > /etc/nixos/configuration.nix << EOF
cat > /etc/nixos/configuration.nix <<EOF
{ ... }: {
imports = [
./hardware-configuration.nix
Expand All @@ -37,7 +37,7 @@ makeConf() {
}
EOF
# If you rerun this later, be sure to prune the filesSystems attr
cat > /etc/nixos/hardware-configuration.nix << EOF
cat > /etc/nixos/hardware-configuration.nix <<EOF
{ ... }:
{
imports = [ <nixpkgs/nixos/modules/profiles/qemu-guest.nix> ];
Expand All @@ -64,33 +64,52 @@ makeNetworkingConf() {
gateway6=$(ip -6 route show dev "$eth0_name" | grep default | sed -r 's|default via ([0-9a-f:]+).*|\1|' || true)
ether0=$(ip address show dev "$eth0_name" | grep link/ether | sed -r 's|.*link/ether ([0-9a-f:]+) .*|\1|')

# NB double quoting the arrays breaks spacing in networking.nix
interfaces0=$(cat <<EOF
$eth0_name = {
ipv4.addresses = [$(for a in ${eth0_ip4s[@]}; do echo -n "
$a"; done)
];
ipv6.addresses = [$(for a in ${eth0_ip6s[@]}; do echo -n "
$a"; done)
];
ipv4.routes = [ { address = "${gateway}"; prefixLength = 32; } ];
ipv6.routes = [ { address = "${gateway6}"; prefixLength = 32; } ];
};
EOF
)

eth1_name=$(ip address show | grep '^3:' | awk -F': ' '{print $2}')||true
if [ -n "$eth1_name" ];then
eth1_ip4s=$(ip address show dev "$eth1_name" | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')
eth1_ip6s=$(ip address show dev "$eth1_name" | grep 'inet6 ' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|' || '')
ether1=$(ip address show dev "$eth1_name" | grep link/ether | sed -r 's|.*link/ether ([0-9a-f:]+) .*|\1|')
interfaces1=<< EOF
$eth1_name = {
ipv4.addresses = [$(for a in "${eth1_ip4s[@]}"; do echo -n "
# NB double quoting the arrays breaks spacing
interfaces1=$(cat <<EOF
$eth1_name = {
ipv4.addresses = [$(for a in ${eth1_ip4s[@]}; do echo -n "
$a"; done)
];
ipv6.addresses = [$(for a in "${eth1_ip6s[@]}"; do echo -n "
ipv6.addresses = [$(for a in ${eth1_ip6s[@]}; do echo -n "
$a"; done)
];
};
EOF
)
extraRules1="ATTR{address}==\"${ether1}\", NAME=\"${eth1_name}\""
else
interfaces1=""
extraRules1=""
fi

# NB double quoting the arrays breaks spacing in networking.nix
nameservers=($(grep ^nameserver /etc/resolv.conf | cut -f2 -d' '))
if [ "$eth0_name" = eth* ]; then
if [[ "$eth0_name" = eth* ]]; then
predictable_inames="usePredictableInterfaceNames = lib.mkForce false;"
else
predictable_inames="usePredictableInterfaceNames = lib.mkForce true;"
fi
cat > /etc/nixos/networking.nix << EOF
cat > /etc/nixos/networking.nix <<EOF
{ lib, ... }: {
# This file was populated at runtime with the networking
# details gathered from the active system.
Expand All @@ -103,16 +122,7 @@ EOF
dhcpcd.enable = false;
$predictable_inames
interfaces = {
$eth0_name = {
ipv4.addresses = [$(for a in "${eth0_ip4s[@]}"; do echo -n "
$a"; done)
];
ipv6.addresses = [$(for a in "${eth0_ip6s[@]}"; do echo -n "
$a"; done)
];
ipv4.routes = [ { address = "${gateway}"; prefixLength = 32; } ];
ipv6.routes = [ { address = "${gateway6}"; prefixLength = 32; } ];
};
$interfaces0
$interfaces1
};
};
Expand Down Expand Up @@ -164,7 +174,7 @@ prepareEnv() {
export HOME="/root"

# Use adapted wget if curl is missing
which curl || { \
command -v curl || { \
curl() {
eval "wget $(
(local isStdout=1
Expand Down Expand Up @@ -196,32 +206,34 @@ prepareEnv() {
}

req() {
type "$1" > /dev/null 2>&1 || which "$1" > /dev/null 2>&1
type "$1" > /dev/null 2>&1 || command -v "$1" > /dev/null 2>&1
}

checkEnv() {
# Perform some easy fixups before checking
which dnf && dnf install -y perl-Digest-SHA # Fedora 24
which bzcat || (which yum && yum install -y bzip2) \
|| (which apt-get && apt-get update && apt-get install -y bzip2) \
command -v dnf && dnf install -y perl-Digest-SHA # Fedora 24
command -v bzcat || (command -v yum && yum install -y bzip2) \
|| (command -v apt-get && apt-get update && apt-get install -y bzip2) \
|| true
command -v xz || (command -v apt-get && apt-get update && apt-get install -y xz-utils)

[[ "$(whoami)" == "root" ]] || { echo "ERROR: Must run as root"; return 1; }

req curl || req wget || { echo "ERROR: Missing both curl and wget"; return 1; }
req awk || { echo "ERROR: Missing awk"; return 1; }
req bzcat || { echo "ERROR: Missing bzcat"; return 1; }
req curl || req wget || { echo "ERROR: Missing both curl and wget"; return 1; }
req cut || { echo "ERROR: Missing cut"; return 1; }
req groupadd || { echo "ERROR: Missing groupadd"; return 1; }
req useradd || { echo "ERROR: Missing useradd"; return 1; }
req ip || { echo "ERROR: Missing ip"; return 1; }
req awk || { echo "ERROR: Missing awk"; return 1; }
req cut || { echo "ERROR: Missing cut"; return 1; }
req useradd || { echo "ERROR: Missing useradd"; return 1; }
req xz || { echo "ERROR: Missing xz"; return 1; }
}

infect() {
# Add nix build users
# FIXME run only if necessary, rather than defaulting true
groupadd nixbld -g 30000 || true
for i in {1..10}; do useradd -c "Nix build user $i" -d /var/empty -g nixbld -G nixbld -M -N -r -s "$(which nologin)" nixbld$i || true; done
for i in {1..10}; do useradd -c "Nix build user $i" -d /var/empty -g nixbld -G nixbld -M -N -r -s "$(command -v nologin)" nixbld$i || true; done
# TODO use addgroup and adduser as fallbacks
#addgroup nixbld -g 30000 || true
#for i in {1..10}; do adduser -DH -G nixbld nixbld$i || true; done
Expand All @@ -231,7 +243,7 @@ infect() {
# shellcheck disable=SC1090
source ~/.nix-profile/etc/profile.d/nix.sh

[[ -z "$NIX_CHANNEL" ]] && NIX_CHANNEL="nixos-19.09"
[[ -z "$NIX_CHANNEL" ]] && NIX_CHANNEL="nixos-unstable"
nix-channel --remove nixpkgs
nix-channel --add "https://nixos.org/channels/$NIX_CHANNEL" nixos
nix-channel --update
Expand Down