Skip to content

Commit

Permalink
Rework nix to use daemon from entrypoint if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
Chuxel committed Aug 31, 2022
1 parent 8807935 commit c6ea115
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 82 deletions.
1 change: 0 additions & 1 deletion src/google-chrome/install.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/bin/bash
# Move to the same directory as this script
cd "$(dirname "${BASH_SOURCE[0]}")"

set -e
Expand Down
7 changes: 6 additions & 1 deletion src/nix/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "nix",
"version": "0.0.1",
"version": "0.1.0",
"name": "Nix Package Manager",
"options": {
"version": {
Expand All @@ -13,6 +13,11 @@
"type": "string",
"default": "",
"description": "Optional space separated list of packages to install."
},
"startDaemon": {
"type": "boolean",
"default": true,
"description": "Attempts to start nix-daemon on container start up to adapt to non-root user UID changes."
}
},
"containerEnv": {
Expand Down
114 changes: 67 additions & 47 deletions src/nix/install.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
#!/bin/bash
# Move to the same directory as this script
cd "$(dirname "${BASH_SOURCE[0]}")"

set -e

# Option defaults
VERSION="${VERSION:-"latest"}"
PACKAGES="${PACKAGES:-""}"
STARTDAEMON="${STARTDAEMON:-"true"}"
USERNAME="${USERNAME:-"automatic"}"

# Nix keys for securly verifying installer download signature
NIX_GPG_KEYS="B541D55301270E0BCF15CA5D8170B4726D7198DE"
GPG_KEY_SERVERS="keyserver hkp://keyserver.ubuntu.com:80
keyserver hkps://keys.openpgp.org
Expand All @@ -22,11 +30,12 @@ fi
. ./utils.sh

# Verify dependencies
check_command curl curl ca-certificates
check_command gpg2 gnupg2
check_command dirmngr dirmngr
check_command xz xz-utils
check_command git git
apt_get_update_if_exists
check_command curl "curl ca-certificates" "curl ca-certificates" "curl ca-certificates"
check_command gpg2 gnupg2 gnupg gnupg2
check_command dirmngr dirmngr dirmngr dirmngr
check_command xz xz-utils xz xz
check_command git git git git

# Determine version
find_version_from_git_tags VERSION https://github.com/NixOS/nix "tags/"
Expand All @@ -39,94 +48,105 @@ if [ "${USERNAME}" = "root" ]; then
useradd -s /bin/bash -u 40000 -g 40000 -m nix
fi

# Create a nixuser group to help deal with UID/GID changes, make that the default group for the user we will install as
groupadd --system -r nixusers
original_group="$(id -g "${USERNAME}")"
original_uid="$(id -u "${USERNAME}")"
usermod -g nixusers "${USERNAME}"

# Adapted from https://nixos.org/download.html#nix-verify-installation
# Create a nix-user group to narrow down which users will be able to access nix.
# See https://nixos.org/manual/nix/stable/installation/single-user.html#single-user-mode
# and https://nixos.org/manual/nix/stable/installation/multi-user.html#restricting-access
groupadd --system -r nix-users
# Create nix dir per https://nixos.org/manual/nix/stable/installation/installing-binary.html#single-user-installation
mkdir /nix
chown ${USERNAME} /nix
# Create temp dir owned by the non-root user
orig_cwd="$(pwd)"
mkdir -p /nix /tmp/nix
chown ${USERNAME} /nix
cd /tmp/nix
tmpdir="$(su ${USERNAME} -c 'mktemp -d')"
cd "${tmpdir}"
# Download and verify install per https://nixos.org/download.html#nix-verify-installation
receive_gpg_keys NIX_GPG_KEYS
curl -sSLf -o ./install-nix https://releases.nixos.org/nix/nix-${VERSION}/install
curl -sSLf -o ./install-nix.asc https://releases.nixos.org/nix/nix-${VERSION}/install.asc
gpg2 --verify ./install-nix.asc
cd "${orig_cwd}"
# Install and post-install processing -- more complicated due to the need to support both root and non-root user
# Perform single-user install since multi-user fails w/o systemd.
original_group="$(id -g "${USERNAME}")"
usermod -g nix-users "${USERNAME}"
su ${USERNAME} -c "$(cat << EOF
set -e
sh /tmp/nix/install-nix --no-daemon --no-modify-profile
ln -s /nix/var/nix/profiles/per-user/${USERNAME}/profile /nix/var/nix/profiles/default
sh ./install-nix --no-daemon --no-modify-profile
# Execute installation steps as non-root user so privs are correct if daemon not used
. /home/${USERNAME}/.nix-profile/etc/profile.d/nix.sh
if [ ! -z "${PACKAGES}" ] && [ "${PACKAGES}" != "none" ]; then
nix-env --install ${PACKAGES}
fi
nix-collect-garbage --delete-old
nix-store --optimise
EOF
)"
chown "${USERNAME}:nixusers" /nix
rm -rf /tmp/nix
# Restore default group we used to install
usermod -a -G nixusers -g "${original_group}" "${USERNAME}"
usermod -a -G nix-users -g "${original_group}" "${USERNAME}"
# Clean up
cd "${orig_cwd}"
rm -rf "${tmpdir}"

# Set nix config
mkdir -p /etc/nix
cat << EOF >> /etc/nix/nix.conf
sandbox = false
trusted-users = ${USERNAME}
EOF

# Setup nixbld group, dir to allow root to function if preferred
# Setup nixbld group, set socket security so we can use w/daemon if preferred - As dscribed in
# https://nixos.org/manual/nix/stable/installation/installing-binary.html#multi-user-installation
# and https://nixos.org/manual/nix/stable/installation/multi-user.html
if ! grep -e "^nixbld:" /etc/group > /dev/null 2>&1; then
groupadd -g 30000 nixbld

fi
for i in $(seq 1 10); do
for i in $(seq 1 32); do
nixbuild_user="nixbld${i}"
if ! id "${nixbuild_user}" > /dev/null 2>&1; then
useradd --system --home-dir /var/empty --gid 30000 --groups nixbld --no-user-group --shell /usr/sbin/nologin --uid $((30000 + i)) "${nixbuild_user}"
fi
done
mkdir -p /nix/var/nix/daemon-socket
chgrp nix-users /nix/var/nix/daemon-socket
chmod ug=rwx,o= /nix/var/nix/daemon-socket

# Setup channels for root user to avoid conflicts, but link profile
cp -R /home/${USERNAME}/.nix-channels /home/${USERNAME}/.nix-defexpr /home/${USERNAME}/.nix-channels /root/
cp /home/${USERNAME}/.nix-channels /root/.nix-channels
# Setup default (root) profile, channel - use real profile path so next derivation created makes the profiles unique
ln -s "$(realpath /nix/var/nix/profiles/per-user/${USERNAME}/profile)" /nix/var/nix/profiles/default
cp -R /home/${USERNAME}/.nix-channels /home/${USERNAME}/.nix-defexpr /root/
ln -s /nix/var/nix/profiles/default /root/.nix-profile

# Setup rcs and profiles to source nix script
snippet='
if [ "${PATH#*$HOME/.nix-profile/bin}" = "${PATH}" ]; then if [ -z "$USER" ]; then USER=$(whoami); fi; . /nix/var/nix/profiles/default/etc/profile.d/nix.sh; fi
# Setup rcs and profiles to source nix script - default path is set automatically by feature, so just need profile specific one
snippet='
if [ "${PATH#*$HOME/.nix-profile/bin}" = "${PATH}" ]; then if [ -z "$USER" ]; then USER=$(whoami); fi; . $HOME/.nix-profile/etc/profile.d/nix.sh; fi
'
update_rc_file /etc/bash.bashrc "${snippet}"
update_rc_file /etc/zsh/zshenv "${snippet}"
update_rc_file /etc/profile.d/nix.sh "${snippet}"
chmod +x /etc/profile.d/nix.sh

# Add optional entrypoint script to attempt to tweak privs for user nix profile if needed
# This is not ideal, but the only option w/o https://github.com/devcontainers/spec/issues/25
# Set up init script to attempt to start up the daemon and fall back on single user mode if all else
# fails. Using the daemon avoids problems if for some reason the user's UID hasn been changed to
# ensure bind mounts have proper permissions on Linux, but is otherwise optional in this case.
echo "Setting up entrypoint..."
cat << EOF > /usr/local/share/nix-init.sh
if [ "${STARTDAEMON}" = "true" ]; then
cat << 'EOF' > /usr/local/share/nix-init.sh
#!/bin/bash
# Nix is very picky about privs under /nix/var, so make sure they are correct in the event the
# user's UID changes. The group privs should be enough for the contents of /nix/store, but update other dirs
if [ "\$(stat -c '%U' /nix/var/nix/profiles/per-user/${USERNAME})" != "${USERNAME}" ]; then
if [ "\$(id -u)" = "0" ]; then
chown ${USERNAME} /nix /nix/store /nix/store/.links
find /nix/var -uid ${original_uid} -execdir chown ${USERNAME} "{}" \+ &
elif type sudo > /dev/null 2>&1; then
sudo chown ${USERNAME} /nix /nix/store /nix/store/.links
sudo find /nix/var -uid ${original_uid} -execdir chown ${USERNAME} "{}" \+ &
else
echo "WARNING: Unable to change nix profile privledges for ${USERNAME}. Try running the container as root."
# Attempt to start daemon, but don't bomb if it fails - we'll assume single user mode then
set +e
if ! pidof nix-daemon > /dev/null 2>&1; then
if [ "$(id -u)" = "0" ]; then
( /nix/var/nix/profiles/default/bin/nix-daemon > /tmp/nix-daemon.log 2>&1 ) &
elif type sudo > /dev/null 2>&1; then
( sudo -n /nix/var/nix/profiles/default/bin/nix-daemon > /tmp/nix-daemon.log 2>&1 ) &
fi
fi
exec "\$@"
exec "$@"
EOF
else
cat << 'EOF' > /usr/local/share/nix-init.sh
#!/bin/bash
exec "$@"
EOF
fi
chmod +x /usr/local/share/nix-init.sh

echo "Done!"
81 changes: 48 additions & 33 deletions src/nix/utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,57 @@ apt_get_update_if_needed()
fi
}

# Function to run apt-get if command exists
apt_get_update_if_exists()
{
if type apt-get > /dev/null 2>&1; then
apt-get update
fi
}

# Checks if packages are installed and installs them if not
check_packages() {
if ! dpkg -s "$@" > /dev/null 2>&1; then
if type dpkg > /dev/null 2>&1 && dpkg -s $1 > /dev/null 2>&1; then
return 0
elif type apk > /dev/null 2>&1 && apk -e info $2 > /dev/null 2>&1; then
return 0
elif type rpm > /dev/null 2>&1 && rpm -q $3 > /dev/null 2>&1; then
return 0
else
echo "Unable to find package manager to check for packages."
exit 1
fi
install_packages "$@"
return $?
}

# Checks if command exists, installs it if not
# check_command <command> "<apt packages to install>" "<apk packages to install>" "<dnf/yum packages to install>"
check_command() {
command_to_check=$1
shift
if type "${command_to_check}" > /dev/null 2>&1; then
return 0
fi
install_packages "$@"
return $?
}

# Installs packages using the appropriate package manager (apt, apk, dnf, or yum)
# install_packages "<apt packages to install>" "<apk packages to install>" "<dnf/yum packages to install>"
install_packages() {
if type apt-get > /dev/null 2>&1; then
apt_get_update_if_needed
apt-get -y install --no-install-recommends "$@"
apt-get -y install --no-install-recommends $1
elif type apk > /dev/null 2>&1; then
apk add -y $2
elif type dnf > /dev/null 2>&1; then
dnf install -y $3
elif type yum > /dev/null 2>&1; then
yum install -y $3
else
echo "Unable to find package manager to install ${command_to_check}"
exit 1
fi
}

Expand All @@ -36,26 +82,9 @@ detect_user() {
fi
}

# Get central common setting
get_common_setting() {
if [ "${common_settings_file_loaded}" != "true" ]; then
curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping."
common_settings_file_loaded=true
fi
if [ -f "/tmp/vsdc-settings.env" ]; then
local multi_line=""
if [ "$2" = "true" ]; then multi_line="-z"; fi
local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')"
if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi
fi
echo "$1=${!1}"
}

# Import the specified key in a variable name passed in as
receive_gpg_keys() {
get_common_setting $1
local keys=${!1}
get_common_setting GPG_KEY_SERVERS true
local keyring_args=""
if [ ! -z "$2" ]; then
mkdir -p "$(dirname \"$2\")"
Expand Down Expand Up @@ -132,7 +161,6 @@ find_version_from_git_tags() {
# Soft version matching that resolves a version for a given package in the *current apt-cache*
# Return value is stored in first argument (the unprocessed version)
apt_cache_version_soft_match() {

# Version
local variable_name="$1"
local requested_version=${!variable_name}
Expand Down Expand Up @@ -193,19 +221,6 @@ update_marker() {
echo "$(echo "$@")" > "${marker_path}"
}


# Checks if command exists, installs it if not
# check_command <command> <package to install>...
check_command() {
command_to_check=$1
shift
if type "${command_to_check}" > /dev/null 2>&1; then
return 0
fi
apt_get_update_if_needed
apt-get -y install --no-install-recommends "$@"
}

# run_if_exists <command> <command arguments>...
run_if_exists() {
if [ -e "$1" ]; then
Expand Down

0 comments on commit c6ea115

Please sign in to comment.