diff --git a/.github/release-notes/v2.4.4.md b/.github/release-notes/v2.4.4.md new file mode 100644 index 0000000..ce86251 --- /dev/null +++ b/.github/release-notes/v2.4.4.md @@ -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) diff --git a/Dockerfile b/Dockerfile index 174710a..ca1bee3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,9 @@ FROM python:3.10-alpine ARG TARGETOS 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 -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 COPY requirements.txt . RUN pip install -U -r requirements.txt --no-cache-dir COPY . . -ENV DRPP_CONTAINER_DEMOTION_UID_GID=$USER_UID_GID +ENV DRPP_IS_IN_CONTAINER=true CMD ["python", "main.py"] diff --git a/README.md b/README.md index c2e0edf..d7d4d46 100644 --- a/README.md +++ b/README.md @@ -198,11 +198,44 @@ For example, if the environment variable `XDG_RUNTIME_DIR` is set to `/run/user/ ### 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. +### 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 -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. diff --git a/config/constants.py b/config/constants.py index b68e0b0..d8bd82d 100644 --- a/config/constants.py +++ b/config/constants.py @@ -2,7 +2,7 @@ import os import sys name = "Discord Rich Presence for Plex" -version = "2.4.3" +version = "2.4.4" plexClientID = "discord-rich-presence-plex" discordClientID = "413407336082833418" @@ -15,4 +15,5 @@ logFilePath = os.path.join(dataDirectoryPath, "console.log") isUnix = sys.platform in ["linux", "darwin"] processID = os.getpid() 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" diff --git a/core/discord.py b/core/discord.py index ae9aaa3..c38b1ed 100644 --- a/core/discord.py +++ b/core/discord.py @@ -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 utils.logging import logger import asyncio @@ -13,7 +13,7 @@ class DiscordIpcService: def __init__(self, ipcPipeNumber: Optional[int]): ipcPipeNumber = ipcPipeNumber or -1 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] = [] for ipcPipeNumber in ipcPipeNumbers: pipeFilename = f"discord-ipc-{ipcPipeNumber}" diff --git a/main.py b/main.py index 6dcfe80..2511309 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,15 @@ -from config.constants import isUnix, containerDemotionUidGid +from config.constants import isInContainer, runtimeDirectory import os import sys -if isUnix and containerDemotionUidGid: - uidGid = int(containerDemotionUidGid) - os.system(f"chown -R {uidGid}:{uidGid} {os.path.dirname(os.path.realpath(__file__))}") - os.setgid(uidGid) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] - os.setuid(uidGid) # pyright: ignore[reportGeneralTypeIssues,reportUnknownMemberType] +if isInContainer: + if not os.path.isdir(runtimeDirectory): + print(f"Runtime directory does not exist. Make sure that it is mounted into the container at {runtimeDirectory}") + exit(1) + 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: try: import subprocess @@ -60,7 +63,7 @@ def main() -> None: logger.info("No users found in the config file") user = authNewUser() if not user: - exit() + exit(1) config["users"].append(user) saveConfig() plexAlertListeners = [PlexAlertListener(user["token"], server) for user in config["users"] for server in user["servers"]]