davmac314 / dinit

Service monitoring / "init" system
Apache License 2.0
583 stars 45 forks source link

Clear environment for all services started. #286

Closed IngwiePhoenix closed 5 months ago

IngwiePhoenix commented 7 months ago

Hello!

I found a bit of a bug. While setting up laminar CI/CD and running a few jobs, I got a weird error from Yarn. Maybe you can spot the problem right away ;)

# curl localhost:2828/log/snort/latest
[laminar] Executing cfg/jobs/snort.run
+ GIT_ARGS=
+ '[' '!' -z '' ']'
+ git clone https://git.v0l.io/Kieran/snort.git .
Cloning into '.'...
warning: unable to access '/root/.config/git/ignore': Permission denied
warning: unable to access '/root/.config/git/attributes': Permission denied
+ '[' '!' -z '' ']'
+ export YARN_CACHE_FOLDER=/usb/yarn-cache
+ YARN_CACHE_FOLDER=/usb/yarn-cache
+ yarn
Error: EACCES: permission denied, open '/root/.config/yarn'
    at Object.openSync[... snip ...]

And here is the dinit unit:

type = process
command = /usr/local/sbin/laminard -v
run-as = laminar
logfile = /var/log/laminar.log
working-dir = /usb/laminar
env-file = laminard.env

The problem: The unit was run-as = laminar but still had $HOME set to /root! This is obviously wrong. So, I used load-options = export-passwd-vars to work around that.

But honestly, this should actually be the default whenever you use run-as.

Is there a way I can just make this the default for all units going forward?

Kind regards, Ingwie

IngwiePhoenix commented 7 months ago

You can see me experimenting between revisions of the script and the results here:

davmac314 commented 7 months ago

I'm a bit unsure whether this is the wrong behaviour. The run-as setting just sets the identity that runs the process - that's all it was meant to do; the load-options = export-passwd-vars exists precisely for this kind of case where you also want various environment variables to be set. I do see why it was unexpected, but I think that's because your use case is a bit unusual: you're seemingly running dinit as root, with HOME set (to root's home directory). Normally when dinit is run by root it's a system instance and wouldn't have HOME set. How are you starting dinit?

One option if you don't want to have to specify load-options for several services might be to clear HOME via the environment file, /etc/dinit/environment. If not running dinit as a system instance you need to explicitly pass -e /etc/dinit/environment to use this. The contents can be:

!unset HOME

... to clear just the home directory, or:

!clear

... to completely clear the environment. (These are documented in the dinit man page).

IngwiePhoenix commented 7 months ago

Thanks for the reply!

I looked into the details a little more once you mentioned "system service". So I was running dinit as part of OpenWrt before but had installed Alpine's APK to get some dev packages installed too. However, the apk installed packages and OpenWrt packages soon started to "meld" and cause a heap of linkage problems... so, I reinstalled.

Since then, I am using Alpine in a chroot off of their own chroot installer script (I realized I had ujail from OpenWrt only a while later...) and thus used the entry script to run dinit on system startup.

Here are the details:

## /etc/init.d/dinit
#!/bin/sh /etc/rc.common

START=50
USE_PROCD=1
PROG=/sdcard/bin/dinit

start_service() {
        procd_open_instance
        procd_set_param command /usb/alpine/enter-chroot dinit -d /sdcard/dinit.d
        procd_set_param respawn
        procd_close_instance
}

## /usb/alpine/enter-chroot
#!/bin/sh
set -e

try_to_mount() {
  local mto mfrom
  mfrom="$1"
  mto="/usb/alpine/$2"
  if [ -z "$2" ]; then
    mto="/usb/alpine/$1"
  fi

  # Exists?
  if [ ! -d "$mto" ]; then
    mkdir -p "$mto"
  fi

  # is not mounted?
  mountpoint -q "$mto" \
  || mount --bind "$mfrom" "$mto"
}

# Mounts
## Dirs
try_to_mount /proc
try_to_mount /sys
### /dev needs --rbind...
mount --rbind /dev /usb/alpine/dev
try_to_mount /root
## Drives
try_to_mount /usb
try_to_mount /sdcard
try_to_mount /mnt/diskstation/bunker
try_to_mount /mnt/diskstation/scratch
## Socks
try_to_mount /var/run/tailscale /run/tailscale

ENV_FILTER_REGEX='(TERM|ARCH|CI|QEMU_EMULATOR|TRAVIS_.*)'

user='root'
if [ $# -ge 2 ] && [ "$1" = '-u' ]; then
    user="$2"; shift 2
fi
oldpwd="$(pwd)"
[ "$(id -u)" -eq 0 ] || _sudo='sudo'

tmpfile="$(mktemp)"
chmod 644 "$tmpfile"
export | sed -En "s/^([^=]+ ${ENV_FILTER_REGEX}=)('.*'|\".*\")$/\1\3/p" > "$tmpfile" || true

cd "$(dirname "$0")"
$_sudo mv "$tmpfile" env.sh
$_sudo chroot . /usr/bin/env -i su -l "$user" \
    sh -c ". /etc/profile; . /env.sh; cd '$oldpwd' 2>/dev/null; \"\$@\"" \
    -- "${@:-fish}"

After reading your reply, I believe the su -l part in the chroot line is what is causing the issue; a login is requested.

I had completely forgotten that there was an environment file alltogether; so I will probably use that going forward. Maybe !clear is what I do need indeed. Though wouldn't that wipe out every process' $HOME variable as well? I have two others that actually rely on it to find their configuration.

Kind regards, Ingwie

davmac314 commented 7 months ago

Though wouldn't that wipe out every process' $HOME variable as well?

Yes, it would prevent HOME from being set, to any value. Whether this is a problem or not depends on the applications that use it: if it's not set, they might choose to lookup the home directory in the user database ("pwd database"). So, simply not having it set (to the wrong value) might be enough to solve any issues.

Regardless, using load-options = export-passwd-vars for affected services should resolve it. This is not the default because it requires accessing the user database, and it isn't needed by most system services even if they do run as another user.

davmac314 commented 6 months ago

@IngwiePhoenix ok if I close this? I know it's probably not working exactly as you would like it to, but it is by design. There are always trade-offs that have to be managed...

IngwiePhoenix commented 5 months ago

Hello!

Apologies for my late reply; this had slipped to the far bottom of my notifications list... so I only see this now.

I made some tests with load-options = export-passwd-vars and that, together with clearing the dinit environment entirely, seems to be what I needed. I do wish there was like a "defaults service" for things like this; most services I use actually do use $HOME of their respective account they run under. So having a way to configure this, as well as logging options and file locations, would be very handy. :)