Opening a shell inside non-systemd nspawn containers

If you try to open shell inside a container that runs e.g. Alpine Linux using machinectl, the following not very descriptive error will appear:

# machinectl shell vpn
Failed to get shell PTY: Protocol error

The reason for this is simply that the container is not running systemd.

Because systemd-nspawn just uses Linux namespaces [1], nsenter can alternatively be used to access the container. For this, we'll need the PID of the init process inside the container:

# systemctl status systemd-nspawn@vpn
● systemd-nspawn@vpn.service - Container vpn
   Loaded: loaded (/lib/systemd/system/systemd-nspawn@.service; disabled; vendor preset: enabled)
   Active: active (running) since Sun 2019-08-11 19:49:19 UTC; 6 months 3 days ago
 Main PID: 795 (systemd-nspawn)
   Status: "Container running."
   CGroup: /machine.slice/systemd-nspawn@vpn.service
           ├─payload
           │ ├─ 797 /sbin/init
           │ ├─1028 /sbin/syslogd -t
           [...]
In this case the PID of init is 797, you can then spawn a login shell inside the container:

nsenter -t 797 -a /bin/sh -l

Depending on the namespaces the container is (or isn't) joined to it can be necessary to specify the appropriate flags manually after consulting the nsenter manpage, for example:

nsenter -t 797 -{m,u,i,n,p,U,C} -a /bin/sh -l

All in all, this can be turned into a nice alias for your .bashrc:

center ()
{
  [ -z "$1" ] && { echo "Usage: center <name>" >&2; return 1; }
  pid=$(sed -n 2p "/sys/fs/cgroup/pids/machine.slice/systemd-nspawn@$1.service/tasks")
  [ -z "$pid" ] && { echo "Container not running" >&2; return 1; }
  nsenter -t $pid -a /bin/sh -l
}

(Note: the above code no longer works on modern systems with cgroups v2)