{"id":37504371,"url":"https://github.com/pbuzdygan/contiwatch","last_synced_at":"2026-04-15T23:01:37.559Z","repository":{"id":332421928,"uuid":"1133711911","full_name":"pbuzdygan/contiwatch","owner":"pbuzdygan","description":"Minimal Docker image watcher with simple containers management actions","archived":false,"fork":false,"pushed_at":"2026-01-29T22:40:14.000Z","size":31152,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-30T12:23:50.378Z","etag":null,"topics":["container-management","contiwatch","docker-image","image-container","update-checker"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pbuzdygan.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-13T18:06:48.000Z","updated_at":"2026-01-29T07:33:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pbuzdygan/contiwatch","commit_stats":null,"previous_names":["pbuzdygan/contiwatch"],"tags_count":73,"template":false,"template_full_name":null,"purl":"pkg:github/pbuzdygan/contiwatch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pbuzdygan%2Fcontiwatch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pbuzdygan%2Fcontiwatch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pbuzdygan%2Fcontiwatch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pbuzdygan%2Fcontiwatch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pbuzdygan","download_url":"https://codeload.github.com/pbuzdygan/contiwatch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pbuzdygan%2Fcontiwatch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29128621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T17:12:17.649Z","status":"ssl_error","status_checked_at":"2026-02-05T17:11:23.670Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["container-management","contiwatch","docker-image","image-container","update-checker"],"created_at":"2026-01-16T07:55:31.991Z","updated_at":"2026-04-15T23:01:37.547Z","avatar_url":"https://github.com/pbuzdygan.png","language":"JavaScript","funding_links":["https://www.buymeacoffee.com/pbuzdygan"],"categories":[],"sub_categories":[],"readme":"# Contiwatch\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"branding/contiwatch_banner.png\" alt=\"CONTIWATCH Banner\" width=\"50%\"\u003e\n\u003c/p\u003e\n\n**Contiwatch** is minimal Docker image watcher inspired by Watchtower. Scans local containers, checks for new images, optionally recreates containers, and sends Discord webhook notifications.\n\n## Features\n- ✅ Scan local Docker daemon for running containers\n- ✅ Remote agent support (token-authenticated)\n- ✅ Pull image tags and detect updates\n- ✅ Global and per-container policy (`contiwatch.policy` label)\n- ✅ Optional update (recreate container) or notify-only\n- ✅ Simple HTML UI for updates, servers, events, and settings\n- ✅ Server maintenance mode to pause scans and updates per server\n- ✅ Experimental containers management UI (opt-in)\n- ✅ Experimental container shell (opt-in)\n- ✅ Experimental container logs (opt-in)\n- ✅ Discord webhook notifications\n\n---\n## Demo / Screenshots\n\n### Main UI\n\n## Policies\nSet on containers via label:\n- `contiwatch.policy=update`\n- `contiwatch.policy=notify_only`\n- `contiwatch.policy=skip`\n\nGlobal default is `notify_only` unless changed in the UI or config.\n\nPolicy behavior:\n- `notify_only`: pulls image metadata, detects updates, sends notification only (no container changes).\n- `update`: pulls the image and recreates the container with the same config; it only starts the new container if it was running before. By default, non-running containers are skipped for updates (reported as `Skipped`); enable `update_stopped_containers` to update stopped containers but keep them stopped.\n- `skip`: ignores the container entirely.\n\nWhen using remote agents, the controller syncs `global_policy` to agents; per-container labels still override the global setting.\nStatus summary includes a Skipped metric for containers that were intentionally skipped.\n\n## Run (container)\n```bash\ndocker build -t contiwatch .\n\ndocker run -d \\\n  --name contiwatch \\\n  -p 8080:8080 \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v contiwatch-data:/data \\\n  contiwatch\n```\n\nOpen `http://localhost:8080`.\n\nFor a production-like controller deployment, do not stop at the minimal example above. In practice you should enable at least:\n- required controller PIN gate for the whole interactive UI/API session\n- a non-default strong agent token for every remote agent\n\nExample controller run with required PIN:\n```bash\ndocker run -d \\\n  --name contiwatch \\\n  -p 8080:8080 \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v contiwatch-data:/data \\\n  -e APP_PIN=\"SET_A_UNIQUE_4_TO_8_DIGIT_PIN\" \\\n  contiwatch\n```\n\n## Agent mode (remote)\nRun on a remote host with Docker socket access and a token:\n```bash\ndocker run -d \\\n  --name contiwatch-agent \\\n  -p 8080:8080 \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v contiwatch-agent-data:/data \\\n  -e CONTIWATCH_AGENT=true \\\n  -e CONTIWATCH_AGENT_TOKEN=\"\u003cTOKEN\u003e\" \\\n  contiwatch\n```\n\n## Container images (GHCR)\nMain releases (multi-arch):\n```bash\ndocker pull ghcr.io/\u003cowner\u003e/\u003crepo\u003e:latest\ndocker pull ghcr.io/\u003cowner\u003e/\u003crepo\u003e:\u003cversion\u003e\n```\n\nDev releases (multi-arch):\n```bash\ndocker pull ghcr.io/\u003cowner\u003e/\u003crepo\u003e:dev_latest\ndocker pull ghcr.io/\u003cowner\u003e/\u003crepo\u003e:dev_\u003cversion\u003e\n```\n\n## Environment\n- `CONTIWATCH_ADDR` (default `:8080`)\n- `CONTIWATCH_CONFIG` (default `/data/config.json`)\n- `TZ` (optional; e.g. `Europe/Warsaw` for local timestamps)\n- `APP_PIN` (required in controller mode; 4-8 digits)\n- `CONTIWATCH_APP_PIN` (optional backward-compatibility fallback for old deployments; use `APP_PIN` in new setup)\n- `CONTIWATCH_AGENT` (optional; set to `true` to run in agent mode)\n- `CONTIWATCH_AGENT_TOKEN` (required in agent mode; bearer token for API access)\n- `CONTIWATCH_REPO` (optional; GitHub repo in `owner/name` form for release links/checks)\n- `CONTIWATCH_CHANNEL` (optional; `main` or `dev` for release checks)\n- `CONTIWATCH_RELEASE_CHECK` (optional; set to `0`/`false` to disable release checks)\n- `CONTIWATCH_GITHUB_TOKEN` (optional; token for private repos or higher GitHub API limits)\n\n## Recommended security setup\nFor the controller instance, the enforced baseline is:\n- set `APP_PIN` (controller will not start without it)\n- keep the default config volume mounted on `/data`\n- use a strong unique `CONTIWATCH_AGENT_TOKEN` on every remote agent\n\nExample controller `docker-compose.yml` security block:\n\n```yaml\nenvironment:\n  CONTIWATCH_ADDR: \":8080\"\n  CONTIWATCH_CONFIG: \"/data/config.json\"\n  TZ: Europe/Warsaw\n  APP_PIN: \"SET_A_UNIQUE_4_TO_8_DIGIT_PIN\"\n```\n\nExample remote agent `compose_agent.yml` security block:\n\n```yaml\nenvironment:\n  CONTIWATCH_AGENT: \"true\"\n  CONTIWATCH_AGENT_TOKEN: \"PUT_LONG_RANDOM_TOKEN_HERE\"\n  CONTIWATCH_ADDR: \":8080\"\n  CONTIWATCH_CONFIG: \"/data/config.json\"\n  TZ: Europe/Warsaw\n```\n\nImportant:\n- `APP_PIN` is for the controller only. Do not use it on remote agents.\n- Do not use predictable values such as `1234`, `1111`, or `0000`.\n- Basic Auth has been removed from controller mode.\n- Agent mode is protected by bearer token, not by PIN Guard.\n- PIN session is per browser window/tab: refresh in the same tab keeps access, but opening a new tab/window requires PIN again.\n- Active work in the same tab is not interrupted by a short session TTL; stale server-side PIN tokens are only cleaned up after prolonged inactivity as a fallback safeguard.\n\n## Config file\n`/data/config.json` fields include:\n- `scan_interval_sec` (in seconds; UI shows minutes)\n- `scheduler_enabled` (if `true`, periodic scans run every `scan_interval_sec`)\n- `global_policy`\n- `discord_webhook_url`\n- `discord_notifications_enabled` (if `false`, no Discord notifications are sent)\n- `discord_notify_on_start`\n- `discord_notify_on_update_detected`\n- `discord_notify_on_container_updated`\n- `update_stopped_containers` (if `true`, `update` policy also updates stopped containers but keeps them stopped)\n- `prune_dangling_images` (if `true`, prune dangling images after updates)\n- `experimental_features` (object of feature flags: `containers`, `containers_sidebar`, `stacks`, `images`, `networks`, `volumes`, `container_shell`, `container_logs`, `container_resources`)\n- `experimental_features.container_shell` (enables container shell UI)\n- `experimental_features.container_logs` (enables container logs UI)\n- `experimental_features.container_resources` (enables container resources UI)\n- `experimental_features.stacks` / `experimental_features.images` / `experimental_features.networks` / `experimental_features.volumes` (enables Container stacks/images/networks/volumes buttons in the Containers top bar)\n- `experimental_features.containers_sidebar` (shows enabled container subfeatures in the sidebar as shortcuts)\n- `local_servers` (list of local Docker daemons with `name`, `socket`, and optional `maintenance`)\n- `remote_servers` (list of remote servers with `name`, `url`, optional `token`, and optional `maintenance`)\n- `remote_servers[].public_ip` (optional public IP/host used for opening container services from the UI)\n- `local_servers[].public_ip` (optional public IP/host used for opening container services from the UI)\n\nSecurity notes:\n- Config file is stored with restricted permissions (`0600`).\n- Sensitive values are not returned in full by controller read APIs:\n  - `GET /api/config` returns `discord_webhook_url` hidden and `discord_webhook_configured` flag.\n  - `GET /api/servers` returns remote token hidden and `token_configured` flag.\n- Controller hardening is environment-driven:\n  - `APP_PIN` is required for controller startup and enables session gating for protected controller API endpoints and browser UI.\n  - Remote agents stay token-protected through `CONTIWATCH_AGENT_TOKEN`.\n- To keep an existing secret when updating:\n  - send `discord_webhook_url=\"__keep__\"` for config updates,\n  - send empty `token` for existing remote server updates.\n\n## API\n- `GET /api/version`\n- `GET /api/meta` (version/channel/repo metadata used by the UI)\n- `GET /api/release` (latest release info + update availability)\n- `GET /api/pin/status`\n- `POST /api/pin/verify`\n- `POST /api/pin/logout`\n- `POST /api/scan` run scan\n- `POST /api/scan/stop` cancel scan\n- `GET /api/scan/state` scan running status\n- `GET /api/status` last scan (local or agent)\n- `GET /api/aggregate` local + remote status\n- `GET/PUT /api/config`\n- `GET/POST /api/servers` (remote servers)\n- `DELETE /api/servers/{name}`\n- `GET/POST /api/locals` (local servers)\n- `GET /api/servers/info` versions + reachability\n- `GET /api/servers/stream` live server info + scan updates (SSE)\n- `POST /api/servers/refresh` trigger on-demand reachability checks (updates stream + returns snapshot)\n- `POST /api/status/refresh` pull last scan snapshots from online agents (updates stream)\n- `GET /api/containers?scope=local:{name}|remote:{name}` list containers for a selected server\n- `POST /api/containers/action` run container action (`start`, `stop`, `restart`, `pause`, `unpause`, `kill`)\n- `GET /api/containers/shell` (WebSocket) interactive shell for a container\n- `GET /api/containers/logs` (WebSocket) stream logs for a container\n- `POST /api/containers/resources` fetch resource metrics for selected containers (`scope`, `container_ids`)\n- `GET /api/images?scope=local:{name}|remote:{name}` list images for a selected server\n- `POST /api/images/pull` pull image (`repository`, optional `tag`)\n- `POST /api/images/prune` prune images (`mode=unused|dangling`)\n- `POST /api/images/remove` remove image by `image_id`\n- `GET /api/stacks?scope=local:{name}|remote:{name}` list compose stacks stored on the controller\n- `GET /api/stacks/get?scope=local:{name}|remote:{name}\u0026name={stack}` fetch compose + env content\n- `PUT /api/stacks/save` save compose + env without deploy\n- `POST /api/stacks/validate` validate compose yaml (Docker Compose config)\n- `POST /api/stacks/action` run stack action (`up`, `down`, `start`, `stop`, `restart`, `kill`, `rm`)\n- `POST /api/update/{container_id}` update container\n- `POST /api/self-update?container={container_id}` update agent container via helper (agent mode only)\n- `GET/POST/DELETE /api/logs`\n- `POST /api/notifications/test` test Discord webhook\n\nNotes:\n- `POST /api/scan` is a one-off trigger; if a scan is already running it returns `409`.\n- `POST /api/update/{container_id}` returns `old_image_id`, `new_image_id`, and `applied_image_id` to help debug tag/image mismatches.\n- Periodic scans are disabled by default; enable via `scheduler_enabled` in the config (UI).\n- Agent mode exposes a limited API surface (token required).\n- Controller mode requires `APP_PIN` and enforces active PIN session for protected API endpoints (except `/api/health`, `/api/version`, `/api/meta`, `/api/release`, `/api/pin/*`).\n- Discord webhook test endpoint validates official Discord webhook URLs (`https://.../api/webhooks/...`).\n\nUI timing:\n- `Last scan` refers to the last update-check scan (`/api/scan` / agent `/api/status`).\n- `Last checked` in server tooltips refers to the last reachability check (`POST /api/servers/refresh`), not the scan time.\n\n## Run (docker compose)\n```bash\ndocker compose up -d --build\n```\n\nBefore first start, edit [docker-compose.yml](/home/buzuser/github/contiwatch_dev/docker-compose.yml) and set:\n- `APP_PIN`\n\nFor a remote agent, edit [compose_agent.yml](/home/buzuser/github/contiwatch_dev/compose_agent.yml) and set a long random `CONTIWATCH_AGENT_TOKEN`.\n\nStop:\n```bash\ndocker compose down\n```\n\n## Permissions\nIf `/data/config.json` shows permission errors, set user IDs in compose:\n```yaml\nenvironment:\n  PUID: \"${PUID}\"\n  PGID: \"${PGID}\"\n```\nUse your host user IDs (from `id`).\n\nIf you see `permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock`, ensure the socket is mounted and the container user can access it. Contiwatch tries to detect the socket group ID automatically; if your setup needs it explicitly, set `DOCKER_GID` to the group id of `/var/run/docker.sock` on the host (e.g. from `stat -c '%g' /var/run/docker.sock`).\n\n## Buy Me a Coffee\nIf you like the results of this project, feel free to support it.\n\n[![\"Buy Me A Coffee\"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/pbuzdygan)\n\u003cp align=\"left\"\u003e\n  \u003cimg src=\"branding/bmc_qr.png\" width=\"25%\" alt=\"BMC QR code\"\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpbuzdygan%2Fcontiwatch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpbuzdygan%2Fcontiwatch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpbuzdygan%2Fcontiwatch/lists"}