Compare commits

..
Author SHA1 Message Date
Waters ChurchandClaude Opus 5 1be5cbef31 The church's own Gitea is where ProdDash lives
The shipped defaults pointed at GitHub, so a fresh install with no
configuration would have looked there — and that repository is going away.
They now name michael/ProdDash on chronicles-ab.waterschurch.org:3000, which
a fresh install was verified to reach with nothing configured at all.

The hostname rather than the IP, because the UniFi DNS record is
authoritative and this survives the NAS changing address. Both production
machines already carry an explicit setting of their own, so neither is
affected either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:00:22 -04:00
Michael McGary 1c8cd63cd0 Merge pull request #20 from michaelmcgary2008/claude/shell-1-5-0
Shell 1.5.0
2026-09-16 18:25:02 -04:00
Waters ChurchandClaude Opus 5 af6322d079 Shell 1.5.0: updates can come from your own git server
The forge setting is a new capability, so the version moves — which is
also how a machine already running 1.4.0 is offered the update at all
(an archive install compares versions, having no commit of its own).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:24:57 -04:00
Michael McGary 168f717398 Merge pull request #19 from michaelmcgary2008/claude/self-hosted-forge
Updates can come from your own git server
2026-09-16 18:23:38 -04:00
Waters ChurchandClaude Opus 5 67bcd3640a Updates can come from your own git server
ProdDash fetched one tar.gz of the branch from GitHub's codeload host, with
that URL shape hardcoded. A `forge` setting now names where the code comes
from: absent or "github" behaves exactly as before, and a Gitea or Forgejo
base URL such as http://10.3.8.90:3000 fetches from a server of your own.

Gitea spells an archive URL differently — /archive/<branch>.tar.gz rather
than /tar.gz/<branch> — and that is the only real difference. Its tarball
carries the same pax header with the same commit hash, so the archive
parser, update detection and module installs are untouched. The "what
changed" link follows the forge too, using each one's own compare and
commits paths.

Verified against both: the same commit and version come back from GitHub
and from a self-hosted Gitea, and a module installs from each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:23:20 -04:00
Michael McGary 095fe66118 Merge pull request #17 from michaelmcgary2008/claude/no-campus-defaults
No campus's address ships in the repo
2026-09-16 10:38:32 -04:00
Michael McGary 6417c3aa54 Merge pull request #16 from michaelmcgary2008/claude/slack-module
Slack module: channel transcripts, PIN-gated sending as the location
2026-09-15 15:25:24 -04:00
Michael McGary c1d9fa0527 Merge pull request #14 from michaelmcgary2008/claude/proddash-app-tweaks
Shell: quieter menu handle, calmer fullscreen, live tile settings
2026-09-15 15:24:27 -04:00
4 changed files with 71 additions and 15 deletions
+20 -5
View File
@@ -100,10 +100,24 @@ with this machine's shell settings — the same keys as the checked-in
| --- | --- |
| `port` | Port ProdDash serves on (default `24500`). The macOS launcher writes this one, so the port it is set to holds however ProdDash is started. |
| `adminPasscode` | If set, the admin page asks for this shared passcode. Leave `""` for none — it's a plain-HTTP LAN tool. |
| `forge` | Where updates and module installs are fetched from. Absent or `"github"` uses GitHub; a Gitea or Forgejo base URL such as `"http://10.3.8.90:3000"` uses a server of your own. Set it per machine in the data directory, alongside `repo`. |
| `theme` | The dashboard palette: `booth` (default), `harbor`, `graphite`, `ember` or `daylight`. The admin page's **Theme** section writes this one — see [Themes](#themes). |
**Updating from your own git server.** ProdDash fetches one `tar.gz` of the
branch — nothing else — so any Gitea or Forgejo works. Point `forge` at its
base URL and `repo` at the owner/repo there:
```json
{ "forge": "http://10.3.8.90:3000", "repo": "michael/ProdDash", "branch": "main" }
```
Gitea spells an archive URL differently from GitHub, which is the only thing
that changes; the tarball itself is identical, commit hash included, so update
detection and module installs behave exactly as before. Make the repository
readable without signing in, or ProdDash cannot fetch it.
Environment variables override both files: `PORT`, `PRODDASH_PASSCODE`, and
`PRODDASH_DATA_DIR` to put the data directory somewhere else (a relative path
`PRODDASH_FORGE`, and `PRODDASH_DATA_DIR` to put the data directory somewhere else (a relative path
is taken from the app folder; `PRODDASH_DATA_DIR=config` restores the old
in-repo location). If the default directory can't be created, the server says
so and falls back to the app's `config/` folder.
@@ -131,10 +145,11 @@ set on a tile itself (a PCO Plan header colour, say) is deliberately kept.
## Installing modules and updating
Everything below happens in the **admin page**; ProdDash talks to its GitHub
repository (`michaelmcgary2008/ProdDash`, branch `main` — change with
`repo` / `branch` in `proddash.json` or the `PRODDASH_REPO` variable,
`owner/repo#branch`). It downloads one small archive of the branch, at most
Everything below happens in the **admin page**; ProdDash talks to its git
repository — `michael/ProdDash` on the church's own Gitea at
`chronicles-ab.waterschurch.org:3000`, branch `main`. Change it with `forge`,
`repo` and `branch` in `proddash.json`, or the `PRODDASH_FORGE` and
`PRODDASH_REPO` variables (`owner/repo#branch`). It downloads one small archive of the branch, at most
every few minutes, only while the admin page is open or for the update check
below. Nothing is installed without someone clicking.
+2 -1
View File
@@ -1,6 +1,7 @@
{
"port": 24500,
"adminPasscode": "",
"repo": "michaelmcgary2008/ProdDash",
"forge": "http://chronicles-ab.waterschurch.org:3000",
"repo": "michael/ProdDash",
"branch": "main"
}
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "proddash",
"version": "1.4.0",
"version": "1.5.0",
"private": true,
"description": "Modular production dashboard for Waters Church — zero-dependency Node server + vanilla client",
"description": "Modular production dashboard for Waters Church \u2014 zero-dependency Node server + vanilla client",
"license": "UNLICENSED",
"engines": {
"node": ">=18"
+47 -7
View File
@@ -13,6 +13,8 @@
* proddash.json in the data directory (this machine's overrides), or env vars:
* PORT (default: value in proddash.json, else 24500)
* PRODDASH_PASSCODE (overrides adminPasscode in proddash.json)
* PRODDASH_FORGE (overrides `forge`: "github", or a Gitea/Forgejo base
* URL such as http://10.3.8.90:3000)
* PRODDASH_DATA_DIR (where module config + layouts are kept; see below)
*/
'use strict';
@@ -764,15 +766,53 @@ function parseRepoSetting(value, fallbackBranch) {
}
const REPO = parseRepoSetting(process.env.PRODDASH_REPO || shellConfig.repo, shellConfig.branch)
|| parseRepoSetting('michaelmcgary2008/ProdDash', 'main');
|| parseRepoSetting('michael/ProdDash', 'main');
/**
* Which server the code comes from. GitHub unless `forge` names another —
* give it a Gitea (or Forgejo) base URL, "http://10.3.8.90:3000", and updates
* and module installs come from there instead. The two hosts differ only in
* how an archive URL is spelled: the tarball itself is the same, pax header
* and commit sha included, so everything downstream is untouched.
*/
function parseForge(value) {
const s = String(value || '').trim().replace(/\/+$/, '');
if (!s || s.toLowerCase() === 'github') {
return { kind: 'github', base: 'https://github.com', archiveBase: 'https://codeload.github.com' };
}
if (!/^https?:\/\//i.test(s)) return null;
// A GitHub URL still means codeload for archives.
if (/^https?:\/\/(www\.)?github\.com$/i.test(s)) {
return { kind: 'github', base: s, archiveBase: 'https://codeload.github.com' };
}
return { kind: 'gitea', base: s, archiveBase: s };
}
const FORGE = parseForge(process.env.PRODDASH_FORGE || shellConfig.forge) || parseForge('github');
/** Where branch archives come from; tests point this at a local mock. */
const ARCHIVE_BASE = String(process.env.PRODDASH_ARCHIVE_BASE || 'https://codeload.github.com').replace(/\/+$/, '');
const ARCHIVE_BASE = String(process.env.PRODDASH_ARCHIVE_BASE || FORGE.archiveBase).replace(/\/+$/, '');
const REMOTE_TTL_MS = 10 * 60 * 1000;
const REMOTE_CHECK_MS = 6 * 60 * 60 * 1000;
const MAX_ARCHIVE_BYTES = 80 * 1024 * 1024;
function tarballUrl() {
return `${ARCHIVE_BASE}/${REPO.owner}/${REPO.repo}/tar.gz/${encodeURIComponent(REPO.branch)}`;
const branch = encodeURIComponent(REPO.branch);
return FORGE.kind === 'gitea'
? `${ARCHIVE_BASE}/${REPO.owner}/${REPO.repo}/archive/${branch}.tar.gz`
: `${ARCHIVE_BASE}/${REPO.owner}/${REPO.repo}/tar.gz/${branch}`;
}
/** The page a human should read to see what changed between two commits. */
function forgeCompareUrl(from, to) {
return `${FORGE.base}/${REPO.owner}/${REPO.repo}/compare/${from}...${to}`;
}
/** The commit list for the branch we follow. */
function forgeCommitsUrl() {
const branch = encodeURIComponent(REPO.branch);
return FORGE.kind === 'gitea'
? `${FORGE.base}/${REPO.owner}/${REPO.repo}/commits/branch/${branch}`
: `${FORGE.base}/${REPO.owner}/${REPO.repo}/commits/${branch}`;
}
function httpError(status, message) {
@@ -929,11 +969,11 @@ function shellStatus() {
else if (local.branch !== REPO.branch) blocker = `this checkout is on branch "${local.branch}", not "${REPO.branch}"`;
else if (local.dirty) blocker = 'this checkout has local changes — commit or discard them first';
}
const gh = `https://github.com/${REPO.owner}/${REPO.repo}`;
return {
version: SHELL_VERSION,
repo: `${REPO.owner}/${REPO.repo}`,
branch: REPO.branch,
forge: FORGE.base,
local: { isGit: local.isGit, sha: local.sha, branch: local.branch, dirty: local.dirty },
remote: { version: remote.version, sha: remote.sha, checkedAt: remote.checkedAt, error: remote.error },
updateAvailable: versionBehind || commitsBehind,
@@ -941,8 +981,8 @@ function shellStatus() {
method: local.isGit ? 'git' : 'archive',
updateBlocker: blocker,
changesUrl: local.sha && remote.sha
? `${gh}/compare/${local.sha.slice(0, 12)}...${remote.sha.slice(0, 12)}`
: `${gh}/commits/${REPO.branch}`,
? forgeCompareUrl(local.sha.slice(0, 12), remote.sha.slice(0, 12))
: forgeCommitsUrl(),
};
}
@@ -1693,7 +1733,7 @@ server.on('listening', () => {
? ' Modules : ' + ids.map((id) => id + (isEnabled(id) ? '' : ' (disabled)') + (manifests.get(id).incompatible ? ' (needs newer ProdDash)' : '')).join(', ')
: ' Modules : none installed');
console.log(' Settings : ' + DATA_DIR + (DATA_DIR === REPO_CONFIG_DIR ? ' (inside the app folder)' : ''));
console.log(' Repo : ' + REPO.owner + '/' + REPO.repo + '@' + REPO.branch);
console.log(' Repo : ' + REPO.owner + '/' + REPO.repo + '@' + REPO.branch + ' (' + FORGE.base + ')');
});
// A restart hands the port over from the exiting ProdDash: wait for it.