Target manual importer from UI controls #31

This commit is contained in:
2026-07-29 21:37:20 +02:00
parent 7be54f9f3c
commit 72562eab95
4 changed files with 61 additions and 5 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ sudo -n sh /opt/importarr/repo-upgrade.sh
The helper refuses to run when the checkout has uncommitted changes, then performs `git pull --ff-only`, reinstalls the package from the repo, restarts `importarr.service`, and prints service status. Use it after changes have been committed and pushed to `main`. The helper refuses to run when the checkout has uncommitted changes, then performs `git pull --ff-only`, reinstalls the package from the repo, restarts `importarr.service`, and prints service status. Use it after changes have been committed and pushed to `main`.
Installed deployments can expose the same operation through the authenticated API. `GET /api/control/update-check` queries the latest release from `IMPORTARR_UPDATE_RELEASE_URL` (default: this repository's Gitea latest-release API) and compares it with the running `IMPORTARR_VERSION`. `POST /api/control/update` performs the same check and only runs the update command when a newer release tag exists. Configure `IMPORTARR_UPDATE_COMMAND` when the default `sh deploy/repo-upgrade.sh` is not correct for the service working directory, and configure `IMPORTARR_RESTART_COMMAND` when the default `systemctl restart importarr.service` needs a wrapper such as sudo. Installed deployments can expose the same operation through the authenticated API. `GET /api/control/update-check` queries the latest release from `IMPORTARR_UPDATE_RELEASE_URL` (default: this repository's Gitea latest-release API) and compares it with the running `IMPORTARR_VERSION`. `POST /api/control/update` performs the same check and only runs the update command when a newer release tag exists. Configure `IMPORTARR_UPDATE_COMMAND` when the default `sh deploy/repo-upgrade.sh` is not correct for the service working directory. The web UI Start, Stop, and Restart controls target `manual-media-import.service` by default; configure `IMPORTARR_START_COMMAND`, `IMPORTARR_STOP_COMMAND`, or `IMPORTARR_RESTART_COMMAND` when those defaults need a wrapper such as sudo.
Release-worthy changes should be committed, tagged with SemVer (`v0.1.1`, `v0.2.0`, ...), pushed with tags, then installed from the tagged checkout or artifact. Release-worthy changes should be committed, tagged with SemVer (`v0.1.1`, `v0.2.0`, ...), pushed with tags, then installed from the tagged checkout or artifact.
+6 -2
View File
@@ -22,7 +22,9 @@ class Settings(BaseModel):
sonarr_url: str | None = None sonarr_url: str | None = None
sonarr_api_key: str | None = None sonarr_api_key: str | None = None
auth_token: str | None = None auth_token: str | None = None
restart_command: list[str] = Field(default_factory=lambda: ["systemctl", "restart", "importarr.service"]) start_command: list[str] = Field(default_factory=lambda: ["systemctl", "start", "manual-media-import.service"])
stop_command: list[str] = Field(default_factory=lambda: ["systemctl", "stop", "manual-media-import.service"])
restart_command: list[str] = Field(default_factory=lambda: ["systemctl", "restart", "manual-media-import.service"])
update_command: list[str] = Field(default_factory=lambda: ["sh", "deploy/repo-upgrade.sh"]) update_command: list[str] = Field(default_factory=lambda: ["sh", "deploy/repo-upgrade.sh"])
update_release_url: str = "https://gitea.delphas.dk/api/v1/repos/daniels/importarr/releases/latest" update_release_url: str = "https://gitea.delphas.dk/api/v1/repos/daniels/importarr/releases/latest"
update_check_timeout_seconds: int = Field(default=15, ge=1) update_check_timeout_seconds: int = Field(default=15, ge=1)
@@ -48,7 +50,9 @@ class Settings(BaseModel):
sonarr_url=os.getenv("IMPORTARR_SONARR_URL"), sonarr_url=os.getenv("IMPORTARR_SONARR_URL"),
sonarr_api_key=_env_secret("IMPORTARR_SONARR_API_KEY"), sonarr_api_key=_env_secret("IMPORTARR_SONARR_API_KEY"),
auth_token=_env_secret("IMPORTARR_AUTH_TOKEN"), auth_token=_env_secret("IMPORTARR_AUTH_TOKEN"),
restart_command=_env_command("IMPORTARR_RESTART_COMMAND", ["systemctl", "restart", "importarr.service"]), start_command=_env_command("IMPORTARR_START_COMMAND", ["systemctl", "start", "manual-media-import.service"]),
stop_command=_env_command("IMPORTARR_STOP_COMMAND", ["systemctl", "stop", "manual-media-import.service"]),
restart_command=_env_command("IMPORTARR_RESTART_COMMAND", ["systemctl", "restart", "manual-media-import.service"]),
update_command=_env_command("IMPORTARR_UPDATE_COMMAND", ["sh", "deploy/repo-upgrade.sh"]), update_command=_env_command("IMPORTARR_UPDATE_COMMAND", ["sh", "deploy/repo-upgrade.sh"]),
update_release_url=os.getenv("IMPORTARR_UPDATE_RELEASE_URL", "https://gitea.delphas.dk/api/v1/repos/daniels/importarr/releases/latest"), update_release_url=os.getenv("IMPORTARR_UPDATE_RELEASE_URL", "https://gitea.delphas.dk/api/v1/repos/daniels/importarr/releases/latest"),
update_check_timeout_seconds=int(os.getenv("IMPORTARR_UPDATE_CHECK_TIMEOUT_SECONDS", "15")), update_check_timeout_seconds=int(os.getenv("IMPORTARR_UPDATE_CHECK_TIMEOUT_SECONDS", "15")),
+3 -2
View File
@@ -258,7 +258,7 @@ def set_queue_control(payload: QueueControlRequest, _: None = Depends(require_wr
def start_queue(_: None = Depends(require_write_auth)) -> dict[str, object]: def start_queue(_: None = Depends(require_write_auth)) -> dict[str, object]:
state.set_app_state("queue_mode", "running") state.set_app_state("queue_mode", "running")
state.set_app_state("cancel_requested", "false") state.set_app_state("cancel_requested", "false")
return control_status() return {"control": control_status(), "command_result": _run_control_command(settings.start_command)}
@app.post("/api/control/pause") @app.post("/api/control/pause")
@@ -270,7 +270,8 @@ def pause_queue(_: None = Depends(require_write_auth)) -> dict[str, object]:
@app.post("/api/control/stop") @app.post("/api/control/stop")
def stop_queue(_: None = Depends(require_write_auth)) -> dict[str, object]: def stop_queue(_: None = Depends(require_write_auth)) -> dict[str, object]:
state.set_app_state("queue_mode", "stopped") state.set_app_state("queue_mode", "stopped")
return control_status() state.set_app_state("cancel_requested", "true")
return {"control": control_status(), "command_result": _run_control_command(settings.stop_command)}
@app.post("/api/control/cancel-current") @app.post("/api/control/cancel-current")
+51
View File
@@ -42,6 +42,57 @@ def test_start_reenables_manual_queue_sync(tmp_path, monkeypatch):
assert len(main.state.list_queue_items()) == 1 assert len(main.state.list_queue_items()) == 1
def test_default_control_commands_target_manual_import_service(monkeypatch):
monkeypatch.delenv("IMPORTARR_START_COMMAND", raising=False)
monkeypatch.delenv("IMPORTARR_STOP_COMMAND", raising=False)
monkeypatch.delenv("IMPORTARR_RESTART_COMMAND", raising=False)
settings = Settings.from_env()
assert settings.start_command == ["systemctl", "start", "manual-media-import.service"]
assert settings.stop_command == ["systemctl", "stop", "manual-media-import.service"]
assert settings.restart_command == ["systemctl", "restart", "manual-media-import.service"]
def test_start_control_starts_manual_import_service(tmp_path, monkeypatch):
main, _download, _movies, _tv = configure_main(tmp_path, monkeypatch)
main.settings.start_command = ["systemctl", "start", "manual-media-import.service"]
calls = []
def fake_run(command, **kwargs):
calls.append(command)
return main.subprocess.CompletedProcess(command, 0, stdout="started", stderr="")
monkeypatch.setattr(main.subprocess, "run", fake_run)
result = main.start_queue()
assert result["control"]["queue_mode"] == "running"
assert result["command_result"]["command"] == ["systemctl", "start", "manual-media-import.service"]
assert calls == [["systemctl", "start", "manual-media-import.service"]]
def test_stop_control_stops_manual_import_service(tmp_path, monkeypatch):
main, _download, _movies, _tv = configure_main(tmp_path, monkeypatch)
main.settings.stop_command = ["systemctl", "stop", "manual-media-import.service"]
calls = []
def fake_run(command, **kwargs):
calls.append(command)
return main.subprocess.CompletedProcess(command, 0, stdout="stopped", stderr="")
monkeypatch.setattr(main.subprocess, "run", fake_run)
result = main.stop_queue()
assert result["control"]["queue_mode"] == "stopped"
assert result["control"]["cancel_requested"] is True
assert result["command_result"]["command"] == ["systemctl", "stop", "manual-media-import.service"]
assert calls == [["systemctl", "stop", "manual-media-import.service"]]
def test_queue_jobs_include_groups_and_manual_context(tmp_path, monkeypatch): def test_queue_jobs_include_groups_and_manual_context(tmp_path, monkeypatch):
main, download, _movies, _tv = configure_main(tmp_path, monkeypatch) main, download, _movies, _tv = configure_main(tmp_path, monkeypatch)
batch = download / "Release" / "Season 1" batch = download / "Release" / "Season 1"