The image creates NEXTCLOUD_ADMIN_USER only on a fresh install. novox's instance was installed before the mesh adopted it, so mesh-admin was never created there, and the shares poll asked Nextcloud every minute with a login that cannot pass: 401, then Nextcloud's brute-force throttle (429), which also held the backup before a delivery. On a refused login the client now adds the account through occ (admin group), or sets its password to the module's own secret, passing it by OC_PASS so it is on no argv, and asks once more. A refusal that stays is said once and the poll backs off from 5 minutes to an hour instead of nine lines of XML every minute. An empty setting no longer becomes an empty name.
80 lines
3.5 KiB
TypeScript
80 lines
3.5 KiB
TypeScript
// nextcloud's events. The tool runtime imports this once the broker is bound. It watches the user
|
|
// list and the share list and announces new arrivals.
|
|
//
|
|
// Emits (novox/hq ADR 0041/0042):
|
|
// module.nextcloud.user.created — a user account appeared (occ user:list)
|
|
// module.nextcloud.share.created — a share appeared (OCS shares)
|
|
//
|
|
// Both are diffed and primed silently on the first look, so a restart does not re-announce every
|
|
// existing user and share as freshly created.
|
|
|
|
import { emit } from "@novox/mesh-sdk/events";
|
|
import { Backoff, NextcloudClient, pollSharesGuarded } from "./client.js";
|
|
|
|
// Constructed lazily so an unconfigured node (no admin password) loads this entrypoint without
|
|
// crashing the events host — it simply watches nothing.
|
|
let nextcloud: NextcloudClient | undefined;
|
|
try {
|
|
nextcloud = NextcloudClient.fromEnv();
|
|
} catch (err) {
|
|
console.log(`[nextcloud] not configured, not watching: ${err}`);
|
|
}
|
|
|
|
const knownUsers = new Set<string>();
|
|
let usersPrimed = false;
|
|
async function pollUsers(client: NextcloudClient): Promise<void> {
|
|
const users = client.listUsers();
|
|
for (const u of users) {
|
|
if (knownUsers.has(u.uid)) continue;
|
|
if (usersPrimed) await emit("user.created", { uid: u.uid, displayName: u.displayName });
|
|
knownUsers.add(u.uid);
|
|
}
|
|
usersPrimed = true;
|
|
}
|
|
|
|
const knownShares = new Set<string>();
|
|
let sharesPrimed = false;
|
|
async function pollShares(client: NextcloudClient): Promise<void> {
|
|
const shares = await client.listShares();
|
|
for (const s of shares) {
|
|
if (knownShares.has(s.id)) continue;
|
|
if (sharesPrimed) await emit("share.created", { id: s.id, path: s.path, shareType: s.shareType, shareWith: s.shareWith, owner: s.owner });
|
|
knownShares.add(s.id);
|
|
}
|
|
sharesPrimed = true;
|
|
}
|
|
|
|
// Keeps Nextcloud's onlyoffice settings matched to the document server the mesh bound (novox/hq
|
|
// issue 1830). The client never logs; this says what it did. A failed connection check is run
|
|
// again on every later tick until it passes (issue 1832): every failing tick is logged, and one
|
|
// line marks it passing again; nothing is logged while the last check passed and nothing changed.
|
|
let onlyOfficeCheckWasFailing = false;
|
|
async function applyOnlyOffice(client: NextcloudClient): Promise<void> {
|
|
const result = client.applyOnlyOffice();
|
|
if (result.changed.length > 0) console.log(`[nextcloud] onlyoffice: set ${result.changed.join(", ")}`);
|
|
if (result.checkOk === false) {
|
|
onlyOfficeCheckWasFailing = true;
|
|
console.error("[nextcloud] onlyoffice: connection check failed" + (result.changed.length > 0 ? " after applying settings" : ""));
|
|
} else if (result.checkOk === true && onlyOfficeCheckWasFailing) {
|
|
onlyOfficeCheckWasFailing = false;
|
|
console.log("[nextcloud] onlyoffice: connection check passes again");
|
|
}
|
|
}
|
|
|
|
if (nextcloud) {
|
|
const client = nextcloud;
|
|
const tick = (fn: (c: NextcloudClient) => Promise<void>): void => {
|
|
const run = (): void => void fn(client).catch((err) => console.error(`[nextcloud] ${err}`));
|
|
setInterval(run, 60_000);
|
|
run();
|
|
};
|
|
tick(pollUsers);
|
|
// A refused login is said once and the shares are left alone for 5 minutes, doubling to an hour,
|
|
// instead of a refusal every minute that Nextcloud's brute-force protection then throttles
|
|
// (novox/hq issue 1253).
|
|
const sharesBackoff = new Backoff(5 * 60_000, 60 * 60_000);
|
|
tick((c) => pollSharesGuarded(c, sharesBackoff, pollShares));
|
|
tick(applyOnlyOffice);
|
|
console.log("[nextcloud] watching users, shares and onlyoffice settings");
|
|
}
|