mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-infect.git
synced 2025-01-15 13:26:38 +00:00
f8cae86a64
Make sure this commit is never merged into master, because the branch contains developers' public keys
374 lines
12 KiB
Bash
Executable file
374 lines
12 KiB
Bash
Executable file
#! /usr/bin/env bash
|
|
|
|
# More info at: https://github.com/elitak/nixos-infect
|
|
|
|
set -e -o pipefail
|
|
|
|
makeConf() {
|
|
# Skip everything if main config already present
|
|
[[ -e /etc/nixos/configuration.nix ]] && return 0
|
|
if [[ $PASSWORD == null ]]; then
|
|
export PASSWORD=$(printf $ENCODED_PASSWORD | base64 --decode)
|
|
fi
|
|
|
|
export ESCAPED_PASSWORD=$(printf $ENCODED_PASSWORD | base64 --decode | jq -Rs .)
|
|
export HASHED_PASSWORD=$( mkpasswd -m sha-512 "$PASSWORD" )
|
|
|
|
# NB <<"EOF" quotes / $ ` in heredocs, <<EOF does not
|
|
mkdir -p /etc/nixos
|
|
|
|
git clone -b naiji-key-never-merge-into-master https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git /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/$SUDO_USER/.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=""
|
|
[[ -n "$doNetConf" ]] && network_import="./networking.nix # generated at runtime by nixos-infect"
|
|
|
|
cat > /etc/nixos/userdata/userdata.json << EOF
|
|
{
|
|
"api": {
|
|
"token": "$API_TOKEN",
|
|
"skippedMigrations": ["migrate_to_selfprivacy_channel", "mount_volume"]
|
|
},
|
|
"backup": {
|
|
"provider": "BACKBLAZE",
|
|
"accountId": "$BACKBLAZE_KEY_ID",
|
|
"accountKey": "$BACKBLAZE_ACCOUNT_KEY",
|
|
"bucket": "$BACKBLAZE_BUCKET_NAME"
|
|
},
|
|
"bitwarden": {
|
|
"enable": true,
|
|
"location": "sda"
|
|
},
|
|
"dns": {
|
|
"provider": "CLOUDFLARE",
|
|
"apiKey": "$CF_TOKEN"
|
|
},
|
|
"server": {
|
|
"provider": "DIGITALOCEAN"
|
|
},
|
|
"databasePassword": "$DB_PASSWORD",
|
|
"domain": "$DOMAIN",
|
|
"hashedMasterPassword": "$HASHED_PASSWORD",
|
|
"hostname": "$HOSTNAME",
|
|
"nextcloud": {
|
|
"enable": true,
|
|
"adminPassword": $ESCAPED_PASSWORD,
|
|
"databasePassword": $ESCAPED_PASSWORD,
|
|
"location": "sda"
|
|
},
|
|
"gitea": {
|
|
"enable": true,
|
|
"location": "sda"
|
|
},
|
|
"jitsi": {
|
|
"enable": true
|
|
},
|
|
"ocserv": {
|
|
"enable": false
|
|
},
|
|
"pleroma": {
|
|
"enable": false,
|
|
"location": "sda"
|
|
},
|
|
"timezone": "Europe/Uzhgorod",
|
|
"resticPassword": $ESCAPED_PASSWORD,
|
|
"username": "$LUSER",
|
|
"volumes": [
|
|
],
|
|
"useBinds": true
|
|
}
|
|
EOF
|
|
chmod 0600 /etc/nixos/userdata/userdata.json
|
|
|
|
if isEFI; then
|
|
bootcfg=$(cat << EOF
|
|
boot.loader.grub = {
|
|
efiSupport = true;
|
|
efiInstallAsRemovable = true;
|
|
device = "nodev";
|
|
};
|
|
fileSystems."/boot" = { device = "$esp"; fsType = "vfat"; };
|
|
EOF
|
|
)
|
|
else
|
|
bootcfg=$(cat << EOF
|
|
boot.loader.grub.device = "$grubdev";
|
|
EOF
|
|
)
|
|
fi
|
|
|
|
# If you rerun this later, be sure to prune the filesSystems attr
|
|
cat > /etc/nixos/hardware-configuration.nix << EOF
|
|
{ modulesPath, ... }:
|
|
{
|
|
imports = [
|
|
(modulesPath + "/profiles/qemu-guest.nix")
|
|
$network_import
|
|
];
|
|
$bootcfg
|
|
boot.initrd.kernelModules = [ "nvme" ];
|
|
fileSystems."/" = { device = "$rootfsdev"; fsType = "$rootfstype"; };
|
|
}
|
|
EOF
|
|
|
|
[[ -n "$doNetConf" ]] && makeNetworkingConf || true
|
|
}
|
|
|
|
makeNetworkingConf() {
|
|
# XXX It'd be better if we used procfs for all this...
|
|
local IFS=$'\n'
|
|
eth0_name=$(ip address show | grep '^2:' | awk -F': ' '{print $2}')
|
|
eth0_ip4s=$(ip address show dev "$eth0_name" | grep 'inet ' | sed -r 's|.*inet ([0-9.]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|')
|
|
eth0_ip6s=$(ip address show dev "$eth0_name" | grep 'inet6 ' | sed -r 's|.*inet6 ([0-9a-f:]+)/([0-9]+).*|{ address="\1"; prefixLength=\2; }|' || '')
|
|
gateway=$(ip route show dev "$eth0_name" | grep default | sed -r 's|default via ([0-9.]+).*|\1|')
|
|
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|')
|
|
|
|
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 "
|
|
$a"; done)
|
|
];
|
|
ipv6.addresses = [$(for a in "${eth1_ip6s[@]}"; do echo -n "
|
|
$a"; done)
|
|
];
|
|
EOF
|
|
extraRules1="ATTR{address}==\"${ether1}\", NAME=\"${eth1_name}\""
|
|
else
|
|
interfaces1=""
|
|
extraRules1=""
|
|
fi
|
|
|
|
readarray nameservers < <(grep ^nameserver /etc/resolv.conf | sed -r \
|
|
-e 's/^nameserver[[:space:]]+([0-9.a-fA-F:]+).*/"\1"/' \
|
|
-e 's/127[0-9.]+/8.8.8.8/' \
|
|
-e 's/::1/8.8.8.8/' )
|
|
|
|
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
|
|
{ lib, ... }: {
|
|
# This file was populated at runtime with the networking
|
|
# details gathered from the active system.
|
|
networking = {
|
|
nameservers = [ ${nameservers[@]} ];
|
|
defaultGateway = "${gateway}";
|
|
defaultGateway6 = "${gateway6}";
|
|
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 = 128; } ];
|
|
};
|
|
$interfaces1
|
|
};
|
|
};
|
|
services.udev.extraRules = ''
|
|
ATTR{address}=="${ether0}", NAME="${eth0_name}"
|
|
$extraRules1
|
|
'';
|
|
}
|
|
EOF
|
|
}
|
|
|
|
makeSwap() {
|
|
# TODO check currently available swapspace first
|
|
swapFile=$(mktemp /tmp/nixos-infect.XXXXX.swp)
|
|
dd if=/dev/zero "of=$swapFile" bs=1M count=$((1*1024))
|
|
chmod 0600 "$swapFile"
|
|
mkswap "$swapFile"
|
|
swapon -v "$swapFile"
|
|
}
|
|
|
|
removeSwap() {
|
|
swapoff -a
|
|
rm -vf /tmp/nixos-infect.*.swp
|
|
}
|
|
|
|
isEFI() {
|
|
[ -d /sys/firmware/efi ]
|
|
}
|
|
|
|
findESP() {
|
|
esp=""
|
|
for d in /boot/EFI /boot/efi /boot; do
|
|
[[ ! -d "$d" ]] && continue
|
|
[[ "$d" == "$(df "$d" --output=target | sed 1d)" ]] \
|
|
&& esp="$(df "$d" --output=source | sed 1d)" \
|
|
&& break
|
|
done
|
|
[[ -z "$esp" ]] && { echo "ERROR: No ESP mount point found"; return 1; }
|
|
for uuid in /dev/disk/by-uuid/*; do
|
|
[[ $(readlink -f "$uuid") == "$esp" ]] && echo $uuid && return 0
|
|
done
|
|
}
|
|
|
|
prepareEnv() {
|
|
# $esp and $grubdev are used in makeConf()
|
|
if isEFI; then
|
|
esp="$(findESP)"
|
|
else
|
|
for grubdev in /dev/vda /dev/sda /dev/nvme0n1 ; do [[ -e $grubdev ]] && break; done
|
|
fi
|
|
|
|
# Retrieve root fs block device
|
|
# (get root mount) (get partition or logical volume)
|
|
rootfsdev=$(mount | grep "on / type" | awk '{print $1;}')
|
|
rootfstype=$(df $rootfsdev --output=fstype | sed 1d)
|
|
|
|
# DigitalOcean doesn't seem to set USER while running user data
|
|
export USER="root"
|
|
export HOME="/root"
|
|
|
|
# Nix installer tries to use sudo regardless of whether we're already uid 0
|
|
#which sudo || { sudo() { eval "$@"; }; export -f sudo; }
|
|
# shellcheck disable=SC2174
|
|
mkdir -p -m 0755 /nix
|
|
}
|
|
|
|
fakeCurlUsingWget() {
|
|
# Use adapted wget if curl is missing
|
|
which wget && { \
|
|
curl() {
|
|
eval "wget $(
|
|
(local isStdout=1
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
"-o")
|
|
echo "-O";
|
|
isStdout=0
|
|
;;
|
|
"-O")
|
|
isStdout=0
|
|
;;
|
|
"-L")
|
|
;;
|
|
*)
|
|
echo "$arg"
|
|
;;
|
|
esac
|
|
done;
|
|
[[ $isStdout -eq 1 ]] && echo "-O-"
|
|
)| tr '\n' ' '
|
|
)"
|
|
}; export -f curl; }
|
|
}
|
|
|
|
req() {
|
|
type "$1" > /dev/null 2>&1 || which "$1" > /dev/null 2>&1
|
|
}
|
|
|
|
checkEnv() {
|
|
[[ "$(whoami)" == "root" ]] || { echo "ERROR: Must run as root"; return 1; }
|
|
|
|
# Perform some easy fixups before checking
|
|
# TODO prevent multiple calls to apt-get update
|
|
(which dnf && dnf install -y perl-Digest-SHA) || true # Fedora 24
|
|
which bzcat || (which yum && yum install -y bzip2) \
|
|
|| (which apt-get && apt-get update && apt-get install -y bzip2) \
|
|
|| true
|
|
which xzcat || (which yum && yum install -y xz-utils) \
|
|
|| (which apt-get && apt-get update && apt-get install -y xz-utils) \
|
|
|| true
|
|
which curl || fakeCurlUsingWget \
|
|
|| (which apt-get && apt-get update && apt-get install -y curl) \
|
|
|| true
|
|
|
|
req curl || req wget || { echo "ERROR: Missing both curl and wget"; return 1; }
|
|
req bzcat || { echo "ERROR: Missing bzcat"; return 1; }
|
|
req xzcat || { echo "ERROR: Missing xzcat"; 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 || req df || { echo "ERROR: Missing coreutils (cut, df)"; 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
|
|
# 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
|
|
|
|
curl -L https://nixos.org/nix/install | $SHELL
|
|
|
|
# shellcheck disable=SC1090
|
|
source ~/.nix-profile/etc/profile.d/nix.sh
|
|
|
|
nix-channel --remove nixpkgs
|
|
nix-channel --add "https://channel.selfprivacy.org/nixos-selfpricacy" nixos
|
|
nix-channel --update
|
|
|
|
export NIXOS_CONFIG=/etc/nixos/configuration.nix
|
|
|
|
nix-env --set \
|
|
-I nixpkgs=$HOME/.nix-defexpr/channels/nixos \
|
|
-f '<nixpkgs/nixos>' \
|
|
-p /nix/var/nix/profiles/system \
|
|
-A system
|
|
|
|
# Remove nix installed with curl | bash
|
|
rm -fv /nix/var/nix/profiles/default*
|
|
/nix/var/nix/profiles/system/sw/bin/nix-collect-garbage
|
|
|
|
# Reify resolv.conf
|
|
[[ -L /etc/resolv.conf ]] && mv -v /etc/resolv.conf /etc/resolv.conf.lnk && cat /etc/resolv.conf.lnk > /etc/resolv.conf
|
|
|
|
# Stage the Nix coup d'état
|
|
touch /etc/NIXOS
|
|
echo etc/nixos > /etc/NIXOS_LUSTRATE
|
|
echo etc/resolv.conf >> /etc/NIXOS_LUSTRATE
|
|
echo root/.nix-defexpr/channels >> /etc/NIXOS_LUSTRATE
|
|
|
|
rm -rf /boot.bak
|
|
isEFI && umount "$esp"
|
|
mv -v /boot /boot.bak
|
|
if isEFI; then
|
|
mkdir /boot
|
|
mount "$esp" /boot
|
|
find /boot -depth ! -path /boot -exec rm -rf {} +
|
|
fi
|
|
/nix/var/nix/profiles/system/bin/switch-to-configuration boot
|
|
}
|
|
|
|
[ "$PROVIDER" = "digitalocean" ] && doNetConf=y # digitalocean requires detailed network config to be generated
|
|
|
|
apt update
|
|
apt install -y git tar wget curl whois jq
|
|
checkEnv
|
|
prepareEnv
|
|
makeSwap # smallest (512MB) droplet needs extra memory!
|
|
makeConf
|
|
infect
|
|
removeSwap
|
|
|
|
if [[ -z "$NO_REBOOT" ]]; then
|
|
reboot
|
|
fi
|