mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-09-24 19:00:21 +01:00
Updated permissions model and non-root support (#871)
- Adds a non-root startup path at user 1000:1000 - skips privilege escalation and ownership checks. Works e.g. for kubernetes deployments (user 1000:1000 and runAsNonRoot enabled). - Remove startup check/chown commands for user-owned folders. Checks can be done with a "Test destination" button in settings which performs a test write. Users are responsible for fixing their own permissions. - Update docs
This commit is contained in:
@@ -35,7 +35,7 @@ wait_for_startup() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if docker exec "$name" sh -lc "id appuser >/dev/null 2>&1 && ps -eo comm,args | awk '\$1 == \"gunicorn\" && index(\$0, \"shelfmark.main:app\") { found=1 } END { exit(found ? 0 : 1) }'" >/dev/null 2>&1; then
|
||||
if docker exec "$name" sh -lc "getent passwd 1000 >/dev/null 2>&1 && ps -eo comm,args | awk '\$1 == \"gunicorn\" && index(\$0, \"shelfmark.main:app\") { found=1 } END { exit(found ? 0 : 1) }'" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -81,7 +81,7 @@ exec /app/entrypoint.sh" >/dev/null
|
||||
run_probe() {
|
||||
local name="$1"
|
||||
local mode="${2:-default}"
|
||||
docker exec -u appuser -e PROBE_MODE="$mode" "$name" sh -lc 'python3 - <<'"'"'PY'"'"'
|
||||
docker exec -u 1000:1000 -e PROBE_MODE="$mode" "$name" sh -lc 'python3 - <<'"'"'PY'"'"'
|
||||
import asyncio
|
||||
import os
|
||||
import shelfmark.bypass.internal_bypasser as ib
|
||||
@@ -183,7 +183,7 @@ scenario_latest_proxy_auth_downloads_readonly() {
|
||||
start_container "$name" "$LATEST_IMAGE" '
|
||||
mkdir -p /app/downloaded_files &&
|
||||
touch /app/downloaded_files/pipfinding.lock /app/downloaded_files/proxy_dir.lock &&
|
||||
chown appuser:appuser /app/downloaded_files/pipfinding.lock /app/downloaded_files/proxy_dir.lock &&
|
||||
chown 1000:1000 /app/downloaded_files/pipfinding.lock /app/downloaded_files/proxy_dir.lock &&
|
||||
chmod 0666 /app/downloaded_files/pipfinding.lock /app/downloaded_files/proxy_dir.lock &&
|
||||
chown root:root /app/downloaded_files &&
|
||||
chmod 0555 /app/downloaded_files &&
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""List configured destination roots that may need permission repair.
|
||||
|
||||
This script is called by the entrypoint to find configured output destination
|
||||
roots from config files under CONFIG_DIR/plugins/.
|
||||
|
||||
Outputs directory paths that need permission fixing (one per line).
|
||||
The entrypoint handles the actual chown operations.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_directories_from_config() -> set[str]:
|
||||
"""Extract configured destination-style paths from config files."""
|
||||
directories = set()
|
||||
|
||||
config_dir = Path(os.getenv("CONFIG_DIR", "/config"))
|
||||
plugins_dir = config_dir / "plugins"
|
||||
|
||||
if not plugins_dir.exists():
|
||||
return directories
|
||||
|
||||
# Keys that can point at output destination roots or legacy equivalents
|
||||
directory_keys = {
|
||||
# Current destination settings
|
||||
"DESTINATION",
|
||||
"DESTINATION_AUDIOBOOK",
|
||||
# Content-type routing destinations
|
||||
"AA_CONTENT_TYPE_DIR_FICTION",
|
||||
"AA_CONTENT_TYPE_DIR_NON_FICTION",
|
||||
"AA_CONTENT_TYPE_DIR_UNKNOWN",
|
||||
"AA_CONTENT_TYPE_DIR_MAGAZINE",
|
||||
"AA_CONTENT_TYPE_DIR_COMIC",
|
||||
"AA_CONTENT_TYPE_DIR_STANDARDS",
|
||||
"AA_CONTENT_TYPE_DIR_MUSICAL_SCORE",
|
||||
"AA_CONTENT_TYPE_DIR_OTHER",
|
||||
# Legacy path settings still recognized in older configs
|
||||
"INGEST_DIR",
|
||||
"INGEST_DIR_AUDIOBOOK",
|
||||
"INGEST_DIR_BOOK_FICTION",
|
||||
"INGEST_DIR_BOOK_NON_FICTION",
|
||||
"INGEST_DIR_BOOK_UNKNOWN",
|
||||
"INGEST_DIR_MAGAZINE",
|
||||
"INGEST_DIR_COMIC_BOOK",
|
||||
"INGEST_DIR_STANDARDS_DOCUMENT",
|
||||
"INGEST_DIR_MUSICAL_SCORE",
|
||||
"INGEST_DIR_OTHER",
|
||||
"LIBRARY_PATH",
|
||||
"LIBRARY_PATH_AUDIOBOOK",
|
||||
}
|
||||
|
||||
# Read all JSON config files
|
||||
for config_file in plugins_dir.glob("*.json"):
|
||||
try:
|
||||
with open(config_file, "r") as f:
|
||||
config = json.load(f)
|
||||
|
||||
for key in directory_keys:
|
||||
if key in config:
|
||||
value = config[key]
|
||||
if value and isinstance(value, str) and value.startswith("/"):
|
||||
directories.add(value)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
continue
|
||||
|
||||
return directories
|
||||
|
||||
|
||||
def main():
|
||||
"""Output configured destination roots that currently exist."""
|
||||
directories = get_directories_from_config()
|
||||
|
||||
# Filter to directories that actually exist
|
||||
existing = []
|
||||
for dir_path in directories:
|
||||
path = Path(dir_path)
|
||||
if path.exists() and path.is_dir():
|
||||
existing.append(dir_path)
|
||||
|
||||
# Output one directory per line
|
||||
for dir_path in sorted(existing):
|
||||
print(dir_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user