mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
chore: log context update
This commit is contained in:
parent
ac5948be40
commit
56aff5db0a
@ -21,6 +21,7 @@ export const createWatchHelper = (config: WatchHelperConfig) => {
|
|||||||
|
|
||||||
const watchById = safeCatch(
|
const watchById = safeCatch(
|
||||||
`watchById`,
|
`watchById`,
|
||||||
|
logger(),
|
||||||
async <TRec>(
|
async <TRec>(
|
||||||
collectionName: string,
|
collectionName: string,
|
||||||
id: RecordId,
|
id: RecordId,
|
||||||
@ -93,6 +94,7 @@ export const createWatchHelper = (config: WatchHelperConfig) => {
|
|||||||
|
|
||||||
const watchAllById = safeCatch(
|
const watchAllById = safeCatch(
|
||||||
`watchAllById`,
|
`watchAllById`,
|
||||||
|
logger(),
|
||||||
async <TRec extends BaseFields>(
|
async <TRec extends BaseFields>(
|
||||||
collectionName: string,
|
collectionName: string,
|
||||||
idName: keyof TRec,
|
idName: keyof TRec,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { ClientResponseError } from 'pocketbase'
|
import { ClientResponseError } from 'pocketbase'
|
||||||
import { logger } from './Logger'
|
import { Logger } from './Logger'
|
||||||
|
|
||||||
let c = 0
|
let c = 0
|
||||||
export const safeCatch = <TIn extends any[], TOut>(
|
export const safeCatch = <TIn extends any[], TOut>(
|
||||||
name: string,
|
name: string,
|
||||||
|
logger: Logger,
|
||||||
cb: (...args: TIn) => Promise<TOut>,
|
cb: (...args: TIn) => Promise<TOut>,
|
||||||
timeoutMs = 5000
|
timeoutMs = 5000
|
||||||
) => {
|
) => {
|
||||||
@ -11,7 +12,7 @@ export const safeCatch = <TIn extends any[], TOut>(
|
|||||||
const _c = c++
|
const _c = c++
|
||||||
const uuid = `${name}:${_c}`
|
const uuid = `${name}:${_c}`
|
||||||
const pfx = `safeCatch:${uuid}`
|
const pfx = `safeCatch:${uuid}`
|
||||||
const { raw, error, warn, dbg } = logger().create(pfx)
|
const { raw, error, warn, dbg } = logger.create(pfx)
|
||||||
raw(`args`, args)
|
raw(`args`, args)
|
||||||
const tid = setTimeout(() => {
|
const tid = setTimeout(() => {
|
||||||
error(`timeout ${timeoutMs}ms waiting for ${pfx}`)
|
error(`timeout ${timeoutMs}ms waiting for ${pfx}`)
|
||||||
|
@ -16,17 +16,18 @@ import {
|
|||||||
rpcService,
|
rpcService,
|
||||||
sqliteService,
|
sqliteService,
|
||||||
} from '$services'
|
} from '$services'
|
||||||
import { logger } from '@pockethost/common'
|
import { logger as loggerService } from '@pockethost/common'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
import { centralDbService } from './services/CentralDbService'
|
import { centralDbService } from './services/CentralDbService'
|
||||||
import { instanceLoggerService } from './services/InstanceLoggerService'
|
import { instanceLoggerService } from './services/InstanceLoggerService'
|
||||||
|
|
||||||
logger({ debug: DEBUG, trace: TRACE, errorTrace: !DEBUG })
|
loggerService({ debug: DEBUG, trace: TRACE, errorTrace: !DEBUG })
|
||||||
|
|
||||||
// npm install eventsource --save
|
// npm install eventsource --save
|
||||||
global.EventSource = require('eventsource')
|
global.EventSource = require('eventsource')
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const { dbg, error, info, warn } = logger().create(`server.ts`)
|
const logger = loggerService().create(`server.ts`)
|
||||||
|
const { dbg, error, info, warn } = logger
|
||||||
info(`Starting`)
|
info(`Starting`)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,6 +46,7 @@ global.EventSource = require('eventsource')
|
|||||||
const pbService = await pocketbase({
|
const pbService = await pocketbase({
|
||||||
cachePath: PH_BIN_CACHE,
|
cachePath: PH_BIN_CACHE,
|
||||||
checkIntervalMs: 1000 * 5 * 60,
|
checkIntervalMs: 1000 * 5 * 60,
|
||||||
|
logger,
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,18 +75,18 @@ global.EventSource = require('eventsource')
|
|||||||
/**
|
/**
|
||||||
* Launch services
|
* Launch services
|
||||||
*/
|
*/
|
||||||
await clientService(url)
|
await clientService({ url, logger })
|
||||||
ftpService({})
|
ftpService({ logger })
|
||||||
await rpcService({})
|
await rpcService({ logger })
|
||||||
await proxyService({
|
await proxyService({
|
||||||
|
logger,
|
||||||
coreInternalUrl: url,
|
coreInternalUrl: url,
|
||||||
})
|
})
|
||||||
await instanceLoggerService({})
|
await instanceLoggerService({ logger })
|
||||||
await sqliteService({})
|
await sqliteService({ logger })
|
||||||
await realtimeLog({})
|
await realtimeLog({ logger })
|
||||||
await instanceService({})
|
await instanceService({ logger })
|
||||||
await centralDbService({})
|
await centralDbService({ logger })
|
||||||
await backupService({})
|
|
||||||
|
|
||||||
info(`Hooking into process exit event`)
|
info(`Hooking into process exit event`)
|
||||||
|
|
||||||
|
@ -1,28 +1,34 @@
|
|||||||
import { PUBLIC_APP_DB } from '$constants'
|
import { PUBLIC_APP_DB } from '$constants'
|
||||||
import { logger, mkSingleton } from '@pockethost/common'
|
import { mkSingleton, SingletonBaseConfig } from '@pockethost/common'
|
||||||
import { proxyService } from './ProxyService'
|
import { proxyService } from './ProxyService'
|
||||||
|
|
||||||
export const centralDbService = mkSingleton(async () => {
|
export type CentralDbServiceConfig = SingletonBaseConfig
|
||||||
const { dbg } = logger().create(`centralDbService`)
|
|
||||||
|
|
||||||
;(await proxyService()).use(
|
export const centralDbService = mkSingleton(
|
||||||
PUBLIC_APP_DB,
|
async (config: CentralDbServiceConfig) => {
|
||||||
['/api(/*)', '/_(/*)', '/'],
|
const { logger } = config
|
||||||
(req, res, meta) => {
|
const { dbg } = logger.create(`centralDbService`)
|
||||||
const { subdomain, coreInternalUrl, proxy } = meta
|
|
||||||
|
|
||||||
if (subdomain !== PUBLIC_APP_DB) return
|
;(await proxyService()).use(
|
||||||
|
PUBLIC_APP_DB,
|
||||||
|
['/api(/*)', '/_(/*)', '/'],
|
||||||
|
(req, res, meta, logger) => {
|
||||||
|
const { dbg } = logger
|
||||||
|
const { subdomain, coreInternalUrl, proxy } = meta
|
||||||
|
|
||||||
const target = coreInternalUrl
|
if (subdomain !== PUBLIC_APP_DB) return
|
||||||
dbg(
|
|
||||||
`Forwarding proxy request for ${req.url} to central instance ${target}`
|
|
||||||
)
|
|
||||||
proxy.web(req, res, { target })
|
|
||||||
},
|
|
||||||
`CentralDbService`
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
const target = coreInternalUrl
|
||||||
shutdown() {},
|
dbg(
|
||||||
|
`Forwarding proxy request for ${req.url} to central instance ${target}`
|
||||||
|
)
|
||||||
|
proxy.web(req, res, { target })
|
||||||
|
},
|
||||||
|
`CentralDbService`
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
shutdown() {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
@ -7,12 +7,12 @@ import {
|
|||||||
SSL_KEY,
|
SSL_KEY,
|
||||||
} from '$constants'
|
} from '$constants'
|
||||||
import { clientService, createPbClient } from '$services'
|
import { clientService, createPbClient } from '$services'
|
||||||
import { logger, mkSingleton } from '@pockethost/common'
|
import { mkSingleton, SingletonBaseConfig } from '@pockethost/common'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { FtpSrv } from 'ftp-srv'
|
import { FtpSrv } from 'ftp-srv'
|
||||||
import { PhFs } from './PhFs'
|
import { PhFs } from './PhFs'
|
||||||
|
|
||||||
export type FtpConfig = {}
|
export type FtpConfig = SingletonBaseConfig & {}
|
||||||
|
|
||||||
export enum FolderNames {
|
export enum FolderNames {
|
||||||
PbData = 'pb_data',
|
PbData = 'pb_data',
|
||||||
@ -49,13 +49,14 @@ const tls = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ftpService = mkSingleton((config: FtpConfig) => {
|
export const ftpService = mkSingleton((config: FtpConfig) => {
|
||||||
const log = logger().create('FtpService')
|
const { logger } = config
|
||||||
const { dbg, info } = log
|
const _ftpServiceLogger = logger.create('FtpService')
|
||||||
|
const { dbg, info } = _ftpServiceLogger
|
||||||
|
|
||||||
const ftpServer = new FtpSrv({
|
const ftpServer = new FtpSrv({
|
||||||
url: 'ftp://0.0.0.0:' + PH_FTP_PORT,
|
url: 'ftp://0.0.0.0:' + PH_FTP_PORT,
|
||||||
anonymous: false,
|
anonymous: false,
|
||||||
log: log.create(`ftpServer`, { errorTrace: false }),
|
log: _ftpServiceLogger.create(`ftpServer`, { errorTrace: false }),
|
||||||
tls,
|
tls,
|
||||||
pasv_url: PH_FTP_PASV_IP,
|
pasv_url: PH_FTP_PASV_IP,
|
||||||
pasv_max: PH_FTP_PASV_PORT_MAX,
|
pasv_max: PH_FTP_PASV_PORT_MAX,
|
||||||
@ -66,13 +67,13 @@ export const ftpService = mkSingleton((config: FtpConfig) => {
|
|||||||
'login',
|
'login',
|
||||||
async ({ connection, username, password }, resolve, reject) => {
|
async ({ connection, username, password }, resolve, reject) => {
|
||||||
const url = (await clientService()).client.url
|
const url = (await clientService()).client.url
|
||||||
const client = createPbClient(url)
|
const client = createPbClient(url, _ftpServiceLogger)
|
||||||
try {
|
try {
|
||||||
await client.client
|
await client.client
|
||||||
.collection('users')
|
.collection('users')
|
||||||
.authWithPassword(username, password)
|
.authWithPassword(username, password)
|
||||||
dbg(`Logged in`)
|
dbg(`Logged in`)
|
||||||
const fs = new PhFs(connection, client)
|
const fs = new PhFs(connection, client, _ftpServiceLogger)
|
||||||
resolve({ fs })
|
resolve({ fs })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(new Error(`Invalid username or password`))
|
reject(new Error(`Invalid username or password`))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DAEMON_PB_DATA_DIR } from '$constants'
|
import { DAEMON_PB_DATA_DIR } from '$constants'
|
||||||
import { Logger, logger } from '@pockethost/common'
|
import { Logger } from '@pockethost/common'
|
||||||
import { existsSync, mkdirSync } from 'fs'
|
import { existsSync, mkdirSync } from 'fs'
|
||||||
import { FileStat, FileSystem, FtpConnection } from 'ftp-srv'
|
import { FileStat, FileSystem, FtpConnection } from 'ftp-srv'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
@ -16,10 +16,14 @@ export class PhFs extends FileSystem {
|
|||||||
private log: Logger
|
private log: Logger
|
||||||
private client: PocketbaseClientApi
|
private client: PocketbaseClientApi
|
||||||
|
|
||||||
constructor(connection: FtpConnection, client: PocketbaseClientApi) {
|
constructor(
|
||||||
|
connection: FtpConnection,
|
||||||
|
client: PocketbaseClientApi,
|
||||||
|
logger: Logger
|
||||||
|
) {
|
||||||
super(connection, { root: '/', cwd: '/' })
|
super(connection, { root: '/', cwd: '/' })
|
||||||
this.client = client
|
this.client = client
|
||||||
this.log = logger().create(`PhFs`)
|
this.log = logger.create(`PhFs`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async chdir(path?: string | undefined): Promise<string> {
|
async chdir(path?: string | undefined): Promise<string> {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import { Logger } from '@pockethost/common'
|
||||||
|
|
||||||
|
export type DaemonContext = {
|
||||||
|
parentLogger: Logger
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
import { SqliteChangeEvent, sqliteService } from '$services'
|
||||||
|
import {
|
||||||
|
InstanceLogFields,
|
||||||
|
InstanceLogFields_Create,
|
||||||
|
newId,
|
||||||
|
pocketNow,
|
||||||
|
RecordId,
|
||||||
|
safeCatch,
|
||||||
|
StreamNames,
|
||||||
|
} from '@pockethost/common'
|
||||||
|
import knex from 'knex'
|
||||||
|
import { AsyncReturnType } from 'type-fest'
|
||||||
|
import { DaemonContext } from './DaemonContext'
|
||||||
|
|
||||||
|
export type SqliteLogger = AsyncReturnType<typeof createSqliteLogger>
|
||||||
|
export const createSqliteLogger = async (
|
||||||
|
logDbPath: string,
|
||||||
|
context: DaemonContext
|
||||||
|
) => {
|
||||||
|
const { parentLogger } = context
|
||||||
|
const _dbLogger = parentLogger.create(`${logDbPath}`)
|
||||||
|
const { dbg } = _dbLogger
|
||||||
|
|
||||||
|
const { getDatabase } = sqliteService()
|
||||||
|
const db = await getDatabase(logDbPath)
|
||||||
|
|
||||||
|
const conn = knex({
|
||||||
|
client: 'sqlite3',
|
||||||
|
connection: {
|
||||||
|
filename: logDbPath,
|
||||||
|
},
|
||||||
|
useNullAsDefault: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const write = safeCatch(
|
||||||
|
`write`,
|
||||||
|
_dbLogger,
|
||||||
|
async (message: string, stream: StreamNames = StreamNames.Info) => {
|
||||||
|
const _in: InstanceLogFields_Create = {
|
||||||
|
id: newId(),
|
||||||
|
message,
|
||||||
|
stream,
|
||||||
|
created: pocketNow(),
|
||||||
|
updated: pocketNow(),
|
||||||
|
}
|
||||||
|
const sql = conn('logs').insert(_in).toString()
|
||||||
|
dbg(`Writing log ${JSON.stringify(_in)} ${sql}`)
|
||||||
|
await db.exec(sql)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const subscribe = (cb: (e: SqliteChangeEvent<InstanceLogFields>) => void) => {
|
||||||
|
let _seenIds: { [_: RecordId]: boolean } | undefined = {}
|
||||||
|
|
||||||
|
const unsub = db.subscribe<InstanceLogFields>((e) => {
|
||||||
|
// dbg(`Caught db modification ${logDbPath}`, e)
|
||||||
|
const { table, record } = e
|
||||||
|
if (table !== 'logs') return
|
||||||
|
if (_seenIds) {
|
||||||
|
_seenIds[record.id] = true
|
||||||
|
}
|
||||||
|
cb(e)
|
||||||
|
})
|
||||||
|
return unsub
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetch = async (limit: number = 100) => {
|
||||||
|
return db.all<InstanceLogFields[]>(
|
||||||
|
`select * from logs order by created desc limit ${limit}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { write, subscribe, fetch }
|
||||||
|
}
|
@ -1,85 +1,30 @@
|
|||||||
import { DAEMON_PB_DATA_DIR } from '$constants'
|
import { DAEMON_PB_DATA_DIR } from '$constants'
|
||||||
import { SqliteChangeEvent, sqliteService } from '$services'
|
import { sqliteService } from '$services'
|
||||||
import {
|
import {
|
||||||
InstanceId,
|
InstanceId,
|
||||||
InstanceLogFields,
|
|
||||||
InstanceLogFields_Create,
|
|
||||||
logger,
|
|
||||||
mkSingleton,
|
mkSingleton,
|
||||||
newId,
|
SingletonBaseConfig,
|
||||||
pocketNow,
|
|
||||||
RecordId,
|
|
||||||
safeCatch,
|
|
||||||
StreamNames,
|
StreamNames,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { mkdirSync } from 'fs'
|
import { mkdirSync } from 'fs'
|
||||||
import knex from 'knex'
|
|
||||||
import { dirname, join } from 'path'
|
import { dirname, join } from 'path'
|
||||||
import { AsyncReturnType } from 'type-fest'
|
import { DaemonContext } from './DaemonContext'
|
||||||
|
import { createSqliteLogger, SqliteLogger } from './SqliteLogger'
|
||||||
export type InstanceLogger = AsyncReturnType<typeof mkApi>
|
|
||||||
const mkApi = async (logDbPath: string) => {
|
|
||||||
const { dbg } = logger().create(`InstanceLogger ${logDbPath}`)
|
|
||||||
|
|
||||||
const { getDatabase } = sqliteService()
|
|
||||||
const db = await getDatabase(logDbPath)
|
|
||||||
|
|
||||||
const conn = knex({
|
|
||||||
client: 'sqlite3',
|
|
||||||
connection: {
|
|
||||||
filename: logDbPath,
|
|
||||||
},
|
|
||||||
useNullAsDefault: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const write = safeCatch(
|
|
||||||
`workerLogger:write`,
|
|
||||||
async (message: string, stream: StreamNames = StreamNames.Info) => {
|
|
||||||
const _in: InstanceLogFields_Create = {
|
|
||||||
id: newId(),
|
|
||||||
message,
|
|
||||||
stream,
|
|
||||||
created: pocketNow(),
|
|
||||||
updated: pocketNow(),
|
|
||||||
}
|
|
||||||
const sql = conn('logs').insert(_in).toString()
|
|
||||||
dbg(`Writing log ${JSON.stringify(_in)} ${sql}`)
|
|
||||||
await db.exec(sql)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const subscribe = (cb: (e: SqliteChangeEvent<InstanceLogFields>) => void) => {
|
|
||||||
let _seenIds: { [_: RecordId]: boolean } | undefined = {}
|
|
||||||
|
|
||||||
const unsub = db.subscribe<InstanceLogFields>((e) => {
|
|
||||||
// dbg(`Caught db modification ${logDbPath}`, e)
|
|
||||||
const { table, record } = e
|
|
||||||
if (table !== 'logs') return
|
|
||||||
if (_seenIds) {
|
|
||||||
_seenIds[record.id] = true
|
|
||||||
}
|
|
||||||
cb(e)
|
|
||||||
})
|
|
||||||
return unsub
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetch = async (limit: number = 100) => {
|
|
||||||
return db.all<InstanceLogFields[]>(
|
|
||||||
`select * from logs order by created desc limit ${limit}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { write, subscribe, fetch }
|
|
||||||
}
|
|
||||||
|
|
||||||
const instances: {
|
const instances: {
|
||||||
[instanceId: InstanceId]: Promise<InstanceLogger>
|
[instanceId: InstanceId]: Promise<SqliteLogger>
|
||||||
} = {}
|
} = {}
|
||||||
|
|
||||||
export const createInstanceLogger = (instanceId: InstanceId) => {
|
export const createInstanceLogger = (
|
||||||
|
instanceId: InstanceId,
|
||||||
|
context: DaemonContext
|
||||||
|
) => {
|
||||||
|
const { parentLogger } = context
|
||||||
|
|
||||||
if (!instances[instanceId]) {
|
if (!instances[instanceId]) {
|
||||||
instances[instanceId] = new Promise<InstanceLogger>(async (resolve) => {
|
instances[instanceId] = new Promise<SqliteLogger>(async (resolve) => {
|
||||||
const { dbg } = logger().create(`WorkerLogger:${instanceId}`)
|
const _instanceLogger = parentLogger.create(`InstanceLogger`)
|
||||||
|
const { dbg } = _instanceLogger
|
||||||
|
|
||||||
const logDbPath = join(
|
const logDbPath = join(
|
||||||
DAEMON_PB_DATA_DIR,
|
DAEMON_PB_DATA_DIR,
|
||||||
@ -98,7 +43,9 @@ export const createInstanceLogger = (instanceId: InstanceId) => {
|
|||||||
migrationsPath: join(__dirname, 'migrations'),
|
migrationsPath: join(__dirname, 'migrations'),
|
||||||
})
|
})
|
||||||
|
|
||||||
const api = await mkApi(logDbPath)
|
const api = await createSqliteLogger(logDbPath, {
|
||||||
|
parentLogger: _instanceLogger,
|
||||||
|
})
|
||||||
await api.write(`Ran migrations`, StreamNames.System)
|
await api.write(`Ran migrations`, StreamNames.System)
|
||||||
resolve(api)
|
resolve(api)
|
||||||
})
|
})
|
||||||
@ -107,13 +54,18 @@ export const createInstanceLogger = (instanceId: InstanceId) => {
|
|||||||
return instances[instanceId]!
|
return instances[instanceId]!
|
||||||
}
|
}
|
||||||
|
|
||||||
export const instanceLoggerService = mkSingleton(() => {
|
export type InstanceLoggerServiceConfig = SingletonBaseConfig
|
||||||
const { dbg } = logger().create(`InstanceLoggerService`)
|
|
||||||
dbg(`Starting up`)
|
export const instanceLoggerService = mkSingleton(
|
||||||
return {
|
(config: InstanceLoggerServiceConfig) => {
|
||||||
get: createInstanceLogger,
|
const { logger } = config
|
||||||
shutdown() {
|
const { dbg } = logger.create(`InstanceLoggerService`)
|
||||||
dbg(`Shutting down`)
|
dbg(`Starting up`)
|
||||||
},
|
return {
|
||||||
|
get: createInstanceLogger,
|
||||||
|
shutdown() {
|
||||||
|
dbg(`Shutting down`)
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DAEMON_PB_DATA_DIR, DENO_PATH } from '$constants'
|
import { DAEMON_PB_DATA_DIR, DENO_PATH } from '$constants'
|
||||||
import { InstanceFields, logger, StreamNames } from '@pockethost/common'
|
import { InstanceFields, Logger, StreamNames } from '@pockethost/common'
|
||||||
import { keys } from '@s-libs/micro-dash'
|
import { keys } from '@s-libs/micro-dash'
|
||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
@ -11,12 +11,15 @@ export type DenoProcessConfig = {
|
|||||||
path: string
|
path: string
|
||||||
port: number
|
port: number
|
||||||
instance: InstanceFields
|
instance: InstanceFields
|
||||||
|
logger: Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DenoApi = AsyncReturnType<typeof createDenoProcess>
|
export type DenoApi = AsyncReturnType<typeof createDenoProcess>
|
||||||
|
|
||||||
export const createDenoProcess = async (config: DenoProcessConfig) => {
|
export const createDenoProcess = async (config: DenoProcessConfig) => {
|
||||||
const { dbg, error } = logger().create(`DenoProcess.ts`)
|
const { logger } = config
|
||||||
|
const _denoLogger = logger.create(`DenoProcess.ts`)
|
||||||
|
const { dbg, error } = _denoLogger
|
||||||
|
|
||||||
const { instance, port, path } = config
|
const { instance, port, path } = config
|
||||||
const internalUrl = mkInternalUrl(port)
|
const internalUrl = mkInternalUrl(port)
|
||||||
@ -34,7 +37,9 @@ export const createDenoProcess = async (config: DenoProcessConfig) => {
|
|||||||
path,
|
path,
|
||||||
]
|
]
|
||||||
|
|
||||||
const denoLogger = await instanceLoggerService().get(instance.id)
|
const denoLogger = await instanceLoggerService().get(instance.id, {
|
||||||
|
parentLogger: _denoLogger,
|
||||||
|
})
|
||||||
|
|
||||||
const denoWrite = (
|
const denoWrite = (
|
||||||
message: string,
|
message: string,
|
||||||
|
@ -16,13 +16,13 @@ import {
|
|||||||
createTimerManager,
|
createTimerManager,
|
||||||
InstanceId,
|
InstanceId,
|
||||||
InstanceStatus,
|
InstanceStatus,
|
||||||
logger,
|
|
||||||
mkSingleton,
|
mkSingleton,
|
||||||
RpcCommands,
|
RpcCommands,
|
||||||
safeCatch,
|
safeCatch,
|
||||||
SaveSecretsPayload,
|
SaveSecretsPayload,
|
||||||
SaveSecretsPayloadSchema,
|
SaveSecretsPayloadSchema,
|
||||||
SaveSecretsResult,
|
SaveSecretsResult,
|
||||||
|
SingletonBaseConfig,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { forEachRight, map } from '@s-libs/micro-dash'
|
import { forEachRight, map } from '@s-libs/micro-dash'
|
||||||
import Bottleneck from 'bottleneck'
|
import Bottleneck from 'bottleneck'
|
||||||
@ -42,12 +42,14 @@ type InstanceApi = {
|
|||||||
startRequest: () => () => void
|
startRequest: () => () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InstanceServiceConfig = {}
|
export type InstanceServiceConfig = SingletonBaseConfig & {}
|
||||||
|
|
||||||
export type InstanceServiceApi = AsyncReturnType<typeof instanceService>
|
export type InstanceServiceApi = AsyncReturnType<typeof instanceService>
|
||||||
export const instanceService = mkSingleton(
|
export const instanceService = mkSingleton(
|
||||||
async (config: InstanceServiceConfig) => {
|
async (config: InstanceServiceConfig) => {
|
||||||
const { dbg, raw, error, warn } = logger().create('InstanceService')
|
const { logger } = config
|
||||||
|
const _instanceLogger = logger.create('InstanceService')
|
||||||
|
const { dbg, raw, error, warn } = _instanceLogger
|
||||||
const { client } = await clientService()
|
const { client } = await clientService()
|
||||||
|
|
||||||
const { registerCommand } = await rpcService()
|
const { registerCommand } = await rpcService()
|
||||||
@ -91,9 +93,8 @@ export const instanceService = mkSingleton(
|
|||||||
const getInstance = (subdomain: string) =>
|
const getInstance = (subdomain: string) =>
|
||||||
instanceLimiter
|
instanceLimiter
|
||||||
.schedule(async () => {
|
.schedule(async () => {
|
||||||
const { dbg, warn, error } = logger().create(
|
const _subdomainLogger = _instanceLogger.create(subdomain)
|
||||||
`InstanceService ${subdomain}`
|
const { dbg, warn, error } = _subdomainLogger
|
||||||
)
|
|
||||||
dbg(`Getting instance`)
|
dbg(`Getting instance`)
|
||||||
{
|
{
|
||||||
const instance = instances[subdomain]
|
const instance = instances[subdomain]
|
||||||
@ -112,6 +113,7 @@ export const instanceService = mkSingleton(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
dbg(`Instance found`)
|
dbg(`Instance found`)
|
||||||
|
_subdomainLogger.breadcrumb(instance.id)
|
||||||
|
|
||||||
dbg(`Checking for verified account`)
|
dbg(`Checking for verified account`)
|
||||||
if (!owner?.verified) {
|
if (!owner?.verified) {
|
||||||
@ -132,9 +134,13 @@ export const instanceService = mkSingleton(
|
|||||||
error(`Failed to get port for ${subdomain}`)
|
error(`Failed to get port for ${subdomain}`)
|
||||||
throw e
|
throw e
|
||||||
})
|
})
|
||||||
|
_subdomainLogger.breadcrumb(newPort.toString())
|
||||||
dbg(`Found port: ${newPort}`)
|
dbg(`Found port: ${newPort}`)
|
||||||
|
|
||||||
const instanceLogger = await instanceLoggerService().get(instance.id)
|
const instanceLogger = await instanceLoggerService().get(
|
||||||
|
instance.id,
|
||||||
|
{ parentLogger: _subdomainLogger }
|
||||||
|
)
|
||||||
|
|
||||||
await clientLimiter.schedule(() => {
|
await clientLimiter.schedule(() => {
|
||||||
dbg(`Instance status: starting`)
|
dbg(`Instance status: starting`)
|
||||||
@ -146,16 +152,25 @@ export const instanceService = mkSingleton(
|
|||||||
|
|
||||||
dbg(`Starting instance`)
|
dbg(`Starting instance`)
|
||||||
await instanceLogger.write(`Starting instance`)
|
await instanceLogger.write(`Starting instance`)
|
||||||
const childProcess = await pbService.spawn({
|
const childProcess = await (async () => {
|
||||||
command: 'serve',
|
try {
|
||||||
slug: instance.id,
|
const cp = await pbService.spawn({
|
||||||
port: newPort,
|
command: 'serve',
|
||||||
version: instance.version,
|
slug: instance.id,
|
||||||
onUnexpectedStop: (code) => {
|
port: newPort,
|
||||||
warn(`${subdomain} exited unexpectedly with ${code}`)
|
version: instance.version,
|
||||||
api.shutdown()
|
onUnexpectedStop: (code) => {
|
||||||
},
|
warn(`${subdomain} exited unexpectedly with ${code}`)
|
||||||
})
|
api.shutdown()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return cp
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not launch PocketBase ${instance.version}. It may be time to upgrade.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
const { pid } = childProcess
|
const { pid } = childProcess
|
||||||
assertTruthy(pid, `Expected PID here but got ${pid}`)
|
assertTruthy(pid, `Expected PID here but got ${pid}`)
|
||||||
@ -190,6 +205,7 @@ export const instanceService = mkSingleton(
|
|||||||
path: workerPath,
|
path: workerPath,
|
||||||
port: newPort,
|
port: newPort,
|
||||||
instance,
|
instance,
|
||||||
|
logger: _instanceLogger,
|
||||||
})
|
})
|
||||||
return api
|
return api
|
||||||
} else {
|
} else {
|
||||||
@ -210,6 +226,7 @@ export const instanceService = mkSingleton(
|
|||||||
port: newPort,
|
port: newPort,
|
||||||
shutdown: safeCatch(
|
shutdown: safeCatch(
|
||||||
`Instance ${subdomain} invocation ${invocation.id} pid ${pid} shutdown`,
|
`Instance ${subdomain} invocation ${invocation.id} pid ${pid} shutdown`,
|
||||||
|
_subdomainLogger,
|
||||||
async () => {
|
async () => {
|
||||||
dbg(`Shutting down`)
|
dbg(`Shutting down`)
|
||||||
await instanceLogger.write(`Shutting down instance`)
|
await instanceLogger.write(`Shutting down instance`)
|
||||||
@ -247,7 +264,7 @@ export const instanceService = mkSingleton(
|
|||||||
|
|
||||||
{
|
{
|
||||||
tm.repeat(
|
tm.repeat(
|
||||||
safeCatch(`idleCheck`, async () => {
|
safeCatch(`idleCheck`, _subdomainLogger, async () => {
|
||||||
raw(
|
raw(
|
||||||
`${subdomain} idle check: ${openRequestCount} open requests`
|
`${subdomain} idle check: ${openRequestCount} open requests`
|
||||||
)
|
)
|
||||||
@ -272,7 +289,7 @@ export const instanceService = mkSingleton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const uptime = safeCatch(`uptime`, async () => {
|
const uptime = safeCatch(`uptime`, _subdomainLogger, async () => {
|
||||||
raw(`${subdomain} uptime`)
|
raw(`${subdomain} uptime`)
|
||||||
await clientLimiter.schedule(() =>
|
await clientLimiter.schedule(() =>
|
||||||
client.pingInvocation(invocation)
|
client.pingInvocation(invocation)
|
||||||
@ -314,7 +331,7 @@ export const instanceService = mkSingleton(
|
|||||||
;(await proxyService()).use(
|
;(await proxyService()).use(
|
||||||
(subdomain) => subdomain !== PUBLIC_APP_DB,
|
(subdomain) => subdomain !== PUBLIC_APP_DB,
|
||||||
['/api(/*)', '/_(/*)', '(/*)'],
|
['/api(/*)', '/_(/*)', '(/*)'],
|
||||||
async (req, res, meta) => {
|
async (req, res, meta, logger) => {
|
||||||
const { subdomain, host, proxy } = meta
|
const { subdomain, host, proxy } = meta
|
||||||
|
|
||||||
// Do not handle central db requests, that is handled separately
|
// Do not handle central db requests, that is handled separately
|
||||||
|
@ -9,10 +9,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
createCleanupManager,
|
createCleanupManager,
|
||||||
createTimerManager,
|
createTimerManager,
|
||||||
logger,
|
|
||||||
safeCatch,
|
safeCatch,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { mkSingleton } from '@pockethost/common/src/mkSingleton'
|
import {
|
||||||
|
mkSingleton,
|
||||||
|
SingletonBaseConfig,
|
||||||
|
} from '@pockethost/common/src/mkSingleton'
|
||||||
import { keys } from '@s-libs/micro-dash'
|
import { keys } from '@s-libs/micro-dash'
|
||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import { chmodSync, existsSync } from 'fs'
|
import { chmodSync, existsSync } from 'fs'
|
||||||
@ -35,7 +37,7 @@ export type PocketbaseServiceApi = AsyncReturnType<
|
|||||||
typeof createPocketbaseService
|
typeof createPocketbaseService
|
||||||
>
|
>
|
||||||
|
|
||||||
export type PocketbaseServiceConfig = {
|
export type PocketbaseServiceConfig = SingletonBaseConfig & {
|
||||||
cachePath: string
|
cachePath: string
|
||||||
checkIntervalMs: number
|
checkIntervalMs: number
|
||||||
}
|
}
|
||||||
@ -60,7 +62,9 @@ export type Releases = Release[]
|
|||||||
export const createPocketbaseService = async (
|
export const createPocketbaseService = async (
|
||||||
config: PocketbaseServiceConfig
|
config: PocketbaseServiceConfig
|
||||||
) => {
|
) => {
|
||||||
const { dbg, error } = logger().create('PocketbaseService')
|
const { logger } = config
|
||||||
|
const _serviceLogger = logger.create('PocketbaseService')
|
||||||
|
const { dbg, error } = _serviceLogger
|
||||||
|
|
||||||
const { cachePath, checkIntervalMs } = config
|
const { cachePath, checkIntervalMs } = config
|
||||||
|
|
||||||
@ -100,7 +104,7 @@ export const createPocketbaseService = async (
|
|||||||
versions[sanitizedTagName] = Promise.resolve('')
|
versions[sanitizedTagName] = Promise.resolve('')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await downloadAndExtract(url, binPath)
|
await downloadAndExtract(url, binPath, _serviceLogger)
|
||||||
|
|
||||||
resolve(binPath)
|
resolve(binPath)
|
||||||
})
|
})
|
||||||
@ -138,85 +142,89 @@ export const createPocketbaseService = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _spawn = safeCatch(`spawnInstance`, async (cfg: SpawnConfig) => {
|
const _spawn = safeCatch(
|
||||||
const _cfg: Required<SpawnConfig> = {
|
`spawnInstance`,
|
||||||
version: maxVersion,
|
_serviceLogger,
|
||||||
port: await getPort(),
|
async (cfg: SpawnConfig) => {
|
||||||
isMothership: false,
|
const _cfg: Required<SpawnConfig> = {
|
||||||
onUnexpectedStop: (code) => {
|
version: maxVersion,
|
||||||
dbg(`Unexpected stop default handler. Exit code: ${code}`)
|
port: await getPort(),
|
||||||
},
|
isMothership: false,
|
||||||
...cfg,
|
onUnexpectedStop: (code) => {
|
||||||
}
|
dbg(`Unexpected stop default handler. Exit code: ${code}`)
|
||||||
const { version, command, slug, port, onUnexpectedStop, isMothership } =
|
},
|
||||||
_cfg
|
...cfg,
|
||||||
const _version = version || maxVersion // If _version is blank, we use the max version available
|
}
|
||||||
const bin = (await getVersion(_version)).binPath
|
const { version, command, slug, port, onUnexpectedStop, isMothership } =
|
||||||
if (!existsSync(bin)) {
|
_cfg
|
||||||
throw new Error(
|
const _version = version || maxVersion // If _version is blank, we use the max version available
|
||||||
`PocketBase binary (${bin}) not found. Contact pockethost.io.`
|
const bin = (await getVersion(_version)).binPath
|
||||||
)
|
if (!existsSync(bin)) {
|
||||||
}
|
throw new Error(
|
||||||
|
`PocketBase binary (${bin}) not found. Contact pockethost.io.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
command,
|
command,
|
||||||
`--dir`,
|
`--dir`,
|
||||||
`${DAEMON_PB_DATA_DIR}/${slug}/pb_data`,
|
`${DAEMON_PB_DATA_DIR}/${slug}/pb_data`,
|
||||||
`--publicDir`,
|
`--publicDir`,
|
||||||
`${DAEMON_PB_DATA_DIR}/${slug}/pb_static`,
|
`${DAEMON_PB_DATA_DIR}/${slug}/pb_static`,
|
||||||
`--migrationsDir`,
|
`--migrationsDir`,
|
||||||
isMothership
|
isMothership
|
||||||
? DAEMON_PB_MIGRATIONS_DIR
|
? DAEMON_PB_MIGRATIONS_DIR
|
||||||
: `${DAEMON_PB_DATA_DIR}/${slug}/pb_migrations`,
|
: `${DAEMON_PB_DATA_DIR}/${slug}/pb_migrations`,
|
||||||
]
|
]
|
||||||
if (command === 'serve') {
|
if (command === 'serve') {
|
||||||
args.push(`--http`)
|
args.push(`--http`)
|
||||||
args.push(mkInternalAddress(port))
|
args.push(mkInternalAddress(port))
|
||||||
}
|
}
|
||||||
|
|
||||||
let isRunning = true
|
let isRunning = true
|
||||||
|
|
||||||
dbg(`Spawning ${slug}`, { bin, args, cli: [bin, ...args].join(' ') })
|
dbg(`Spawning ${slug}`, { bin, args, cli: [bin, ...args].join(' ') })
|
||||||
const ls = spawn(bin, args)
|
const ls = spawn(bin, args)
|
||||||
cm.add(() => ls.kill())
|
cm.add(() => ls.kill())
|
||||||
|
|
||||||
ls.stdout.on('data', (data) => {
|
ls.stdout.on('data', (data) => {
|
||||||
dbg(`${slug} stdout: ${data}`)
|
dbg(`${slug} stdout: ${data}`)
|
||||||
})
|
|
||||||
|
|
||||||
ls.stderr.on('data', (data) => {
|
|
||||||
error(`${slug} stderr: ${data}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
ls.on('close', (code) => {
|
|
||||||
dbg(`${slug} closed with code ${code}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
const exited = new Promise<number | null>((resolve) => {
|
|
||||||
ls.on('exit', (code) => {
|
|
||||||
dbg(`${slug} exited with code ${code}`)
|
|
||||||
isRunning = false
|
|
||||||
if (code) onUnexpectedStop?.(code)
|
|
||||||
resolve(code)
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
ls.on('error', (err) => {
|
ls.stderr.on('data', (data) => {
|
||||||
dbg(`${slug} had error ${err}`)
|
error(`${slug} stderr: ${data}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
const url = mkInternalUrl(port)
|
ls.on('close', (code) => {
|
||||||
if (command === 'serve') {
|
dbg(`${slug} closed with code ${code}`)
|
||||||
await tryFetch(url, async () => isRunning)
|
})
|
||||||
|
|
||||||
|
const exited = new Promise<number | null>((resolve) => {
|
||||||
|
ls.on('exit', (code) => {
|
||||||
|
dbg(`${slug} exited with code ${code}`)
|
||||||
|
isRunning = false
|
||||||
|
if (code) onUnexpectedStop?.(code)
|
||||||
|
resolve(code)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ls.on('error', (err) => {
|
||||||
|
dbg(`${slug} had error ${err}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
const url = mkInternalUrl(port)
|
||||||
|
if (command === 'serve') {
|
||||||
|
await tryFetch(_serviceLogger)(url, async () => isRunning)
|
||||||
|
}
|
||||||
|
const api: PocketbaseProcess = {
|
||||||
|
url,
|
||||||
|
exited,
|
||||||
|
pid: ls.pid,
|
||||||
|
kill: () => ls.kill(),
|
||||||
|
}
|
||||||
|
return api
|
||||||
}
|
}
|
||||||
const api: PocketbaseProcess = {
|
)
|
||||||
url,
|
|
||||||
exited,
|
|
||||||
pid: ls.pid,
|
|
||||||
kill: () => ls.kill(),
|
|
||||||
}
|
|
||||||
return api
|
|
||||||
})
|
|
||||||
|
|
||||||
const shutdown = () => {
|
const shutdown = () => {
|
||||||
dbg(`Shutting down pocketbaseService`)
|
dbg(`Shutting down pocketbaseService`)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PUBLIC_APP_DOMAIN } from '$constants'
|
import { PUBLIC_APP_DOMAIN } from '$constants'
|
||||||
import { logger, mkSingleton } from '@pockethost/common'
|
import { Logger, mkSingleton, SingletonBaseConfig } from '@pockethost/common'
|
||||||
import { isFunction } from '@s-libs/micro-dash'
|
import { isFunction } from '@s-libs/micro-dash'
|
||||||
import {
|
import {
|
||||||
createServer,
|
createServer,
|
||||||
@ -21,14 +21,17 @@ export type ProxyMiddleware = (
|
|||||||
coreInternalUrl: string
|
coreInternalUrl: string
|
||||||
proxy: Server
|
proxy: Server
|
||||||
host: string
|
host: string
|
||||||
}
|
},
|
||||||
|
logger: Logger
|
||||||
) => void | Promise<void>
|
) => void | Promise<void>
|
||||||
|
|
||||||
export type ProxyServiceConfig = {
|
export type ProxyServiceConfig = SingletonBaseConfig & {
|
||||||
coreInternalUrl: string
|
coreInternalUrl: string
|
||||||
}
|
}
|
||||||
export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
||||||
const { dbg, error, info, trace, warn } = logger().create('ProxyService')
|
const { logger } = config
|
||||||
|
const _proxyLogger = logger.create('ProxyService')
|
||||||
|
const { dbg, error, info, trace, warn } = _proxyLogger
|
||||||
|
|
||||||
const { coreInternalUrl } = config
|
const { coreInternalUrl } = config
|
||||||
|
|
||||||
@ -38,7 +41,7 @@ export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const server = createServer(async (req, res) => {
|
const server = createServer(async (req, res) => {
|
||||||
dbg(`Incoming request ${req.headers.host}/${req.url}`)
|
dbg(`Incoming request ${req.method} ${req.headers.host}/${req.url}`)
|
||||||
if (!req.headers.host?.endsWith(PUBLIC_APP_DOMAIN)) {
|
if (!req.headers.host?.endsWith(PUBLIC_APP_DOMAIN)) {
|
||||||
warn(
|
warn(
|
||||||
`Request for ${req.headers.host} rejected because host does not end in ${PUBLIC_APP_DOMAIN}`
|
`Request for ${req.headers.host} rejected because host does not end in ${PUBLIC_APP_DOMAIN}`
|
||||||
@ -89,7 +92,8 @@ export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
|||||||
handler: ProxyMiddleware,
|
handler: ProxyMiddleware,
|
||||||
handlerName: string
|
handlerName: string
|
||||||
) => {
|
) => {
|
||||||
const { dbg, trace } = logger().create(`ProxyService:${handlerName}`)
|
const _handlerLogger = _proxyLogger.create(`${handlerName}`)
|
||||||
|
const { dbg, trace } = _handlerLogger
|
||||||
dbg({ subdomainFilter, urlFilters })
|
dbg({ subdomainFilter, urlFilters })
|
||||||
|
|
||||||
const _urlFilters = Array.isArray(urlFilters)
|
const _urlFilters = Array.isArray(urlFilters)
|
||||||
@ -105,6 +109,10 @@ export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
|||||||
if (!host) {
|
if (!host) {
|
||||||
throw new Error(`Host not found`)
|
throw new Error(`Host not found`)
|
||||||
}
|
}
|
||||||
|
const _requestLogger = _handlerLogger.create(host)
|
||||||
|
const { dbg, trace } = _requestLogger
|
||||||
|
_requestLogger.breadcrumb(req.method)
|
||||||
|
_requestLogger.breadcrumb(req.url)
|
||||||
const [subdomain, ...junk] = host.split('.')
|
const [subdomain, ...junk] = host.split('.')
|
||||||
if (!subdomain) {
|
if (!subdomain) {
|
||||||
throw new Error(`${host} has no subdomain.`)
|
throw new Error(`${host} has no subdomain.`)
|
||||||
@ -133,7 +141,12 @@ export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
dbg(`${url} matches ${urlFilters}, sending to handler`)
|
dbg(`${url} matches ${urlFilters}, sending to handler`)
|
||||||
return handler(req, res, { host, subdomain, coreInternalUrl, proxy })
|
return handler(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
{ host, subdomain, coreInternalUrl, proxy },
|
||||||
|
_requestLogger
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { PUBLIC_APP_DB } from '$src/constants'
|
import { PUBLIC_APP_DB } from '$src/constants'
|
||||||
import {
|
import {
|
||||||
InstanceFields,
|
InstanceFields,
|
||||||
logger,
|
|
||||||
mkSingleton,
|
mkSingleton,
|
||||||
RecordId,
|
RecordId,
|
||||||
|
SingletonBaseConfig,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import Bottleneck from 'bottleneck'
|
import Bottleneck from 'bottleneck'
|
||||||
import { text } from 'node:stream/consumers'
|
import { text } from 'node:stream/consumers'
|
||||||
@ -12,7 +12,7 @@ import { JsonifiableObject } from 'type-fest/source/jsonifiable'
|
|||||||
import { instanceLoggerService } from './InstanceLoggerService'
|
import { instanceLoggerService } from './InstanceLoggerService'
|
||||||
import { proxyService } from './ProxyService'
|
import { proxyService } from './ProxyService'
|
||||||
|
|
||||||
export type RealtimeLogConfig = {}
|
export type RealtimeLogConfig = SingletonBaseConfig & {}
|
||||||
|
|
||||||
const mkEvent = (name: string, data: JsonifiableObject) => {
|
const mkEvent = (name: string, data: JsonifiableObject) => {
|
||||||
return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`
|
return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`
|
||||||
@ -20,20 +20,21 @@ const mkEvent = (name: string, data: JsonifiableObject) => {
|
|||||||
|
|
||||||
export type RealtimeLog = ReturnType<typeof realtimeLog>
|
export type RealtimeLog = ReturnType<typeof realtimeLog>
|
||||||
export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
|
export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
|
||||||
const { dbg, error } = logger().create(`RealtimeLog.ts`)
|
const { logger } = config
|
||||||
|
const _realtimeLogger = logger.create(`RealtimeLog`)
|
||||||
|
const { dbg, error } = _realtimeLogger
|
||||||
|
|
||||||
;(await proxyService()).use(
|
;(await proxyService()).use(
|
||||||
PUBLIC_APP_DB,
|
PUBLIC_APP_DB,
|
||||||
'/logs',
|
'/logs',
|
||||||
async (req, res, meta) => {
|
async (req, res, meta, logger) => {
|
||||||
const { subdomain, host, coreInternalUrl } = meta
|
const { subdomain, host, coreInternalUrl } = meta
|
||||||
if (!req.url?.startsWith('/logs')) {
|
if (!req.url?.startsWith('/logs')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { dbg, error, trace } = logger().create(
|
const _requestLogger = logger.create(`${subdomain}`)
|
||||||
`RealtimeLog:${subdomain}:${host}`
|
const { dbg, error, trace } = _requestLogger
|
||||||
)
|
|
||||||
|
|
||||||
const write = async (data: any) => {
|
const write = async (data: any) => {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
@ -115,7 +116,9 @@ export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
|
|||||||
/**
|
/**
|
||||||
* Get a database connection
|
* Get a database connection
|
||||||
*/
|
*/
|
||||||
const instanceLogger = await instanceLoggerService().get(instanceId)
|
const instanceLogger = await instanceLoggerService().get(instanceId, {
|
||||||
|
parentLogger: _requestLogger,
|
||||||
|
})
|
||||||
const { subscribe } = instanceLogger
|
const { subscribe } = instanceLogger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -182,8 +185,6 @@ export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
|
|||||||
})
|
})
|
||||||
.catch(error)
|
.catch(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
`RealtimeLogService`
|
`RealtimeLogService`
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { clientService } from '$services'
|
import { clientService } from '$services'
|
||||||
import {
|
import {
|
||||||
assertTruthy,
|
assertTruthy,
|
||||||
logger,
|
|
||||||
mkSingleton,
|
mkSingleton,
|
||||||
RpcCommands,
|
RpcCommands,
|
||||||
RpcFields,
|
RpcFields,
|
||||||
RpcStatus,
|
RpcStatus,
|
||||||
RPC_COMMANDS,
|
RPC_COMMANDS,
|
||||||
|
SingletonBaseConfig,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { isObject } from '@s-libs/micro-dash'
|
import { isObject } from '@s-libs/micro-dash'
|
||||||
import Ajv, { JSONSchemaType, ValidateFunction } from 'ajv'
|
import Ajv, { JSONSchemaType, ValidateFunction } from 'ajv'
|
||||||
@ -29,10 +29,11 @@ export type RpcRunner<
|
|||||||
TResult extends JsonObject
|
TResult extends JsonObject
|
||||||
> = (job: RpcFields<TPayload, TResult>) => Promise<TResult>
|
> = (job: RpcFields<TPayload, TResult>) => Promise<TResult>
|
||||||
|
|
||||||
export type RpcServiceConfig = {}
|
export type RpcServiceConfig = SingletonBaseConfig & {}
|
||||||
|
|
||||||
export const rpcService = mkSingleton(async (config: RpcServiceConfig) => {
|
export const rpcService = mkSingleton(async (config: RpcServiceConfig) => {
|
||||||
const { dbg, error } = logger().create('RpcService')
|
const { logger } = config
|
||||||
|
const { dbg, error } = logger.create('RpcService')
|
||||||
const { client } = await clientService()
|
const { client } = await clientService()
|
||||||
|
|
||||||
const limiter = new Bottleneck({ maxConcurrent: 1 })
|
const limiter = new Bottleneck({ maxConcurrent: 1 })
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
createCleanupManager,
|
createCleanupManager,
|
||||||
createEvent,
|
createEvent,
|
||||||
logger,
|
|
||||||
mkSingleton,
|
mkSingleton,
|
||||||
|
SingletonBaseConfig,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import Bottleneck from 'bottleneck'
|
import Bottleneck from 'bottleneck'
|
||||||
import { Database as SqliteDatabase, open } from 'sqlite'
|
import { Database as SqliteDatabase, open } from 'sqlite'
|
||||||
@ -28,12 +28,13 @@ export type SqliteServiceApi = {
|
|||||||
cb: SqliteChangeHandler<TRecord>
|
cb: SqliteChangeHandler<TRecord>
|
||||||
) => SqliteUnsubscribe
|
) => SqliteUnsubscribe
|
||||||
}
|
}
|
||||||
export type SqliteServiceConfig = {}
|
export type SqliteServiceConfig = SingletonBaseConfig & {}
|
||||||
|
|
||||||
export type SqliteService = ReturnType<typeof sqliteService>
|
export type SqliteService = ReturnType<typeof sqliteService>
|
||||||
|
|
||||||
export const sqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
export const sqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
||||||
const { dbg, trace } = logger().create(`sqliteService`)
|
const { logger } = config
|
||||||
|
const { dbg, trace } = logger.create(`sqliteService`)
|
||||||
const connections: { [_: string]: Promise<SqliteServiceApi> } = {}
|
const connections: { [_: string]: Promise<SqliteServiceApi> } = {}
|
||||||
|
|
||||||
const cm = createCleanupManager()
|
const cm = createCleanupManager()
|
||||||
@ -41,7 +42,8 @@ export const sqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
|||||||
const limiter = new Bottleneck({ maxConcurrent: 1 })
|
const limiter = new Bottleneck({ maxConcurrent: 1 })
|
||||||
|
|
||||||
const getDatabase = async (filename: string): Promise<SqliteServiceApi> => {
|
const getDatabase = async (filename: string): Promise<SqliteServiceApi> => {
|
||||||
const { dbg } = logger().create(`sqliteService:${filename}`)
|
const _dbLogger = logger.create(`${filename}`)
|
||||||
|
const { dbg } = _dbLogger
|
||||||
|
|
||||||
trace(`Fetching database for ${filename}`, connections)
|
trace(`Fetching database for ${filename}`, connections)
|
||||||
if (!connections[filename]) {
|
if (!connections[filename]) {
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
InstanceFields_Create,
|
InstanceFields_Create,
|
||||||
InstanceId,
|
InstanceId,
|
||||||
InstanceStatus,
|
InstanceStatus,
|
||||||
logger,
|
|
||||||
safeCatch,
|
safeCatch,
|
||||||
UserFields,
|
UserFields,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
@ -16,12 +15,14 @@ import { MixinContext } from './PbClient'
|
|||||||
export type InstanceApi = ReturnType<typeof createInstanceMixin>
|
export type InstanceApi = ReturnType<typeof createInstanceMixin>
|
||||||
|
|
||||||
export const createInstanceMixin = (context: MixinContext) => {
|
export const createInstanceMixin = (context: MixinContext) => {
|
||||||
const { dbg, raw } = logger().create('InstanceMixin')
|
const { logger } = context
|
||||||
|
const { dbg, raw } = logger.create('InstanceMixin')
|
||||||
|
|
||||||
const { client, rawDb } = context
|
const { client, rawDb } = context
|
||||||
|
|
||||||
const createInstance = safeCatch(
|
const createInstance = safeCatch(
|
||||||
`createInstance`,
|
`createInstance`,
|
||||||
|
logger,
|
||||||
(payload: InstanceFields_Create): Promise<InstanceFields> => {
|
(payload: InstanceFields_Create): Promise<InstanceFields> => {
|
||||||
return client.collection('instances').create<InstanceFields>(payload)
|
return client.collection('instances').create<InstanceFields>(payload)
|
||||||
}
|
}
|
||||||
@ -29,6 +30,7 @@ export const createInstanceMixin = (context: MixinContext) => {
|
|||||||
|
|
||||||
const getInstanceBySubdomain = safeCatch(
|
const getInstanceBySubdomain = safeCatch(
|
||||||
`getInstanceBySubdomain`,
|
`getInstanceBySubdomain`,
|
||||||
|
logger,
|
||||||
(subdomain: string): Promise<[InstanceFields, UserFields] | []> =>
|
(subdomain: string): Promise<[InstanceFields, UserFields] | []> =>
|
||||||
client
|
client
|
||||||
.collection('instances')
|
.collection('instances')
|
||||||
@ -46,6 +48,7 @@ export const createInstanceMixin = (context: MixinContext) => {
|
|||||||
|
|
||||||
const getInstanceById = safeCatch(
|
const getInstanceById = safeCatch(
|
||||||
`getInstanceById`,
|
`getInstanceById`,
|
||||||
|
logger,
|
||||||
async (
|
async (
|
||||||
instanceId: InstanceId
|
instanceId: InstanceId
|
||||||
): Promise<[InstanceFields, UserFields] | []> => {
|
): Promise<[InstanceFields, UserFields] | []> => {
|
||||||
@ -66,6 +69,7 @@ export const createInstanceMixin = (context: MixinContext) => {
|
|||||||
|
|
||||||
const updateInstance = safeCatch(
|
const updateInstance = safeCatch(
|
||||||
`updateInstance`,
|
`updateInstance`,
|
||||||
|
logger,
|
||||||
async (instanceId: InstanceId, fields: Partial<InstanceFields>) => {
|
async (instanceId: InstanceId, fields: Partial<InstanceFields>) => {
|
||||||
await client.collection('instances').update(instanceId, fields)
|
await client.collection('instances').update(instanceId, fields)
|
||||||
}
|
}
|
||||||
@ -73,6 +77,7 @@ export const createInstanceMixin = (context: MixinContext) => {
|
|||||||
|
|
||||||
const updateInstanceStatus = safeCatch(
|
const updateInstanceStatus = safeCatch(
|
||||||
`updateInstanceStatus`,
|
`updateInstanceStatus`,
|
||||||
|
logger,
|
||||||
async (instanceId: InstanceId, status: InstanceStatus) => {
|
async (instanceId: InstanceId, status: InstanceStatus) => {
|
||||||
await updateInstance(instanceId, { status })
|
await updateInstance(instanceId, { status })
|
||||||
}
|
}
|
||||||
@ -80,17 +85,19 @@ export const createInstanceMixin = (context: MixinContext) => {
|
|||||||
|
|
||||||
const getInstance = safeCatch(
|
const getInstance = safeCatch(
|
||||||
`getInstance`,
|
`getInstance`,
|
||||||
|
logger,
|
||||||
async (instanceId: InstanceId) => {
|
async (instanceId: InstanceId) => {
|
||||||
return client.collection('instances').getOne<InstanceFields>(instanceId)
|
return client.collection('instances').getOne<InstanceFields>(instanceId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const getInstances = safeCatch(`getInstances`, async () => {
|
const getInstances = safeCatch(`getInstances`, logger, async () => {
|
||||||
return client.collection('instances').getFullList<InstanceFields>()
|
return client.collection('instances').getFullList<InstanceFields>()
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateInstances = safeCatch(
|
const updateInstances = safeCatch(
|
||||||
'updateInstances',
|
'updateInstances',
|
||||||
|
logger,
|
||||||
async (cb: (rec: InstanceFields) => Partial<InstanceFields>) => {
|
async (cb: (rec: InstanceFields) => Partial<InstanceFields>) => {
|
||||||
const res = await client
|
const res = await client
|
||||||
.collection('instances')
|
.collection('instances')
|
||||||
@ -116,6 +123,7 @@ export const createInstanceMixin = (context: MixinContext) => {
|
|||||||
|
|
||||||
const updateInstanceSeconds = safeCatch(
|
const updateInstanceSeconds = safeCatch(
|
||||||
`updateInstanceSeconds`,
|
`updateInstanceSeconds`,
|
||||||
|
logger,
|
||||||
async (instanceId: InstanceId, forPeriod = new Date()) => {
|
async (instanceId: InstanceId, forPeriod = new Date()) => {
|
||||||
const startIso = startOfMonth(forPeriod).toISOString()
|
const startIso = startOfMonth(forPeriod).toISOString()
|
||||||
const endIso = endOfMonth(forPeriod).toISOString()
|
const endIso = endOfMonth(forPeriod).toISOString()
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
InstanceFields,
|
InstanceFields,
|
||||||
InvocationFields,
|
InvocationFields,
|
||||||
logger,
|
|
||||||
pocketNow,
|
pocketNow,
|
||||||
safeCatch,
|
safeCatch,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
@ -12,12 +11,14 @@ export const createInvocationMixin = (
|
|||||||
context: MixinContext,
|
context: MixinContext,
|
||||||
instanceApi: InstanceApi
|
instanceApi: InstanceApi
|
||||||
) => {
|
) => {
|
||||||
const { dbg } = logger().create('InvocationMixin')
|
const { logger } = context
|
||||||
|
const { dbg } = logger.create('InvocationMixin')
|
||||||
|
|
||||||
const { client } = context
|
const { client } = context
|
||||||
|
|
||||||
const createInvocation = safeCatch(
|
const createInvocation = safeCatch(
|
||||||
`createInvocation`,
|
`createInvocation`,
|
||||||
|
logger,
|
||||||
async (instance: InstanceFields, pid: number) => {
|
async (instance: InstanceFields, pid: number) => {
|
||||||
const init: Partial<InvocationFields> = {
|
const init: Partial<InvocationFields> = {
|
||||||
startedAt: pocketNow(),
|
startedAt: pocketNow(),
|
||||||
@ -34,6 +35,7 @@ export const createInvocationMixin = (
|
|||||||
|
|
||||||
const pingInvocation = safeCatch(
|
const pingInvocation = safeCatch(
|
||||||
`pingInvocation`,
|
`pingInvocation`,
|
||||||
|
logger,
|
||||||
async (invocation: InvocationFields) => {
|
async (invocation: InvocationFields) => {
|
||||||
const totalSeconds =
|
const totalSeconds =
|
||||||
(+new Date() - Date.parse(invocation.startedAt)) / 1000
|
(+new Date() - Date.parse(invocation.startedAt)) / 1000
|
||||||
@ -50,6 +52,7 @@ export const createInvocationMixin = (
|
|||||||
|
|
||||||
const finalizeInvocation = safeCatch(
|
const finalizeInvocation = safeCatch(
|
||||||
`finalizeInvocation`,
|
`finalizeInvocation`,
|
||||||
|
logger,
|
||||||
async (invocation: InvocationFields) => {
|
async (invocation: InvocationFields) => {
|
||||||
dbg('finalizing')
|
dbg('finalizing')
|
||||||
const totalSeconds =
|
const totalSeconds =
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { DAEMON_PB_DATA_DIR, PUBLIC_APP_DB } from '$constants'
|
import { DAEMON_PB_DATA_DIR, PUBLIC_APP_DB } from '$constants'
|
||||||
import { logger, safeCatch } from '@pockethost/common'
|
import { Logger, safeCatch } from '@pockethost/common'
|
||||||
import { Knex } from 'knex'
|
import { Knex } from 'knex'
|
||||||
import { default as PocketBase, default as pocketbaseEs } from 'pocketbase'
|
import { default as PocketBase, default as pocketbaseEs } from 'pocketbase'
|
||||||
import { createBackupMixin } from './BackupMixin'
|
|
||||||
import { createInstanceMixin } from './InstanceMIxin'
|
import { createInstanceMixin } from './InstanceMIxin'
|
||||||
import { createInvocationMixin } from './InvocationMixin'
|
import { createInvocationMixin } from './InvocationMixin'
|
||||||
import { createRawPbClient } from './RawPbClient'
|
import { createRawPbClient } from './RawPbClient'
|
||||||
@ -10,26 +9,30 @@ import { createRpcHelper } from './RpcHelper'
|
|||||||
|
|
||||||
export type PocketbaseClientApi = ReturnType<typeof createPbClient>
|
export type PocketbaseClientApi = ReturnType<typeof createPbClient>
|
||||||
|
|
||||||
export type MixinContext = { client: pocketbaseEs; rawDb: Knex }
|
export type MixinContext = { client: pocketbaseEs; rawDb: Knex; logger: Logger }
|
||||||
|
|
||||||
export const createPbClient = (url: string) => {
|
export const createPbClient = (url: string, logger: Logger) => {
|
||||||
const { info } = logger().create('PbClient')
|
const _clientLogger = logger.create('PbClient')
|
||||||
|
const { info } = _clientLogger
|
||||||
|
|
||||||
info(`Initializing client: ${url}`)
|
info(`Initializing client: ${url}`)
|
||||||
const rawDb = createRawPbClient(
|
const rawDb = createRawPbClient(
|
||||||
`${DAEMON_PB_DATA_DIR}/${PUBLIC_APP_DB}/pb_data/data.db`
|
`${DAEMON_PB_DATA_DIR}/${PUBLIC_APP_DB}/pb_data/data.db`,
|
||||||
|
_clientLogger
|
||||||
)
|
)
|
||||||
|
|
||||||
const client = new PocketBase(url)
|
const client = new PocketBase(url)
|
||||||
|
|
||||||
const adminAuthViaEmail = safeCatch(
|
const adminAuthViaEmail = safeCatch(
|
||||||
`adminAuthViaEmail`,
|
`adminAuthViaEmail`,
|
||||||
|
_clientLogger,
|
||||||
(email: string, password: string) =>
|
(email: string, password: string) =>
|
||||||
client.admins.authWithPassword(email, password)
|
client.admins.authWithPassword(email, password)
|
||||||
)
|
)
|
||||||
|
|
||||||
const createFirstAdmin = safeCatch(
|
const createFirstAdmin = safeCatch(
|
||||||
`createFirstAdmin`,
|
`createFirstAdmin`,
|
||||||
|
_clientLogger,
|
||||||
(email: string, password: string) =>
|
(email: string, password: string) =>
|
||||||
client.admins
|
client.admins
|
||||||
.create({ email, password, passwordConfirm: password })
|
.create({ email, password, passwordConfirm: password })
|
||||||
@ -40,10 +43,9 @@ export const createPbClient = (url: string) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const context: MixinContext = { client, rawDb }
|
const context: MixinContext = { client, rawDb, logger: _clientLogger }
|
||||||
const rpcApi = createRpcHelper(context)
|
const rpcApi = createRpcHelper(context)
|
||||||
const instanceApi = createInstanceMixin(context)
|
const instanceApi = createInstanceMixin(context)
|
||||||
const backupApi = createBackupMixin(context)
|
|
||||||
const invocationApi = createInvocationMixin(context, instanceApi)
|
const invocationApi = createInvocationMixin(context, instanceApi)
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
@ -55,7 +57,6 @@ export const createPbClient = (url: string) => {
|
|||||||
...rpcApi,
|
...rpcApi,
|
||||||
...instanceApi,
|
...instanceApi,
|
||||||
...invocationApi,
|
...invocationApi,
|
||||||
...backupApi,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return api
|
return api
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { logger } from '@pockethost/common'
|
import { Logger } from '@pockethost/common'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import knex from 'knex'
|
import knex from 'knex'
|
||||||
|
|
||||||
export const createRawPbClient = (filename: string) => {
|
export const createRawPbClient = (filename: string, logger: Logger) => {
|
||||||
const { dbg } = logger().create(`rawPbClient`)
|
const { dbg } = logger.create(`rawPbClient`)
|
||||||
dbg(filename)
|
dbg(filename)
|
||||||
|
|
||||||
if (!existsSync(filename)) {
|
if (!existsSync(filename)) {
|
||||||
|
@ -18,9 +18,10 @@ export type RpcHelperConfig = MixinContext
|
|||||||
export type RpcHelper = ReturnType<typeof createRpcHelper>
|
export type RpcHelper = ReturnType<typeof createRpcHelper>
|
||||||
|
|
||||||
export const createRpcHelper = (config: RpcHelperConfig) => {
|
export const createRpcHelper = (config: RpcHelperConfig) => {
|
||||||
const { client, rawDb } = config
|
const { client, rawDb, logger } = config
|
||||||
const onNewRpc = safeCatch(
|
const onNewRpc = safeCatch(
|
||||||
`onNewRpc`,
|
`onNewRpc`,
|
||||||
|
logger,
|
||||||
async (cb: (e: RpcFields<any, any>) => void) => {
|
async (cb: (e: RpcFields<any, any>) => void) => {
|
||||||
const unsub = await client
|
const unsub = await client
|
||||||
.collection(RPC_COLLECTION)
|
.collection(RPC_COLLECTION)
|
||||||
@ -32,7 +33,7 @@ export const createRpcHelper = (config: RpcHelperConfig) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const resetRpcs = safeCatch(`resetRpcs`, async () =>
|
const resetRpcs = safeCatch(`resetRpcs`, logger, async () =>
|
||||||
rawDb(RPC_COLLECTION)
|
rawDb(RPC_COLLECTION)
|
||||||
.whereNotIn('status', [
|
.whereNotIn('status', [
|
||||||
RpcStatus.FinishedError,
|
RpcStatus.FinishedError,
|
||||||
@ -44,7 +45,7 @@ export const createRpcHelper = (config: RpcHelperConfig) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const incompleteRpcs = safeCatch(`incompleteRpcs`, async () => {
|
const incompleteRpcs = safeCatch(`incompleteRpcs`, logger, async () => {
|
||||||
return client
|
return client
|
||||||
.collection(RPC_COLLECTION)
|
.collection(RPC_COLLECTION)
|
||||||
.getFullList<RpcFields<any, any>>(100, {
|
.getFullList<RpcFields<any, any>>(100, {
|
||||||
@ -54,6 +55,7 @@ export const createRpcHelper = (config: RpcHelperConfig) => {
|
|||||||
|
|
||||||
const rejectRpc = safeCatch(
|
const rejectRpc = safeCatch(
|
||||||
`rejectRpc`,
|
`rejectRpc`,
|
||||||
|
logger,
|
||||||
async (rpc: RpcFields<any, any>, err: Error) => {
|
async (rpc: RpcFields<any, any>, err: Error) => {
|
||||||
const fields: Partial<RpcFields<any, any>> = {
|
const fields: Partial<RpcFields<any, any>> = {
|
||||||
status: RpcStatus.FinishedError,
|
status: RpcStatus.FinishedError,
|
||||||
@ -67,6 +69,7 @@ export const createRpcHelper = (config: RpcHelperConfig) => {
|
|||||||
|
|
||||||
const setRpcStatus = safeCatch(
|
const setRpcStatus = safeCatch(
|
||||||
`setRpcStatus`,
|
`setRpcStatus`,
|
||||||
|
logger,
|
||||||
async (
|
async (
|
||||||
rpc: RpcFields<any, any>,
|
rpc: RpcFields<any, any>,
|
||||||
status: RpcStatus,
|
status: RpcStatus,
|
||||||
|
@ -5,12 +5,19 @@ import {
|
|||||||
PUBLIC_APP_DOMAIN,
|
PUBLIC_APP_DOMAIN,
|
||||||
PUBLIC_APP_PROTOCOL,
|
PUBLIC_APP_PROTOCOL,
|
||||||
} from '$constants'
|
} from '$constants'
|
||||||
import { logger, mkSingleton } from '@pockethost/common'
|
import { Logger, mkSingleton } from '@pockethost/common'
|
||||||
import { createPbClient } from './PbClient'
|
import { createPbClient } from './PbClient'
|
||||||
|
|
||||||
export const clientService = mkSingleton(async (url: string) => {
|
export type ClientServiceConfig = {
|
||||||
const { dbg, error } = logger().create(`client singleton`)
|
logger: Logger
|
||||||
const client = createPbClient(url)
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clientService = mkSingleton(async (cfg: ClientServiceConfig) => {
|
||||||
|
const { logger, url } = cfg
|
||||||
|
const _clientLogger = logger.create(`client singleton`)
|
||||||
|
const { dbg, error } = _clientLogger
|
||||||
|
const client = createPbClient(url, _clientLogger)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
|
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export * from './BackupService'
|
|
||||||
export * from './clientService/clientService'
|
export * from './clientService/clientService'
|
||||||
export * from './clientService/PbClient'
|
export * from './clientService/PbClient'
|
||||||
export * from './FtpService/FtpService'
|
export * from './FtpService/FtpService'
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { logger } from '@pockethost/common'
|
import { Logger } from '@pockethost/common'
|
||||||
import { chmodSync } from 'fs'
|
import { chmodSync } from 'fs'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { dirname } from 'path'
|
import { dirname } from 'path'
|
||||||
import { Extract } from 'unzipper'
|
import { Extract } from 'unzipper'
|
||||||
|
|
||||||
export const downloadAndExtract = async (url: string, binPath: string) => {
|
export const downloadAndExtract = async (
|
||||||
const { dbg, error } = logger().create('downloadAndExtract')
|
url: string,
|
||||||
|
binPath: string,
|
||||||
|
logger: Logger
|
||||||
|
) => {
|
||||||
|
const { dbg, error } = logger.create('downloadAndExtract')
|
||||||
|
|
||||||
await new Promise<void>(async (resolve, reject) => {
|
await new Promise<void>(async (resolve, reject) => {
|
||||||
dbg(`Fetching ${url}`)
|
dbg(`Fetching ${url}`)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
export * from './backupInstance'
|
|
||||||
export * from './downloadAndExtract'
|
export * from './downloadAndExtract'
|
||||||
export * from './ensureDirExists'
|
export * from './ensureDirExists'
|
||||||
export * from './env'
|
export * from './env'
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { logger, safeCatch } from '@pockethost/common'
|
import { Logger, safeCatch } from '@pockethost/common'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
|
|
||||||
export const pexec = safeCatch(`pexec`, (cmd: string) => {
|
export const pexec = (logger: Logger) =>
|
||||||
const { dbg, error } = logger().create('pexec')
|
safeCatch(`pexec`, logger, (cmd: string) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
const { dbg, error } = logger.create('pexec')
|
||||||
dbg(cmd)
|
return new Promise<void>((resolve, reject) => {
|
||||||
exec(cmd, (err, stdout, stderr) => {
|
dbg(cmd)
|
||||||
dbg(stdout)
|
exec(cmd, (err, stdout, stderr) => {
|
||||||
if (err) {
|
dbg(stdout)
|
||||||
error(`${err}`)
|
if (err) {
|
||||||
error(stderr)
|
error(`${err}`)
|
||||||
reject(err)
|
error(stderr)
|
||||||
return
|
reject(err)
|
||||||
}
|
return
|
||||||
resolve()
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
@ -1,29 +1,32 @@
|
|||||||
import { logger, safeCatch } from '@pockethost/common'
|
import { Logger, safeCatch } from '@pockethost/common'
|
||||||
|
|
||||||
export const tryFetch = safeCatch(
|
export const tryFetch = (logger: Logger) =>
|
||||||
`tryFetch`,
|
safeCatch(
|
||||||
(url: string, preflight?: () => Promise<boolean>) => {
|
`tryFetch`,
|
||||||
const { dbg } = logger().create('tryFetch')
|
logger,
|
||||||
return new Promise<void>((resolve, reject) => {
|
(url: string, preflight?: () => Promise<boolean>) => {
|
||||||
const tryFetch = async () => {
|
const { dbg } = logger.create('tryFetch')
|
||||||
if (preflight) {
|
return new Promise<void>((resolve, reject) => {
|
||||||
dbg(`Checking preflight`)
|
const tryFetch = async () => {
|
||||||
const shouldFetch = await preflight()
|
if (preflight) {
|
||||||
if (!shouldFetch) {
|
dbg(`Checking preflight`)
|
||||||
throw new Error(`tryFetch failed preflight, aborting`)
|
const shouldFetch = await preflight()
|
||||||
|
if (!shouldFetch) {
|
||||||
|
reject(new Error(`tryFetch failed preflight, aborting`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dbg(`Trying to fetch ${url} `)
|
||||||
|
const res = await fetch(url)
|
||||||
|
dbg(`Fetch ${url} successful`)
|
||||||
|
resolve()
|
||||||
|
} catch (e) {
|
||||||
|
dbg(`Could not fetch ${url}, trying again in 1s`)
|
||||||
|
setTimeout(tryFetch, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
tryFetch()
|
||||||
dbg(`Trying to fetch ${url} `)
|
})
|
||||||
const res = await fetch(url)
|
}
|
||||||
dbg(`Fetch ${url} successful`)
|
)
|
||||||
resolve()
|
|
||||||
} catch (e) {
|
|
||||||
dbg(`Could not fetch ${url}, trying again in 1s`)
|
|
||||||
setTimeout(tryFetch, 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tryFetch()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -44,7 +44,8 @@ export type PocketbaseClient = ReturnType<typeof createPocketbaseClient>
|
|||||||
|
|
||||||
export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
||||||
const { url } = config
|
const { url } = config
|
||||||
const { dbg, error } = logger()
|
const _logger = logger()
|
||||||
|
const { dbg, error } = _logger
|
||||||
|
|
||||||
const client = new PocketBase(url)
|
const client = new PocketBase(url)
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
|
|
||||||
const logOut = () => authStore.clear()
|
const logOut = () => authStore.clear()
|
||||||
|
|
||||||
const createUser = safeCatch(`createUser`, (email: string, password: string) =>
|
const createUser = safeCatch(`createUser`, _logger, (email: string, password: string) =>
|
||||||
client
|
client
|
||||||
.collection('users')
|
.collection('users')
|
||||||
.create({
|
.create({
|
||||||
@ -70,7 +71,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const confirmVerification = safeCatch(`confirmVerification`, (token: string) =>
|
const confirmVerification = safeCatch(`confirmVerification`, _logger, (token: string) =>
|
||||||
client
|
client
|
||||||
.collection('users')
|
.collection('users')
|
||||||
.confirmVerification(token)
|
.confirmVerification(token)
|
||||||
@ -79,7 +80,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const requestPasswordReset = safeCatch(`requestPasswordReset`, (email: string) =>
|
const requestPasswordReset = safeCatch(`requestPasswordReset`, _logger, (email: string) =>
|
||||||
client
|
client
|
||||||
.collection('users')
|
.collection('users')
|
||||||
.requestPasswordReset(email)
|
.requestPasswordReset(email)
|
||||||
@ -90,6 +91,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
|
|
||||||
const requestPasswordResetConfirm = safeCatch(
|
const requestPasswordResetConfirm = safeCatch(
|
||||||
`requestPasswordResetConfirm`,
|
`requestPasswordResetConfirm`,
|
||||||
|
_logger,
|
||||||
(token: string, password: string) =>
|
(token: string, password: string) =>
|
||||||
client
|
client
|
||||||
.collection('users')
|
.collection('users')
|
||||||
@ -99,11 +101,11 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const authViaEmail = safeCatch(`authViaEmail`, (email: string, password: string) =>
|
const authViaEmail = safeCatch(`authViaEmail`, _logger, (email: string, password: string) =>
|
||||||
client.collection('users').authWithPassword(email, password)
|
client.collection('users').authWithPassword(email, password)
|
||||||
)
|
)
|
||||||
|
|
||||||
const refreshAuthToken = safeCatch(`refreshAuthToken`, () =>
|
const refreshAuthToken = safeCatch(`refreshAuthToken`, _logger, () =>
|
||||||
client.collection('users').authRefresh()
|
client.collection('users').authRefresh()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -123,6 +125,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
|
|
||||||
const getInstanceById = safeCatch(
|
const getInstanceById = safeCatch(
|
||||||
`getInstanceById`,
|
`getInstanceById`,
|
||||||
|
_logger,
|
||||||
(id: InstanceId): Promise<InstanceFields | undefined> =>
|
(id: InstanceId): Promise<InstanceFields | undefined> =>
|
||||||
client.collection('instances').getOne<InstanceFields>(id)
|
client.collection('instances').getOne<InstanceFields>(id)
|
||||||
)
|
)
|
||||||
@ -145,7 +148,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
return map(e.data.data, (v, k) => (v ? v.message : undefined)).filter((v) => !!v)
|
return map(e.data.data, (v, k) => (v ? v.message : undefined)).filter((v) => !!v)
|
||||||
}
|
}
|
||||||
|
|
||||||
const resendVerificationEmail = safeCatch(`resendVerificationEmail`, async () => {
|
const resendVerificationEmail = safeCatch(`resendVerificationEmail`, _logger, async () => {
|
||||||
const user = client.authStore.model
|
const user = client.authStore.model
|
||||||
assertExists(user, `Login required`)
|
assertExists(user, `Login required`)
|
||||||
await client.collection('users').requestVerification(user.email)
|
await client.collection('users').requestVerification(user.email)
|
||||||
@ -210,7 +213,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
unsub()
|
unsub()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const _check = safeCatch(`_checkVerified`, refreshAuthToken)
|
const _check = safeCatch(`_checkVerified`, _logger, refreshAuthToken)
|
||||||
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user