first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { sdk } from '../sdk'
|
||||
import { storeJson } from '../fileModels/store.json'
|
||||
import { i18n } from '../i18n'
|
||||
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
const inputSpec = InputSpec.of({
|
||||
igdbClientId: Value.text({
|
||||
name: i18n('IGDB Client ID'),
|
||||
description: i18n('Optional Twitch application client ID used by IGDB.'),
|
||||
required: false,
|
||||
default: null,
|
||||
}),
|
||||
igdbClientSecret: Value.text({
|
||||
name: i18n('IGDB Client Secret'),
|
||||
description: i18n('Optional Twitch application secret used by IGDB.'),
|
||||
required: false,
|
||||
masked: true,
|
||||
default: null,
|
||||
}),
|
||||
mobygamesApiKey: Value.text({
|
||||
name: i18n('MobyGames API Key'),
|
||||
description: i18n('Optional API key used to retrieve MobyGames metadata.'),
|
||||
required: false,
|
||||
masked: true,
|
||||
default: null,
|
||||
}),
|
||||
steamGridDbApiKey: Value.text({
|
||||
name: i18n('SteamGridDB API Key'),
|
||||
description: i18n('Optional API key used to retrieve SteamGridDB artwork.'),
|
||||
required: false,
|
||||
masked: true,
|
||||
default: null,
|
||||
}),
|
||||
})
|
||||
|
||||
export const configure = sdk.Action.withInput(
|
||||
'configure',
|
||||
async () => ({
|
||||
name: i18n('Configure Metadata Providers'),
|
||||
description: i18n(
|
||||
'Save optional API credentials supported by RomM 3.5.0.',
|
||||
),
|
||||
warning: i18n(
|
||||
'Saved values are passed to RomM after the service is restarted. Leave a field blank to remove it.',
|
||||
),
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
inputSpec,
|
||||
async () => ({
|
||||
igdbClientId: null,
|
||||
igdbClientSecret: null,
|
||||
mobygamesApiKey: null,
|
||||
steamGridDbApiKey: null,
|
||||
}),
|
||||
async ({ effects, input }) => {
|
||||
await storeJson.merge(effects, {
|
||||
igdbClientId: input.igdbClientId ?? '',
|
||||
igdbClientSecret: input.igdbClientSecret ?? '',
|
||||
mobygamesApiKey: input.mobygamesApiKey ?? '',
|
||||
steamGridDbApiKey: input.steamGridDbApiKey ?? '',
|
||||
})
|
||||
|
||||
return {
|
||||
version: '1',
|
||||
title: i18n('Configuration Saved'),
|
||||
message: i18n(
|
||||
'Restart RomM to apply the metadata provider configuration.',
|
||||
),
|
||||
result: null,
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
import { sdk } from '../sdk'
|
||||
import { configure } from './configure'
|
||||
|
||||
export const actions = sdk.Actions.of().addAction(configure)
|
||||
@@ -0,0 +1,23 @@
|
||||
import { sdk } from './sdk'
|
||||
import { storeJson } from './fileModels/store.json'
|
||||
import { databaseName, databaseUser } from './utils'
|
||||
|
||||
export const { createBackup, restoreInit } = sdk.setupBackups(
|
||||
async ({ effects }) =>
|
||||
sdk.Backups.withMysqlDump({
|
||||
imageId: 'mariadb',
|
||||
dbVolume: 'database',
|
||||
datadir: '/var/lib/mysql',
|
||||
database: databaseName,
|
||||
user: databaseUser,
|
||||
password: async () => {
|
||||
const store = await storeJson.read().once()
|
||||
if (!store?.databasePassword) {
|
||||
throw new Error('RomM database password is missing')
|
||||
}
|
||||
return store.databasePassword
|
||||
},
|
||||
engine: 'mariadb',
|
||||
readyTimeout: 120000,
|
||||
}).addVolume('main'),
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
import { sdk } from './sdk'
|
||||
|
||||
export const setDependencies = sdk.setupDependencies(async () => ({}))
|
||||
@@ -0,0 +1,19 @@
|
||||
import { FileHelper, z } from '@start9labs/start-sdk'
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
const shape = z
|
||||
.object({
|
||||
databaseRootPassword: z.string().catch(''),
|
||||
databasePassword: z.string().catch(''),
|
||||
authSecret: z.string().catch(''),
|
||||
igdbClientId: z.string().catch(''),
|
||||
igdbClientSecret: z.string().catch(''),
|
||||
mobygamesApiKey: z.string().catch(''),
|
||||
steamGridDbApiKey: z.string().catch(''),
|
||||
})
|
||||
.strip()
|
||||
|
||||
export const storeJson = FileHelper.json(
|
||||
{ base: sdk.volumes.main, subpath: './store.json' },
|
||||
shape,
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
export const DEFAULT_LANG = 'en_US'
|
||||
|
||||
const dict = {
|
||||
Database: 0,
|
||||
'MariaDB is ready': 1,
|
||||
'MariaDB is not ready': 2,
|
||||
'Web Interface': 3,
|
||||
'RomM is ready': 4,
|
||||
'RomM is not ready': 5,
|
||||
'RomM Web Interface': 6,
|
||||
'Browse, scan, and manage your game library': 7,
|
||||
'IGDB Client ID': 8,
|
||||
'Optional Twitch application client ID used by IGDB.': 9,
|
||||
'IGDB Client Secret': 10,
|
||||
'Optional Twitch application secret used by IGDB.': 11,
|
||||
'MobyGames API Key': 12,
|
||||
'Optional API key used to retrieve MobyGames metadata.': 13,
|
||||
'SteamGridDB API Key': 14,
|
||||
'Optional API key used to retrieve SteamGridDB artwork.': 15,
|
||||
'Configure Metadata Providers': 16,
|
||||
'Save optional API credentials supported by RomM 3.5.0.': 17,
|
||||
'Saved values are passed to RomM after the service is restarted. Leave a field blank to remove it.': 18,
|
||||
'Configuration Saved': 19,
|
||||
'Restart RomM to apply the metadata provider configuration.': 20,
|
||||
} as const
|
||||
|
||||
export type I18nKey = keyof typeof dict
|
||||
export type LangDict = Record<(typeof dict)[I18nKey], string>
|
||||
export default dict
|
||||
@@ -0,0 +1,3 @@
|
||||
import { LangDict } from './default'
|
||||
|
||||
export default {} satisfies Record<string, LangDict>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { setupI18n } from '@start9labs/start-sdk'
|
||||
import defaultDict, { DEFAULT_LANG } from './dictionaries/default'
|
||||
import translations from './dictionaries/translations'
|
||||
|
||||
export const i18n = setupI18n(defaultDict, translations, DEFAULT_LANG)
|
||||
@@ -0,0 +1,10 @@
|
||||
export { createBackup } from './backups'
|
||||
export { main } from './main'
|
||||
export { init, uninit } from './init'
|
||||
export { actions } from './actions'
|
||||
|
||||
import { buildManifest } from '@start9labs/start-sdk'
|
||||
import { manifest as sdkManifest } from './manifest'
|
||||
import { versionGraph } from './versions'
|
||||
|
||||
export const manifest = buildManifest(versionGraph, sdkManifest)
|
||||
@@ -0,0 +1,18 @@
|
||||
import { actions } from '../actions'
|
||||
import { restoreInit } from '../backups'
|
||||
import { setDependencies } from '../dependencies'
|
||||
import { setInterfaces } from '../interfaces'
|
||||
import { sdk } from '../sdk'
|
||||
import { versionGraph } from '../versions'
|
||||
import { seedStore } from './seedStore'
|
||||
|
||||
export const init = sdk.setupInit(
|
||||
restoreInit,
|
||||
versionGraph,
|
||||
seedStore,
|
||||
setInterfaces,
|
||||
setDependencies,
|
||||
actions,
|
||||
)
|
||||
|
||||
export const uninit = sdk.setupUninit(versionGraph)
|
||||
@@ -0,0 +1,16 @@
|
||||
import { utils } from '@start9labs/start-sdk'
|
||||
import { storeJson } from '../fileModels/store.json'
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
const secret = () =>
|
||||
utils.getDefaultString({ charset: 'a-z,A-Z,0-9', len: 64 })
|
||||
|
||||
export const seedStore = sdk.setupOnInit(async (effects, kind) => {
|
||||
if (kind !== 'install') return
|
||||
|
||||
await storeJson.merge(effects, {
|
||||
databaseRootPassword: secret(),
|
||||
databasePassword: secret(),
|
||||
authSecret: secret(),
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
import { sdk } from './sdk'
|
||||
import { i18n } from './i18n'
|
||||
import { uiHostId, uiPort } from './utils'
|
||||
|
||||
export const setInterfaces = sdk.setupInterfaces(async ({ effects }) => {
|
||||
const host = sdk.MultiHost.of(effects, uiHostId)
|
||||
const origin = await host.bindPort(uiPort, {
|
||||
protocol: 'http',
|
||||
preferredExternalPort: 80,
|
||||
})
|
||||
const ui = sdk.createInterface(effects, {
|
||||
name: i18n('RomM Web Interface'),
|
||||
id: 'ui',
|
||||
description: i18n('Browse, scan, and manage your game library'),
|
||||
type: 'ui',
|
||||
masked: false,
|
||||
schemeOverride: null,
|
||||
username: null,
|
||||
path: '',
|
||||
query: {},
|
||||
})
|
||||
|
||||
return [await origin.export([ui])]
|
||||
})
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
import { sdk } from './sdk'
|
||||
import { i18n } from './i18n'
|
||||
import { storeJson } from './fileModels/store.json'
|
||||
import {
|
||||
databaseName,
|
||||
databasePort,
|
||||
databaseUser,
|
||||
mainMounts,
|
||||
uiPort,
|
||||
} from './utils'
|
||||
|
||||
export const main = sdk.setupMain(async ({ effects }) => {
|
||||
const store = await storeJson.read().const(effects)
|
||||
if (
|
||||
!store?.databaseRootPassword ||
|
||||
!store.databasePassword ||
|
||||
!store.authSecret
|
||||
) {
|
||||
throw new Error('RomM internal secrets have not been initialized')
|
||||
}
|
||||
|
||||
const mariadb = sdk.SubContainer.of(
|
||||
effects,
|
||||
{ imageId: 'mariadb' },
|
||||
sdk.Mounts.of().mountVolume({
|
||||
volumeId: 'database',
|
||||
subpath: null,
|
||||
mountpoint: '/var/lib/mysql',
|
||||
readonly: false,
|
||||
}),
|
||||
'romm-mariadb-sub',
|
||||
)
|
||||
|
||||
const romm = sdk.SubContainer.of(
|
||||
effects,
|
||||
{ imageId: 'romm' },
|
||||
sdk.Mounts.of()
|
||||
.mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: 'library',
|
||||
mountpoint: mainMounts.library,
|
||||
readonly: false,
|
||||
})
|
||||
.mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: 'resources',
|
||||
mountpoint: mainMounts.resources,
|
||||
readonly: false,
|
||||
})
|
||||
.mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: 'assets',
|
||||
mountpoint: mainMounts.assets,
|
||||
readonly: false,
|
||||
})
|
||||
.mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: 'config',
|
||||
mountpoint: mainMounts.config,
|
||||
readonly: false,
|
||||
})
|
||||
.mountVolume({
|
||||
volumeId: 'main',
|
||||
subpath: 'redis-data',
|
||||
mountpoint: mainMounts.redis,
|
||||
readonly: false,
|
||||
}),
|
||||
'romm-app-sub',
|
||||
)
|
||||
|
||||
return sdk.Daemons.of(effects)
|
||||
.addDaemon('mariadb', {
|
||||
subcontainer: mariadb,
|
||||
exec: {
|
||||
command: sdk.useEntrypoint(),
|
||||
runAsInit: true,
|
||||
env: {
|
||||
MARIADB_ROOT_PASSWORD: store.databaseRootPassword,
|
||||
MARIADB_DATABASE: databaseName,
|
||||
MARIADB_USER: databaseUser,
|
||||
MARIADB_PASSWORD: store.databasePassword,
|
||||
},
|
||||
},
|
||||
ready: {
|
||||
display: i18n('Database'),
|
||||
gracePeriod: 120000,
|
||||
fn: () =>
|
||||
sdk.healthCheck.checkPortListening(effects, databasePort, {
|
||||
successMessage: i18n('MariaDB is ready'),
|
||||
errorMessage: i18n('MariaDB is not ready'),
|
||||
}),
|
||||
},
|
||||
requires: [],
|
||||
})
|
||||
.addDaemon('romm', {
|
||||
subcontainer: romm,
|
||||
exec: {
|
||||
command: sdk.useEntrypoint(),
|
||||
runAsInit: true,
|
||||
env: {
|
||||
DB_HOST: '127.0.0.1',
|
||||
DB_PORT: String(databasePort),
|
||||
DB_NAME: databaseName,
|
||||
DB_USER: databaseUser,
|
||||
DB_PASSWD: store.databasePassword,
|
||||
ROMM_AUTH_SECRET_KEY: store.authSecret,
|
||||
IGDB_CLIENT_ID: store.igdbClientId,
|
||||
IGDB_CLIENT_SECRET: store.igdbClientSecret,
|
||||
MOBYGAMES_API_KEY: store.mobygamesApiKey,
|
||||
STEAMGRIDDB_API_KEY: store.steamGridDbApiKey,
|
||||
},
|
||||
},
|
||||
ready: {
|
||||
display: i18n('Web Interface'),
|
||||
gracePeriod: 180000,
|
||||
fn: () =>
|
||||
sdk.healthCheck.checkPortListening(effects, uiPort, {
|
||||
successMessage: i18n('RomM is ready'),
|
||||
errorMessage: i18n('RomM is not ready'),
|
||||
}),
|
||||
},
|
||||
requires: ['mariadb'],
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
export const short = {
|
||||
en_US: 'A self-hosted ROM manager for browsing and organizing game libraries',
|
||||
es_ES: 'Un gestor de ROM autoalojado para explorar y organizar bibliotecas de juegos',
|
||||
de_DE: 'Ein selbst gehosteter ROM-Manager zum Durchsuchen und Organisieren von Spielesammlungen',
|
||||
pl_PL: 'Samodzielnie hostowany menedżer ROM-ów do przeglądania i organizowania bibliotek gier',
|
||||
fr_FR: 'Un gestionnaire de ROM auto-hébergé pour parcourir et organiser les ludothèques',
|
||||
}
|
||||
|
||||
export const long = {
|
||||
en_US:
|
||||
'RomM scans, enriches, browses, and manages a personal game library through a responsive web interface. This package runs RomM with a private MariaDB database and persistent library storage.',
|
||||
es_ES:
|
||||
'RomM analiza, enriquece, explora y administra una biblioteca personal de juegos mediante una interfaz web adaptable. Este paquete ejecuta RomM con una base de datos MariaDB privada y almacenamiento persistente.',
|
||||
de_DE:
|
||||
'RomM scannt, ergänzt, durchsucht und verwaltet eine persönliche Spielesammlung über eine responsive Weboberfläche. Dieses Paket betreibt RomM mit einer privaten MariaDB-Datenbank und persistentem Speicher.',
|
||||
pl_PL:
|
||||
'RomM skanuje, wzbogaca, przegląda i zarządza osobistą biblioteką gier przez responsywny interfejs WWW. Ten pakiet uruchamia RomM z prywatną bazą MariaDB i trwałym magazynem.',
|
||||
fr_FR:
|
||||
'RomM analyse, enrichit, parcourt et gère une ludothèque personnelle dans une interface web adaptative. Ce paquet exécute RomM avec une base MariaDB privée et un stockage persistant.',
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { setupManifest } from '@start9labs/start-sdk'
|
||||
import { long, short } from './i18n'
|
||||
|
||||
export const manifest = setupManifest({
|
||||
id: 'romm',
|
||||
title: 'RomM',
|
||||
license: 'AGPL-3.0',
|
||||
packageRepo: 'https://github.com/alex/romm-startos',
|
||||
upstreamRepo: 'https://github.com/rommapp/romm',
|
||||
marketingUrl: 'https://romm.app/',
|
||||
donationUrl: null,
|
||||
description: { short, long },
|
||||
volumes: ['main', 'database'],
|
||||
images: {
|
||||
romm: {
|
||||
source: {
|
||||
dockerTag:
|
||||
'rommapp/romm:3.5.0@sha256:9ff83725e98e5dfc0b871cb88ca378c539fad66b7afcbe6aad562d2b84d5b802',
|
||||
},
|
||||
arch: ['x86_64', 'aarch64'],
|
||||
},
|
||||
mariadb: {
|
||||
source: {
|
||||
dockerTag:
|
||||
'mariadb:11.4.5@sha256:49117dcc565cf51aa57ac5fca59ab31213402ff0eae6ffc13c46a37b938f7e4b',
|
||||
},
|
||||
arch: ['x86_64', 'aarch64'],
|
||||
},
|
||||
},
|
||||
dependencies: {},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
import { StartSdk } from '@start9labs/start-sdk'
|
||||
import { manifest } from './manifest'
|
||||
|
||||
export const sdk = StartSdk.of().withManifest(manifest).build(true)
|
||||
@@ -0,0 +1,13 @@
|
||||
export const uiPort = 8080
|
||||
export const databasePort = 3306
|
||||
export const uiHostId = 'ui'
|
||||
export const databaseName = 'romm'
|
||||
export const databaseUser = 'romm'
|
||||
|
||||
export const mainMounts = {
|
||||
library: '/romm/library',
|
||||
resources: '/romm/resources',
|
||||
assets: '/romm/assets',
|
||||
config: '/romm/config',
|
||||
redis: '/redis-data',
|
||||
} as const
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IMPOSSIBLE, VersionInfo } from '@start9labs/start-sdk'
|
||||
|
||||
export const current = VersionInfo.of({
|
||||
version: '3.5.0:0',
|
||||
releaseNotes: {
|
||||
en_US: 'Initial native StartOS 0.4 package for RomM 3.5.0.',
|
||||
es_ES: 'Paquete nativo inicial de StartOS 0.4 para RomM 3.5.0.',
|
||||
de_DE: 'Erstes natives StartOS-0.4-Paket für RomM 3.5.0.',
|
||||
pl_PL: 'Pierwszy natywny pakiet StartOS 0.4 dla RomM 3.5.0.',
|
||||
fr_FR: 'Premier paquet StartOS 0.4 natif pour RomM 3.5.0.',
|
||||
},
|
||||
migrations: {
|
||||
up: async () => {},
|
||||
down: IMPOSSIBLE,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
import { VersionGraph } from '@start9labs/start-sdk'
|
||||
import { current } from './current'
|
||||
|
||||
export const versionGraph = VersionGraph.of({ current, other: [] })
|
||||
Reference in New Issue
Block a user