Fix for permission issues while running under Docker

This commit is contained in:
Phin 2024-01-31 21:20:19 +05:30
parent 2bb484b61a
commit 46593378d9
6 changed files with 59 additions and 17 deletions

8
.github/release-notes/v2.4.4.md vendored Normal file
View file

@ -0,0 +1,8 @@
### Release Notes
* While running under Docker, to resolve permission issues, the script will now change its user ID to match that of the owner of the runtime directory mounted into the container
### Installation Instructions
* [Regular](https://github.com/phin05/discord-rich-presence-plex/blob/v2.4.4/README.md#installation)
* [Docker](https://github.com/phin05/discord-rich-presence-plex/blob/v2.4.4/README.md#run-with-docker)

View file

@ -2,12 +2,9 @@ FROM python:3.10-alpine
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
RUN OSARCH="$TARGETOS-$TARGETARCH"; if [[ "$OSARCH" = "linux-386" || "$OSARCH" = "linux-arm" ]]; then apk --no-cache add build-base python3 python3-dev python3-tkinter openssl bash git meson py3-pip sudo freetype-dev fribidi-dev harfbuzz-dev jpeg-dev lcms2-dev libimagequant-dev openjpeg-dev tcl-dev tiff-dev tk-dev zlib-dev; fi RUN OSARCH="$TARGETOS-$TARGETARCH"; if [[ "$OSARCH" = "linux-386" || "$OSARCH" = "linux-arm" ]]; then apk --no-cache add build-base python3 python3-dev python3-tkinter openssl bash git meson py3-pip sudo freetype-dev fribidi-dev harfbuzz-dev jpeg-dev lcms2-dev libimagequant-dev openjpeg-dev tcl-dev tiff-dev tk-dev zlib-dev; fi
ARG USERNAME=app
ARG USER_UID_GID=10000
RUN addgroup -g $USER_UID_GID $USERNAME && adduser -u $USER_UID_GID -G $USERNAME -D $USERNAME
WORKDIR /app WORKDIR /app
COPY requirements.txt . COPY requirements.txt .
RUN pip install -U -r requirements.txt --no-cache-dir RUN pip install -U -r requirements.txt --no-cache-dir
COPY . . COPY . .
ENV DRPP_CONTAINER_DEMOTION_UID_GID=$USER_UID_GID ENV DRPP_IS_IN_CONTAINER=true
CMD ["python", "main.py"] CMD ["python", "main.py"]

View file

@ -198,11 +198,44 @@ For example, if the environment variable `XDG_RUNTIME_DIR` is set to `/run/user/
### Example ### Example
``` ```
docker run -v ./data:/app/data -v /run/user/1000:/run/app:ro -d --restart unless-stopped --name drpp ghcr.io/phin05/discord-rich-presence-plex:latest docker run -v ./drpp:/app/data -v /run/user/1000:/run/app:ro -d --restart unless-stopped --name drpp ghcr.io/phin05/discord-rich-presence-plex:latest
``` ```
If you're running the container for the first time (when there are no users in the config), make sure that the `PLEX_SERVER_NAME` environment variable is set (see the [environment variables](#configuration---environment-variables) section above), and check the container logs for the authentication link. If you're running the container for the first time (when there are no users in the config), make sure that the `PLEX_SERVER_NAME` environment variable is set (see the [environment variables](#configuration---environment-variables) section above), and check the container logs for the authentication link.
### Containerised Discord
If you wish to run Discord in a container as well, you need to mount a designated directory from the host machine into your Discord container at the path where Discord would store its Unix socket file. You can determine this path by checking the environment variables inside the container as per the [volumes](#volumes) section above, or you can set one of the environment variables yourself. That same host directory needs to be mounted into this script's container at `/run/app`. Ensure that the designated directory being mounted into the containers is owned by the user the containerized Discord process is running as.
Depending on the Discord container image you're using, there might be a lot of resource usage overhead and other complications.
#### Example using [kasmweb/discord](https://hub.docker.com/r/kasmweb/discord)
```yaml
services:
kasmcord:
container_name: kasmcord
image: kasmweb/discord:1.14.0
restart: unless-stopped
ports:
- 6901:6901
shm_size: 512m
environment:
VNC_PW: password
XDG_RUNTIME_DIR: /run/user/1000
volumes:
- ./kasmcord:/run/user/1000
user: "0"
entrypoint: sh -c "chown kasm-user:kasm-user /run/user/1000 && su kasm-user -c \"/dockerstartup/kasm_default_profile.sh /dockerstartup/vnc_startup.sh /dockerstartup/kasm_startup.sh\""
drpp:
container_name: drpp
image: ghcr.io/phin05/discord-rich-presence-plex:latest
restart: unless-stopped
volumes:
- ./kasmcord:/run/app:ro
- ./drpp:/app/data
```
### Docker on Windows and macOS ### Docker on Windows and macOS
The container image for this script is based on Linux. Docker uses virtualisation to run Linux containers on Windows and macOS. In such cases, if you want to run this script in a container, you need to run Discord in a container as well, using an image based on Linux, like [kasmweb/discord](https://hub.docker.com/r/kasmweb/discord) for example. You can mount a designated directory from the host machine into the Discord container at the path where Discord would store its Unix socket file. You can determine this path by checking the environment variables inside the container as per the [volumes](#volumes) section above. That same host directory needs to be mounted into the script's container as well at `/run/app`. This method is not recommended, because depending on the Discord container image you're using, there might be a lot of resource usage overhead or other complications related to containerising interactive desktop applications. The container image for this script is based on Linux. Docker uses virtualisation to run Linux containers on Windows and macOS. In such cases, if you want to run this script in a container, you need to run Discord in a container as well, as per the instructions above.

View file

@ -2,7 +2,7 @@ import os
import sys import sys
name = "Discord Rich Presence for Plex" name = "Discord Rich Presence for Plex"
version = "2.4.3" version = "2.4.4"
plexClientID = "discord-rich-presence-plex" plexClientID = "discord-rich-presence-plex"
discordClientID = "413407336082833418" discordClientID = "413407336082833418"
@ -15,4 +15,5 @@ logFilePath = os.path.join(dataDirectoryPath, "console.log")
isUnix = sys.platform in ["linux", "darwin"] isUnix = sys.platform in ["linux", "darwin"]
processID = os.getpid() processID = os.getpid()
isInteractive = sys.stdin and sys.stdin.isatty() isInteractive = sys.stdin and sys.stdin.isatty()
containerDemotionUidGid = os.environ.get("DRPP_CONTAINER_DEMOTION_UID_GID", "") isInContainer = os.environ.get("DRPP_IS_IN_CONTAINER", "") == "true"
runtimeDirectory = "/run/app"

View file

@ -1,4 +1,4 @@
from config.constants import discordClientID, isUnix, processID from config.constants import discordClientID, isUnix, processID, runtimeDirectory
from typing import Any, Optional from typing import Any, Optional
from utils.logging import logger from utils.logging import logger
import asyncio import asyncio
@ -13,7 +13,7 @@ class DiscordIpcService:
def __init__(self, ipcPipeNumber: Optional[int]): def __init__(self, ipcPipeNumber: Optional[int]):
ipcPipeNumber = ipcPipeNumber or -1 ipcPipeNumber = ipcPipeNumber or -1
ipcPipeNumbers = range(10) if ipcPipeNumber == -1 else [ipcPipeNumber] ipcPipeNumbers = range(10) if ipcPipeNumber == -1 else [ipcPipeNumber]
ipcPipeBase = ("/run/app" if os.path.isdir("/run/app") else os.environ.get("XDG_RUNTIME_DIR", os.environ.get("TMPDIR", os.environ.get("TMP", os.environ.get("TEMP", "/tmp"))))) if isUnix else r"\\?\pipe" ipcPipeBase = (runtimeDirectory if os.path.isdir(runtimeDirectory) else os.environ.get("XDG_RUNTIME_DIR", os.environ.get("TMPDIR", os.environ.get("TMP", os.environ.get("TEMP", "/tmp"))))) if isUnix else r"\\?\pipe"
self.ipcPipes: list[str] = [] self.ipcPipes: list[str] = []
for ipcPipeNumber in ipcPipeNumbers: for ipcPipeNumber in ipcPipeNumbers:
pipeFilename = f"discord-ipc-{ipcPipeNumber}" pipeFilename = f"discord-ipc-{ipcPipeNumber}"

17
main.py
View file

@ -1,12 +1,15 @@
from config.constants import isUnix, containerDemotionUidGid from config.constants import isInContainer, runtimeDirectory
import os import os
import sys import sys
if isUnix and containerDemotionUidGid: if isInContainer:
uidGid = int(containerDemotionUidGid) if not os.path.isdir(runtimeDirectory):
os.system(f"chown -R {uidGid}:{uidGid} {os.path.dirname(os.path.realpath(__file__))}") print(f"Runtime directory does not exist. Make sure that it is mounted into the container at {runtimeDirectory}")
os.setgid(uidGid) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] exit(1)
os.setuid(uidGid) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] statResult = os.stat(runtimeDirectory)
os.system(f"chown -R {statResult.st_uid}:{statResult.st_gid} {os.path.dirname(os.path.realpath(__file__))}")
os.setgid(statResult.st_gid) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType]
os.setuid(statResult.st_uid) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType]
else: else:
try: try:
import subprocess import subprocess
@ -60,7 +63,7 @@ def main() -> None:
logger.info("No users found in the config file") logger.info("No users found in the config file")
user = authNewUser() user = authNewUser()
if not user: if not user:
exit() exit(1)
config["users"].append(user) config["users"].append(user)
saveConfig() saveConfig()
plexAlertListeners = [PlexAlertListener(user["token"], server) for user in config["users"] for server in user["servers"]] plexAlertListeners = [PlexAlertListener(user["token"], server) for user in config["users"] for server in user["servers"]]