Extract config into .env; add deploy.sh with overlay guard + rollback
- Move user-specific config (login, media roots, port, AP ssid/psk, service user) into a gitignored .env; add .env.example template. - config.py reads .env at startup via a tiny zero-dependency parser (works for both `uv run` and systemd), and fails loudly if required vars are unset. - load_secret_key falls back to an ephemeral key on a read-only filesystem instead of crashing. - systemd units become .template files; deploy.sh renders them with the .env-derived user/paths. - deploy.sh: refuses to run on a read-only overlay, git pull --ff-only, uv sync, ensure secret key, re-apply the AP from .env, install units, restart, health-check with automatic rollback to the previous commit. - Scrub literal credentials/ssid out of the README; document .env + deploy.sh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3a50ba1e3f
commit
ed9da9a065
8 changed files with 297 additions and 40 deletions
22
.env.example
Normal file
22
.env.example
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Copy this file to .env and fill in real values.
|
||||
# .env is gitignored -- never commit real secrets.
|
||||
# Values must be simple (no spaces / shell-special chars) -- this file is both
|
||||
# parsed by the app and sourced by deploy.sh.
|
||||
|
||||
# --- App login (custom login form) ---
|
||||
MEDIAPI_USERNAME=changeme
|
||||
MEDIAPI_PASSWORD=changeme
|
||||
|
||||
# --- Media ---
|
||||
# colon-separated list of directories to browse
|
||||
MEDIAPI_MEDIA_ROOTS=/localmedia
|
||||
MEDIAPI_PORT=8080
|
||||
|
||||
# --- System: systemd services run as this Linux user ---
|
||||
MEDIAPI_USER=pi
|
||||
|
||||
# --- WiFi access point (re-applied by deploy.sh) ---
|
||||
MEDIAPI_WIFI_COUNTRY=US
|
||||
MEDIAPI_AP_SSID=changeme
|
||||
MEDIAPI_AP_PASSWORD=changeme
|
||||
MEDIAPI_AP_CONN_NAME=mediapi-ap
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -5,6 +5,9 @@ __pycache__/
|
|||
# uv / virtualenv
|
||||
.venv/
|
||||
|
||||
# Real per-deployment config / secrets
|
||||
.env
|
||||
|
||||
# Flask session signing key -- generated per-deployment, must stay secret
|
||||
instance/secret_key
|
||||
|
||||
|
|
|
|||
93
README.md
93
README.md
|
|
@ -25,35 +25,40 @@ git remote set-url --add --push origin git@github.com:jmtremblay2/mediapi.git
|
|||
|
||||
# AP on the raspberry pi
|
||||
* must have access point that boots up on system boot
|
||||
* ssid: mediapipi, pw: mediapipi
|
||||
* ssid + password come from `.env` (`MEDIAPI_AP_SSID` / `MEDIAPI_AP_PASSWORD`)
|
||||
* fine if it's slow, fine if it does not boot right away (wait for other services)
|
||||
* (I will connect to the AP from my phone and control what the pi plays)
|
||||
|
||||
## Setup (Raspberry Pi OS Bookworm / NetworkManager)
|
||||
|
||||
`deploy.sh` creates/updates the AP connection for you from the `.env` values
|
||||
(see [Configuration](#configuration-env) below), so normally you don't run
|
||||
these by hand. For reference, this is what it does — substitute the
|
||||
`MEDIAPI_*` values from your `.env`:
|
||||
|
||||
```bash
|
||||
# set the wifi regulatory domain (required, affects allowed channels/power)
|
||||
sudo raspi-config nonint do_wifi_country US
|
||||
sudo raspi-config nonint do_wifi_country "$MEDIAPI_WIFI_COUNTRY"
|
||||
|
||||
# create the AP connection profile on wlan0
|
||||
# ipv4.method=shared makes NetworkManager run its own DHCP server (dnsmasq)
|
||||
# on wlan0 for the phone to get an address from -- no internet sharing/NAT
|
||||
# involved since there's no other upstream connection active
|
||||
sudo nmcli connection add type wifi ifname wlan0 con-name mediapi-ap \
|
||||
sudo nmcli connection add type wifi ifname wlan0 con-name "$MEDIAPI_AP_CONN_NAME" \
|
||||
autoconnect yes connection.autoconnect-priority 100 save yes \
|
||||
802-11-wireless.mode ap 802-11-wireless.band bg 802-11-wireless.ssid mediapipi \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk mediapipi \
|
||||
802-11-wireless.mode ap 802-11-wireless.band bg 802-11-wireless.ssid "$MEDIAPI_AP_SSID" \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "$MEDIAPI_AP_PASSWORD" \
|
||||
ipv4.method shared
|
||||
|
||||
# bring it up now (it will also auto-start on every boot from here on)
|
||||
sudo nmcli connection up mediapi-ap
|
||||
sudo nmcli connection up "$MEDIAPI_AP_CONN_NAME"
|
||||
|
||||
# verify
|
||||
nmcli connection show mediapi-ap
|
||||
nmcli connection show "$MEDIAPI_AP_CONN_NAME"
|
||||
ip addr show wlan0
|
||||
```
|
||||
|
||||
To remove it later: `sudo nmcli connection delete mediapi-ap`
|
||||
To remove it later: `sudo nmcli connection delete "$MEDIAPI_AP_CONN_NAME"`
|
||||
|
||||
# app to browse media
|
||||
media will be stored at
|
||||
|
|
@ -81,12 +86,28 @@ the app should have two buttons up top that show the two modes all the time. the
|
|||
* plays on both HDMI mirrored if connected.
|
||||
|
||||
## basic auth
|
||||
user: jujualexevan
|
||||
pw: bambas
|
||||
fine to keep known devices logged in forever pretty much
|
||||
* login username + password come from `.env` (`MEDIAPI_USERNAME` / `MEDIAPI_PASSWORD`)
|
||||
* fine to keep known devices logged in forever pretty much
|
||||
|
||||
easy to run ... I don't want to have to download a gazillion things
|
||||
|
||||
## Configuration (.env)
|
||||
|
||||
All per-deployment config (login, media paths, port, the AP ssid/password,
|
||||
the Linux user the services run as) lives in a single `.env` file at the repo
|
||||
root. It is **gitignored** — never commit it. Copy the template and fill it in:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# then edit .env
|
||||
```
|
||||
|
||||
`.env` is read two ways, so it stays the single source of truth:
|
||||
* the Flask app parses it at startup (`mediapi/config.py`, no extra dependency),
|
||||
* `deploy.sh` sources it to configure the AP + render the systemd units.
|
||||
|
||||
Keep values simple (no spaces / shell-special characters).
|
||||
|
||||
## Setup (mediapi app)
|
||||
|
||||
Implemented as a small Flask app (`mediapi/`) + a permanently-running `mpv`
|
||||
|
|
@ -94,37 +115,57 @@ player controlled over its JSON IPC socket. mpv renders video to HDMI
|
|||
directly (DRM/KMS, no desktop needed); the phone browser only shows
|
||||
metadata/controls, never the video image itself.
|
||||
|
||||
### First-time install (on the Pi)
|
||||
|
||||
```bash
|
||||
# system deps: mpv for HDMI/DRM playback
|
||||
sudo apt update
|
||||
sudo apt install -y mpv
|
||||
|
||||
# mpv needs access to the GPU/DRM devices to render to HDMI
|
||||
sudo usermod -aG video,render jm
|
||||
# (use the MEDIAPI_USER from your .env)
|
||||
sudo usermod -aG video,render "$USER"
|
||||
# log out/in (or reboot) for the new group membership to take effect
|
||||
|
||||
# install uv (Python package/venv manager) if not already present
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
# installs to ~/.local/bin/uv -- confirm with `which uv`; if it differs,
|
||||
# update the ExecStart path in systemd/mediapi-app.service to match
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh # installs to ~/.local/bin/uv
|
||||
|
||||
cd /home/jm/mediapi
|
||||
uv sync # creates .venv and installs dependencies from pyproject.toml
|
||||
# create your .env (see Configuration above)
|
||||
cp .env.example .env && $EDITOR .env
|
||||
|
||||
# install the two systemd services (mpv player + the web app)
|
||||
sudo cp systemd/mediapi-mpv.service systemd/mediapi-app.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now mediapi-mpv mediapi-app
|
||||
# deploy: syncs deps, configures the AP, installs+starts the services
|
||||
./deploy.sh
|
||||
|
||||
# verify
|
||||
systemctl status mediapi-mpv mediapi-app
|
||||
ls -l /run/mediapi/mpv.sock
|
||||
```
|
||||
|
||||
Then, from a phone connected to the `mediapipi` AP, browse to
|
||||
Then, from a phone connected to the AP (`MEDIAPI_AP_SSID`), browse to
|
||||
`http://<pi-ap-ip>:8080/` (the AP's gateway address, typically `10.42.0.1` —
|
||||
confirm with `ip addr show wlan0` on the pi) and log in with the
|
||||
`jujualexevan` / `bambas` credentials above.
|
||||
`MEDIAPI_USERNAME` / `MEDIAPI_PASSWORD` from your `.env`.
|
||||
|
||||
### Deploying updates
|
||||
|
||||
`deploy.sh` pulls the latest commit from `origin`, syncs deps, re-applies the
|
||||
AP config, reinstalls the systemd units (rendered from the `.template` files
|
||||
using your `.env`), restarts the services, and health-checks the app —
|
||||
**rolling back to the previous commit automatically if it fails to come up.**
|
||||
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
It **refuses to run while the read-only overlay is active** (changes would
|
||||
vanish on reboot) and prints the disable/re-enable steps. So the update loop is:
|
||||
|
||||
```bash
|
||||
sudo raspi-config nonint do_overlayfs 1 && sudo reboot # disable overlay
|
||||
# ... after reboot:
|
||||
cd ~/mediapi && ./deploy.sh
|
||||
sudo raspi-config nonint do_overlayfs 0 && sudo reboot # re-enable overlay
|
||||
```
|
||||
|
||||
Notes / things to double check on the actual hardware (couldn't be verified
|
||||
from a dev machine):
|
||||
|
|
@ -132,10 +173,10 @@ from a dev machine):
|
|||
on current Bookworm Pi4 images (needed for DRM output) — worth a quick check.
|
||||
* If HDMI isn't picked automatically, `mpv --drm-connector=help` (with a
|
||||
display attached) lists connectors to pin one explicitly via the
|
||||
`ExecStart` line in `systemd/mediapi-mpv.service`.
|
||||
`ExecStart` line in `systemd/mediapi-mpv.service.template`.
|
||||
* If audio doesn't come out of the TV, check `aplay -l` for the HDMI ALSA
|
||||
device name (usually `vc4-hdmi`) and add e.g.
|
||||
`--audio-device=alsa/plughw:CARD=vc4hdmi0,DEV=0` to the mpv unit, or run
|
||||
`sudo raspi-config nonint do_audio 2` to force HDMI as the default output.
|
||||
`--audio-device=alsa/plughw:CARD=vc4hdmi0,DEV=0` to the mpv unit template, or
|
||||
run `sudo raspi-config nonint do_audio 2` to force HDMI as the default output.
|
||||
|
||||
To stop/remove: `sudo systemctl disable --now mediapi-mpv mediapi-app`
|
||||
|
|
|
|||
139
deploy.sh
Executable file
139
deploy.sh
Executable file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# deploy.sh -- pull the latest mediapi and (re)deploy it on the Raspberry Pi.
|
||||
#
|
||||
# Runs ON the Pi. It:
|
||||
# 0. refuses to run if the root filesystem is a read-only overlay
|
||||
# 1. pulls the latest commit from origin (forgejo) with --ff-only
|
||||
# 2. syncs Python deps with uv
|
||||
# 3. ensures the session secret key exists (while the card is writable)
|
||||
# 4. re-applies the WiFi country + AP connection from .env
|
||||
# 5. renders + installs the systemd unit templates
|
||||
# 6. restarts the services
|
||||
# 7. health-checks the app, and rolls back to the previous commit if it
|
||||
# fails to come up
|
||||
#
|
||||
# Config comes from .env (copy .env.example -> .env first). Needs sudo for
|
||||
# nmcli / systemctl / writing to /etc; you'll be prompted as needed.
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# --- load config ----------------------------------------------------
|
||||
if [[ ! -f .env ]]; then
|
||||
echo "ERROR: .env not found. Copy .env.example to .env and fill it in." >&2
|
||||
exit 1
|
||||
fi
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source .env
|
||||
set +a
|
||||
|
||||
MEDIAPI_USER="${MEDIAPI_USER:-$(id -un)}"
|
||||
MEDIAPI_PORT="${MEDIAPI_PORT:-8080}"
|
||||
UV="$(command -v uv || echo "$HOME/.local/bin/uv")"
|
||||
|
||||
# --- 0. refuse to run on a read-only overlay ------------------------
|
||||
if findmnt -no FSTYPE / | grep -q overlay; then
|
||||
cat >&2 <<EOF
|
||||
ERROR: the root filesystem is a read-only overlay -- any changes would vanish
|
||||
on the next reboot. Disable the overlay, reboot, re-run this script, then
|
||||
re-enable it:
|
||||
|
||||
sudo raspi-config nonint do_overlayfs 1 # disable overlay
|
||||
sudo reboot
|
||||
# ... after reboot:
|
||||
cd $PROJECT_DIR && ./deploy.sh
|
||||
sudo raspi-config nonint do_overlayfs 0 # re-enable overlay
|
||||
sudo reboot
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- 1. pull latest -------------------------------------------------
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
PREV_REF="$(git rev-parse HEAD)"
|
||||
echo "==> Pulling latest from origin/$BRANCH ..."
|
||||
if ! git pull --ff-only origin "$BRANCH"; then
|
||||
echo "ERROR: git pull failed (uncommitted changes or non-fast-forward)." >&2
|
||||
exit 1
|
||||
fi
|
||||
NEW_REF="$(git rev-parse HEAD)"
|
||||
echo " $PREV_REF -> $NEW_REF"
|
||||
|
||||
# --- render + install systemd units (used again on rollback) --------
|
||||
install_units() {
|
||||
for unit in mediapi-mpv mediapi-app; do
|
||||
sed -e "s|\${MEDIAPI_USER}|${MEDIAPI_USER}|g" \
|
||||
-e "s|\${PROJECT_DIR}|${PROJECT_DIR}|g" \
|
||||
-e "s|\${UV}|${UV}|g" \
|
||||
"systemd/${unit}.service.template" \
|
||||
| sudo tee "/etc/systemd/system/${unit}.service" >/dev/null
|
||||
done
|
||||
sudo systemctl daemon-reload
|
||||
}
|
||||
|
||||
# --- 2. sync python deps -------------------------------------------
|
||||
echo "==> Syncing dependencies (uv sync) ..."
|
||||
"$UV" sync
|
||||
|
||||
# --- 3. ensure session secret key exists ---------------------------
|
||||
if [[ ! -f instance/secret_key ]]; then
|
||||
echo "==> Generating instance/secret_key ..."
|
||||
mkdir -p instance
|
||||
python3 -c "import os; print(os.urandom(32).hex())" > instance/secret_key
|
||||
chmod 600 instance/secret_key
|
||||
fi
|
||||
|
||||
# --- 4. wifi country + AP connection -------------------------------
|
||||
echo "==> Applying WiFi country + AP config ..."
|
||||
sudo raspi-config nonint do_wifi_country "${MEDIAPI_WIFI_COUNTRY}"
|
||||
if nmcli -g NAME con show | grep -qx "${MEDIAPI_AP_CONN_NAME}"; then
|
||||
echo " updating existing AP connection '${MEDIAPI_AP_CONN_NAME}'"
|
||||
sudo nmcli con modify "${MEDIAPI_AP_CONN_NAME}" \
|
||||
802-11-wireless.ssid "${MEDIAPI_AP_SSID}" \
|
||||
wifi-sec.key-mgmt wpa-psk \
|
||||
wifi-sec.psk "${MEDIAPI_AP_PASSWORD}"
|
||||
else
|
||||
echo " creating AP connection '${MEDIAPI_AP_CONN_NAME}'"
|
||||
sudo nmcli con add type wifi ifname wlan0 con-name "${MEDIAPI_AP_CONN_NAME}" \
|
||||
autoconnect yes connection.autoconnect-priority 100 save yes \
|
||||
802-11-wireless.mode ap 802-11-wireless.band bg \
|
||||
802-11-wireless.ssid "${MEDIAPI_AP_SSID}" \
|
||||
wifi-sec.key-mgmt wpa-psk wifi-sec.psk "${MEDIAPI_AP_PASSWORD}" \
|
||||
ipv4.method shared
|
||||
fi
|
||||
|
||||
# --- 5 & 6. install units + restart --------------------------------
|
||||
echo "==> Installing systemd units + restarting services ..."
|
||||
install_units
|
||||
sudo systemctl enable mediapi-mpv mediapi-app >/dev/null 2>&1 || true
|
||||
sudo systemctl restart mediapi-mpv
|
||||
sudo systemctl restart mediapi-app
|
||||
|
||||
# --- 7. health check + rollback ------------------------------------
|
||||
echo "==> Health check on http://127.0.0.1:${MEDIAPI_PORT}/login ..."
|
||||
healthy=0
|
||||
for _ in $(seq 1 15); do
|
||||
if curl -fsS -o /dev/null "http://127.0.0.1:${MEDIAPI_PORT}/login"; then
|
||||
healthy=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if [[ "$healthy" -ne 1 ]]; then
|
||||
echo "ERROR: app did not become healthy -- rolling back to $PREV_REF" >&2
|
||||
git reset --hard "$PREV_REF"
|
||||
"$UV" sync
|
||||
install_units
|
||||
sudo systemctl restart mediapi-mpv mediapi-app
|
||||
echo "--- last 30 lines of mediapi-app log: ---" >&2
|
||||
sudo journalctl -u mediapi-app -n 30 --no-pager >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Deploy OK. Services healthy on port ${MEDIAPI_PORT}."
|
||||
systemctl --no-pager --lines=0 status mediapi-mpv mediapi-app || true
|
||||
|
|
@ -1,18 +1,67 @@
|
|||
import logging
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
INSTANCE_DIR = os.path.join(BASE_DIR, "instance")
|
||||
SECRET_KEY_PATH = os.path.join(INSTANCE_DIR, "secret_key")
|
||||
DOTENV_PATH = os.path.join(BASE_DIR, ".env")
|
||||
|
||||
|
||||
def load_dotenv(path=DOTENV_PATH):
|
||||
"""Minimal .env loader (no dependency). Populates os.environ for any key
|
||||
not already set, so real environment variables always win over the file.
|
||||
Works for both `uv run run.py` and the systemd-launched service."""
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
with open(path) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
key, _, val = line.partition("=")
|
||||
key = key.strip()
|
||||
val = val.strip().strip('"').strip("'")
|
||||
if key:
|
||||
os.environ.setdefault(key, val)
|
||||
|
||||
|
||||
def require(name):
|
||||
val = os.environ.get(name)
|
||||
if not val:
|
||||
raise RuntimeError(
|
||||
f"{name} is not set -- copy .env.example to .env and fill it in"
|
||||
)
|
||||
return val
|
||||
|
||||
|
||||
def load_secret_key():
|
||||
os.makedirs(INSTANCE_DIR, exist_ok=True)
|
||||
if not os.path.exists(SECRET_KEY_PATH):
|
||||
"""Read the persisted session-signing key, generating it if missing.
|
||||
|
||||
On a read-only overlay filesystem the write will fail; in that case we
|
||||
fall back to an ephemeral in-memory key so the app still starts (sessions
|
||||
just won't survive a restart). deploy.sh generates this file while the
|
||||
card is writable, so in normal operation the write path isn't hit."""
|
||||
if os.path.exists(SECRET_KEY_PATH):
|
||||
with open(SECRET_KEY_PATH) as f:
|
||||
return f.read().strip()
|
||||
|
||||
key = os.urandom(32).hex()
|
||||
try:
|
||||
os.makedirs(INSTANCE_DIR, exist_ok=True)
|
||||
with open(SECRET_KEY_PATH, "w") as f:
|
||||
f.write(os.urandom(32).hex())
|
||||
with open(SECRET_KEY_PATH) as f:
|
||||
return f.read().strip()
|
||||
f.write(key)
|
||||
except OSError:
|
||||
log.warning(
|
||||
"could not persist secret key (read-only filesystem?) -- using an "
|
||||
"ephemeral key; logins won't survive an app restart"
|
||||
)
|
||||
return key
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class Config:
|
||||
|
|
@ -20,11 +69,14 @@ class Config:
|
|||
PERMANENT_SESSION_LIFETIME = timedelta(days=3650)
|
||||
SESSION_COOKIE_SAMESITE = "Lax"
|
||||
|
||||
USERNAME = "jujualexevan"
|
||||
PASSWORD = "bambas"
|
||||
USERNAME = require("MEDIAPI_USERNAME")
|
||||
PASSWORD = require("MEDIAPI_PASSWORD")
|
||||
|
||||
MEDIA_ROOTS = ["/localmedia"]
|
||||
MPV_SOCKET = "/run/mediapi/mpv.sock"
|
||||
MEDIA_ROOTS = [
|
||||
p for p in os.environ.get("MEDIAPI_MEDIA_ROOTS", "/localmedia").split(":") if p
|
||||
]
|
||||
MPV_SOCKET = os.environ.get("MEDIAPI_MPV_SOCKET", "/run/mediapi/mpv.sock")
|
||||
PORT = int(os.environ.get("MEDIAPI_PORT", "8080"))
|
||||
|
||||
VIDEO_EXTENSIONS = {
|
||||
".mp4", ".mkv", ".avi", ".mov", ".m4v", ".webm", ".mpg", ".mpeg", ".ts", ".flv", ".wmv",
|
||||
|
|
|
|||
2
run.py
2
run.py
|
|
@ -3,4 +3,4 @@ from mediapi import create_app
|
|||
app = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8080, threaded=True)
|
||||
app.run(host="0.0.0.0", port=app.config["PORT"], threaded=True)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ Wants=network-online.target mediapi-mpv.service
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=jm
|
||||
WorkingDirectory=/home/jm/mediapi
|
||||
ExecStart=/home/jm/.local/bin/uv run run.py
|
||||
User=${MEDIAPI_USER}
|
||||
WorkingDirectory=${PROJECT_DIR}
|
||||
ExecStart=${UV} run run.py
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
|
|
@ -4,7 +4,7 @@ After=local-fs.target
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=jm
|
||||
User=${MEDIAPI_USER}
|
||||
SupplementaryGroups=video render
|
||||
RuntimeDirectory=mediapi
|
||||
RuntimeDirectoryMode=0770
|
||||
Loading…
Reference in a new issue