diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ad3566..80b835e 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,33 @@ Version numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html --- +## [0.9.1] — 2026-10-28 + +### Fixed + +- **A dependency on a disabled container no longer blocks recovery forever.** If + `depends_on` named a container that is disabled in Inspector, the dependent app was + never restarted. Inspector now only waits for dependencies it is watching, and the + container page warns when the selected dependency is disabled. +- **`PORTAINER_API_TOKEN` now reaches the container.** The public `compose.yml` didn't + pass it in, so setting it as a Portainer stack variable had no effect. Existing + stacks need the line `- PORTAINER_API_TOKEN=${PORTAINER_API_TOKEN:-}` added under + `environment:` (Stacks → inspector → Editor). +- The "depends_on … is disabled or not found" warning was logged on every check + (since 0.8.0). It is now logged once, and again only when the container settings + change. + +### Changed + +- Guide: new sections **6. Apps behind a VPN container** (why depends_on matters, how to + spot a VPN-routed app, setup, what Inspector does) and **7. Portainer stack redeploy** + (why it's needed, what a redeploy does to the whole stack, requirements, where to find + every value, adding the token, the other settings, Test connection errors). +- Clearer form hints: `vpn_routed` only affects logging; `depends_on` works for any + dependency; where to find the endpoint ID. + +--- + ## [0.9.0] — 2026-10-28 ### Added diff --git a/INSTALL.md b/INSTALL.md index 36a40a7..97ec467 100755 --- a/INSTALL.md +++ b/INSTALL.md @@ -56,6 +56,7 @@ TZ=America/Los_Angeles | `INSPECTOR_PORT` | Port on your server for the dashboard. Change it if `8080` is taken. | No (default `8080`) | | `INSPECTOR_TAG` | Version to run, e.g. `0.8.0`. See [Updating](#4-updating). | No (default `latest`) | | `PUID` / `PGID` | User and group that own Inspector's files. Unraid users often use `99` / `100` so the files match other appdata. | No (default `1000` / `1000`) | +| `PORTAINER_API_TOKEN` | A Portainer access token. Only needed if you use **Portainer stack redeploy** for VPN-routed apps; see the in-app guide (Help → Portainer). | No | ## 3. Deploy and sign in diff --git a/compose.yml b/compose.yml index 487e495..dac2070 100755 --- a/compose.yml +++ b/compose.yml @@ -14,6 +14,8 @@ # INSPECTOR_TAG Image version to run (default "latest"; e.g. 0.8.0 to pin). # PUID / PGID User/group Inspector runs as (default 1000/1000). # Unraid users often prefer 99 / 100. +# PORTAINER_API_TOKEN Portainer access token, only if you use stack redeploy +# (Settings -> Portainer in the dashboard). # # No config file to create, no docker group ID to look up: Inspector creates # its config on first start and detects Docker socket permissions by itself. @@ -36,3 +38,4 @@ services: - INSPECTOR_PASSWORD=${INSPECTOR_PASSWORD:-} - PUID=${PUID:-1000} - PGID=${PGID:-1000} + - PORTAINER_API_TOKEN=${PORTAINER_API_TOKEN:-} diff --git a/src/inspector/__init__.py b/src/inspector/__init__.py index 3e2f46a..d69d16e 100755 --- a/src/inspector/__init__.py +++ b/src/inspector/__init__.py @@ -1 +1 @@ -__version__ = "0.9.0" +__version__ = "0.9.1" diff --git a/src/inspector/assets/guide/guide.html b/src/inspector/assets/guide/guide.html index e46ee8e..c7e8ccd 100755 --- a/src/inspector/assets/guide/guide.html +++ b/src/inspector/assets/guide/guide.html @@ -78,10 +78,12 @@ 3. The Status page 4. Notifications 5. Managing containers - 6. Events history - 7. Other settings - 8. "Requires restart" fields - 9. Troubleshooting + 6. Apps behind a VPN + 7. Portainer stack redeploy + 8. Events history + 9. Other settings + 10. "Requires restart" fields + 11. Troubleshooting
@@ -175,7 +177,7 @@ Each card shows:

under Apps). Pushover: enter your application API token and your user key.
  • Click Send test. You should get a message on your phone within seconds. If it fails, the reason appears next to the button.
  • -
  • Click Save, then restart Inspector once (see section 8), +
  • Click Save, then restart Inspector once (see section 10), because turning a channel on or off is a "requires restart" field.
  • You'll get one alert when a container first fails, when Inspector restarts it, and when it @@ -220,32 +222,159 @@ Inspector to act on it.

    Reachability, Dependencies and Recovery groups
    Bottom of a container's page: an app behind a VPN container with an HTTP check and stack-redeploy recovery.
    -

    Apps behind a VPN container

    -

    If an app shares a VPN container's network (network_mode: container:vpn or similar), set these on the app's page:

    + + +
    +

    6. Apps behind a VPN container

    +

    Why these apps need special handling

    +

    A common setup routes download and indexer apps through a VPN container. Those apps don't get a +network of their own: their compose file says network_mode: container:<vpn> (or +service:<vpn>) and all their traffic goes through the VPN container. That causes +two problems for a watchdog:

    + +

    How to tell if an app is behind a VPN

    + +

    Setting it up

      -
    1. Dependencies → depends_on: pick the VPN container, and turn on vpn_routed. - Inspector checks the VPN first and waits for it to recover before touching the app.
    2. -
    3. Recovery → recovery_action: choose Restart, then redeploy stack if that fails - and enter the stack name exactly as Portainer shows it. When the VPN container gets - recreated, a plain restart of the app fails; redeploying the stack reconnects it.
    4. +
    5. Add the VPN container to Inspector and keep it enabled (restart on UNHEALTHY and NOT + RUNNING is a good start). Inspector can only wait for containers it is watching: if the VPN + container is disabled, its apps are recovered normally, as if there were no dependency, and the + container page shows a warning.
    6. +
    7. On each app behind it, set Dependencies → depends_on to the VPN container.
    8. +
    9. Turn on vpn_routed. This only changes the log: Inspector writes down when it holds off + on the app because the VPN is down. The waiting itself comes from depends_on.
    10. +
    11. Add an HTTP check (section 5) using your server's IP and the + port published on the VPN container, and tick UNREACHABLE. Docker often still reports + these apps as healthy when the tunnel is down; the HTTP check notices.
    12. +
    13. Under Recovery, choose Restart, then redeploy stack if that fails and enter the + stack name. This needs the Portainer connection from section 7.
    -

    Stack redeploys need the Portainer connection in Settings → Portainer:

    +

    What Inspector then does

    +

    Every check, it looks at the VPN container first. While the VPN is anything other than healthy +(or starting, or running without a health check), the apps that depend on it are not restarted; +Inspector fixes the VPN first. Once the VPN is healthy again, the apps are handled normally: a +restart first, and a stack redeploy if the restart fails or doesn't help.

    +

    depends_on isn't only for VPNs. Use it for any "B needs A" pair, such as an +app and its database: Inspector won't restart the app while its database is down.

    +
    + +
    +

    7. Portainer stack redeploy

    +

    Why Inspector needs Portainer

    +

    Inspector talks to Docker directly, so it can restart or start a container. It can't +recreate one: that needs the container's compose definition, which lives in Portainer. A +stack redeploy asks Portainer to do exactly what its Update the stack button does. That's +how VPN-routed apps get reattached to a recreated VPN container. You only need this if you use one +of the two "redeploy" recovery actions.

    +

    A redeploy affects the whole stack. Portainer pulls the latest images +and recreates every container in that stack, not just the failing app, so all of them blink +off for a moment. That's why Restart, then redeploy stack if that fails is the recommended +choice: most problems are fixed by the plain restart, and the redeploy is only used when that +doesn't work.

    +

    Requirements

    + + +

    Step 1: find the values

    + + + + + + + +
    SettingWhere to find it
    base_urlThe address you open Portainer at, without anything after the port: + usually http://<server-ip>:9000 or https://<server-ip>:9443. + Use the server's IP, not localhost: Inspector runs in its own container.
    endpoint_idOn Portainer's Home page, click your environment (often + local). The browser address then ends like #!/2/docker/dashboard: the + number after #!/ is the endpoint ID (here 2).
    stack nameStacks list, Name column, spelled exactly the same. + This goes on each app's container page, not on the Portainer page.
    verify_sslLeave off for http:// and for Portainer's built-in + self-signed certificate on 9443. Turn on only if Portainer has a real certificate.
    API tokenClick your username (top right) → My account → + Access tokens → Add access token. Name it inspector and copy the + token straight away: Portainer shows it only once.
    + +

    Step 2: give Inspector the token

    +

    Recommended: an environment variable. The token then never lands in +config.yml or its backups.

      -
    1. In Portainer, open My account → Access tokens and create a token.
    2. -
    3. Easiest and safest: add it to the Inspector stack as an environment variable (for example - PORTAINER_API_TOKEN) and redeploy Inspector. Or choose Saved in config.yml - and paste it in.
    4. -
    5. In Settings → Portainer, add an environment with Portainer's URL and endpoint ID - (usually 1 or 2; it's the number in the URL when you open the environment in Portainer). - Click Test connection: it should list your stacks.
    6. -
    7. Turn Portainer on and save.
    8. +
    9. In Portainer open Stacks → inspector → Editor.
    10. +
    11. Check that the compose file has this line under environment: (it's in the + compose file from version 0.9.1 on; add it if your stack is older): + - PORTAINER_API_TOKEN=${PORTAINER_API_TOKEN:-}
    12. +
    13. Under Environment variables click Add an environment variable: name + PORTAINER_API_TOKEN, value the token.
    14. +
    15. Click Update the stack. Inspector restarts with the token available.
    16. +
    +

    Alternative: save it in Inspector. In the next step choose Saved in config.yml +and paste the token. It is masked in the dashboard, but anyone who can read Inspector's config +folder (or a backup of it) can read the token.

    + +

    Step 3: connect Inspector

    +
      +
    1. Open Settings → Portainer. Under Add environment, give it a name (your own + label, e.g. home) and fill in base_url, endpoint_id and verify_ssl.
    2. +
    3. API token stored as: Environment variable with the name + PORTAINER_API_TOKEN, or Saved in config.yml with the token.
    4. +
    5. Click Test connection. It should list the stacks on that endpoint; check that your + app's stack is there. Then Save new environment.
    6. +
    7. At the top of the page turn enabled on, pick the default_environment, and + Save Portainer.
    8. +
    9. On each app's container page, set the recovery action and stack name + (section 6). Leave portainer_environment on Default unless + you connected more than one Portainer.
    Portainer settings
    Settings → Portainer with one environment; the token comes from an environment variable.
    + +

    The other settings on the page

    + + + + + +
    SettingWhat it does
    default_environmentWhich connection apps use when they don't pick one. With a + single environment you can leave it on Automatic.
    stack_redeploy_cooldown_secondsMinimum time between two redeploys of the same + stack (default 300). Several apps often share a stack; this stops them triggering a redeploy + each.
    post_redeploy_wait_secondsHow long Inspector waits after a redeploy for the + stack to come up before judging it again (default 90).
    + +

    If Test connection fails

    + + + + + + +
    MessageFix
    environment variable … is not setThe compose line from step 2 is missing, or + the stack wasn't updated after adding the variable.
    the API token was rejectedToken copied wrong, deleted, or from a user without + access. Create a new one.
    no stacks found on endpoint NWrong endpoint ID; check the number in the + Portainer address again.
    request failed …Wrong address or port, http vs https + mixed up, or localhost used instead of the server IP. With https and a + self-signed certificate, turn verify_ssl off.
    -

    6. Events history

    +

    8. Events history

    The Events tab is a day-by-day timeline of failures, restarts, recoveries, mutes, and maintenance. Use Prev day / Next day to look back.

    Events timeline @@ -253,7 +382,7 @@ maintenance. Use Prev day / Next day to look back.

    -

    7. Other settings

    +

    9. Other settings

    Inspector loop settings
    Settings → Inspector loop.
    @@ -266,14 +395,14 @@ maintenance. Use Prev day / Next day to look back.

    - +
    MaintenanceA daily quiet window (e.g. 02:00–05:00) so nightly backups or updates don't trigger restarts. Turn on maintenance_window on each container that should follow it.
    Notification muteTurns the per-container Mute notifications buttons on or off, and sets how long a mute lasts.
    Uptime KumaConnection to your Uptime Kuma status page, used by container reachability checks.
    PortainerConnection to Portainer, used to redeploy stacks. See Apps behind a VPN container.
    PortainerConnection to Portainer, used to redeploy stacks. See section 7.

    At the bottom of every settings page is a strip showing where the config file lives and whether Inspector can write to it.

    -

    8. "Requires restart" fields

    +

    10. "Requires restart" fields

    Most changes apply on the next check. Fields marked REQUIRES RESTART (like turning a notification channel or the dashboard on or off) only take effect after Inspector restarts:

    @@ -285,7 +414,7 @@ dashboard on or off) only take effect after Inspector restarts:

    -

    9. Troubleshooting

    +

    11. Troubleshooting

    diff --git a/src/inspector/assets/guide/settings-portainer.png b/src/inspector/assets/guide/settings-portainer.png index edfc67e..3d6f302 100755 Binary files a/src/inspector/assets/guide/settings-portainer.png and b/src/inspector/assets/guide/settings-portainer.png differ diff --git a/src/inspector/dashboard.py b/src/inspector/dashboard.py index acc0bde..f132182 100755 --- a/src/inspector/dashboard.py +++ b/src/inspector/dashboard.py @@ -2177,7 +2177,7 @@ def _portainer_env_form(env_name: str, env, csrf_token: str, *, suggest_name: st + _settings_field("name", "env_name", env_name or suggest_name, hint="Your own label, referenced by containers") + _settings_field("endpoint_id", "endpoint_id", str(env.endpoint_id if env else 1), kind="number", - hint="Portainer → Environments; the number in the URL (usually 1 or 2)") + hint="Open the environment from Portainer's Home page; the number after #!/ in the address (often 1 or 2)") + "" + _settings_field("base_url", "base_url", env.base_url if env else "", hint="e.g. http://192.168.1.10:9000 or https://portainer.example.com") @@ -2190,7 +2190,7 @@ def _portainer_env_form(env_name: str, env, csrf_token: str, *, suggest_name: st + f'
    ' + _settings_field("environment variable name", "api_token_env", (env.api_token_env if env else "") or "PORTAINER_API_TOKEN", - hint="Set this variable on the Inspector container (Portainer → stack → Environment variables), then redeploy Inspector.") + hint="Add it to the Inspector stack's environment variables; the compose file must pass it in. Step by step: Help → Portainer stack redeploy, step 2.") + "
    " + f'
    ' + _settings_secret_field("api_token", "api_token", bool(env and env.api_token)) @@ -2451,18 +2451,25 @@ def _render_container_advanced(name: str, cfg, app: AppConfig) -> str: ) others = [c.name for c in app.containers if c.name != name] + disabled = {c.name for c in app.containers if not c.enabled} dep_value = getattr(cfg, "depends_on", "") or "" - dep_options = [("", "— none —")] + [(n, n) for n in others] + dep_options = [("", "— none —")] + [(n, n + (" (disabled)" if n in disabled else "")) for n in others] if dep_value and dep_value not in others: dep_options.append((dep_value, f"{dep_value} (not configured)")) + dep_warn = "" + if dep_value in disabled: + dep_warn = _warn(f"{_escape(dep_value)} is disabled, so Inspector can't see its status and won't wait for it. " + "Enable it to make this dependency count.") deps = ( '

    Dependencies

    ' '

    Check this container after the one it relies on, and hold off recovering it ' "while that one is still failing.

    " + _settings_select("depends_on", "depends_on", dep_value, dep_options, - hint="e.g. the VPN container, for apps that use network_mode: container:") + hint="e.g. the VPN container, for apps that use network_mode: container:. " + "Also works for any other dependency, like an app and its database.") + + dep_warn + _settings_field("vpn_routed", "vpn_routed", "on" if getattr(cfg, "vpn_routed", False) else "", kind="switch", - hint="Routes through the container above. Logs why recovery is deferred while it is down.") + hint="Only affects logging: says in the log when recovery waits on the VPN container. The waiting itself comes from depends_on.") + "
    " ) diff --git a/src/inspector/main.py b/src/inspector/main.py index bcc0635..eab5044 100755 --- a/src/inspector/main.py +++ b/src/inspector/main.py @@ -293,8 +293,10 @@ def _check_one( if ok: dep = container_config.depends_on - if dep: - dep_status = states[dep].current_status if dep in states else None + # Only wait on a dependency Inspector is watching. A disabled one has + # no state, and waiting on it would block recovery forever. + if dep and dep in states: + dep_status = states[dep].current_status if dep_status not in (HEALTHY, STARTING, NO_HEALTH_CHECK): if container_config.vpn_routed: log.info( @@ -454,8 +456,10 @@ def run_loop(config_ref: ConfigRef) -> int: for gone in states.keys() - current_enabled.keys(): states.pop(gone, None) log.info("Stopped watching container (removed or disabled): %s", gone) + if current_enabled != enabled_configs: + # Only re-order (and re-log ordering warnings) when the config changed. + check_order = _topological_order(current_enabled) enabled_configs = current_enabled - check_order = _topological_order(enabled_configs) # Integration clients hold their config; rebuild them when the # settings page changes Uptime Kuma / Portainer.
    ProblemFix
    Red READ-ONLY badge at the bottom of Settings