Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
PORT=5000

# Directory containing your docker-compose stacks. MUST be mounted at this
# exact same path on the host and inside the diun-updater container (see the
# exact same path on the host and inside the dockpull container (see the
# comment in docker-compose.yml for why).
STACKS_DIR=/opt/stacks

Expand Down Expand Up @@ -32,14 +32,14 @@ BASE_URL=http://localhost:5000
# --- Background checks & notifications (all optional; also editable in the UI) ---
# Whether the server checks for updates on a schedule. Default: true.
# BACKGROUND_CHECK_ENABLED=true
# How often the background check runs, in hours (1-168). Default: 6.
# CHECK_INTERVAL_HOURS=6
# Daily local time (HH:MM, 24h) for the scheduled scan. Default: 09:00.
# SCHEDULED_CHECK_TIME=09:00
# Discord (or compatible) webhook URL to notify when updates are found.
# Leave unset to disable notifications.
# DISCORD_WEBHOOK_URL=

# Name of this app's OWN container. It is excluded from the dashboard so it
# can't be told to update (and thereby restart) itself mid-update. Defaults to
# "diun-updater" (the container_name in the shipped docker-compose.yml); change
# "dockpull" (the container_name in the shipped docker-compose.yml); change
# it only if you rename the service.
SELF_CONTAINER_NAME=diun-updater
SELF_CONTAINER_NAME=dockpull
16 changes: 8 additions & 8 deletions API_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ All request/response bodies are JSON unless noted otherwise.
- Auth is a single shared password (`ADMIN_PASSWORD`), compared in constant
time, no user accounts/database.
- On successful login, the server sets a signed, httpOnly cookie named
`diun_session` (`SameSite=Lax`, `Secure` when served over HTTPS,
`dockpull_session` (`SameSite=Lax`, `Secure` when served over HTTPS,
`Max-Age` = `SESSION_TTL` seconds).
- Protected routes (everything except `/api/auth/login` and `/api/health`)
require a valid `diun_session` cookie. If it is missing, invalid, or
require a valid `dockpull_session` cookie. If it is missing, invalid, or
expired, the server responds `401 Unauthorized` with
`{ "error": "unauthorized" }`.

Expand All @@ -24,7 +24,7 @@ All request/response bodies are JSON unless noted otherwise.
- Auth: none.
- Body: `{ "password": "string" }`
- Response:
- `200 { "ok": true }` + `Set-Cookie: diun_session=...` on success.
- `200 { "ok": true }` + `Set-Cookie: dockpull_session=...` on success.
- `401 { "error": "invalid_password" }` on bad password.
- `429 { "error": "too_many_attempts" }` after too many failed attempts
from one client IP (temporary lockout).
Expand All @@ -33,7 +33,7 @@ All request/response bodies are JSON unless noted otherwise.

- Auth: cookie.
- Body: none.
- Response: `200 { "ok": true }`, clears the `diun_session` cookie.
- Response: `200 { "ok": true }`, clears the `dockpull_session` cookie.

### `GET /api/auth/me`

Expand Down Expand Up @@ -63,7 +63,7 @@ All request/response bodies are JSON unless noted otherwise.
- Auth: cookie.
- Response: `text/event-stream` (SSE). Emits
`data: {"type":"containers-changed"}` whenever server state changes (a check
ran, an update finished, or a pin/hide changed) so dashboards can refresh
ran, an update finished, or a pin changed) so dashboards can refresh
without a manual reload. Comment lines (`: ...`) are sent as keepalives.

### `POST /api/update/:name`
Expand Down Expand Up @@ -149,7 +149,7 @@ separate section, but can still be updated by hand.
"defaultFilter": "updates",
"autoCheckOnOpen": true,
"backgroundCheckEnabled": true,
"backgroundCheckIntervalHours": 6,
"scheduledCheckTime": "09:00",
"discordEnabled": false,
"discordWebhookUrl": ""
}
Expand All @@ -158,7 +158,7 @@ separate section, but can still be updated by hand.
- `autoCheckOnOpen` — whether the dashboard runs a check automatically on
first open.
- `backgroundCheckEnabled` — whether the server runs a scheduled check.
- `backgroundCheckIntervalHours` — interval for that check (1–168).
- `scheduledCheckTime` — daily local time (HH:MM) for the scheduled scan.
- `discordEnabled` — whether to send Discord notifications on new updates.
- `discordWebhookUrl` — Discord (or compatible) webhook URL, or `""`.

Expand All @@ -167,7 +167,7 @@ separate section, but can still be updated by hand.
- Auth: cookie.
- Body: a partial patch of the settings object, e.g. `{ "defaultFilter":
"all" }`. Unknown keys are ignored; invalid values for known keys return
`400 { "error": "invalid_value" }`. Changing the interval/enable re-arms the
`400 { "error": "invalid_value" }`. Changing the time/enable re-arms the
background scheduler immediately.
- Response: `200` — the full, updated settings object.

Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Diun Web Updater
# DockPull

A small, self-hosted, mobile-first web UI for updating Docker containers that
are managed by `docker compose` (e.g. via [Dockge](https://github.com/louislam/dockge)).
Expand Down Expand Up @@ -106,9 +106,9 @@ existing compose file (e.g. a `management` stack) and fill in the two secrets.

```yaml
services:
diun-updater:
dockpull:
image: ghcr.io/strandedturtle/diupdater:edge
container_name: diun-updater
container_name: dockpull
restart: unless-stopped
ports:
- "5000:5000"
Expand All @@ -122,16 +122,16 @@ services:
# match STACKS_DIR above, or updates fail with "compose file not found"
# and relative bind mounts in your other stacks break on recreate.
- /opt/stacks:/opt/stacks
- diun-updater-data:/data
- dockpull-data:/data

volumes:
diun-updater-data:
dockpull-data:
```

Generate the secret (`openssl rand -hex 32`), then start just this service:

```bash
docker compose up -d diun-updater
docker compose up -d dockpull
```

Then open `http://<host-ip>:5000`.
Expand Down Expand Up @@ -199,7 +199,7 @@ critical part is the **same-path stacks mount**:
- /var/run/docker.sock:/var/run/docker.sock
# ⚠️ SAME PATH on host and in container — do not change one side only:
- ${STACKS_DIR}:${STACKS_DIR}
- diun-updater-data:/data # persistent SQLite (events/history/pins)
- dockpull-data:/data # persistent SQLite (events/history/pins)
```

**Why same-path?** This app calls `docker compose` against the *host* Docker
Expand All @@ -226,10 +226,10 @@ Check it's healthy:

```bash
curl -s http://localhost:5000/api/health # -> {"ok":true}
docker logs diun-updater # -> "...server listening at ..."
docker logs dockpull # -> "...server listening at ..."
```

The SQLite database is created automatically in the `diun-updater-data` volume
The SQLite database is created automatically in the `dockpull-data` volume
on first start. The first time you load the UI you'll get the login screen —
enter `ADMIN_PASSWORD`, and the dashboard will run an initial update check.

Expand Down Expand Up @@ -298,17 +298,17 @@ under `errors`).

### Background checks & Discord notifications

By default the server also checks on a schedule (every 6h) so badges stay fresh
By default the server runs a daily scan (09:00, server-local time) so badges stay fresh
even when the app is closed. Configure it under **Settings → Background checks &
Discord**:

- **Background checks** on/off and interval.
- **Daily scan** on/off and the time of day it runs.
- **Discord webhook URL** — paste a Discord channel webhook to get a message when
updates are found, then use **Send test message** to verify it. Each update is
announced once (no repeats on every check).

These can also be seeded from the environment (`BACKGROUND_CHECK_ENABLED`,
`CHECK_INTERVAL_HOURS`, `DISCORD_WEBHOOK_URL`); the Settings UI overrides at
`SCHEDULED_CHECK_TIME`, `DISCORD_WEBHOOK_URL`); the Settings UI overrides at
runtime.

---
Expand All @@ -328,9 +328,9 @@ All configuration is via environment variables (see `.env.example`).
| `SESSION_TTL` | `604800` | | Login cookie lifetime in seconds (7 days). |
| `BASE_URL` | `http://localhost:5000` | | Public URL; if `https`, the cookie is set `Secure`. |
| `DISCORD_WEBHOOK_URL` | — | | Discord webhook for update notifications (optional; also set in Settings). |
| `CHECK_INTERVAL_HOURS` | `6` | | Background check interval in hours (1–168). |
| `SCHEDULED_CHECK_TIME` | `09:00` | | Daily local time (HH:MM) for the scheduled scan. |
| `BACKGROUND_CHECK_ENABLED` | `true` | | Whether the scheduled background check runs. |
| `SELF_CONTAINER_NAME` | `diun-updater` | | This app's own container name, excluded from the dashboard so it can't update itself. |
| `SELF_CONTAINER_NAME` | `dockpull` | | This app's own container name, excluded from the dashboard so it can't update itself. |

The two required vars are enforced at startup — the server refuses to boot
without them (a `SKIP_CONFIG_CHECK=1` escape hatch exists for skeleton
Expand All @@ -354,7 +354,7 @@ smoke-tests only; never use it in production).
open internet or fronting it with Cloudflare Access if exposure matters.
- **The app excludes its own container** from the dashboard (it can't safely
update itself). Update the updater the normal way:
`docker compose pull diun-updater && docker compose up -d diun-updater`.
`docker compose pull dockpull && docker compose up -d dockpull`.

---

Expand All @@ -381,7 +381,7 @@ quay.io work.
pending event automatically (this also covers multi-arch images, where the
registry digest and the running digest legitimately differ). If a badge sticks,
tap **Check for updates** again; if it persists, there may be a genuinely newer
image — check the History tab and `docker logs diun-updater`.
image — check the History tab and `docker logs dockpull`.

**Can't log in / cookie not sticking.** If you're on `https`, make sure
`BASE_URL` is your `https://` URL (otherwise the `Secure` cookie won't be set
Expand Down Expand Up @@ -430,5 +430,5 @@ cd client && npm run build # production bundle -> client/dist/
Build the production image manually (build context must be the repo root):

```bash
docker build -f server/Dockerfile -t diun-updater .
docker build -f server/Dockerfile -t dockpull .
```
4 changes: 2 additions & 2 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Diun Updater" />
<meta name="apple-mobile-web-app-title" content="DockPull" />
<link rel="apple-touch-icon" href="/icon-192.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<title>Diun Updater</title>
<title>DockPull</title>
</head>
<body>
<div id="root"></div>
Expand Down
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "diun-updater-client",
"name": "dockpull-client",
"version": "0.1.0",
"private": true,
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion client/src/AuthPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function AuthPage({ onAuthed }) {
return (
<div className="auth-page">
<div className="auth-card">
<h1>Diun Updater</h1>
<h1>DockPull</h1>
<p className="subtitle">Sign in to manage your containers</p>
<form onSubmit={handleSubmit}>
<div className="field">
Expand Down
2 changes: 1 addition & 1 deletion client/src/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import UpdateCard from './components/UpdateCard.jsx';
import UpdateAllButton from './components/UpdateAllButton.jsx';
import StackGroup from './components/StackGroup.jsx';

const AUTOCHECK_SESSION = 'diun.autochecked';
const AUTOCHECK_SESSION = 'dockpull.autochecked';
const UNGROUPED = 'Ungrouped';

function hasUpdate(c) {
Expand Down
22 changes: 13 additions & 9 deletions client/src/components/BottomNav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,19 @@ const HistoryIcon = () => (
);

const SettingsIcon = () => (
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="3" stroke="currentColor" strokeWidth="2" />
<path
d="M19.4 13a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-.33-1.82l-1-1a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 01-1.82-.33l-.06-.06a1.65 1.65 0 01-.33-1.82V5a1.65 1.65 0 00-1.65-1.65h-1.4A1.65 1.65 0 009.7 5v.1a1.65 1.65 0 01-.33 1.82 1.65 1.65 0 01-1.82.33l-.06-.06a1.65 1.65 0 00-1.82.33l-1 1a1.65 1.65 0 00-.33 1.82 1.65 1.65 0 01.33 1.82l-.06.06a1.65 1.65 0 00-.33 1.82l1 1a1.65 1.65 0 001.82.33 1.65 1.65 0 011.82.33l.06.06a1.65 1.65 0 01.33 1.82V19A1.65 1.65 0 0011 20.65h1.4A1.65 1.65 0 0014 19v-.1a1.65 1.65 0 01.33-1.82 1.65 1.65 0 011.82-.33l.06.06a1.65 1.65 0 001.82-.33l1-1a1.65 1.65 0 00.33-1.82z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
</svg>
);

Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function Header({ pendingCount = 0, onLoggedOut }) {
return (
<header className="app-header">
<Link to="/" className="title-link">
<span>Diun Updater</span>
<span>DockPull</span>
{pendingCount > 0 && <span className="badge">{pendingCount}</span>}
</Link>
<nav className="header-nav" aria-label="Primary">
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/StackGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const Chevron = ({ open }) => (
* - children: the cards
*/
export default function StackGroup({ title, count, updateCount = 0, storageKey, defaultOpen = true, children }) {
const key = `diun.group.${storageKey}`;
const key = `dockpull.group.${storageKey}`;
const [open, setOpen] = useState(() => {
try {
const stored = localStorage.getItem(key);
Expand Down
4 changes: 2 additions & 2 deletions client/src/hooks/useTheme.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from 'react';

const STORAGE_KEY = 'diun-theme';
const STORAGE_KEY = 'dockpull-theme';

function getInitialTheme() {
if (typeof window === 'undefined') return 'dark';
Expand Down Expand Up @@ -49,7 +49,7 @@ function setTheme(theme) {

/**
* Returns `{ theme, toggle }`. `theme` is 'dark' | 'light', read from
* localStorage('diun-theme') or `prefers-color-scheme` on first use.
* localStorage('dockpull-theme') or `prefers-color-scheme` on first use.
* `toggle()` flips the theme, persists it, and sets `data-theme` on
* `<html>` (which is all styles/themes.css needs to re-theme everything).
*/
Expand Down
32 changes: 14 additions & 18 deletions client/src/pages/SettingsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ export default function SettingsPage() {
<h3>Background checks &amp; Discord</h3>
<div className="settings-row">
<div className="settings-row-label">
<span>Background checks</span>
<span>Daily scan</span>
<span className="settings-row-desc">
Periodically check for updates even when the app is closed.
Run a scan once a day even when the app is closed.
</span>
</div>
<button
Expand All @@ -208,23 +208,19 @@ export default function SettingsPage() {
</div>
<div className="settings-row">
<div className="settings-row-label">
<span>Check every</span>
<span className="settings-row-desc">How often the background check runs.</span>
<span>Daily scan time</span>
<span className="settings-row-desc">
When the daily scan runs (server's local time) — set this to when you want your
Discord ping.
</span>
</div>
<select
className="settings-select"
value={settings?.backgroundCheckIntervalHours ?? 6}
onChange={(e) =>
saveSetting({ backgroundCheckIntervalHours: Number(e.target.value) }).catch(() => {})
}
<input
type="time"
className="settings-input settings-time"
value={settings?.scheduledCheckTime || '09:00'}
onChange={(e) => saveSetting({ scheduledCheckTime: e.target.value }).catch(() => {})}
disabled={!settings || !settings?.backgroundCheckEnabled}
>
<option value={1}>1 hour</option>
<option value={3}>3 hours</option>
<option value={6}>6 hours</option>
<option value={12}>12 hours</option>
<option value={24}>24 hours</option>
</select>
/>
</div>
<div className="settings-row settings-row-stack">
<div className="settings-row-label">
Expand Down Expand Up @@ -329,7 +325,7 @@ export default function SettingsPage() {

<section className="settings-section">
<h3>About</h3>
<p className="about-app-name">Diun Updater</p>
<p className="about-app-name">DockPull</p>
<p className="settings-row-desc">
A small dashboard for checking your containers' images for updates and applying
them by hand.
Expand Down
5 changes: 5 additions & 0 deletions client/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1271,3 +1271,8 @@ a {
max-height: 240px;
overflow-y: auto;
}

.settings-time {
width: auto;
min-width: 120px;
}
2 changes: 1 addition & 1 deletion client/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default defineConfig({
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: 'Diun Updater',
name: 'DockPull',
short_name: 'Updater',
theme_color: '#0f1117',
background_color: '#0f1117',
Expand Down
Loading
Loading