v0.8 support

This commit is contained in:
Ben Allfree 2022-11-02 10:05:52 -07:00
parent 1661bf6821
commit 870870e62b
42 changed files with 2823 additions and 916 deletions

View File

@ -1,4 +1,4 @@
FROM node:18-alpine as buildbox
FROM node:18-alpine as pockethost-buildbox
COPY --from=golang:1.19-alpine /usr/local/go/ /usr/local/go/
ENV PATH="/usr/local/go/bin:${PATH}"
RUN apk add python3 py3-pip make gcc musl-dev g++ bash

8
Dockerfile-prod Normal file
View File

@ -0,0 +1,8 @@
FROM node:18-alpine as pockethost-buildbox
COPY --from=golang:1.19-alpine /usr/local/go/ /usr/local/go/
ENV PATH="/usr/local/go/bin:${PATH}"
RUN apk add python3 py3-pip make gcc musl-dev g++ bash
WORKDIR /src
COPY . .
RUN yarn
RUN yarn build

16
docker/build.yaml Normal file
View File

@ -0,0 +1,16 @@
version: '3'
services:
build:
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: build
working_dir: /src
command: bash -c "yarn && yarn build"
volumes:
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src

View File

@ -1,39 +1,6 @@
version: '3'
services:
prepbox:
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: prepbox
working_dir: /src
command: bash -c "yarn"
volumes:
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
profiles: ['build']
buildbox:
environment:
- GOPATH=/go
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: buildbox
working_dir: /src
command: bash -c "yarn build"
volumes:
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
depends_on:
prepbox:
condition: service_completed_successfully
profiles: ['build']
www:
env_file:
- .env.local
@ -45,7 +12,6 @@ services:
working_dir: /src
command: bash -c "yarn dev:www --host=www"
volumes:
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
networks:
- app-network
@ -54,7 +20,6 @@ services:
depends_on:
daemon:
condition: service_started
profiles: ['serve']
daemon:
env_file:
- .env.local
@ -67,14 +32,11 @@ services:
restart: unless-stopped
volumes:
- ./mount/daemon/instances:/data
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
networks:
- app-network
ports:
- '9001:3000'
profiles: ['serve']
nginx:
image: nginx:mainline-alpine
container_name: nginx
@ -91,7 +53,6 @@ services:
- ./mount/nginx/ssl:/mount/nginx/ssl
networks:
- app-network
profiles: ['serve']
networks:
app-network:

16
docker/install.yaml Normal file
View File

@ -0,0 +1,16 @@
version: '3'
services:
prepbox:
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: prepbox
working_dir: /src
command: bash -c "yarn"
volumes:
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src

17
docker/migrate-dev.yaml Normal file
View File

@ -0,0 +1,17 @@
version: '3'
services:
prepbox:
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: prepbox
working_dir: /src
command: bash -c "yarn migrate"
volumes:
- ./mount/daemon/instances:/data
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src

17
docker/migrate-prod.yaml Normal file
View File

@ -0,0 +1,17 @@
version: '3'
services:
prepbox:
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: prepbox
working_dir: /src
command: bash -c "yarn && yarn build && yarn migrate"
volumes:
- /home/pockethost/data:/data
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src

View File

@ -1,41 +1,6 @@
version: '3'
services:
prepbox:
environment:
- GOPATH=/go
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: prepbox
working_dir: /src
command: bash -c "yarn"
volumes:
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
profiles: ['build']
buildbox:
environment:
- GOPATH=/go
env_file:
- .env.local
build:
context: ..
dockerfile: Dockerfile
container_name: buildbox
working_dir: /src
command: bash -c "yarn build"
volumes:
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
depends_on:
prepbox:
condition: service_completed_successfully
profiles: ['build']
www:
env_file:
- .env.local
@ -47,7 +12,6 @@ services:
working_dir: /src
command: bash -c "yarn start:www --host=www"
volumes:
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
networks:
- app-network
@ -56,7 +20,6 @@ services:
depends_on:
daemon:
condition: service_started
profiles: ['serve']
daemon:
env_file:
- .env.local
@ -71,14 +34,11 @@ services:
- SHELL=/bin/bash
volumes:
- /home/pockethost/data:/data
- ./mount/cache/go:/go
- ./mount/cache/yarn:/usr/local/share/.cache/yarn/v6
- ..:/src
networks:
- app-network
ports:
- '9001:3000'
profiles: ['serve']
nginx:
image: nginx:mainline-alpine
container_name: nginx
@ -95,7 +55,6 @@ services:
- ./mount/nginx/ssl:/mount/nginx/ssl
networks:
- app-network
profiles: ['serve']
networks:
app-network:

View File

@ -15,7 +15,9 @@
"dev:daemon": "cd packages/daemon && yarn dev",
"start": "concurrently 'yarn start:www' 'yarn start:daemon'",
"start:www": "cd packages/pockethost.io && yarn start",
"start:daemon": "cd packages/daemon && yarn start"
"start:daemon": "cd packages/daemon && yarn start",
"migrate": "yarn migrate:daemon",
"migrate:daemon": "cd packages/daemon && yarn migrate"
},
"workspaces": {
"packages": [

View File

@ -31,7 +31,7 @@
"cross-fetch": "^3.1.5",
"eventsource": "^2.0.2",
"find-up": "^6.3.0",
"pocketbase": "^0.7.1",
"pocketbase": "^0.8.0-rc1",
"prompts": "^2.4.2",
"tmp": "^0.2.1"
},

View File

@ -6,6 +6,6 @@
"dependencies": {
"@s-libs/micro-dash": "^14.1.0",
"nanoid": "^4.0.0",
"pocketbase": "^0.7.0"
"pocketbase": "^0.8.0-rc1"
}
}

View File

@ -1,25 +1,38 @@
import PocketBase from 'pocketbase'
import PocketBase, { Record } from 'pocketbase'
import { CollectionName, RecordId } from './schema'
export interface RecordSubscription<T = Record> {
action: string
record: T
}
export type RealtimeEventHandler<TRec> = (e: RecordSubscription<TRec>) => void
export const createRealtimeSubscriptionManager = (pocketbase: PocketBase) => {
const subscriptions: { [_: string]: number } = {}
const subscribe = <TRec>(slug: string, cb: (rec: TRec) => void) => {
const subscribeOne = <TRec>(
collection: CollectionName,
id: RecordId,
cb: (e: RecordSubscription<TRec>) => void
) => {
const slug = `${collection}/${id}`
if (subscriptions[slug]) {
subscriptions[slug]++
} else {
subscriptions[slug] = 1
pocketbase.realtime.subscribe(slug, (e) => {
pocketbase.collection(collection).subscribeOne<TRec>(id, (e) => {
console.log(`Realtime update`, { e })
cb(e.record as unknown as TRec)
cb(e)
})
}
return () => {
subscriptions[slug]--
if (subscriptions[slug] === 0) {
pocketbase.realtime.unsubscribe(slug)
pocketbase.collection(collection).unsubscribe(id)
}
}
}
return subscribe
return { subscribeOne }
}

View File

@ -1,5 +1,5 @@
export function assertExists<TType>(
v: unknown,
v: TType,
message = `Value does not exist`
): asserts v is NonNullable<TType> {
if (typeof v === 'undefined') {

View File

@ -1,3 +1,4 @@
export * from './assert'
export * from './RealtimeSubscriptionManager'
export * from './releases'
export * from './schema'

View File

@ -0,0 +1,142 @@
Abbey
Ace
Alice
Alvin
Amber
Andy
Bailey
Bart
Becca
Belle
Benny
Biscuit
Bonnie
Boo
Brooke
Bruno
Bunny
Buttons
Callie
Carmen
Cassidy
Charlotte
Chase
Cheeks
China
Cindy
Cinnamon
Cody
Cookie
Crumpet
Cupcake
Daisy
Dallas
Dancer
Dani
Dasher
Deckle
Dirk
Doily
Dresden
Droplet
Elliott
Emily
Emma
Fancy
Fifi
Fiona
Flake
Fluffy
Frankie
Frisky
Frosty
Frosty
Giggles
Ginger
Goofball
Gordon
Gretel
Gumdrop
Gus
Haley
Hansel
Harry
Honeybunches
Hugsgy
Humphrey
Hunny
Ike
Iris
Jackie
Jangles
Jellybean
Jenna
Jesse
Joey
Josie
Junior
Kelly
Kibbles
Killer
Kim
Kit Kat
Ladybug
Laurie
Leo
Lil Nibbler
Maddie
Maple Syrup
Marshmallow
Maximilian
Maxine
Mickey
Mimi
Mr. Gopher
Muffin
Nibbler
Nibbly
Nora
Opal
Oscar
Pansy
Peach
Philbert
Pierre
Pixie
Poofball
Powderpuff
Precious
Puddin
Quinn
Rainbow Dash
Ralph
Ripple
Rockin Robin
Rocky
Rosie
Rudolph
Runt
Rusty
Sadie
Sandy
Sawyer
Scout
Simon
Snowball
Snuggles
Sophie
Spike
Stella
Sugarplum
Sunny
Sweetie Pie
Sydney
Tadpole
Tammy
Tater Tot
Tori
Twinkle
Wally
Whiskers
Willow
Zelda

View File

@ -0,0 +1,57 @@
import { find, last } from '@s-libs/micro-dash'
import { assertExists } from './assert'
export const RELEASES = {
ermine: {
weight: 1,
versions: ['0.7.9', '0.7.8', '0.7.7'],
},
lollipop: {
weight: 2,
versions: ['0.8.0-rc1'],
},
}
export type PlatformId = keyof typeof RELEASES
export type VersionId = string
export const LATEST_PLATFORM: keyof typeof RELEASES = 'lollipop'
export const USE_LATEST_VERSION = 'latest'
function assertPlatform(platformId: string): asserts platformId is PlatformId {
const hasPlatform = platformId in RELEASES
if (!hasPlatform) {
throw new Error(`Expected ${platformId} to exist here`)
}
}
export { assertPlatform }
export const versionFor = (platformId: PlatformId, version: VersionId) => {
const platform = RELEASES[platformId]
if (version === USE_LATEST_VERSION) {
const _v = last(platform.versions)
assertExists(_v, `Expected ${platformId} to have versions (latest)`)
return _v
}
const _v = find(platform.versions, (v) => v === version)
assertExists(_v, `Expected ${platformId} to have version (${version})`)
return _v
}
export const binFor = (
platformId: PlatformId,
version: VersionId = 'latest'
) => {
const _version = versionFor(platformId, version)
return `pocketbase-${platformId}-${_version}`
}
export const humanVersion = (platformId: PlatformId, version: VersionId) => {
const platform = RELEASES[platformId]
const _version = versionFor(platformId, version)
const humanVersion =
version === USE_LATEST_VERSION ? `${_version} (latest)` : _version
return humanVersion
}

View File

@ -1,3 +1,5 @@
import { PlatformId, VersionId } from './releases'
export type RecordId = string
export type UserId = RecordId
export type InstanceId = RecordId
@ -8,6 +10,7 @@ export type IsoDate = string
export type ProcessId = number
export type Username = string
export type Password = string
export type CollectionName = string
export const pocketNow = () => new Date().toISOString()
@ -20,26 +23,21 @@ export enum InstanceStatus {
Failed = 'failed',
}
export type Instance_In = {
uid?: UserId
subdomain?: Subdomain
status?: InstanceStatus
}
export type PocketbaseRecord<TIdType extends RecordId> = {
id: TIdType
created: IsoDate
updated: IsoDate
}
export type Instance_Out = PocketbaseRecord<InstanceId> & {
uid: UserId
export type InstancesRecord = {
id: RecordId
subdomain: Subdomain
uid: UserId
status: InstanceStatus
platform: PlatformId
version: VersionId
}
export type Any_Record_Out = Instance_Out
export type InstancesRecord_New = Omit<InstancesRecord, 'id'>
export type Instance_Out_ByIdCollection = {
[_: InstanceId]: Instance_Out
export type UserRecord = {
id: RecordId
email: string
verified: boolean
}
export type InstanceRecordById = { [_: InstanceId]: InstancesRecord }

View File

@ -3,45 +3,24 @@
"version": "0.0.1",
"license": "MIT",
"scripts": {
"build": "mkdir -p dist && esbuild src/server.ts --bundle --platform=node > dist/server.js && echo 'Build complete' `date`",
"dev": "yarn build && concurrently 'yarn dev:watch' 'yarn dev:serve'",
"dev:watch": "chokidar 'src/**' '../pocketbase/src/**' -c 'yarn build'",
"dev:serve": "nodemon dist/server.js",
"start": "node dist/server.js"
},
"targets": {
"server": {
"engines": {
"node": ">=18"
},
"source": "src/server.ts",
"includeNodeModules": [
"@pockethost/common",
"get-port",
"pocketbase",
"@s-libs/micro-dash"
]
}
"build": "echo 'Build complete' `date`",
"dev": "tsx watch src/server.ts",
"start": "tsx src/server.ts",
"migrate": "tsx src/migrate/migrate.ts"
},
"dependencies": {
"@pockethost/common": "0.0.1",
"@s-libs/micro-dash": "^14.1.0",
"@types/http-proxy": "^1.17.9",
"@types/node": "^18.11.9",
"bottleneck": "^2.19.5",
"chokidar-cli": "^3.0.0",
"event-source-polyfill": "^1.0.31",
"get-port": "^6.1.2",
"http-proxy": "^1.18.1",
"node-fetch": "^3.2.10",
"pocketbase": "^0.7.0",
"ts-node": "^10.9.1"
"pocketbase": "^0.8.0-rc1"
},
"devDependencies": {
"chokidar-cli": "^3.0.0",
"concurrently": "^7.4.0",
"esbuild": "^0.15.11",
"nodemon": "^2.0.20",
"parcel": "^2.7.0",
"ts-node": "^10.9.1"
"tsx": "^3.11.0"
}
}

View File

@ -1,12 +1,9 @@
import { InstanceStatus } from '@pockethost/common'
import { binFor, InstanceStatus } from '@pockethost/common'
import { forEachRight, map } from '@s-libs/micro-dash'
import Bottleneck from 'bottleneck'
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
import { ChildProcessWithoutNullStreams } from 'child_process'
import getPort from 'get-port'
import fetch from 'node-fetch'
import {
DAEMON_PB_BIN_DIR,
DAEMON_PB_DATA_DIR,
DAEMON_PB_IDLE_TTL,
DAEMON_PB_PASSWORD,
DAEMON_PB_PORT_BASE,
@ -17,8 +14,10 @@ import {
PUBLIC_PB_PROTOCOL,
PUBLIC_PB_SUBDOMAIN,
} from './constants'
import { collections_001 } from './migrations'
import { createPbClient } from './PbClient'
import { mkInternalUrl } from './util/internal'
import { tryFetch } from './util/tryFetch'
import { _spawn } from './util/_spawn'
type Instance = {
process: ChildProcessWithoutNullStreams
@ -29,80 +28,15 @@ type Instance = {
startRequest: () => () => void
}
const tryFetch = (url: string) =>
new Promise<void>((resolve, reject) => {
const tryFetch = () => {
console.log(`Trying to connect to instance ${url} `)
fetch(url)
.then(() => {
console.log(`Connection to ${url} successful`)
resolve()
})
.catch((e) => {
console.error(`Could not connect to ${url}`)
setTimeout(tryFetch, 1000)
})
}
tryFetch()
})
const mkInternalAddress = (port: number) => `127.0.0.1:${port}`
const mkInternalUrl = (port: number) => `http://${mkInternalAddress(port)}`
export const createInstanceManger = async () => {
const instances: { [_: string]: Instance } = {}
const _spawn = async (cfg: {
subdomain: string
port: number
bin: string
}) => {
const { subdomain, port, bin } = cfg
const cmd = `${DAEMON_PB_BIN_DIR}/${bin}`
const args = [
`serve`,
`--dir`,
`${DAEMON_PB_DATA_DIR}/${subdomain}/pb_data`,
`--http`,
mkInternalAddress(port),
]
console.log(`Spawning ${subdomain}`, { cmd, args })
const ls = spawn(cmd, args)
ls.stdout.on('data', (data) => {
console.log(`${subdomain} stdout: ${data}`)
})
ls.stderr.on('data', (data) => {
console.error(`${subdomain} stderr: ${data}`)
})
ls.on('close', (code) => {
console.log(`${subdomain} closed with code ${code}`)
})
ls.on('exit', (code) => {
instances[subdomain]?.heartbeat(true)
delete instances[subdomain]
if (subdomain !== PUBLIC_PB_SUBDOMAIN) {
client.updateInstanceStatus(subdomain, InstanceStatus.Idle)
}
console.log(`${subdomain} exited with code ${code}`)
})
ls.on('error', (err) => {
console.log(`${subdomain} had error ${err}`)
})
await tryFetch(mkInternalUrl(port))
return ls
}
const coreInternalUrl = mkInternalUrl(DAEMON_PB_PORT_BASE)
const client = createPbClient(coreInternalUrl)
const mainProcess = await _spawn({
subdomain: PUBLIC_PB_SUBDOMAIN,
port: DAEMON_PB_PORT_BASE,
bin: 'pocketbase',
bin: binFor('lollipop'),
})
instances[PUBLIC_PB_SUBDOMAIN] = {
process: mainProcess,
@ -118,7 +52,6 @@ export const createInstanceManger = async () => {
await tryFetch(coreInternalUrl)
try {
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
await client.migrate(collections_001)
} catch (e) {
console.error(
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
@ -160,7 +93,7 @@ export const createInstanceManger = async () => {
console.log(`${subdomain} found in DB`)
const exclude = map(instances, (i) => i.port)
const newPort = await getPort({
port: 8090,
port: DAEMON_PB_PORT_BASE,
exclude,
}).catch((e) => {
console.error(`Failed to get port for ${subdomain}`)
@ -173,7 +106,15 @@ export const createInstanceManger = async () => {
const childProcess = await _spawn({
subdomain,
port: newPort,
bin: instance.bin || 'pocketbase',
bin: binFor(instance.platform, instance.version),
cleanup: (code) => {
instances[subdomain]?.heartbeat(true)
delete instances[subdomain]
if (subdomain !== PUBLIC_PB_SUBDOMAIN) {
client.updateInstanceStatus(subdomain, InstanceStatus.Idle)
}
console.log(`${subdomain} exited with code ${code}`)
},
})
const api: Instance = (() => {

View File

@ -1,19 +1,9 @@
import { InstanceStatus } from '@pockethost/common'
import PocketBase, { Record, User } from 'pocketbase'
import { Collection_Serialized } from './migrations'
const safeCatch = <TIn extends any[], TOut>(
name: string,
cb: (...args: TIn) => Promise<TOut>
) => {
return (...args: TIn) => {
console.log(`${name}`)
return cb(...args).catch((e: any) => {
console.error(`${name} failed: ${e}`)
throw e
})
}
}
import { InstancesRecord, InstanceStatus, UserRecord } from '@pockethost/common'
import { reduce } from '@s-libs/micro-dash'
import Bottleneck from 'bottleneck'
import PocketBase, { Collection } from 'pocketbase'
import { Collection_Serialized } from './migrate/migrations'
import { safeCatch } from './util/safeCatch'
export const createPbClient = (url: string) => {
console.log(`Initializing client: ${url}`)
@ -22,14 +12,15 @@ export const createPbClient = (url: string) => {
const adminAuthViaEmail = safeCatch(
`adminAuthViaEmail`,
(email: string, password: string) =>
client.admins.authViaEmail(email, password)
client.admins.authWithPassword(email, password)
)
const getInstanceBySubdomain = safeCatch(
`getInstanceBySubdomain`,
(subdomain: string): Promise<[Record, User] | []> =>
client.records
.getList(`instances`, 1, 1, {
(subdomain: string): Promise<[InstancesRecord, UserRecord] | []> =>
client
.collection('instances')
.getList<InstancesRecord>(1, 1, {
filter: `subdomain = '${subdomain}'`,
})
.then((recs) => {
@ -40,9 +31,12 @@ export const createPbClient = (url: string) => {
}
const [instance] = recs.items
if (!instance) return []
return client.users.getOne(instance.uid).then((user) => {
return [instance, user]
})
return client
.collection('users')
.getOne<UserRecord>(instance.uid)
.then((user) => {
return [instance, user]
})
})
)
@ -54,14 +48,41 @@ export const createPbClient = (url: string) => {
if (!instance) {
throw new Error(`Expected item here for ${subdomain}`)
}
await client.records.update(`instances`, instance.id, { status })
await client.collection('instances').update(instance.id, { status })
}
)
const migrate = safeCatch(
`migrate`,
async (collections: Collection_Serialized[]) => {
await client.collections.import(collections)
await client.collections.import(collections as Collection[])
}
)
const updateInstances = safeCatch(
'updateInstances',
async (cb: (rec: InstancesRecord) => Partial<InstancesRecord>) => {
const res = await client
.collection('instances')
.getFullList<InstancesRecord>(200)
const limiter = new Bottleneck({ maxConcurrent: 1 })
const promises = reduce(
res,
(c, r) => {
c.push(
limiter.schedule(() => {
const toUpdate = cb(r)
console.log(
`Updating instnace ${r.id} with ${JSON.stringify(toUpdate)}`
)
return client.collection('instances').update(r.id, toUpdate)
})
)
return c
},
[] as Promise<void>[]
)
await Promise.all(promises)
}
)
@ -70,5 +91,6 @@ export const createPbClient = (url: string) => {
getInstanceBySubdomain,
updateInstanceStatus,
migrate,
updateInstances,
}
}

View File

@ -0,0 +1,65 @@
import { binFor, InstanceStatus } from '@pockethost/common'
import { chdir } from 'process'
import {
DAEMON_PB_BIN_DIR,
DAEMON_PB_DATA_DIR,
DAEMON_PB_PASSWORD,
DAEMON_PB_PORT_BASE,
DAEMON_PB_USERNAME,
PUBLIC_PB_DOMAIN,
PUBLIC_PB_PROTOCOL,
PUBLIC_PB_SUBDOMAIN,
} from '../constants'
import { createPbClient } from '../PbClient'
import { mkInternalUrl } from '../util/internal'
import { tryFetch } from '../util/tryFetch'
import { _spawn } from '../util/_spawn'
import { collections_001 } from './migrations'
import { pexec } from './pexec'
const PB_BIN = `${DAEMON_PB_BIN_DIR}/${binFor('lollipop')}`
const DATA_ROOT = `${DAEMON_PB_DATA_DIR}/${PUBLIC_PB_SUBDOMAIN}`
;(async () => {
console.log(`Backing up`)
chdir(DATA_ROOT)
await pexec(`tar -czvf ${+new Date()}.tgz pb_data`)
console.log(`Upgrading`)
await pexec(`${PB_BIN} upgrade --dir=pb_data`)
// Add `platform` and `bin` required columns (migrate db json)
try {
const mainProcess = await _spawn({
subdomain: PUBLIC_PB_SUBDOMAIN,
port: DAEMON_PB_PORT_BASE,
bin: binFor('lollipop'),
})
try {
const coreInternalUrl = mkInternalUrl(DAEMON_PB_PORT_BASE)
const client = createPbClient(coreInternalUrl)
await tryFetch(coreInternalUrl)
await client.adminAuthViaEmail(DAEMON_PB_USERNAME, DAEMON_PB_PASSWORD)
await client.migrate(collections_001)
await client.updateInstances((instance) => {
return {
status: instance.status || InstanceStatus.Idle,
platform: instance.platform || 'ermine',
version: instance.version || 'latest',
}
})
} catch (e) {
console.error(
`***WARNING*** CANNOT AUTHENTICATE TO ${PUBLIC_PB_PROTOCOL}://${PUBLIC_PB_SUBDOMAIN}.${PUBLIC_PB_DOMAIN}/_/`
)
console.error(
`***WARNING*** LOG IN MANUALLY, ADJUST .env, AND RESTART DOCKER`
)
} finally {
console.log(`Exiting process`)
mainProcess.kill()
}
} catch (e) {
console.error(`${e}`)
}
})()

View File

@ -6,27 +6,90 @@ export type Collection_Serialized = Omit<Partial<Collection>, 'schema'> & {
export const collections_001: Collection_Serialized[] = [
{
id: 'systemprofiles0',
name: 'profiles',
system: true,
listRule: 'userId = @request.user.id',
viewRule: 'userId = @request.user.id',
createRule: 'userId = @request.user.id',
updateRule: 'userId = @request.user.id',
deleteRule: null,
id: 'etae8tuiaxl6xfv',
name: 'instances',
type: 'base',
system: false,
schema: [
{
id: 'pbfielduser',
name: 'userId',
type: 'user',
system: true,
id: 'qdtuuld1',
name: 'subdomain',
type: 'text',
system: false,
required: true,
unique: true,
options: {
maxSelect: 1,
cascadeDelete: true,
min: null,
max: 50,
pattern: '^[a-z][\\-a-z]+$',
},
},
{
id: 'rbj14krn',
name: 'uid',
type: 'relation',
system: false,
required: true,
unique: false,
options: {
maxSelect: 1,
collectionId: 'systemprofiles0',
cascadeDelete: false,
},
},
{
id: 'c2y74d7h',
name: 'status',
type: 'text',
system: false,
required: true,
unique: false,
options: {
min: null,
max: null,
pattern: '',
},
},
{
id: 'yxby5r6b',
name: 'platform',
type: 'text',
system: false,
required: true,
unique: false,
options: {
min: null,
max: null,
pattern: '',
},
},
{
id: '4ydffkv3',
name: 'version',
type: 'text',
system: false,
required: true,
unique: false,
options: {
min: null,
max: null,
pattern: '',
},
},
],
listRule: 'uid=@request.auth.id',
viewRule: 'uid = @request.auth.id',
createRule: "uid = @request.auth.id && (status = 'idle' || status = '')",
updateRule: null,
deleteRule: null,
options: {},
},
{
id: 'systemprofiles0',
name: 'users',
type: 'auth',
system: false,
schema: [
{
id: 'pbfieldname',
name: 'name',
@ -61,68 +124,20 @@ export const collections_001: Collection_Serialized[] = [
},
},
],
},
{
id: 'etae8tuiaxl6xfv',
name: 'instances',
system: false,
listRule: 'uid=@request.user.id',
viewRule: 'uid = @request.user.id',
createRule: "uid = @request.user.id && (status = 'idle' || status = '')",
updateRule: null,
listRule: 'id = @request.auth.id',
viewRule: 'id = @request.auth.id',
createRule: '',
updateRule: 'id = @request.auth.id',
deleteRule: null,
schema: [
{
id: 'qdtuuld1',
name: 'subdomain',
type: 'text',
system: false,
required: true,
unique: true,
options: {
min: null,
max: 50,
pattern: '^[a-z][\\-a-z]+$',
},
},
{
id: 'rbj14krn',
name: 'uid',
type: 'user',
system: false,
required: true,
unique: false,
options: {
maxSelect: 1,
cascadeDelete: false,
},
},
{
id: 'c2y74d7h',
name: 'status',
type: 'text',
system: false,
required: false,
unique: false,
options: {
min: null,
max: null,
pattern: '',
},
},
{
id: '3rinhcnt',
name: 'bin',
type: 'text',
system: false,
required: false,
unique: false,
options: {
min: 0,
max: 30,
pattern: '',
},
},
],
options: {
allowEmailAuth: true,
allowOAuth2Auth: true,
allowUsernameAuth: false,
exceptEmailDomains: null,
manageRule: null,
minPasswordLength: 8,
onlyEmailDomains: null,
requireEmail: true,
},
},
]

View File

@ -0,0 +1,16 @@
import { exec } from 'child_process'
export const pexec = (cmd: string) => {
return new Promise<void>((resolve, reject) => {
console.log(cmd)
exec(cmd, (err, stdout, stderr) => {
console.log(stdout)
console.error(stderr)
if (err) {
reject(err)
return
}
resolve()
})
})
}

View File

@ -0,0 +1,55 @@
import { spawn } from 'child_process'
import { existsSync } from 'fs'
import { DAEMON_PB_BIN_DIR, DAEMON_PB_DATA_DIR } from '../constants'
import { mkInternalAddress, mkInternalUrl } from './internal'
import { tryFetch } from './tryFetch'
export const _spawn = async (cfg: {
subdomain: string
port: number
bin: string
cleanup?: (code: number | null) => void
}) => {
const { subdomain, port, bin, cleanup } = cfg
const cmd = `${DAEMON_PB_BIN_DIR}/${bin}`
if (!existsSync(cmd)) {
throw new Error(
`PocketBase binary (${bin}) not found. Contact pockethost.io.`
)
}
const args = [
`serve`,
`--dir`,
`${DAEMON_PB_DATA_DIR}/${subdomain}/pb_data`,
`--http`,
mkInternalAddress(port),
]
console.log(`Spawning ${subdomain}`, { cmd, args })
const ls = spawn(cmd, args)
ls.stdout.on('data', (data) => {
console.log(`${subdomain} stdout: ${data}`)
})
ls.stderr.on('data', (data) => {
console.error(`${subdomain} stderr: ${data}`)
})
ls.on('close', (code) => {
console.log(`${subdomain} closed with code ${code}`)
})
ls.on(
'exit',
cleanup ||
((code) => {
console.log(`Exited with ${code}`)
})
)
ls.on('error', (err) => {
console.log(`${subdomain} had error ${err}`)
})
await tryFetch(mkInternalUrl(port))
return ls
}

View File

@ -0,0 +1,3 @@
export const mkInternalAddress = (port: number) => `127.0.0.1:${port}`
export const mkInternalUrl = (port: number) =>
`http://${mkInternalAddress(port)}`

View File

@ -0,0 +1,12 @@
export const safeCatch = <TIn extends any[], TOut>(
name: string,
cb: (...args: TIn) => Promise<TOut>
) => {
return (...args: TIn) => {
console.log(`${name}`)
return cb(...args).catch((e: any) => {
console.error(`${name} failed: ${e}`)
throw e
})
}
}

View File

@ -0,0 +1,18 @@
import fetch from 'node-fetch'
export const tryFetch = (url: string) =>
new Promise<void>((resolve, reject) => {
const tryFetch = () => {
console.log(`Trying to connect to instance ${url} `)
fetch(url)
.then(() => {
console.log(`Connection to ${url} successful`)
resolve()
})
.catch((e) => {
console.error(`Could not connect to ${url}`)
setTimeout(tryFetch, 1000)
})
}
tryFetch()
})

21
packages/pocketbase/build.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
CGO_ENABLED=0
SRC=src
TARGET=build/$PLATFORM/$VERSION
DIST=./dist
rm -rf $TARGET
mkdir -p $TARGET
mkdir -p $DIST
cp -r $SRC/* $TARGET
echo `pwd`
cd $TARGET
echo "Building ${BIN}"
echo "Fetching pocketbase version $VERSION"
go get github.com/pocketbase/pocketbase@v$VERSION
echo "Tidying modules"
go mod tidy
echo "Building ${BIN}"
go build -o ../../../$DIST/$BIN
echo "Build ${BIN} complete" `date`

View File

@ -0,0 +1,39 @@
import { binFor, RELEASES } from '@pockethost/common'
import { forEach } from '@s-libs/micro-dash'
import Bottleneck from 'bottleneck'
import { exec } from 'child_process'
import Listr from 'listr'
const limiter = new Bottleneck({ maxConcurrent: 10 })
const pexec = (cmd: string, cwd = __dirname) => {
return new Promise<void>((resolve, reject) => {
console.log(cmd)
exec(cmd, { cwd }, (err, stdout, stderr) => {
console.log(stdout)
console.error(stderr)
if (err) {
reject(err)
return
}
resolve()
})
})
}
const tasks = new Listr([], { concurrent: true })
forEach(RELEASES, (info, platform) => {
forEach(info.versions, (version) => {
const cmd = `VERSION=${version} PLATFORM=${platform} BIN=${binFor(
platform,
version
)} ./build.sh`
tasks.add({
title: `${platform}: ${version}`,
task: () => limiter.schedule(() => pexec(cmd)),
})
})
})
tasks.run().catch((err) => {
console.error(err)
})

View File

@ -1,16 +1,20 @@
{
"name": "enchanted-pocketbase",
"name": "@pockethost/pocketbase",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"build": "cd src && CGO_ENABLED=0 go build -o ../dist/pocketbase && echo 'Build complete' `date`",
"build:arm64": "cd src && GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o ../dist/pocketbase && echo 'Build complete' `date`",
"build:beta:arm64": "cd src && GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o ../dist/pocketbase-beta && echo 'Build complete' `date`",
"build:386": "cd src && GOOS=linux GOARCH=386 CGO_ENABLED=0 go build -o ../dist/pocketbase && echo 'Build complete' `date`",
"build:beta:386": "cd src && GOOS=linux GOARCH=386 CGO_ENABLED=0 go build -o ../dist/pocketbase-beta && echo 'Build complete' `date`",
"watch:beta:386": "chokidar 'src/**/*' -c 'yarn build:beta:386' --initial"
"build": "tsx build.ts"
},
"devDependencies": {
"chokidar-cli": "^3.0.0"
"chokidar-cli": "^3.0.0",
"tsx": "^3.11.0"
},
"dependencies": {
"@pockethost/common": "0.0.1",
"@s-libs/micro-dash": "^14.1.0",
"@types/listr": "^0.14.4",
"@types/node": "^18.11.9",
"bottleneck": "^2.19.5",
"listr": "^0.14.3"
}
}

View File

@ -2,31 +2,31 @@ module pocketbase
go 1.19
require github.com/pocketbase/pocketbase v0.7.9
require github.com/pocketbase/pocketbase v0.8.0-rc1
require (
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go v1.44.102 // indirect
github.com/aws/aws-sdk-go-v2 v1.16.16 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.8 // indirect
github.com/aws/aws-sdk-go-v2/config v1.17.7 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.12.20 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.17 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.33 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.23 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.17 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.14 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.18 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.17 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.27.11 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.23 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.16.19 // indirect
github.com/aws/smithy-go v1.13.3 // indirect
github.com/aws/aws-sdk-go v1.44.126 // indirect
github.com/aws/aws-sdk-go-v2 v1.17.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 // indirect
github.com/aws/aws-sdk-go-v2/config v1.17.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.12.23 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.19 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.19 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.25 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.17.1 // indirect
github.com/aws/smithy-go v1.13.4 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.3.4 // indirect
github.com/fatih/color v1.13.0 // indirect
@ -38,48 +38,48 @@ require (
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/wire v0.5.0 // indirect
github.com/googleapis/gax-go/v2 v2.5.1 // indirect
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/pocketbase/dbx v1.6.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/pocketbase/dbx v1.7.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.5.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.23.0 // indirect
gocloud.dev v0.26.0 // indirect
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 // indirect
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220921155015-db77216a4ee9 // indirect
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45 // indirect
golang.org/x/tools v0.1.12 // indirect
gocloud.dev v0.27.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/image v0.1.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/oauth2 v0.1.0 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/term v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.1.0 // indirect
golang.org/x/tools v0.2.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.96.0 // indirect
google.golang.org/api v0.101.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.39.0 // indirect
modernc.org/ccgo/v3 v3.16.9 // indirect
modernc.org/libc v1.19.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8 // indirect
modernc.org/libc v1.21.4 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.19.1 // indirect
modernc.org/sqlite v1.19.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,103 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
"resolveJsonModule": true /* Enable importing .json files. */,
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@ -3,7 +3,7 @@
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"dev": "vite dev --force",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@ -30,7 +30,7 @@
"@s-libs/micro-dash": "12",
"@types/js-cookie": "^3.0.2",
"js-cookie": "^3.0.1",
"pocketbase": "^0.7.0",
"pocketbase": "^0.8.0-rc1",
"random-word-slugs": "^0.1.6",
"sass": "^1.54.9",
"svelte-highlight": "^6.2.1"

View File

@ -3,11 +3,13 @@ import {
assertExists,
createRealtimeSubscriptionManager,
type InstanceId,
type Instance_In,
type Instance_Out
type InstancesRecord,
type InstancesRecord_New,
type RealtimeEventHandler,
type UserRecord
} from '@pockethost/common'
import { keys, map } from '@s-libs/micro-dash'
import PocketBase, { Admin, BaseAuthStore, ClientResponseError, Record, User } from 'pocketbase'
import PocketBase, { Admin, BaseAuthStore, ClientResponseError, Record } from 'pocketbase'
import type { Unsubscriber } from 'svelte/store'
import { safeCatch } from '../util/safeCatch'
@ -16,7 +18,7 @@ export type AuthChangeHandler = (user: BaseAuthStore) => void
export type AuthToken = string
export type AuthStoreProps = {
token: AuthToken
model: User | null
model: UserRecord | null
isValid: boolean
}
@ -27,66 +29,76 @@ export const createPocketbaseClient = (url: string) => {
const { authStore } = client
const user = () => authStore.model
const user = () => authStore.model as AuthStoreProps['model']
const isLoggedIn = () => authStore.isValid
const logOut = () => authStore.clear()
const createUser = safeCatch(`createUser`, (email: string, password: string) =>
client.users.create({
email,
password,
passwordConfirm: password
})
client
.collection('users')
.create({
email,
password,
passwordConfirm: password
})
.then(() => {
console.log(`Sending verification email to ${email}`)
return client.collection('users').requestVerification(email)
})
)
const authViaEmail = safeCatch(`authViaEmail`, (email: string, password: string) =>
client.users.authViaEmail(email, password)
client.collection('users').authWithPassword(email, password)
)
const refreshAuthToken = safeCatch(`refreshAuthToken`, () => client.users.refresh())
const refreshAuthToken = safeCatch(`refreshAuthToken`, () =>
client.collection('users').authRefresh()
)
const createInstance = safeCatch(
`createInstance`,
(payload: Instance_In): Promise<Instance_Out> => {
return client.records.create('instances', payload).then((r) => r as unknown as Instance_Out)
(payload: InstancesRecord_New): Promise<InstancesRecord> => {
return client.collection('instances').create<InstancesRecord>(payload)
}
)
const getInstanceById = safeCatch(
`getInstanceById`,
(id: InstanceId): Promise<Instance_Out | undefined> =>
client.records.getOne('instances', id).then((r) => r as unknown as Instance_Out)
(id: InstanceId): Promise<InstancesRecord | undefined> =>
client.collection('instances').getOne<InstancesRecord>(id)
)
const subscribe = createRealtimeSubscriptionManager(client)
const { subscribeOne } = createRealtimeSubscriptionManager(client)
const watchInstanceById = (id: InstanceId, cb: (rec: Instance_Out) => void): Unsubscriber => {
const slug = `instances/${id}`
getInstanceById(id).then((v) => {
if (!v) return
cb(v)
const watchInstanceById = (
id: InstanceId,
cb: RealtimeEventHandler<InstancesRecord>
): Unsubscriber => {
getInstanceById(id).then((record) => {
console.log(`Got instnace`, record)
assertExists(record, `Expected instance ${id} here`)
cb({ action: 'init', record })
})
return subscribe(slug, cb)
return subscribeOne('instances', id, cb)
}
const getAllInstancesById = safeCatch(`getAllInstancesById`, async () =>
(
await client.records.getFullList('instances').catch((e) => {
console.error(`getAllInstancesById failed with ${e}`)
throw e
})
await client
.collection('instances')
.getFullList()
.catch((e) => {
console.error(`getAllInstancesById failed with ${e}`)
throw e
})
).reduce((c, v) => {
c[v.id] = v
return c
}, {} as Record)
)
const setInstance = safeCatch(`setInstance`, (instanceId: InstanceId, fields: Instance_In) => {
return client.records.update('instances', instanceId, fields)
})
const parseError = (e: Error): string[] => {
if (!(e instanceof ClientResponseError)) return [e.message]
if (e.data.message && keys(e.data.data).length === 0) return [e.data.message]
@ -96,13 +108,14 @@ export const createPocketbaseClient = (url: string) => {
const resendVerificationEmail = safeCatch(`resendVerificationEmail`, async () => {
const user = client.authStore.model
assertExists(user, `Login required`)
await client.users.requestVerification(user.email)
await client.collection('users').requestVerification(user.email)
})
const getAuthStoreProps = (): AuthStoreProps => {
const { token, model, isValid } = client.authStore
const { token, model, isValid } = client.authStore as AuthStoreProps
// console.log(`curent authstore`, { token, model, isValid })
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`)
return {
token,
model,
@ -132,7 +145,10 @@ export const createPocketbaseClient = (url: string) => {
* out of date, or fields in the user record may have changed in the backend.
*/
refreshAuthToken()
.catch(() => {})
.catch((e) => {
console.error(`Clearing auth store: ${e}`)
client.authStore.clear()
})
.finally(() => {
fireAuthChange(getAuthStoreProps())
})
@ -154,7 +170,7 @@ export const createPocketbaseClient = (url: string) => {
unsub()
return
}
const _check = safeCatch(`_checkVerified`, () => client.users.refresh())
const _check = safeCatch(`_checkVerified`, refreshAuthToken)
setTimeout(_check, 1000)
// FIXME - THIS DOES NOT WORK, WE HAVE TO POLL INSTEAD. FIX IN V0.8
@ -169,7 +185,6 @@ export const createPocketbaseClient = (url: string) => {
return {
getAuthStoreProps,
parseError,
subscribe,
getInstanceById,
createInstance,
authViaEmail,
@ -180,7 +195,6 @@ export const createPocketbaseClient = (url: string) => {
user,
watchInstanceById,
getAllInstancesById,
setInstance,
resendVerificationEmail
}
}

View File

@ -5,25 +5,27 @@
import { PUBLIC_PB_PROTOCOL } from '$env/static/public'
import { PUBLIC_PB_DOMAIN } from '$src/env'
import { client } from '$src/pocketbase'
import { humanVersion, type InstancesRecord } from '@pockethost/common'
import { assertExists } from '@pockethost/common/src/assert'
import type { Instance_Out } from '@pockethost/common/src/schema'
import { onDestroy, onMount } from 'svelte'
import type { Unsubscriber } from 'svelte/store'
const { instanceId } = $page.params
let instance: Instance_Out | undefined
let instance: InstancesRecord | undefined
let url: string
let code: string = ''
let unsub: Unsubscriber = () => {}
onMount(() => {
onMount(async () => {
const { watchInstanceById } = client()
unsub = watchInstanceById(instanceId, (r) => {
instance = r
assertExists(instance, `Expected instance here`)
const { subdomain } = instance
console.log(`Handling instance update`, r)
const { action, record } = r
instance = record
assertExists(record, `Expected instance here`)
const { subdomain } = record
url = `${PUBLIC_PB_PROTOCOL}://${subdomain}.${PUBLIC_PB_DOMAIN}`
code = `const url = '${url}'\nconst client = new PocketBase(url)`
})
@ -50,6 +52,10 @@
JavaScript:
<CodeSample {code} />
</div>
<div>
Running {instance.platform}
{humanVersion(instance.platform, instance.version)}
</div>
{/if}
<div class="text-center py-5">

View File

@ -5,7 +5,7 @@
import { PUBLIC_PB_DOMAIN } from '$src/env'
import { client } from '$src/pocketbase'
import { createCleanupManagerSync } from '$util/CleanupManager'
import type { Instance_Out, Instance_Out_ByIdCollection } from '@pockethost/common/src/schema'
import { humanVersion, type InstanceRecordById, type InstancesRecord } from '@pockethost/common'
import { forEach, values } from '@s-libs/micro-dash'
import { onDestroy, onMount } from 'svelte'
import { fade } from 'svelte/transition'
@ -13,18 +13,18 @@
// Wait for the instance call to complete before rendering the UI
let hasPageLoaded = false
let apps: Instance_Out_ByIdCollection = {}
let apps: InstanceRecordById = {}
// This will update when the `apps` value changes
$: isFirstApplication = values(apps).length === 0
let appsArray: Instance_Out[]
let appsArray: InstancesRecord[]
$: {
appsArray = values(apps)
}
const cm = createCleanupManagerSync()
let _touch = 0 // This is a fake var because without it the watcher callback will not update UI when the apps object changes
const _update = (_apps: Instance_Out_ByIdCollection) => {
const _update = (_apps: InstanceRecordById) => {
apps = _apps
_touch++
}
@ -38,7 +38,8 @@
const instanceId = app.id
const unsub = watchInstanceById(instanceId, (r) => {
_update({ ...apps, [r.id]: r })
const { action, record } = r
_update({ ...apps, [record.id]: record })
})
cm.add(unsub)
})
@ -74,6 +75,8 @@
</div>
<h2 class="mb-4 font-monospace">{app.subdomain}</h2>
Running {app.platform}
{humanVersion(app.platform, app.version)}
<div class="d-flex justify-content-around">
<a href={`/app/instances/${app.id}`} class="btn btn-light">

View File

@ -1,6 +1,6 @@
import { goto } from '$app/navigation'
import { client } from '$src/pocketbase'
import { InstanceStatus } from '@pockethost/common'
import { InstanceStatus, LATEST_PLATFORM, USE_LATEST_VERSION } from '@pockethost/common'
export type FormErrorHandler = (value: string) => void
@ -83,7 +83,9 @@ export const handleCreateNewInstance = async (
const record = await createInstance({
subdomain: instanceName,
uid: id,
status: InstanceStatus.Idle
status: InstanceStatus.Idle,
platform: LATEST_PLATFORM,
version: USE_LATEST_VERSION
})
await goto(`/app/instances/${record.id}`)

View File

@ -93,9 +93,9 @@ git clone git@github.com:benallfree/pockethost.git
cd pockethost/docker
cp .env-template-dev .env.local # Edit as needed - defaults should work
cd ..
docker-compose -f docker/docker-compose-dev.yaml build
docker-compose -f docker/docker-compose-dev.yaml --profile=build up --remove-orphans
docker-compose -f docker/docker-compose-dev.yaml --profile=serve up --remove-orphans
docker-compose -f docker/build.yaml up --remove-orphans
docker-compose -f docker/migrate.yaml up --remove-orphans
docker-compose -f docker/dev.yaml up --remove-orphans
open https://pockethost.test
```
@ -108,10 +108,9 @@ git clone git@github.com:benallfree/pockethost.git
cd pockethost/docker
cp .env-template-prod .env.local # Edit as needed - defaults should work
cd ..
docker-compose -f docker/docker-compose-prod.yaml build
# Use 'buildbox' to test your build before launching service
docker-compose -f docker/docker-compose-prod.yaml --profile=build up --remove-orphans
docker-compose -f docker/docker-compose-prod.yaml --profile=serve up --remove-orphans
docker-compose -f docker/build.yaml up --remove-orphans
docker-compose -f docker/migrate.yaml up --remove-orphans
docker-compose -f docker/prod.yaml up --remove-orphans
```
**2. Refresh Certbot**
@ -131,9 +130,10 @@ open https://pockethost.io
# Release History
**next**
**0.4.0**
- [ ]
- [x] PocketBase 0.8 support
- [x] Introduced "platforms" concept for version control
**0.3.2**

759
yarn.lock

File diff suppressed because it is too large Load Diff