Request for daemon + browser-support for krellian-kiosk

Thanks for the ongoing help…

After adding the util-linux package to stage-packages, the full logs for the the krellian-kiosk snap are here.

The line…

/snap/krellian-kiosk/x1/bin/xwayland-kiosk-launch: line 151: bin/drop-snap-daemon.sh: No such file or directory

…suggests I did something wrong with drop-snap-daemon.sh

Maybe this was missed somewhere along the way, but you also need to specify require('systemd'); in your javascript somewhere, as that’s the package which will inherit the ports from systemd via listen-streams, etc.

I suspected I was missing something there! Unfortunately it appears that the systemd npm package may not work with the version of Node.js currently used by Electron, and is no longer maintained:

npm WARN notsup Unsupported engine for systemd@0.4.0: wanted: {"node":">=0.6.11 <0.11.0"} (current: {"node":"12.18.2","npm":"6.14.5"})
npm WARN notsup Not compatible with your version of node/npm: systemd@0.4.0

When my snap is installed using --devmode I don’t see the DENIED errors, but it’s still failing to start due to not being able to find drop-snap-daemon.sh (logs here).

it seems that the snap is trying to do XWayland things which afaict need to be done as root.

The application does use XWayland, as recommended in the Electron kiosk tutorial https://ubuntu.com/tutorials/electron-kiosk

If XWayland can not be used under snap_daemon then that’s going to be a problem.

@alan_g, @ogra: Do you know whether anyone has successfully run an Electron kiosk app (which uses XWayland on Ubuntu Core) as the snap_daemon user? I know @ogra has previously suggested this approach and @alan_g was updating tutorials to reflect current best practice.

you could try doing:

    command: desktop-launch xwayland-kiosk-launch $SNAP/bin/drop-snap-daemon.sh $SNAP/krellian-kiosk/krellian-kiosk --no-sandbox -p systemd

in your app

Did you try the suggested workaround in this comment https://github.com/rubenv/node-systemd/issues/12#issuecomment-85501926 ?

nope, i don’t, but i know it is supposed to be possible to drop privileges to it … i guess you need to do this inside the application initialization or right before you start it since, as you noticed, XWayland needs to start as root first … but once you have a DISPLAY set you should be able to run as user

I think that’s what I started with but snapcraft kept telling me the $SNAP was redundant. Anyway I’ve added it back and will test.

I’ve rolled a patch for that and generated a new build with both fixes but I’m struggling to test it because I keep getting weird errors and timeouts on snap install/snap remove. Even at one point got “snap: command not found”!

All I can think is that microSD card in my Pi is wearing out, so I’ll try again next week with a fresh card + install.

@benfrancis what I think you should try first is to ignore the bin/drop-snap-daemon.sh bit for now and just work on getting listen-streams working to inherit the systemd file descriptors for your desired port. After you have confirmed that is working, then resume trying to get setpriv to work.

Could anyone point towards documentation for the “sockets” section of the snapcraft.yaml section @ijohnson proposed and exactly what it does?

I’ve been trying to get the systemd hand-off of the socket working as suggested, looking at the systemd configurations that snap is auto-generating and reading some background into this feature. The snapcraft.yaml reference mentions a “socket” (as opposed to “sockets”) feature, but doesn’t go into much detail as to how it should be used.

@ijohnson I note that the example project you linked to in your original email and the now un-maintained “systemd” npm library it uses are intended to provide “socket activation” for services, whereby services can be started on demand by systemd and shut down when not in use.

It occurs to me that whilst this might make sense for a simple Node.js server, it’s not really desirable for my Electron-based digital signage application which acts as both a web client (used to render web pages to the screen) and web server (used for remote control).

Whilst I haven’t yet been able to get the socket handoff working, I have noticed that the above configuration results in a snap which is no longer started on system boot, but is only started when a TCP connection is opened on port 80 (e.g. via an HTTP request). This isn’t going to work in my case, because the UI needs to be started on boot, and not terminated when the server hasn’t received any requests for a while.

I explained at the start of this thread why the (relatively simple) kiosk application I’m trying to package as a snap acts as both a web client and web server. That seemingly requires two things which ordinarily require root access:

  1. Starting a web server on port 80
  2. Starting a graphical interface using XWayland

In order to meet current snap store policies it seems my application can not be allowed to run as root, but so far the proposed solutions for making the server component run as a non-root user do not appear to work for the graphical client component of the application which uses XWayland and needs to run continuously from boot.

Have I understood this correctly, and can you offer any further advice on how I can modify my application so that it can be allowed into the snap store? I’m starting to wonder whether Ubuntu Core is a good fit for my use case after all :frowning:

Thanks

Sorry the documentation has a typo, this should be sockets. I’ll make sure this gets fixed and work on creating better examples in the docs…

That is part of the intended feature set, but that’s not why it was recommended for your project, for your project what is desirable about this is that your application doesn’t need to open port 80 by itself, and instead systemd opens that port and provides it via a file descriptor to your service when your service starts. Indeed, many projects (including some parts of snapd itself) rely on this in order to shut themselves down and start back up when a request is issued, but that is not strictly necessary. You can still have your service startup on boot and always be running when you use socket activation. Apologies if this was unclear. However read on…

Yes, it appears that we have stumbled onto a bug here, wherein there is not currently a way to actually start a daemon that is using socket activation except through actually socket activating it. I’m fairly certain this was unintentional and that this is a bug, but I guess what I expected to have worked is to have an install hook like this:

#!/bin/sh
snapctl start --enable "$SNAP_NAME.daemon"

which would manual start the daemon, however it appears looking at the code in snapd, in the case of there being a socket for the service “daemon”, we just plain skip over actually starting the daemon which is unintentional to say the least, and instead just make sure the socket is ready. I will file a bug to this effect and try to get that fixed for you. In the meantime, you could instead have something (which is hacky indeed) like this in your install hook that actually just manually socket activates the application:

#!/bin/bash -ex
snapctl start --enable "$SNAP_DAEMON.daemon"

attempts=0
until curl http://localhost:80/some-non-existent-route; do
    sleep 1
    attempts=$(( attempts + 1 ))
    if [ "$attempts" = 10 ]; then
        exit 1
    fi
done

I say a non-existent route since you should at least then just trigger a 404 and not accidentally do something else that is unintended, and for the purposes of this hook you don’t even really need to make a complete HTTP request, you just need to try and write some data to that port so that systemd then activates the service. The loop is necessary since there is a race enabling the socket unit and trying to actually trigger the socket activation.

I made a full example of this here: https://github.com/anonymouse64/test-go-socket-activation-snap (but in Go not NodeJS since I don’t have much NodeJS experience). While doing so, I noticed that there is a slight error in the provided script to drop permissions, it should be calling exec on the setpriv command, since AFAICT only the PID that systemd launches is allowed to use the FD that systemd provides. So changing your drop-snap-daemon.sh script to this should get you further:

#!/bin/sh -e
exec "$SNAP/usr/bin/setpriv" --clear-groups --reuid snap_daemon --regid snap_daemon -- "$@"

If you wouldn’t mind giving that a try and seeing if you can make it further, I’m still hopeful we can make it work for your application, but do note that as mentioned originally, we still could allow your snap to use browser-sandbox in a daemon application with appropriate vetting if this still proves intractable.

To be clear, is this all done by the xwayland-kiosk-launch component of your script or does your NodeJS application need to do part of this as well? I am not familiar much with XWayland kiosks or electron for that matter…

Also, from above, you will need to ensure that all scripts / intermediary wrappers for your app end with an exec ... to ensure the same PID is used.

It is true, we’re lacking the means for expressing whether one wants to act on the socket, timer, or a corresponding service. There was some previous discussion in: Command line interface to manipulate services but the problem was not fully addressed then.

OTOH, if a service is socket activated, I expect it to be running only once it has been activated. While here we’re trying to use the socket activation as means of obtaining file descriptors bound to specific access-limited ports to avoid running as root.

1 Like

Might it make sense to extend the install-mode feature added by the following PR to cover this case?

While the current behaviour is probably the most common, there are certainly cases where you want socket activation and to be started on boot. For example, if you’ve got a set of co-dependent services then socket activation can be used as an alternative to before/after to order their start up.

1 Like

Yes, I think @jamesh is right in that we should extend install-mode to cover additional use cases, I guess my expectation is that socket activated services by default do not start up on their own and must be socket activated, but the bug I was trying to point out was that we have no way to actually start a socket activated service except for activating it which is awkward as can be seen in my examples above.

Thank you for the ongoing help. The fact that we’re uncovering bugs in the platform at least reassures me that I’m not just struggling with something obvious, but that we’re actually in slightly uncharted territory.

This is all handled by the xwayland-kiosk-launch command, which comes from the xwayland-kiosk-helper, as described in the Electron-based kiosk snap tutorial. I don’t fully understand all the ins and outs of how it works, but it launches “X11 applications on a Wayland kiosk, using Xwayland and a basic window manager to enforce fullscreen kiosk behaviour”.

OK, good news, I now have this working here https://github.com/krellian/kiosk/pull/75

OK, I made this change and I’m getting the same permissions error as before:

Feb 01 18:57:23 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[1917]: /snap/krellian-kiosk/x3/bin/xwayland-kiosk-launch: line 151: /snap/krellian-kiosk/x3/bin/drop-snap-daemon.sh: Permission denied

(Full output here.)

The output of sudo journalctl --no-pager | grep DENIED includes the following errors:

Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 audit[6469]: AVC apparmor="DENIED" operation="open" profile="snap.krellian-kiosk.krellian-kiosk" name="/var/cache/fontconfig/" pid=6469 comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 kernel: audit: type=1400 audit(1612206093.283:501): apparmor="DENIED" operation="open" profile="snap.krellian-kiosk.krellian-kiosk" name="/var/cache/fontconfig/" pid=6469 comm="ls" requested_mask="r" denied_mask="r" fsuid=0 ouid=0
Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 audit[6471]: AVC apparmor="DENIED" operation="bind" profile="snap.krellian-kiosk.krellian-kiosk" pid=6471 comm="Xwayland" family="unix" sock_type="stream" protocol=0 requested_mask="bind" denied_mask="bind" addr="@/tmp/.X11-unix/X24"
Feb 01 19:01:33 Unknown-b8-27-eb-f2-7b-0b-2 kernel: audit: type=1400 audit(1612206093.339:502): apparmor="DENIED" operation="bind" profile="snap.krellian-kiosk.krellian-kiosk" pid=6471 comm="Xwayland" family="unix" sock_type="stream" protocol=0 requested_mask="bind" denied_mask="bind" addr="@/tmp/.X11-unix/X24"

The output of ls /snap/krellian-kiosk/current/bin -al | grep drop-snap-daemon.sh is:

-rw-r--r-- 1 root root 105 Feb 1 17:44 drop-snap-daemon.sh

Suggesting the shell script is not in fact executable. How do I fix that? Is it sufficient to add the executable flag to the file in my local source tree and check it into git?

Thank you. Do you have a bug number I can follow so that I know when I can remove the hack?

I’ve not used hooks before. Can you explain why this should be in the install hook? My understanding from the documentation is that the install hook is only executed on first install, whereas presumably this socket activation needs to happen every time the application is started.

Can you explain what you mean by this? What edits do I need to make where exactly? The only shell script I’ve added myself is drop-snap-daemon.sh

Thanks

Given this, I think that your application probably doesn’t need to setup any kind of XWayland things and thus doesn’t need root for any of that. In any case we should find out as we get closer to the solution I think…

:tada: so that means that your listen-streams are working and your application is reading the file descriptors from systemd properly, and we just need to sort out the permission dropping bits.

this still looks suspicious to me, but is not related to dropping privileges AFAICT, so let’s ignore it for now…

Yes, you can run chmod +x drop-snap-daemon.sh and then commit the script to git and rebuild the snap. Let’s tackle this part next and see if this is sufficient to get a bit further.

Sure, here is the bug: Bug #1914121 “no way to actually start a socket activated servic...” : Bugs : snapd

Hmm, that’s a fair point I hadn’t thought about on reboots, I think to handle that case then unfortunately you would want to have the shell script in the install hook instead be a one-shot daemon that is also in your snap, so move the install hook script to somewhere in $SNAP/bin/activate-svc.sh or something (and make sure it is executable :wink:), then add something like this to your snapcraft.yaml in the apps section:

apps:
  activator:
    command: bin/activate-svc.sh
    plugs:
      - network
      - network-bind # may not be necessary but just for good luck

but let’s not try this until we can get the current version you have with the hook working to make sure we can get the user permission dropping bits to work :smiley:

Then you are fine, the other scripts will already have this, and we just needed to add this to the drop-snap-daemon.sh script

@ijohnson Quick update:

I made drop-snap-daemon.sh executable (added to the PR), and I now get past the previous error to a new one:

Feb 02 18:11:15 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[2595]: /snap/krellian-kiosk/x5/bin/drop-snap-daemon.sh: 2: exec: /snap/krellian-kiosk/x5/usr/bin/setpriv: not found

What am I missing? Why is setpriv not included inside the snap? I added util-linux to stage-packages in the snapcraft.yaml as suggested (see here).

Ah, so apparently on 20.04 (i.e. base: core20) the right package is util-linux, but on Ubuntu 18.04, setpriv comes from the verbatim setpriv package, so just change util-linux to setpriv in your stage-packages and then that should get you further

1 Like

OK, that got me further. I think setpriv is now working.

Electron now segfaults on startup with the following trace:

Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: A JavaScript error occurred in the main process
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: Uncaught Exception:
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: Error: Failed to get 'appData' path
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at App.l._setDefaultAppPaths (electron/js2c/browser_init.js:5:1420)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at Object.<anonymous> (electron/js2c/browser_init.js:205:2351)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at Object../lib/browser/init.ts (electron/js2c/browser_init.js:205:3580)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at __webpack_require__ (electron/js2c/browser_init.js:1:128)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at electron/js2c/browser_init.js:1:1200
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at electron/js2c/browser_init.js:1:1267
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at NativeModule.compile (internal/bootstrap/loaders.js:287:5)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at NativeModule.compileForPublicLoader (internal/bootstrap/loaders.js:222:8)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at loadNativeModule (internal/modules/cjs/helpers.js:23:9)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]:     at Module._load (internal/modules/cjs/loader.js:698:15)
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: No protocol specified
Feb 03 16:22:13 Unknown-b8-27-eb-f2-7b-0b-2 krellian-kiosk.krellian-kiosk[8363]: /snap/krellian-kiosk/x6/bin/xwayland-kiosk-launch: line 151:  8535 Segmentation fault      "$@"

It looks like it’s crashing while trying to execute app.getPath('appData'), which according to the Electron documentation tries to access $XDG_CONFIG_HOME or ~/.config on Linux.

Given this works when starting as root, my best guess is that this is due to differences in the environment variables between the root and snap_daemon users.

Could the solution be as simple as setting an environment variable in my snapcraft.yaml, or is this due to another constraint of snap confinement? If the latter, is there a recommended way to work around this in snaps?

I would assume this will be a problem for anyone trying to run an Electron-based application as the snap_daemon user.

Thanks

That does indeed look like an issue with incorrect permissions, though I admit I’m a bit disappointed that electron just segfaults when presented with something like a permission denied error :-/

Anyways, yes one way to fix this is to set XDG_CONFIG_HOME to somewhere that snap_daemon will be able to own. I would recommend setting this to $SNAP_DATA/snap-daemon-home/.config, and add something like this to the drop-snap-daemon.sh script:

#!/bin/bash -ex
# setup a $SNAP_USER_DATA under $SNAP_DATA for snap-daemon user
mkdir -p $SNAP_DATA/snap-daemon-home/.config
chown -R snap_daemon:snap_daemon $SNAP_DATA/snap-daemon-home/

# set XDG_CONFIG_HOME to use our $SNAP_USER_DATA 
XDG_CONFIG_HOME=$SNAP_DATA/snap-daemon-home/.config 
export XDG_CONFIG_HOME

# maybe this is unnecessary, but just to be safe if electron needs $HOME for anything else
HOME=$SNAP_DATA/snap-daemon-home
export HOME

exec $SNAP/... # the existing command you have
1 Like

Thanks for the quick reply!

Ah, I may have unfairly blamed Electron there, it may be xwayland-kiosk-launch which is segfaulting.

Unfortunately it still seems to segfault after adding those lines to drop-snap-daemon.sh to set the environment variable and permissions, but I can’t tell why. Full output here.

I think the shell script worked the first time around, but generates errors on subsequent executions. Here is what it ends up looking like:

$ ls -al /var/snap/krellian-kiosk/x7/snap-daemon-home/.config/
total 12
drwxr-xr-x 3 snap_daemon snap_daemon 4096 Feb  3 18:55 .
drwxr-xr-x 3 snap_daemon snap_daemon 4096 Feb  3 18:55 ..
drwx------ 3 snap_daemon snap_daemon 4096 Feb  3 18:55 krellian-kiosk

In contrast with the socket activation code, would it make more sense for these first lines of shell script to go in an install hook instead, since it should only need doing once?

Anyway, so far I haven’t been able to figure out why xwayland-kiosk-launch is still crashing when run under the snap_daemon user, but there was a suggestion earlier that XWayland has to run as root, so maybe that’s the problem? :frowning:

@ogra Would you mind expanding a little on how you think this could work? What exactly are you suggesting I could do “inside the application initialization” and what do you mean by that? You can see my ongoing attempt to run my Electron kiosk application in the PR here, which uses setpriv to drop down from root in a shell script called as part of the startup command.

Is it possible the above segfault in xwayland-kiosk-launch is because I’m dropping down from root too early?

Thanks