Fix release asset deployment

Package built frontend assets in wheels and force clean upgrades.

Run API-triggered updates in a transient unit with a safe delay.
This commit is contained in:
2026-08-09 09:11:28 +02:00
parent 2d593f4b6c
commit c65b5c8406
9 changed files with 85 additions and 15 deletions
+7 -2
View File
@@ -49,18 +49,23 @@ if ! git rev-parse --verify --quiet "refs/tags/$RELEASE_TAG" >/dev/null; then
fi
git checkout --detach "$RELEASE_TAG"
test "$(git describe --tags --exact-match HEAD)" = "$RELEASE_TAG"
"$VENV/bin/pip" install --upgrade "$REPO_DIR"
"$VENV/bin/pip" install --upgrade --force-reinstall --no-cache-dir "$REPO_DIR"
install -m 0644 "$REPO_DIR/deploy/importarr.service" /etc/systemd/system/importarr.service
install -m 0755 "$REPO_DIR/deploy/repo-upgrade.sh" "$PREFIX/repo-upgrade.sh"
systemctl daemon-reload
GIT_SHA="$(git rev-parse --short=12 HEAD 2>/dev/null || printf development)"
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
VERSION="$RELEASE_TAG"
cat > "$PREFIX/build.env" <<EOF
BUILD_ENV_TMP="$PREFIX/build.env.tmp.$$"
trap 'rm -f "$BUILD_ENV_TMP"' EXIT HUP INT TERM
cat > "$BUILD_ENV_TMP" <<EOF
IMPORTARR_VERSION=$VERSION
IMPORTARR_GIT_SHA=$GIT_SHA
IMPORTARR_BUILD_DATE=$BUILD_DATE
EOF
chmod 0644 "$BUILD_ENV_TMP"
mv -f "$BUILD_ENV_TMP" "$PREFIX/build.env"
trap - EXIT HUP INT TERM
systemctl restart "$SERVICE"
systemctl restart manual-media-import.timer
systemctl --no-pager --full status "$SERVICE"
+7 -2
View File
@@ -11,7 +11,7 @@ install -d -m 0755 /opt/importarr
REPO_DIR="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
python3 -m venv /opt/importarr/venv
/opt/importarr/venv/bin/pip install --upgrade pip
/opt/importarr/venv/bin/pip install --upgrade "$REPO_DIR"
/opt/importarr/venv/bin/pip install --upgrade --force-reinstall --no-cache-dir "$REPO_DIR"
if [ ! -f /etc/importarr/importarr.env ]; then
install -m 0600 "$REPO_DIR/deploy/importarr.env.example" /etc/importarr/importarr.env
echo "Created /etc/importarr/importarr.env; edit it before starting the service."
@@ -31,11 +31,16 @@ fi
GIT_SHA="$(git -C "$REPO_DIR" rev-parse --short=12 HEAD 2>/dev/null || printf development)"
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
VERSION="$(git -C "$REPO_DIR" describe --tags --exact-match HEAD 2>/dev/null || /opt/importarr/venv/bin/python -c 'from importarr import __version__; print(__version__)')"
cat > /opt/importarr/build.env <<EOF
BUILD_ENV_TMP="/opt/importarr/build.env.tmp.$$"
trap 'rm -f "$BUILD_ENV_TMP"' EXIT HUP INT TERM
cat > "$BUILD_ENV_TMP" <<EOF
IMPORTARR_VERSION=$VERSION
IMPORTARR_GIT_SHA=$GIT_SHA
IMPORTARR_BUILD_DATE=$BUILD_DATE
EOF
chmod 0644 "$BUILD_ENV_TMP"
mv -f "$BUILD_ENV_TMP" /opt/importarr/build.env
trap - EXIT HUP INT TERM
systemctl daemon-reload
systemctl enable importarr.service
systemctl enable manual-media-import.timer
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "importarr-ui",
"version": "0.1.2",
"version": "0.1.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "importarr-ui",
"version": "0.1.2",
"version": "0.1.3",
"dependencies": {
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-slot": "^1.2.3",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "importarr-ui",
"private": true,
"version": "0.1.2",
"version": "0.1.3",
"type": "module",
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.1.2"
__version__ = "0.1.3"
+10 -6
View File
@@ -402,13 +402,17 @@ def _schedule_update(command: list[str]) -> None:
if not command:
raise HTTPException(status_code=500, detail="update command is not configured")
try:
subprocess.Popen(
["/bin/sh", "-c", 'sleep 1; exec "$@"', "importarr-update", *command],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True,
result = subprocess.run(
["systemd-run", "--unit=importarr-update", "--collect", "--no-block", "--on-active=2s", "--", *command],
check=False,
capture_output=True,
text=True,
timeout=10,
)
if result.returncode != 0:
raise HTTPException(status_code=500, detail=f"update command failed to schedule: {result.stderr[-1000:]}")
except subprocess.TimeoutExpired as exc:
raise HTTPException(status_code=504, detail="update command timed out while scheduling") from exc
except OSError as exc:
raise HTTPException(status_code=500, detail=f"update command failed to start: {exc.__class__.__name__}") from exc
+4 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "importarr"
version = "0.1.2"
version = "0.1.3"
description = "Arr-style manual SABnzbd import service"
readme = "README.md"
requires-python = ">=3.12"
@@ -23,6 +23,9 @@ test = ["pytest>=8.2", "pytest-asyncio>=0.23"]
importarr = "importarr.main:run"
manual-media-import = "importarr.worker:main"
[tool.hatch.build.targets.wheel]
packages = ["importarr"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]
+27
View File
@@ -1,6 +1,8 @@
import fcntl
import os
import subprocess
import sys
import zipfile
from pathlib import Path
@@ -30,3 +32,28 @@ def test_repo_upgrade_refuses_concurrent_run(tmp_path):
assert result.returncode == 1
assert "another Importarr upgrade is already running" in result.stderr
def test_wheel_contains_built_frontend_assets(tmp_path):
root = Path(__file__).parents[1]
subprocess.run(
[sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", str(tmp_path), str(root)],
check=True,
capture_output=True,
text=True,
)
wheel = next(tmp_path.glob("importarr-*.whl"))
with zipfile.ZipFile(wheel) as archive:
packaged = set(archive.namelist())
assert "importarr/static/index.html" in packaged
assert "importarr/static/assets/app.js" in packaged
assert "importarr/static/assets/app.css" in packaged
def test_repo_upgrade_forces_fresh_install_without_skipping_dependencies():
script = (Path(__file__).parents[1] / "deploy/repo-upgrade.sh").read_text()
assert "pip\" install --upgrade --force-reinstall --no-cache-dir" in script
assert "--no-deps" not in script
+26
View File
@@ -194,6 +194,32 @@ def test_update_schedules_exact_latest_release_tag(tmp_path, monkeypatch):
assert response["status"] == "update_scheduled"
def test_update_is_scheduled_outside_service_cgroup(tmp_path, monkeypatch):
monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db"))
import importarr.main as main
calls = []
def fake_run(command, **kwargs):
calls.append((command, kwargs))
return main.subprocess.CompletedProcess(command, 0, stdout="Running as unit", stderr="")
monkeypatch.setattr(main.subprocess, "run", fake_run)
main._schedule_update(["/bin/sh", "/opt/importarr/repo-upgrade.sh", "v1.2.3"])
assert calls[0][0] == [
"systemd-run",
"--unit=importarr-update",
"--collect",
"--no-block",
"--on-active=2s",
"--",
"/bin/sh",
"/opt/importarr/repo-upgrade.sh",
"v1.2.3",
]
def test_update_endpoint_requires_expected_tag(tmp_path, monkeypatch):
monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db"))
import importarr.main as main