mirror of
https://github.com/pockethost/pockethost.git
synced 2025-06-07 22:56:39 +00:00
refactor: logging and async services
This commit is contained in:
parent
f5c9f349e3
commit
80f10f528d
27
packages/common/src/Logger.ts
Normal file
27
packages/common/src/Logger.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export type Config = {
|
||||||
|
debug: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Logger = ReturnType<typeof createLogger>
|
||||||
|
|
||||||
|
export const createLogger = (config: Config) => {
|
||||||
|
const { debug } = config
|
||||||
|
const dbg = (...args: any[]) => {
|
||||||
|
if (!debug) return
|
||||||
|
console.log(`[DBG]`, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
const warn = (...args: any[]) => {
|
||||||
|
console.log(`[WARN]`, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = (...args: any[]) => {
|
||||||
|
console.log(`[INFO]`, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = (...args: any[]) => {
|
||||||
|
console.error(`[ERROR]`, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dbg, warn, info, error }
|
||||||
|
}
|
57
packages/common/src/PromiseHelper.ts
Normal file
57
packages/common/src/PromiseHelper.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { ClientResponseError } from 'pocketbase'
|
||||||
|
import { Logger } from './Logger'
|
||||||
|
|
||||||
|
export type PromiseHelperConfig = {
|
||||||
|
logger: Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PromiseHelper = ReturnType<typeof createPromiseHelper>
|
||||||
|
|
||||||
|
export const createPromiseHelper = (config: PromiseHelperConfig) => {
|
||||||
|
const { logger } = config
|
||||||
|
const { dbg, error, warn } = logger
|
||||||
|
|
||||||
|
let inside = ''
|
||||||
|
let c = 0
|
||||||
|
const safeCatch = <TIn extends any[], TOut>(
|
||||||
|
name: string,
|
||||||
|
cb: (...args: TIn) => Promise<TOut>
|
||||||
|
) => {
|
||||||
|
return (...args: TIn) => {
|
||||||
|
const _c = c++
|
||||||
|
const uuid = `${name}:${_c}`
|
||||||
|
const pfx = `[safeCatch:${uuid}]`
|
||||||
|
// dbg(uuid, ...args)
|
||||||
|
const tid = setTimeout(() => {
|
||||||
|
warn(pfx, `timeout waiting for ${pfx}`)
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
inside = pfx
|
||||||
|
return cb(...args)
|
||||||
|
.then((res) => {
|
||||||
|
// dbg(uuid, `finished`)
|
||||||
|
inside = ''
|
||||||
|
clearTimeout(tid)
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
if (e instanceof ClientResponseError) {
|
||||||
|
error(pfx, `PocketBase API error ${e}`)
|
||||||
|
error(pfx, JSON.stringify(e.data, null, 2))
|
||||||
|
if (e.status === 400) {
|
||||||
|
error(
|
||||||
|
pfx,
|
||||||
|
`It looks like you don't have permission to make this request.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error(pfx, `failed: ${e}`)
|
||||||
|
}
|
||||||
|
error(pfx, e)
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { safeCatch }
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
export * from './assert'
|
export * from './assert'
|
||||||
|
export * from './Logger'
|
||||||
|
export * from './pocketbase-client-helpers'
|
||||||
|
export * from './PromiseHelper'
|
||||||
export * from './releases'
|
export * from './releases'
|
||||||
export * from './schema'
|
export * from './schema'
|
||||||
export * from './TimerManager'
|
export * from './TimerManager'
|
||||||
|
110
packages/common/src/pocketbase-client-helpers/RpcHelper.ts
Normal file
110
packages/common/src/pocketbase-client-helpers/RpcHelper.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import type pocketbaseEs from 'pocketbase'
|
||||||
|
import type { RecordSubscription } from 'pocketbase'
|
||||||
|
import type { JsonObject } from 'type-fest'
|
||||||
|
import { Logger } from '../Logger'
|
||||||
|
import { PromiseHelper } from '../PromiseHelper'
|
||||||
|
import { RecordId, RpcCommands, UserId } from '../schema'
|
||||||
|
import type { WatchHelper } from './WatchHelper'
|
||||||
|
|
||||||
|
export type RpcHelperConfig = {
|
||||||
|
client: pocketbaseEs
|
||||||
|
watchHelper: WatchHelper
|
||||||
|
promiseHelper: PromiseHelper
|
||||||
|
logger: Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RpcHelper = ReturnType<typeof createRpcHelper>
|
||||||
|
|
||||||
|
export enum RpcStatus {
|
||||||
|
New = 'new',
|
||||||
|
Queued = 'queued',
|
||||||
|
Running = 'running',
|
||||||
|
Starting = 'starting',
|
||||||
|
FinishedSuccess = 'finished-success',
|
||||||
|
FinishedError = 'finished-error',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RpcPayloadBase = JsonObject
|
||||||
|
|
||||||
|
export type RpcRecord_In<TRecord extends RpcRecord<any, any>> = Pick<
|
||||||
|
TRecord,
|
||||||
|
'userId' | 'payload' | 'cmd'
|
||||||
|
>
|
||||||
|
|
||||||
|
export type RpcRecord<
|
||||||
|
TPayload extends RpcPayloadBase,
|
||||||
|
TRes extends JsonObject
|
||||||
|
> = {
|
||||||
|
id: RecordId
|
||||||
|
userId: UserId
|
||||||
|
cmd: string
|
||||||
|
payload: TPayload
|
||||||
|
status: RpcStatus
|
||||||
|
message: string
|
||||||
|
result: TRes
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RPC_COLLECTION = `rpc`
|
||||||
|
|
||||||
|
export const createRpcHelper = (config: RpcHelperConfig) => {
|
||||||
|
const {
|
||||||
|
client,
|
||||||
|
watchHelper: { watchById },
|
||||||
|
promiseHelper: { safeCatch },
|
||||||
|
} = config
|
||||||
|
|
||||||
|
const mkRpc = <TPayload extends JsonObject, TResult extends JsonObject>(
|
||||||
|
cmd: RpcCommands
|
||||||
|
) => {
|
||||||
|
type ConcreteRpcRecord = RpcRecord<TPayload, TResult>
|
||||||
|
|
||||||
|
return safeCatch(
|
||||||
|
cmd,
|
||||||
|
async (
|
||||||
|
payload: TPayload,
|
||||||
|
cb?: (data: RecordSubscription<ConcreteRpcRecord>) => void
|
||||||
|
) => {
|
||||||
|
const _user = client.authStore.model
|
||||||
|
if (!_user) {
|
||||||
|
throw new Error(`Expected authenticated user here.`)
|
||||||
|
}
|
||||||
|
const { id: userId } = _user
|
||||||
|
const rpcIn: RpcRecord_In<ConcreteRpcRecord> = {
|
||||||
|
cmd,
|
||||||
|
userId,
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
const rec = await client
|
||||||
|
.collection(RPC_COLLECTION)
|
||||||
|
.create<ConcreteRpcRecord>(rpcIn)
|
||||||
|
return new Promise<ConcreteRpcRecord['result']>(
|
||||||
|
async (resolve, reject) => {
|
||||||
|
const unsub = watchById<ConcreteRpcRecord>(
|
||||||
|
RPC_COLLECTION,
|
||||||
|
rec.id,
|
||||||
|
(data) => {
|
||||||
|
if (data.record.status === RpcStatus.FinishedSuccess) {
|
||||||
|
unsub.then((u) => {
|
||||||
|
u()
|
||||||
|
resolve(data.record.result)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data.record.status === RpcStatus.FinishedError) {
|
||||||
|
unsub.then((u) => {
|
||||||
|
reject(data.record.message)
|
||||||
|
u()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb?.(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mkRpc }
|
||||||
|
}
|
72
packages/common/src/pocketbase-client-helpers/WatchHelper.ts
Normal file
72
packages/common/src/pocketbase-client-helpers/WatchHelper.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import type { RecordId } from '@pockethost/common'
|
||||||
|
import type pocketbaseEs from 'pocketbase'
|
||||||
|
import type { RecordSubscription, UnsubscribeFunc } from 'pocketbase'
|
||||||
|
import { Logger } from '../Logger'
|
||||||
|
import { PromiseHelper } from '../PromiseHelper'
|
||||||
|
|
||||||
|
export type WatchHelperConfig = {
|
||||||
|
client: pocketbaseEs
|
||||||
|
promiseHelper: PromiseHelper
|
||||||
|
logger: Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WatchHelper = ReturnType<typeof createWatchHelper>
|
||||||
|
|
||||||
|
export const createWatchHelper = (config: WatchHelperConfig) => {
|
||||||
|
const {
|
||||||
|
client,
|
||||||
|
promiseHelper: { safeCatch },
|
||||||
|
} = config
|
||||||
|
|
||||||
|
const watchById = safeCatch(
|
||||||
|
`subscribe`,
|
||||||
|
async <TRec>(
|
||||||
|
collectionName: string,
|
||||||
|
id: RecordId,
|
||||||
|
cb: (data: RecordSubscription<TRec>) => void,
|
||||||
|
initialFetch = true
|
||||||
|
) => {
|
||||||
|
const unsub = await client
|
||||||
|
.collection(collectionName)
|
||||||
|
.subscribe<TRec>(id, cb)
|
||||||
|
if (initialFetch) {
|
||||||
|
const initial = await client.collection(collectionName).getOne<TRec>(id)
|
||||||
|
if (!initial) {
|
||||||
|
throw new Error(`Expected ${collectionName}.${id} to exist.`)
|
||||||
|
}
|
||||||
|
cb({ action: 'update', record: initial })
|
||||||
|
}
|
||||||
|
return unsub
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const watchAllById = safeCatch(
|
||||||
|
`watchAllById`,
|
||||||
|
async <TRec>(
|
||||||
|
collectionName: string,
|
||||||
|
idName: keyof TRec,
|
||||||
|
idValue: RecordId,
|
||||||
|
cb: (data: RecordSubscription<TRec>) => void,
|
||||||
|
initialFetch = true
|
||||||
|
): Promise<UnsubscribeFunc> => {
|
||||||
|
const unsub = client
|
||||||
|
.collection(collectionName)
|
||||||
|
.subscribe<TRec>('*', (e) => {
|
||||||
|
// console.log(e.record.instanceId, id)
|
||||||
|
if (e.record[idName] !== idValue) return
|
||||||
|
cb(e)
|
||||||
|
})
|
||||||
|
if (initialFetch) {
|
||||||
|
const existing = await client
|
||||||
|
.collection(collectionName)
|
||||||
|
.getFullList<TRec>(100, {
|
||||||
|
filter: `${idName.toString()} = '${idValue}'`,
|
||||||
|
})
|
||||||
|
existing.forEach((record) => cb({ action: 'init', record }))
|
||||||
|
}
|
||||||
|
return unsub
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return { watchById, watchAllById }
|
||||||
|
}
|
2
packages/common/src/pocketbase-client-helpers/index.ts
Normal file
2
packages/common/src/pocketbase-client-helpers/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './RpcHelper'
|
||||||
|
export * from './WatchHelper'
|
@ -7,7 +7,7 @@ import {
|
|||||||
InstanceId,
|
InstanceId,
|
||||||
InstancesRecord,
|
InstancesRecord,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { MixinContext } from './PbClient'
|
import { MixinContext } from './PbClient'
|
||||||
|
|
||||||
export type BackupApi = ReturnType<typeof createBackupMixin>
|
export type BackupApi = ReturnType<typeof createBackupMixin>
|
||||||
|
@ -8,8 +8,8 @@ import {
|
|||||||
import { reduce } from '@s-libs/micro-dash'
|
import { reduce } from '@s-libs/micro-dash'
|
||||||
import Bottleneck from 'bottleneck'
|
import Bottleneck from 'bottleneck'
|
||||||
import { endOfMonth, startOfMonth } from 'date-fns'
|
import { endOfMonth, startOfMonth } from 'date-fns'
|
||||||
import { dbg } from '../util/dbg'
|
import { dbg } from '../util/logger'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { MixinContext } from './PbClient'
|
import { MixinContext } from './PbClient'
|
||||||
|
|
||||||
export type InstanceApi = ReturnType<typeof createInstanceMixin>
|
export type InstanceApi = ReturnType<typeof createInstanceMixin>
|
||||||
|
@ -3,8 +3,8 @@ import {
|
|||||||
InvocationRecord,
|
InvocationRecord,
|
||||||
pocketNow,
|
pocketNow,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { dbg } from '../util/dbg'
|
import { dbg } from '../util/logger'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { InstanceApi } from './InstanceMIxin'
|
import { InstanceApi } from './InstanceMIxin'
|
||||||
import { MixinContext } from './PbClient'
|
import { MixinContext } from './PbClient'
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { JobRecord, JobStatus } from '@pockethost/common'
|
import { JobRecord, JobStatus } from '@pockethost/common'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { MixinContext } from './PbClient'
|
import { MixinContext } from './PbClient'
|
||||||
|
|
||||||
export enum RecordSubscriptionActions {
|
export enum RecordSubscriptionActions {
|
||||||
|
@ -6,7 +6,8 @@ import {
|
|||||||
} from 'pocketbase'
|
} from 'pocketbase'
|
||||||
import { DAEMON_PB_DATA_DIR, PUBLIC_PB_SUBDOMAIN } from '../constants'
|
import { DAEMON_PB_DATA_DIR, PUBLIC_PB_SUBDOMAIN } from '../constants'
|
||||||
import { Collection_Serialized } from '../migrate/schema'
|
import { Collection_Serialized } from '../migrate/schema'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { info } from '../util/logger'
|
||||||
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { createBackupMixin } from './BackupMixin'
|
import { createBackupMixin } from './BackupMixin'
|
||||||
import { createInstanceMixin } from './InstanceMIxin'
|
import { createInstanceMixin } from './InstanceMIxin'
|
||||||
import { createInvocationMixin } from './InvocationMixin'
|
import { createInvocationMixin } from './InvocationMixin'
|
||||||
@ -18,7 +19,7 @@ export type PocketbaseClientApi = ReturnType<typeof createPbClient>
|
|||||||
export type MixinContext = { client: pocketbaseEs; rawDb: Knex }
|
export type MixinContext = { client: pocketbaseEs; rawDb: Knex }
|
||||||
|
|
||||||
export const createPbClient = (url: string) => {
|
export const createPbClient = (url: string) => {
|
||||||
console.log(`Initializing client: ${url}`)
|
info(`Initializing client: ${url}`)
|
||||||
const rawDb = createRawPbClient(
|
const rawDb = createRawPbClient(
|
||||||
`${DAEMON_PB_DATA_DIR}/${PUBLIC_PB_SUBDOMAIN}/pb_data/data.db`
|
`${DAEMON_PB_DATA_DIR}/${PUBLIC_PB_SUBDOMAIN}/pb_data/data.db`
|
||||||
)
|
)
|
||||||
|
@ -7,8 +7,8 @@ import {
|
|||||||
PUBLIC_PB_SUBDOMAIN,
|
PUBLIC_PB_SUBDOMAIN,
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { backupInstance } from '../util/backupInstance'
|
import { backupInstance } from '../util/backupInstance'
|
||||||
import { error } from '../util/dbg'
|
import { dbg, error, info } from '../util/logger'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { pexec } from './pexec'
|
import { pexec } from './pexec'
|
||||||
import { schema } from './schema'
|
import { schema } from './schema'
|
||||||
import { withInstance } from './withInstance'
|
import { withInstance } from './withInstance'
|
||||||
@ -20,11 +20,11 @@ safeCatch(`root`, async () => {
|
|||||||
PUBLIC_PB_SUBDOMAIN,
|
PUBLIC_PB_SUBDOMAIN,
|
||||||
`${+new Date()}`,
|
`${+new Date()}`,
|
||||||
async (progress) => {
|
async (progress) => {
|
||||||
console.log(progress)
|
dbg(progress)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(`Upgrading`)
|
info(`Upgrading`)
|
||||||
await pexec(`${PB_BIN} upgrade --dir=pb_data`)
|
await pexec(`${PB_BIN} upgrade --dir=pb_data`)
|
||||||
|
|
||||||
await withInstance(async (client) => {
|
await withInstance(async (client) => {
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { dbg, error } from '../util/logger'
|
||||||
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
|
|
||||||
export const pexec = safeCatch(`pexec`, (cmd: string) => {
|
export const pexec = safeCatch(`pexec`, (cmd: string) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
console.log(cmd)
|
dbg(cmd)
|
||||||
exec(cmd, (err, stdout, stderr) => {
|
exec(cmd, (err, stdout, stderr) => {
|
||||||
console.log(stdout)
|
dbg(stdout)
|
||||||
console.error(stderr)
|
|
||||||
if (err) {
|
if (err) {
|
||||||
|
error(`${err}`)
|
||||||
|
error(stderr)
|
||||||
reject(err)
|
reject(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import {
|
|||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { createPbClient, PocketbaseClientApi } from '../db/PbClient'
|
import { createPbClient, PocketbaseClientApi } from '../db/PbClient'
|
||||||
import { mkInternalUrl } from '../util/internal'
|
import { mkInternalUrl } from '../util/internal'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { error, info } from '../util/logger'
|
||||||
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { spawnInstance } from '../util/spawnInstance'
|
import { spawnInstance } from '../util/spawnInstance'
|
||||||
import { tryFetch } from '../util/tryFetch'
|
import { tryFetch } from '../util/tryFetch'
|
||||||
|
|
||||||
@ -31,18 +32,16 @@ export const withInstance = safeCatch(
|
|||||||
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
|
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
|
||||||
await cb(client)
|
await cb(client)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
error(
|
||||||
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
|
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
|
||||||
)
|
)
|
||||||
console.error(
|
error(`***WARNING*** LOG IN MANUALLY, ADJUST .env, AND RESTART DOCKER`)
|
||||||
`***WARNING*** LOG IN MANUALLY, ADJUST .env, AND RESTART DOCKER`
|
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
console.log(`Exiting process`)
|
info(`Exiting process`)
|
||||||
mainProcess.kill()
|
mainProcess.kill()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`${e}`)
|
error(`${e}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -13,6 +13,7 @@ import { createInstanceService } from './services/InstanceService'
|
|||||||
import { createJobService } from './services/JobService'
|
import { createJobService } from './services/JobService'
|
||||||
import { createProxyService } from './services/ProxyService'
|
import { createProxyService } from './services/ProxyService'
|
||||||
import { mkInternalUrl } from './util/internal'
|
import { mkInternalUrl } from './util/internal'
|
||||||
|
import { dbg, error, info } from './util/logger'
|
||||||
import { spawnInstance } from './util/spawnInstance'
|
import { spawnInstance } from './util/spawnInstance'
|
||||||
// npm install eventsource --save
|
// npm install eventsource --save
|
||||||
global.EventSource = require('eventsource')
|
global.EventSource = require('eventsource')
|
||||||
@ -36,14 +37,12 @@ global.EventSource = require('eventsource')
|
|||||||
const instanceService = await createInstanceService(client)
|
const instanceService = await createInstanceService(client)
|
||||||
try {
|
try {
|
||||||
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
|
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
|
||||||
console.log(`Logged in`)
|
dbg(`Logged in`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
error(
|
||||||
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
|
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
|
||||||
)
|
)
|
||||||
console.error(
|
error(`***WARNING*** LOG IN MANUALLY, ADJUST .env, AND RESTART DOCKER`)
|
||||||
`***WARNING*** LOG IN MANUALLY, ADJUST .env, AND RESTART DOCKER`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyService = await createProxyService(instanceService)
|
const proxyService = await createProxyService(instanceService)
|
||||||
@ -51,7 +50,7 @@ global.EventSource = require('eventsource')
|
|||||||
const backupService = await createBackupService(client, jobService)
|
const backupService = await createBackupService(client, jobService)
|
||||||
|
|
||||||
process.once('SIGUSR2', async () => {
|
process.once('SIGUSR2', async () => {
|
||||||
console.log(`SIGUSR2 detected`)
|
info(`SIGUSR2 detected`)
|
||||||
proxyService.shutdown()
|
proxyService.shutdown()
|
||||||
instanceService.shutdown()
|
instanceService.shutdown()
|
||||||
jobService.shutdown()
|
jobService.shutdown()
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
import Bottleneck from 'bottleneck'
|
import Bottleneck from 'bottleneck'
|
||||||
import { PocketbaseClientApi } from '../db/PbClient'
|
import { PocketbaseClientApi } from '../db/PbClient'
|
||||||
import { backupInstance } from '../util/backupInstance'
|
import { backupInstance } from '../util/backupInstance'
|
||||||
import { dbg } from '../util/dbg'
|
import { dbg } from '../util/logger'
|
||||||
import { JobServiceApi } from './JobService'
|
import { JobServiceApi } from './JobService'
|
||||||
|
|
||||||
export const createBackupService = async (
|
export const createBackupService = async (
|
||||||
@ -66,7 +66,7 @@ export const createBackupService = async (
|
|||||||
tm.repeat(async () => {
|
tm.repeat(async () => {
|
||||||
const backupRec = await client.getNextBackupJob()
|
const backupRec = await client.getNextBackupJob()
|
||||||
if (!backupRec) {
|
if (!backupRec) {
|
||||||
dbg(`No backups requested`)
|
// dbg(`No backups requested`)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const instance = await client.getInstance(backupRec.instanceId)
|
const instance = await client.getInstance(backupRec.instanceId)
|
||||||
|
@ -16,10 +16,10 @@ import {
|
|||||||
PUBLIC_APP_PROTOCOL,
|
PUBLIC_APP_PROTOCOL,
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { PocketbaseClientApi } from '../db/PbClient'
|
import { PocketbaseClientApi } from '../db/PbClient'
|
||||||
import { dbg } from '../util/dbg'
|
|
||||||
import { mkInternalUrl } from '../util/internal'
|
import { mkInternalUrl } from '../util/internal'
|
||||||
|
import { dbg, error, warn } from '../util/logger'
|
||||||
import { now } from '../util/now'
|
import { now } from '../util/now'
|
||||||
import { safeCatch } from '../util/safeAsync'
|
import { safeCatch } from '../util/promiseHelper'
|
||||||
import { PocketbaseProcess, spawnInstance } from '../util/spawnInstance'
|
import { PocketbaseProcess, spawnInstance } from '../util/spawnInstance'
|
||||||
|
|
||||||
type InstanceApi = {
|
type InstanceApi = {
|
||||||
@ -68,7 +68,7 @@ export const createInstanceService = async (client: PocketbaseClientApi) => {
|
|||||||
port: DAEMON_PB_PORT_BASE,
|
port: DAEMON_PB_PORT_BASE,
|
||||||
exclude,
|
exclude,
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
console.error(`Failed to get port for ${subdomain}`)
|
error(`Failed to get port for ${subdomain}`)
|
||||||
throw e
|
throw e
|
||||||
})
|
})
|
||||||
dbg(`Found port for ${subdomain}: ${newPort}`)
|
dbg(`Found port for ${subdomain}: ${newPort}`)
|
||||||
@ -81,7 +81,7 @@ export const createInstanceService = async (client: PocketbaseClientApi) => {
|
|||||||
port: newPort,
|
port: newPort,
|
||||||
bin: binFor(instance.platform, instance.version),
|
bin: binFor(instance.platform, instance.version),
|
||||||
onUnexpectedStop: (code) => {
|
onUnexpectedStop: (code) => {
|
||||||
console.warn(`${subdomain} exited unexpectedly with ${code}`)
|
warn(`${subdomain} exited unexpectedly with ${code}`)
|
||||||
api.shutdown()
|
api.shutdown()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ import { default as knexFactory } from 'knex'
|
|||||||
import pocketbaseEs from 'pocketbase'
|
import pocketbaseEs from 'pocketbase'
|
||||||
import { AsyncReturnType } from 'type-fest'
|
import { AsyncReturnType } from 'type-fest'
|
||||||
import { PocketbaseClientApi } from '../db/PbClient'
|
import { PocketbaseClientApi } from '../db/PbClient'
|
||||||
import { error } from '../util/dbg'
|
import { dbg, error } from '../util/logger'
|
||||||
|
|
||||||
export type JobServiceApi = AsyncReturnType<typeof createJobService>
|
export type JobServiceApi = AsyncReturnType<typeof createJobService>
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export const createJobService = async (client: PocketbaseClientApi) => {
|
|||||||
if (!handler) {
|
if (!handler) {
|
||||||
throw new Error(`Job handler ${cmd} is not registered`)
|
throw new Error(`Job handler ${cmd} is not registered`)
|
||||||
}
|
}
|
||||||
console.log(`Running job ${job.id}`, job)
|
dbg(`Running job ${job.id}`, job)
|
||||||
await client.setJobStatus(job, JobStatus.Running)
|
await client.setJobStatus(job, JobStatus.Running)
|
||||||
await handler(job)
|
await handler(job)
|
||||||
await client.setJobStatus(job, JobStatus.FinishedSuccess)
|
await client.setJobStatus(job, JobStatus.FinishedSuccess)
|
||||||
|
@ -7,8 +7,8 @@ import {
|
|||||||
PUBLIC_APP_PROTOCOL,
|
PUBLIC_APP_PROTOCOL,
|
||||||
PUBLIC_PB_SUBDOMAIN,
|
PUBLIC_PB_SUBDOMAIN,
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { dbg, info } from '../util/dbg'
|
|
||||||
import { mkInternalUrl } from '../util/internal'
|
import { mkInternalUrl } from '../util/internal'
|
||||||
|
import { dbg, error, info } from '../util/logger'
|
||||||
import { InstanceServiceApi } from './InstanceService'
|
import { InstanceServiceApi } from './InstanceService'
|
||||||
|
|
||||||
export type ProxyServiceApi = AsyncReturnType<typeof createProxyService>
|
export type ProxyServiceApi = AsyncReturnType<typeof createProxyService>
|
||||||
@ -22,7 +22,7 @@ export const createProxyService = async (
|
|||||||
dbg(`Incoming request ${req.headers.host}/${req.url}`)
|
dbg(`Incoming request ${req.headers.host}/${req.url}`)
|
||||||
|
|
||||||
const die = (msg: string) => {
|
const die = (msg: string) => {
|
||||||
console.error(`ERROR: ${msg}`)
|
error(msg)
|
||||||
res.writeHead(403, {
|
res.writeHead(403, {
|
||||||
'Content-Type': `text/plain`,
|
'Content-Type': `text/plain`,
|
||||||
})
|
})
|
||||||
|
@ -6,9 +6,9 @@ import { Database } from 'sqlite3'
|
|||||||
import tmp from 'tmp'
|
import tmp from 'tmp'
|
||||||
import { DAEMON_PB_DATA_DIR } from '../constants'
|
import { DAEMON_PB_DATA_DIR } from '../constants'
|
||||||
import { pexec } from '../migrate/pexec'
|
import { pexec } from '../migrate/pexec'
|
||||||
import { dbg, error } from './dbg'
|
|
||||||
import { ensureDirExists } from './ensureDirExists'
|
import { ensureDirExists } from './ensureDirExists'
|
||||||
import { safeCatch } from './safeAsync'
|
import { dbg, error } from './logger'
|
||||||
|
import { safeCatch } from './promiseHelper'
|
||||||
|
|
||||||
export type BackupProgress = {
|
export type BackupProgress = {
|
||||||
current: number
|
current: number
|
||||||
@ -75,7 +75,7 @@ export const backupInstance = safeCatch(
|
|||||||
unsafeCleanup: true,
|
unsafeCleanup: true,
|
||||||
})
|
})
|
||||||
const backupTmpTargetRoot = resolve(tmpObj.name)
|
const backupTmpTargetRoot = resolve(tmpObj.name)
|
||||||
console.log({
|
dbg({
|
||||||
instanceId,
|
instanceId,
|
||||||
dataRoot,
|
dataRoot,
|
||||||
backupTgzRoot,
|
backupTgzRoot,
|
||||||
@ -109,7 +109,7 @@ export const backupInstance = safeCatch(
|
|||||||
error(`${e}`)
|
error(`${e}`)
|
||||||
throw e
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
console.log(`Removing again ${backupTmpTargetRoot}`)
|
dbg(`Removing again ${backupTmpTargetRoot}`)
|
||||||
tmpObj.removeCallback()
|
tmpObj.removeCallback()
|
||||||
chdir(_cwd)
|
chdir(_cwd)
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import { DEBUG } from '../constants'
|
|
||||||
|
|
||||||
export const dbg = (...args: any[]) => {
|
|
||||||
if (!DEBUG) return
|
|
||||||
console.log(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const info = (...args: any[]) => {
|
|
||||||
console.log(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const error = (...args: any[]) => {
|
|
||||||
console.error(...args)
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { mkdirSync } from 'fs'
|
import { mkdirSync } from 'fs'
|
||||||
import { dbg } from './dbg'
|
import { dbg } from './logger'
|
||||||
|
|
||||||
export const ensureDirExists = (path: string) => {
|
export const ensureDirExists = (path: string) => {
|
||||||
try {
|
try {
|
||||||
|
5
packages/daemon/src/util/logger.ts
Normal file
5
packages/daemon/src/util/logger.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createLogger } from '@pockethost/common'
|
||||||
|
import { DEBUG } from '../constants'
|
||||||
|
|
||||||
|
export const logger = createLogger({ debug: DEBUG })
|
||||||
|
export const { dbg, info, warn, error } = logger
|
5
packages/daemon/src/util/promiseHelper.ts
Normal file
5
packages/daemon/src/util/promiseHelper.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createPromiseHelper } from '@pockethost/common'
|
||||||
|
import { logger } from './logger'
|
||||||
|
|
||||||
|
export const promiseHelper = createPromiseHelper({ logger })
|
||||||
|
export const { safeCatch } = promiseHelper
|
@ -1,66 +0,0 @@
|
|||||||
import Bottleneck from 'bottleneck'
|
|
||||||
import { ClientResponseError } from 'pocketbase'
|
|
||||||
import { dbg, error } from './dbg'
|
|
||||||
|
|
||||||
const limiter = new Bottleneck({ maxConcurrent: 1 })
|
|
||||||
|
|
||||||
let inside = ''
|
|
||||||
export const serialAsync = <TIn extends any[], TOut>(
|
|
||||||
name: string,
|
|
||||||
cb: (...args: TIn) => Promise<TOut>
|
|
||||||
) => {
|
|
||||||
const _cb = safeCatch(name, cb)
|
|
||||||
|
|
||||||
return (...args: TIn) => {
|
|
||||||
return limiter.schedule(() => {
|
|
||||||
if (inside) {
|
|
||||||
throw new Error(
|
|
||||||
`Already in async function ${inside}, can't execute ${name}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return _cb(...args).finally(() => {
|
|
||||||
inside = ''
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let c = 0
|
|
||||||
export const safeCatch = <TIn extends any[], TOut>(
|
|
||||||
name: string,
|
|
||||||
cb: (...args: TIn) => Promise<TOut>
|
|
||||||
) => {
|
|
||||||
return (...args: TIn) => {
|
|
||||||
const _c = c++
|
|
||||||
const uuid = `${name}:${_c}`
|
|
||||||
dbg(uuid, ...args)
|
|
||||||
const tid = setTimeout(() => {
|
|
||||||
dbg(uuid, `WARNING: timeout waiting for ${uuid}`)
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
inside = uuid
|
|
||||||
return cb(...args)
|
|
||||||
.then((res) => {
|
|
||||||
dbg(uuid, `finished`)
|
|
||||||
inside = ''
|
|
||||||
clearTimeout(tid)
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
.catch((e: any) => {
|
|
||||||
if (e instanceof ClientResponseError) {
|
|
||||||
error(uuid, `PocketBase API error ${e}`)
|
|
||||||
error(uuid, JSON.stringify(e.data, null, 2))
|
|
||||||
if (e.status === 400) {
|
|
||||||
error(
|
|
||||||
uuid,
|
|
||||||
`It looks like you don't have permission to make this request.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error(uuid, `failed: ${e}`)
|
|
||||||
}
|
|
||||||
error(uuid, e)
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,9 @@ import { spawn } from 'child_process'
|
|||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { AsyncReturnType } from 'type-fest'
|
import { AsyncReturnType } from 'type-fest'
|
||||||
import { DAEMON_PB_BIN_DIR, DAEMON_PB_DATA_DIR } from '../constants'
|
import { DAEMON_PB_BIN_DIR, DAEMON_PB_DATA_DIR } from '../constants'
|
||||||
import { dbg } from './dbg'
|
|
||||||
import { mkInternalAddress, mkInternalUrl } from './internal'
|
import { mkInternalAddress, mkInternalUrl } from './internal'
|
||||||
import { safeCatch } from './safeAsync'
|
import { dbg, error } from './logger'
|
||||||
|
import { safeCatch } from './promiseHelper'
|
||||||
import { tryFetch } from './tryFetch'
|
import { tryFetch } from './tryFetch'
|
||||||
export type PocketbaseProcess = AsyncReturnType<typeof spawnInstance>
|
export type PocketbaseProcess = AsyncReturnType<typeof spawnInstance>
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ export const spawnInstance = safeCatch(`spawnInstance`, async (cfg: Config) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ls.stderr.on('data', (data) => {
|
ls.stderr.on('data', (data) => {
|
||||||
console.error(`${subdomain} stderr: ${data}`)
|
error(`${subdomain} stderr: ${data}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
ls.on('close', (code) => {
|
ls.on('close', (code) => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { dbg, error } from './dbg'
|
import { dbg, error } from './logger'
|
||||||
import { safeCatch } from './safeAsync'
|
import { safeCatch } from './promiseHelper'
|
||||||
|
|
||||||
export const tryFetch = safeCatch(
|
export const tryFetch = safeCatch(
|
||||||
`tryFetch`,
|
`tryFetch`,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import CopyButton from '$components/CopyButton.svelte'
|
||||||
|
import { dbg } from '$util/logger'
|
||||||
import { Highlight } from 'svelte-highlight'
|
import { Highlight } from 'svelte-highlight'
|
||||||
import { typescript } from 'svelte-highlight/languages'
|
import { typescript } from 'svelte-highlight/languages'
|
||||||
import 'svelte-highlight/styles/github.css'
|
import 'svelte-highlight/styles/github.css'
|
||||||
import CopyButton from '$components/CopyButton.svelte'
|
|
||||||
|
|
||||||
export let code: string
|
export let code: string
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
console.log('copied')
|
dbg('copied')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { client } from '$src/pocketbase'
|
import { client } from '$src/pocketbase'
|
||||||
|
import { warn } from '$util/logger'
|
||||||
import publicRoutes from '$util/public-routes.json'
|
import publicRoutes from '$util/public-routes.json'
|
||||||
import { getRouter } from '$util/utilities'
|
import { getRouter } from '$util/utilities'
|
||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
@ -11,7 +12,7 @@
|
|||||||
|
|
||||||
const { pathname } = router
|
const { pathname } = router
|
||||||
if (!publicRoutes.includes(pathname)) {
|
if (!publicRoutes.includes(pathname)) {
|
||||||
console.warn(`${pathname} is a private route`)
|
warn(`${pathname} is a private route`)
|
||||||
window.location.href = '/'
|
window.location.href = '/'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { createGenericSyncEvent } from '$util/events'
|
import { createGenericSyncEvent } from '$util/events'
|
||||||
import {
|
import {
|
||||||
assertExists,
|
assertExists,
|
||||||
|
createWatchHelper,
|
||||||
JobCommands,
|
JobCommands,
|
||||||
JobStatus,
|
JobStatus,
|
||||||
type BackupRecord,
|
type BackupRecord,
|
||||||
@ -13,6 +14,8 @@ import {
|
|||||||
type InstancesRecord_New,
|
type InstancesRecord_New,
|
||||||
type JobRecord,
|
type JobRecord,
|
||||||
type JobRecord_In,
|
type JobRecord_In,
|
||||||
|
type Logger,
|
||||||
|
type PromiseHelper,
|
||||||
type UserRecord
|
type UserRecord
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { keys, map } from '@s-libs/micro-dash'
|
import { keys, map } from '@s-libs/micro-dash'
|
||||||
@ -24,7 +27,6 @@ import PocketBase, {
|
|||||||
type RecordSubscription,
|
type RecordSubscription,
|
||||||
type UnsubscribeFunc
|
type UnsubscribeFunc
|
||||||
} from 'pocketbase'
|
} from 'pocketbase'
|
||||||
import { safeCatch } from '../util/safeCatch'
|
|
||||||
|
|
||||||
export type AuthChangeHandler = (user: BaseAuthStore) => void
|
export type AuthChangeHandler = (user: BaseAuthStore) => void
|
||||||
|
|
||||||
@ -35,9 +37,18 @@ export type AuthStoreProps = {
|
|||||||
isValid: boolean
|
isValid: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PocketbaseClientConfig = {
|
||||||
|
url: string
|
||||||
|
logger: Logger
|
||||||
|
promiseHelper: PromiseHelper
|
||||||
|
}
|
||||||
export type PocketbaseClient = ReturnType<typeof createPocketbaseClient>
|
export type PocketbaseClient = ReturnType<typeof createPocketbaseClient>
|
||||||
|
|
||||||
export const createPocketbaseClient = (url: string) => {
|
export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||||
|
const { url, logger, promiseHelper } = config
|
||||||
|
const { dbg, error } = logger
|
||||||
|
const { safeCatch } = promiseHelper
|
||||||
|
|
||||||
const client = new PocketBase(url)
|
const client = new PocketBase(url)
|
||||||
|
|
||||||
const { authStore } = client
|
const { authStore } = client
|
||||||
@ -57,7 +68,7 @@ export const createPocketbaseClient = (url: string) => {
|
|||||||
passwordConfirm: password
|
passwordConfirm: password
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// console.log(`Sending verification email to ${email}`)
|
// dbg(`Sending verification email to ${email}`)
|
||||||
return client.collection('users').requestVerification(email)
|
return client.collection('users').requestVerification(email)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -99,6 +110,9 @@ export const createPocketbaseClient = (url: string) => {
|
|||||||
client.collection('users').authRefresh()
|
client.collection('users').authRefresh()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const watchHelper = createWatchHelper({ client, promiseHelper, logger })
|
||||||
|
const { watchById, watchAllById } = watchHelper
|
||||||
|
|
||||||
const createInstance = safeCatch(
|
const createInstance = safeCatch(
|
||||||
`createInstance`,
|
`createInstance`,
|
||||||
(payload: InstancesRecord_New): Promise<InstancesRecord> => {
|
(payload: InstancesRecord_New): Promise<InstancesRecord> => {
|
||||||
@ -115,41 +129,15 @@ export const createPocketbaseClient = (url: string) => {
|
|||||||
const watchInstanceById = async (
|
const watchInstanceById = async (
|
||||||
id: InstanceId,
|
id: InstanceId,
|
||||||
cb: (data: RecordSubscription<InstancesRecord>) => void
|
cb: (data: RecordSubscription<InstancesRecord>) => void
|
||||||
): Promise<UnsubscribeFunc> => {
|
): Promise<UnsubscribeFunc> => watchById('instances', id, cb)
|
||||||
getInstanceById(id).then((record) => {
|
|
||||||
// console.log(`Got instnace`, record)
|
|
||||||
assertExists(record, `Expected instance ${id} here`)
|
|
||||||
cb({ action: 'init', record })
|
|
||||||
})
|
|
||||||
return client.collection('instances').subscribe<InstancesRecord>(id, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
const watchBackupsByInstanceId = async (
|
const watchBackupsByInstanceId = async (
|
||||||
id: InstanceId,
|
id: InstanceId,
|
||||||
cb: (data: RecordSubscription<BackupRecord>) => void
|
cb: (data: RecordSubscription<BackupRecord>) => void
|
||||||
): Promise<UnsubscribeFunc> => {
|
): Promise<UnsubscribeFunc> => watchAllById('backups', 'instanceId', id, cb)
|
||||||
const unsub = client.collection('backups').subscribe<BackupRecord>('*', (e) => {
|
|
||||||
// console.log(e.record.instanceId, id)
|
|
||||||
if (e.record.instanceId !== id) return
|
|
||||||
cb(e)
|
|
||||||
})
|
|
||||||
const existingBackups = await client
|
|
||||||
.collection('backups')
|
|
||||||
.getFullList<BackupRecord>(100, { filter: `instanceId = '${id}'` })
|
|
||||||
existingBackups.forEach((record) => cb({ action: 'init', record }))
|
|
||||||
return unsub
|
|
||||||
}
|
|
||||||
|
|
||||||
const getAllInstancesById = safeCatch(`getAllInstancesById`, async () =>
|
const getAllInstancesById = safeCatch(`getAllInstancesById`, async () =>
|
||||||
(
|
(await client.collection('instances').getFullList()).reduce((c, v) => {
|
||||||
await client
|
|
||||||
.collection('instances')
|
|
||||||
.getFullList()
|
|
||||||
.catch((e) => {
|
|
||||||
// console.error(`getAllInstancesById failed with ${e}`)
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
).reduce((c, v) => {
|
|
||||||
c[v.id] = v
|
c[v.id] = v
|
||||||
return c
|
return c
|
||||||
}, {} as Record)
|
}, {} as Record)
|
||||||
@ -169,7 +157,7 @@ export const createPocketbaseClient = (url: string) => {
|
|||||||
|
|
||||||
const getAuthStoreProps = (): AuthStoreProps => {
|
const getAuthStoreProps = (): AuthStoreProps => {
|
||||||
const { token, model, isValid } = client.authStore as AuthStoreProps
|
const { token, model, isValid } = client.authStore as AuthStoreProps
|
||||||
// console.log(`current authStore`, { token, model, isValid })
|
// dbg(`current authStore`, { token, model, isValid })
|
||||||
if (model instanceof Admin) throw new Error(`Admin models not supported`)
|
if (model instanceof Admin) throw new Error(`Admin models not supported`)
|
||||||
if (model && !model.email) throw new Error(`Expected model to be a user here`)
|
if (model && !model.email) throw new Error(`Expected model to be a user here`)
|
||||||
return {
|
return {
|
||||||
@ -202,7 +190,7 @@ export const createPocketbaseClient = (url: string) => {
|
|||||||
*/
|
*/
|
||||||
refreshAuthToken()
|
refreshAuthToken()
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
// console.error(`Clearing auth store: ${e}`)
|
dbg(`Clearing auth store: ${e}`)
|
||||||
client.authStore.clear()
|
client.authStore.clear()
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@ -218,7 +206,7 @@ export const createPocketbaseClient = (url: string) => {
|
|||||||
* watch on the user record and update auth accordingly.
|
* watch on the user record and update auth accordingly.
|
||||||
*/
|
*/
|
||||||
const unsub = onAuthChange((authStore) => {
|
const unsub = onAuthChange((authStore) => {
|
||||||
// console.log(`onAuthChange`, { ...authStore })
|
// dbg(`onAuthChange`, { ...authStore })
|
||||||
const { model } = authStore
|
const { model } = authStore
|
||||||
if (!model) return
|
if (!model) return
|
||||||
if (model instanceof Admin) return
|
if (model instanceof Admin) return
|
||||||
@ -230,9 +218,9 @@ export const createPocketbaseClient = (url: string) => {
|
|||||||
setTimeout(_check, 1000)
|
setTimeout(_check, 1000)
|
||||||
|
|
||||||
// FIXME - THIS DOES NOT WORK, WE HAVE TO POLL INSTEAD. FIX IN V0.8
|
// FIXME - THIS DOES NOT WORK, WE HAVE TO POLL INSTEAD. FIX IN V0.8
|
||||||
// console.log(`watching _users`)
|
// dbg(`watching _users`)
|
||||||
// unsub = subscribe<User>(`users/${model.id}`, (user) => {
|
// unsub = subscribe<User>(`users/${model.id}`, (user) => {
|
||||||
// console.log(`realtime _users change`, { ...user })
|
// dbg(`realtime _users change`, { ...user })
|
||||||
// fireAuthChange({ ...authStore, model: user })
|
// fireAuthChange({ ...authStore, model: user })
|
||||||
// })
|
// })
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { browser } from '$app/environment'
|
import { browser, dev } from '$app/environment'
|
||||||
import { PUBLIC_PB_DOMAIN, PUBLIC_PB_SUBDOMAIN } from '$src/env'
|
import { PUBLIC_PB_DOMAIN, PUBLIC_PB_SUBDOMAIN } from '$src/env'
|
||||||
|
import { createLogger, createPromiseHelper } from '@pockethost/common'
|
||||||
import { createPocketbaseClient, type PocketbaseClient } from './PocketbaseClient'
|
import { createPocketbaseClient, type PocketbaseClient } from './PocketbaseClient'
|
||||||
|
|
||||||
export const client = (() => {
|
export const client = (() => {
|
||||||
@ -7,9 +8,11 @@ export const client = (() => {
|
|||||||
return () => {
|
return () => {
|
||||||
if (!browser) throw new Error(`PocketBase client not supported in SSR`)
|
if (!browser) throw new Error(`PocketBase client not supported in SSR`)
|
||||||
if (clientInstance) return clientInstance
|
if (clientInstance) return clientInstance
|
||||||
console.log(`Initializing pocketbase client`)
|
const logger = createLogger({ debug: dev })
|
||||||
|
logger.info(`Initializing pocketbase client`)
|
||||||
const url = `https://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}`
|
const url = `https://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}`
|
||||||
clientInstance = createPocketbaseClient(url)
|
const promiseHelper = createPromiseHelper({ logger })
|
||||||
|
clientInstance = createPocketbaseClient({ url, logger, promiseHelper })
|
||||||
return clientInstance
|
return clientInstance
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
import AuthStateGuard from '$components/helpers/AuthStateGuard.svelte'
|
import AuthStateGuard from '$components/helpers/AuthStateGuard.svelte'
|
||||||
import { client } from '$src/pocketbase'
|
import { client } from '$src/pocketbase'
|
||||||
import { createCleanupManagerSync } from '$util/CleanupManager'
|
import { createCleanupManagerSync } from '$util/CleanupManager'
|
||||||
import { assertExists } from '@pockethost/common/src/assert'
|
import { dbg } from '$util/logger'
|
||||||
|
import { assertExists } from '@pockethost/common'
|
||||||
import { onDestroy, onMount } from 'svelte'
|
import { onDestroy, onMount } from 'svelte'
|
||||||
import { instance } from './store'
|
import { instance } from './store'
|
||||||
|
|
||||||
@ -14,7 +15,7 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const { watchInstanceById } = client()
|
const { watchInstanceById } = client()
|
||||||
watchInstanceById(instanceId, (r) => {
|
watchInstanceById(instanceId, (r) => {
|
||||||
console.log(`Handling instance update`, r)
|
dbg(`Handling instance update`, r)
|
||||||
const { action, record } = r
|
const { action, record } = r
|
||||||
assertExists(record, `Expected instance here`)
|
assertExists(record, `Expected instance here`)
|
||||||
instance.set(record)
|
instance.set(record)
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const { watchBackupsByInstanceId } = client()
|
const { watchBackupsByInstanceId } = client()
|
||||||
watchBackupsByInstanceId(instance.id, (r) => {
|
watchBackupsByInstanceId(instance.id, (r) => {
|
||||||
// console.log(`Handling backup update`, r)
|
// dbg(`Handling backup update`, r)
|
||||||
const { action, record } = r
|
const { action, record } = r
|
||||||
const _backups = reduce(
|
const _backups = reduce(
|
||||||
$backups,
|
$backups,
|
||||||
@ -43,7 +43,7 @@
|
|||||||
return Date.parse(e.created)
|
return Date.parse(e.created)
|
||||||
}).reverse()
|
}).reverse()
|
||||||
)
|
)
|
||||||
// console.log(record.id)
|
// dbg(record.id)
|
||||||
}).then(cm.add)
|
}).then(cm.add)
|
||||||
})
|
})
|
||||||
onDestroy(cm.cleanupAll)
|
onDestroy(cm.cleanupAll)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
import { PUBLIC_PB_DOMAIN } from '$src/env'
|
import { PUBLIC_PB_DOMAIN } from '$src/env'
|
||||||
import { client } from '$src/pocketbase'
|
import { client } from '$src/pocketbase'
|
||||||
import { createCleanupManagerSync } from '$util/CleanupManager'
|
import { createCleanupManagerSync } from '$util/CleanupManager'
|
||||||
|
import { error } from '$util/logger'
|
||||||
import { humanVersion, type InstanceRecordById, type InstancesRecord } from '@pockethost/common'
|
import { humanVersion, type InstanceRecordById, type InstancesRecord } from '@pockethost/common'
|
||||||
import { forEach, values } from '@s-libs/micro-dash'
|
import { forEach, values } from '@s-libs/micro-dash'
|
||||||
import { onDestroy, onMount } from 'svelte'
|
import { onDestroy, onMount } from 'svelte'
|
||||||
@ -51,7 +52,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(`Failed to fetch instances`)
|
error(`Failed to fetch instances`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { client } from '$src/pocketbase'
|
import { client } from '$src/pocketbase'
|
||||||
import { InstanceStatus, LATEST_PLATFORM, USE_LATEST_VERSION } from '@pockethost/common'
|
import { InstanceStatus, LATEST_PLATFORM, USE_LATEST_VERSION } from '@pockethost/common'
|
||||||
|
import { dbg, error, warn } from './logger'
|
||||||
|
|
||||||
export type FormErrorHandler = (value: string) => void
|
export type FormErrorHandler = (value: string) => void
|
||||||
|
|
||||||
export const handleFormError = (error: any, setError?: FormErrorHandler) => {
|
export const handleFormError = (error: any, setError?: FormErrorHandler) => {
|
||||||
const { parseError } = client()
|
const { parseError } = client()
|
||||||
console.error(`Form error: ${error}`, { error })
|
error(`Form error: ${error}`, { error })
|
||||||
|
|
||||||
if (setError) {
|
if (setError) {
|
||||||
const message = parseError(error)[0]
|
const message = parseError(error)[0]
|
||||||
@ -177,10 +178,10 @@ export const handleInstanceGeneratorWidget = async (
|
|||||||
// populated the form with their existing login. Try using it.
|
// populated the form with their existing login. Try using it.
|
||||||
await handleLogin(email, password, undefined, false)
|
await handleLogin(email, password, undefined, false)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`Account ${email} already exists. Logged in.`)
|
dbg(`Account ${email} already exists. Logged in.`)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn(`Login failed, attempting account creation.`)
|
warn(`Login failed, attempting account creation.`)
|
||||||
// This means login has failed.
|
// This means login has failed.
|
||||||
// Either their credentials were incorrect, or the account
|
// Either their credentials were incorrect, or the account
|
||||||
// did not exist, or there is a system issue.
|
// did not exist, or there is a system issue.
|
||||||
@ -188,15 +189,15 @@ export const handleInstanceGeneratorWidget = async (
|
|||||||
// is already in use.
|
// is already in use.
|
||||||
return handleRegistration(email, password)
|
return handleRegistration(email, password)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`Account created, proceeding to log in.`)
|
dbg(`Account created, proceeding to log in.`)
|
||||||
// This means registration succeeded. That's good.
|
// This means registration succeeded. That's good.
|
||||||
// Log in using the new credentials
|
// Log in using the new credentials
|
||||||
return handleLogin(email, password, undefined, false)
|
return handleLogin(email, password, undefined, false)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`Logged in after account creation`)
|
dbg(`Logged in after account creation`)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(`Panic, auth system down`)
|
error(`Panic, auth system down`)
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
// If registration succeeds, login should always succeed.
|
// If registration succeeds, login should always succeed.
|
||||||
// If a login fails at this point, the system is broken.
|
// If a login fails at this point, the system is broken.
|
||||||
@ -206,7 +207,7 @@ export const handleInstanceGeneratorWidget = async (
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn(`User input error`)
|
warn(`User input error`)
|
||||||
// This is just for clarity
|
// This is just for clarity
|
||||||
// If registration fails at this point, it means both
|
// If registration fails at this point, it means both
|
||||||
// login and account creation failed.
|
// login and account creation failed.
|
||||||
@ -218,16 +219,16 @@ export const handleInstanceGeneratorWidget = async (
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`User before instance creation is `, user())
|
dbg(`User before instance creation is `, user())
|
||||||
// We can only get here if we are successfully logged in using the credentials
|
// We can only get here if we are successfully logged in using the credentials
|
||||||
// provided by the user.
|
// provided by the user.
|
||||||
// Instance creation could still fail if the name is taken
|
// Instance creation could still fail if the name is taken
|
||||||
await handleCreateNewInstance(instanceName)
|
await handleCreateNewInstance(instanceName)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`Creation of ${instanceName} succeeded`)
|
dbg(`Creation of ${instanceName} succeeded`)
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.warn(`Creation of ${instanceName} failed`)
|
warn(`Creation of ${instanceName} failed`)
|
||||||
// The instance creation could most likely fail if the name is taken.
|
// The instance creation could most likely fail if the name is taken.
|
||||||
// In any case, bail out to show errors.
|
// In any case, bail out to show errors.
|
||||||
if (e.data?.data?.subdomain?.code === 'validation_not_unique') {
|
if (e.data?.data?.subdomain?.code === 'validation_not_unique') {
|
||||||
@ -240,7 +241,7 @@ export const handleInstanceGeneratorWidget = async (
|
|||||||
throw new Error(`Instance creation: ${messages[0]}`)
|
throw new Error(`Instance creation: ${messages[0]}`)
|
||||||
})
|
})
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Caught widget error`, { error })
|
error(`Caught widget error`, { error })
|
||||||
handleFormError(error, setError)
|
handleFormError(error, setError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
packages/pockethost.io/src/util/logger.ts
Normal file
5
packages/pockethost.io/src/util/logger.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createLogger } from '@pockethost/common'
|
||||||
|
import { PUBLIC_DEBUG } from '../env'
|
||||||
|
|
||||||
|
export const logger = createLogger({ debug: PUBLIC_DEBUG })
|
||||||
|
export const { dbg, info, warn, error } = logger
|
@ -1,16 +0,0 @@
|
|||||||
import { dev } from '$app/environment'
|
|
||||||
|
|
||||||
export const safeCatch = <TIn extends any[], TOut>(
|
|
||||||
name: string,
|
|
||||||
cb: (...args: TIn) => Promise<TOut>
|
|
||||||
) => {
|
|
||||||
return (...args: TIn) => {
|
|
||||||
if (dev) {
|
|
||||||
console.log(`${name}`)
|
|
||||||
}
|
|
||||||
return cb(...args).catch((e: any) => {
|
|
||||||
console.error(`${name} failed: ${e}`)
|
|
||||||
throw e
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ import { browser } from '$app/environment'
|
|||||||
import { client } from '$src/pocketbase'
|
import { client } from '$src/pocketbase'
|
||||||
import type { AuthStoreProps } from '$src/pocketbase/PocketbaseClient'
|
import type { AuthStoreProps } from '$src/pocketbase/PocketbaseClient'
|
||||||
import { writable } from 'svelte/store'
|
import { writable } from 'svelte/store'
|
||||||
|
import { dbg } from './logger'
|
||||||
|
|
||||||
export const authStoreState = writable<AuthStoreProps>({ isValid: false, model: null, token: '' })
|
export const authStoreState = writable<AuthStoreProps>({ isValid: false, model: null, token: '' })
|
||||||
export const isUserLoggedIn = writable(false)
|
export const isUserLoggedIn = writable(false)
|
||||||
@ -15,14 +16,14 @@ if (browser) {
|
|||||||
* Listen for auth change events. When we get at least one, the auth state is initialized.
|
* Listen for auth change events. When we get at least one, the auth state is initialized.
|
||||||
*/
|
*/
|
||||||
onAuthChange((authStoreProps) => {
|
onAuthChange((authStoreProps) => {
|
||||||
console.log(`onAuthChange in store`, { ...authStoreProps })
|
dbg(`onAuthChange in store`, { ...authStoreProps })
|
||||||
authStoreState.set(authStoreProps)
|
authStoreState.set(authStoreProps)
|
||||||
isAuthStateInitialized.set(true)
|
isAuthStateInitialized.set(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update derived stores when authStore changes
|
// Update derived stores when authStore changes
|
||||||
authStoreState.subscribe((authStoreProps) => {
|
authStoreState.subscribe((authStoreProps) => {
|
||||||
console.log(`subscriber change`, authStoreProps)
|
dbg(`subscriber change`, authStoreProps)
|
||||||
isUserLoggedIn.set(authStoreProps.isValid)
|
isUserLoggedIn.set(authStoreProps.isValid)
|
||||||
isUserVerified.set(!!authStoreProps.model?.verified)
|
isUserVerified.set(!!authStoreProps.model?.verified)
|
||||||
})
|
})
|
||||||
|
@ -139,7 +139,7 @@ open https://pockethost.io
|
|||||||
- [x] fix: incorrect instance information displaying on dashboard details
|
- [x] fix: incorrect instance information displaying on dashboard details
|
||||||
- [x] fix: more helpful error message when backup fails for nonexistent instance
|
- [x] fix: more helpful error message when backup fails for nonexistent instance
|
||||||
- [x] chore: move version number to base package.json
|
- [x] chore: move version number to base package.json
|
||||||
- [ ] chore: refactor logging and async helpers
|
- [x] refctor: logging and async helpers
|
||||||
- [x] chore: restore autocancellation
|
- [x] chore: restore autocancellation
|
||||||
- [x] chore: rebuild with go 1.19.3 and include in bin name
|
- [x] chore: rebuild with go 1.19.3 and include in bin name
|
||||||
- [ ] fix: Disallow backups if data dir doesn't exist
|
- [ ] fix: Disallow backups if data dir doesn't exist
|
||||||
|
Loading…
x
Reference in New Issue
Block a user