Add persistent local review stack
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
.git
|
||||||
|
.venv
|
||||||
|
.review-data
|
||||||
|
deploy/importarr.review.env
|
||||||
|
.pytest_cache
|
||||||
|
.mypy_cache
|
||||||
|
.ruff_cache
|
||||||
|
.tox
|
||||||
|
**/__pycache__
|
||||||
|
**/*.pyc
|
||||||
|
**/*.pyo
|
||||||
|
*.egg-info
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
@@ -6,3 +6,5 @@ __pycache__/
|
|||||||
*.db
|
*.db
|
||||||
*.partial
|
*.partial
|
||||||
AGENTS.local.md
|
AGENTS.local.md
|
||||||
|
.review-data/
|
||||||
|
deploy/importarr.review.env
|
||||||
|
|||||||
@@ -95,6 +95,42 @@ pytest
|
|||||||
uvicorn importarr.main:app --reload
|
uvicorn importarr.main:app --reload
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Persistent local review environment
|
||||||
|
|
||||||
|
The review Compose stack builds the current working tree, including uncommitted
|
||||||
|
UI/API changes, and stays running for pre-commit or pre-push inspection. It is
|
||||||
|
separate from production: the web port is bound to localhost by default, state and
|
||||||
|
sample media live under the ignored `.review-data/` directory, external Arr/SAB
|
||||||
|
services are not required, and service-control/update commands are safe no-ops.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
deploy/review/review.sh up # build current files and start in background
|
||||||
|
deploy/review/review.sh update # rebuild changed files and recreate as needed
|
||||||
|
deploy/review/review.sh status
|
||||||
|
deploy/review/review.sh logs # follow logs; Ctrl-C leaves the stack running
|
||||||
|
deploy/review/review.sh stop # stop containers, preserving them and data
|
||||||
|
deploy/review/review.sh down # remove containers/network, preserving data
|
||||||
|
deploy/review/review.sh reset # remove stack and all local review data
|
||||||
|
```
|
||||||
|
|
||||||
|
On first use the wrapper copies `deploy/importarr.review.env.example` to the
|
||||||
|
ignored `deploy/importarr.review.env`. Adjust `REVIEW_PORT` there if port 18765
|
||||||
|
is occupied, then review `http://127.0.0.1:18765/`. For review from a trusted
|
||||||
|
internal network, set `REVIEW_BIND_ADDRESS` to the host's LAN address and use
|
||||||
|
that address in the URL. Do not use `0.0.0.0` or expose this review stack to an
|
||||||
|
untrusted network; the safe default is `127.0.0.1`. To exercise imports without
|
||||||
|
real integrations, place disposable folders in `.review-data/downloads/`; movie
|
||||||
|
and TV destinations are `.review-data/movies/` and `.review-data/tv/`.
|
||||||
|
Each checkout gets its own Compose project and image name, so worktrees do not
|
||||||
|
replace each other's containers or images. Read-only and cleanup commands do not
|
||||||
|
create the local env file when it is absent.
|
||||||
|
|
||||||
|
Agent workflow: run tests, run `update`, confirm `status` reports healthy, and
|
||||||
|
leave the stack running for the reviewer. Reviewer workflow: inspect the UI and
|
||||||
|
API, use `logs` when needed, and use `down` after review (or `reset` when the
|
||||||
|
saved review state is no longer useful). Re-run `update` after every working-tree
|
||||||
|
change that should be reviewed.
|
||||||
|
|
||||||
## Operations
|
## Operations
|
||||||
|
|
||||||
Importarr intentionally does not document private deployment topology, hostnames, reverse proxies, monitoring, backups, or operator workflows in this repository. Keep those details in your own ops runbooks.
|
Importarr intentionally does not document private deployment topology, hostnames, reverse proxies, monitoring, backups, or operator workflows in this repository. Keep those details in your own ops runbooks.
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
services:
|
||||||
|
importarr:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: ${REVIEW_IMAGE:-importarr-review:local}
|
||||||
|
env_file:
|
||||||
|
- ${REVIEW_SERVICE_ENV_FILE:-importarr.review.env.example}
|
||||||
|
ports:
|
||||||
|
- "${REVIEW_BIND_ADDRESS:-127.0.0.1}:${REVIEW_PORT:-18765}:8765"
|
||||||
|
volumes:
|
||||||
|
- ../.review-data/config:/config
|
||||||
|
- ../.review-data/downloads:/data/downloads/manual
|
||||||
|
- ../.review-data/movies:/data/movies
|
||||||
|
- ../.review-data/tv:/data/tv
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8765/health', timeout=2)"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 6
|
||||||
|
start_period: 5s
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Copied to importarr.review.env by deploy/review/review.sh. No secrets or
|
||||||
|
# external services are required for review.
|
||||||
|
REVIEW_PORT=18765
|
||||||
|
# Keep this on loopback unless review access from a trusted network is needed.
|
||||||
|
REVIEW_BIND_ADDRESS=127.0.0.1
|
||||||
|
IMPORTARR_BIND_HOST=0.0.0.0
|
||||||
|
IMPORTARR_BIND_PORT=8765
|
||||||
|
IMPORTARR_STATE_PATH=/config/importarr.db
|
||||||
|
IMPORTARR_DOWNLOAD_ROOT=/data/downloads/manual
|
||||||
|
IMPORTARR_MOVIES_ROOT=/data/movies
|
||||||
|
IMPORTARR_TV_ROOT=/data/tv
|
||||||
|
IMPORTARR_SAB_URL=http://127.0.0.1:9
|
||||||
|
IMPORTARR_SAB_CATEGORY=review
|
||||||
|
IMPORTARR_POLL_SECONDS=3600
|
||||||
|
IMPORTARR_LOG_LEVEL=info
|
||||||
|
|
||||||
|
# UI control operations must not control host services or update this checkout.
|
||||||
|
IMPORTARR_START_COMMAND=/bin/true
|
||||||
|
IMPORTARR_STOP_COMMAND=/bin/true
|
||||||
|
IMPORTARR_RESTART_COMMAND=/bin/true
|
||||||
|
IMPORTARR_UPDATE_COMMAND=/bin/true
|
||||||
|
IMPORTARR_UPDATE_RELEASE_URL=http://127.0.0.1:9/releases/latest
|
||||||
|
IMPORTARR_UPDATE_CHECK_TIMEOUT_SECONDS=1
|
||||||
Executable
+108
@@ -0,0 +1,108 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
repo_dir=$(CDPATH= cd -- "$script_dir/../.." && pwd)
|
||||||
|
compose_file="$repo_dir/deploy/docker-compose.review.yml"
|
||||||
|
env_file="$repo_dir/deploy/importarr.review.env"
|
||||||
|
env_example="$repo_dir/deploy/importarr.review.env.example"
|
||||||
|
data_dir="$repo_dir/.review-data"
|
||||||
|
repo_name=$(printf '%s' "${repo_dir##*/}" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9_-' '-')
|
||||||
|
repo_id=$(printf '%s' "$repo_dir" | cksum | awk '{print $1}')
|
||||||
|
project_name="importarr-review-${repo_name}-${repo_id}"
|
||||||
|
review_image="${project_name}-importarr:review"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: $0 {up|update|status|logs|stop|down|reset}" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
command -v docker >/dev/null 2>&1 || {
|
||||||
|
echo "docker is required" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if docker info >/dev/null 2>&1; then
|
||||||
|
docker_with_sudo=false
|
||||||
|
elif command -v sudo >/dev/null 2>&1 && sudo -n docker info >/dev/null 2>&1; then
|
||||||
|
docker_with_sudo=true
|
||||||
|
else
|
||||||
|
echo "cannot access the Docker daemon with docker or sudo -n docker" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
selected_env_file=$env_example
|
||||||
|
if [ -f "$env_file" ]; then
|
||||||
|
selected_env_file=$env_file
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$docker_with_sudo" = true ]; then
|
||||||
|
sudo -n env REVIEW_IMAGE="$review_image" REVIEW_SERVICE_ENV_FILE="$selected_env_file" \
|
||||||
|
docker compose --project-name "$project_name" --project-directory "$repo_dir/deploy" \
|
||||||
|
--env-file "$selected_env_file" -f "$compose_file" "$@"
|
||||||
|
else
|
||||||
|
REVIEW_IMAGE="$review_image" REVIEW_SERVICE_ENV_FILE="$selected_env_file" \
|
||||||
|
docker compose --project-name "$project_name" --project-directory "$repo_dir/deploy" \
|
||||||
|
--env-file "$selected_env_file" -f "$compose_file" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_env_file() {
|
||||||
|
if [ ! -f "$env_file" ]; then
|
||||||
|
cp "$env_example" "$env_file"
|
||||||
|
echo "Created $env_file from the safe review defaults."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_data() {
|
||||||
|
if rm -rf "$data_dir" 2>/dev/null && [ ! -e "$data_dir" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [ "$docker_with_sudo" = true ]; then
|
||||||
|
sudo -n rm -rf "$data_dir"
|
||||||
|
else
|
||||||
|
echo "cannot remove root-owned review data without passwordless sudo: $data_dir" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
up|update)
|
||||||
|
ensure_env_file
|
||||||
|
mkdir -p "$data_dir/config" "$data_dir/downloads" "$data_dir/movies" "$data_dir/tv"
|
||||||
|
compose up --detach --build --wait
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
compose ps
|
||||||
|
container_id=$(compose ps --quiet importarr)
|
||||||
|
[ -n "$container_id" ] || {
|
||||||
|
echo "review service is not running" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
health=$(if [ "$docker_with_sudo" = true ]; then
|
||||||
|
sudo -n docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id"
|
||||||
|
else
|
||||||
|
docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$container_id"
|
||||||
|
fi)
|
||||||
|
[ "$health" = healthy ] || {
|
||||||
|
echo "review service is not healthy (status: $health)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
;;
|
||||||
|
logs)
|
||||||
|
compose logs --follow --tail=200
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
compose stop
|
||||||
|
;;
|
||||||
|
down)
|
||||||
|
compose down --remove-orphans
|
||||||
|
;;
|
||||||
|
reset)
|
||||||
|
compose down --remove-orphans
|
||||||
|
remove_data
|
||||||
|
echo "Removed review data: $data_dir"
|
||||||
|
;;
|
||||||
|
*) usage ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user