Skip to content

Integrated tmux cannot start if user's login shell is /sbin/nologin #282

@lina-bh

Description

@lina-bh

Describe the bug
When the beets-flask process' user has its shell set to /sbin/nologin, as is done by Kubernetes when running with a user in securityContext.runAsUser that does not already exist in the container, beets-flask repeatedly crashes because the tmux server instantly exits.

Expected behavior
beets-flask runs and the integrated tmux works.

To Reproduce

This is what my Pod looks like from kubectl get --output=yaml pods [my-pod-name]:

apiVersion: v1
kind: Pod
metadata:
  name: beets-flask
spec:
  containers:
  - command:
    - /bin/sh
    - -c
    - umask 002 && . /repo/entrypoint.sh
    image: ghcr.io/pspitzner/beets-flask:v2.0.0-rc2@sha256:3e2933e58f176898b23a634f129efb57a81c9ff17c03395810a9bf7272ab658a
    name: beets-flask
    ports:
    - containerPort: 5001
      protocol: TCP
    securityContext:
      runAsGroup: 995
      runAsUser: 970
    volumeMounts:
    - mountPath: /config/beets-flask/config.yaml
      name: beets-flask-config
      readOnly: true
      subPath: config.yaml
    - mountPath: /config/beets/config.yaml
      name: beets-config
      readOnly: true
      subPath: config.yaml
    - mountPath: /srv/downloads
      name: downloads
    - mountPath: /srv/Music
      name: music
    - mountPath: /config/beets-flask
      name: beets-flask-config-dir
    - mountPath: /logs
      name: logs
  securityContext:
    fsGroup: 995
  volumes:
  - configMap:
      name: beets-flask-config
    name: beets-flask-config
  - configMap:
      name: beets-config
    name: beets-config
  - hostPath:
      path: /srv/downloads
      type: Directory
    name: downloads
  - hostPath:
      path: /srv/Music
      type: Directory
    name: music
  - emptyDir: {}
    name: logs
  - emptyDir: {}
    name: beets-flask-config-dir

If you possibly wanted to reproduce this you'd also need to upload ConfigMaps with the beets and beets-flask configuration files to the correct names with the correct keys, and adjust volume paths and groups accordingly. Yes I know Kubernetes is incredibly overkill for this. I happen to like using it.

The workaround in this case was to create another ConfigMap with a tmux configuration file setting default-shell to /bin/bash and mounting it at /. So, I would suggest that a fix for this would involve explicitly specifying /bin/bash as the user's shell. That looks something like this:

apiVersion: v1
kind: ConfigMap
metadata:
  name: tmux-conf
data:
  tmux.conf: |-
    set -g default-shell /bin/bash
    setenv -g PATH /repo/backend/.venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Your deployment or pod:

containers:
- ...
  volumeMounts:
  - name: tmux-conf
    subPath: tmux.conf
    mountPath: /.tmux.conf
volumes:
- name: tmux-conf
  configMap:
    name: tmux-conf

Technical Details
Version v2.0.0-rc1.

The exception that crashes beets-flask:

[INFO] beets-flask: Setting up Web-Terminal
Process SpawnProcess-20:
Traceback (most recent call last):
  File "/repo/backend/beets_flask/server/websocket/terminal.py", line 53, in register_tmux
    session = server.new_session(
              ^^^^^^^^^^^^^^^^^^^
  File "/repo/backend/.venv/lib/python3.12/site-packages/libtmux/server.py", line 591, in new_session
    return Session.from_session_id(
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/repo/backend/.venv/lib/python3.12/site-packages/libtmux/session.py", line 145, in from_session_id
    session = fetch_obj(
              ^^^^^^^^^^
  File "/repo/backend/.venv/lib/python3.12/site-packages/libtmux/neo.py", line 230, in fetch_obj
    obj_formatters_filtered = fetch_objs(
                              ^^^^^^^^^^^
  File "/repo/backend/.venv/lib/python3.12/site-packages/libtmux/neo.py", line 209, in fetch_objs
    raise exc.LibTmuxException(proc.stderr)
libtmux.exc.LibTmuxException: ['no server running on /tmp/tmux-970/default']

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
    self.run()
  File "/usr/local/lib/python3.12/multiprocessing/process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File "/repo/backend/.venv/lib/python3.12/site-packages/uvicorn/_subprocess.py", line 80, in subprocess_started
    target(sockets=sockets)
  File "/repo/backend/.venv/lib/python3.12/site-packages/uvicorn/supervisors/multiprocess.py", line 63, in target
    return self.real_target(sockets)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/repo/backend/.venv/lib/python3.12/site-packages/uvicorn/server.py", line 67, in run
    return asyncio_run(self.serve(sockets=sockets), loop_factory=self.config.get_loop_factory())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 195, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/base_events.py", line 691, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/repo/backend/.venv/lib/python3.12/site-packages/uvicorn/server.py", line 71, in serve
    await self._serve(sockets)
  File "/repo/backend/.venv/lib/python3.12/site-packages/uvicorn/server.py", line 78, in _serve
    config.load()
  File "/repo/backend/.venv/lib/python3.12/site-packages/uvicorn/config.py", line 445, in load
    self.loaded_app = self.loaded_app()
                      ^^^^^^^^^^^^^^^^^
  File "/repo/backend/beets_flask/server/app.py", line 44, in create_app
    register_socketio(app)
  File "/repo/backend/beets_flask/server/websocket/__init__.py", line 55, in register_socketio
    register_tmux()
  File "/repo/backend/beets_flask/server/websocket/terminal.py", line 57, in register_tmux
    session = server.sessions.get(session_name="beets-socket-term")  # type: ignore
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/repo/backend/.venv/lib/python3.12/site-packages/libtmux/_internal/query_list.py", line 567, in get
    raise ObjectDoesNotExist
libtmux._internal.query_list.ObjectDoesNotExist

Pod spec is mentioned above. It's like a slightly more complicated docker-compose.yaml.

/config/beets-flask/config.yaml:

gui:
  terminal:
    start_path: "/srv/downloads" # the directory where to start new terminal sessions
  inbox:
    folders:
      slskd:
        name: "/srv/downloads"
        path: "/srv/downloads"
        autotag: "off"

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions