mirror of https://github.com/requarks/wiki
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
70 lines
3.4 KiB
70 lines
3.4 KiB
/**
|
|
* Sync every storage target that has a remote to keep in step with, and whose site is due.
|
|
*
|
|
* The git target's schedule, and what makes it a synchronized store rather than a local repository
|
|
* that happens to get committed to. A change is committed the moment it is made — that happens on the
|
|
* request, not here — and this is what pushes those commits and brings back what other people pushed.
|
|
*
|
|
* **This runs every minute; the site's `syncInterval` decides what actually happens.** The interval is
|
|
* a per-site setting, so one schedule cannot express it — the tick is therefore as fine as the
|
|
* shortest interval anybody can ask for, and each site is skipped on the ticks that are not its own.
|
|
*
|
|
* Due-ness is read off the clock rather than off a record of when each target last synced. A site with
|
|
* a five-minute interval syncs on every fifth minute of the epoch, which needs nothing stored, means
|
|
* two instances agree without coordinating, survives a restart, and cannot drift. What it gives up is
|
|
* catching up on a missed tick: an instance that was down at the moment simply waits for the next one,
|
|
* which for something that runs all day is the right trade.
|
|
*
|
|
* Every target is attempted whatever the ones before it did: a site whose credentials have expired
|
|
* must not stop the rest of them syncing. `executeAction` records the outcome on the target itself,
|
|
* so a failure shows up on its Status card in the admin area rather than only in this log.
|
|
*
|
|
* **One instance at a time.** The scheduler hands a job to a single instance, which is the one whose
|
|
* working copy is synced. Every instance in a high-availability set keeps its own, so they each fall
|
|
* in step at their own turn rather than fighting over one repository.
|
|
*/
|
|
export async function task(): Promise<void> {
|
|
const syncable = await WIKI.models.storage.syncableTargets()
|
|
if (syncable.length < 1) {
|
|
return
|
|
}
|
|
|
|
const minute = Math.floor(Temporal.Now.instant().epochMilliseconds / 60_000)
|
|
const targets = syncable.filter((target) => {
|
|
const interval = WIKI.models.storage.syncIntervalFor(target.siteId)
|
|
// -> An interval nothing can be made of is a site that is never synced on a schedule, rather than
|
|
// one synced every minute. `validateSiteConfig` refuses to store such a value in the first
|
|
// place, so this is the belt to that braces.
|
|
return interval > 0 && minute % interval === 0
|
|
})
|
|
if (targets.length < 1) {
|
|
return
|
|
}
|
|
|
|
// -> A pull creates content, and content records who authored it. There is nobody behind a
|
|
// scheduled run, so it is recorded against the wiki's own administrator.
|
|
const actorId = await WIKI.models.users.getSystemActorId()
|
|
if (!actorId) {
|
|
WIKI.logger.warn(
|
|
'Syncing storage targets: no active administrator to attribute incoming content to [ SKIPPED ]'
|
|
)
|
|
return
|
|
}
|
|
|
|
WIKI.logger.info(`Syncing ${targets.length} storage target(s)...`)
|
|
let failed = 0
|
|
for (const target of targets) {
|
|
try {
|
|
const message = await WIKI.models.storage.executeAction(target, 'sync', actorId)
|
|
WIKI.logger.info(`Synced ${target.title} for site ${target.siteId}: ${message ?? 'done'}`)
|
|
} catch (err: any) {
|
|
failed++
|
|
WIKI.logger.warn(`Could not sync ${target.title} for site ${target.siteId} [ FAILED ]`)
|
|
WIKI.logger.warn(err.message)
|
|
}
|
|
}
|
|
WIKI.logger.info(
|
|
`Syncing storage targets: ${targets.length - failed} of ${targets.length} succeeded [ COMPLETED ]`
|
|
)
|
|
}
|