Observer embryo, Maestro done

This commit is contained in:
STEINNI
2026-06-13 13:47:46 +00:00
parent 932b6e4752
commit 26aefd3fe2
45 changed files with 1889 additions and 143 deletions
+12
View File
@@ -0,0 +1,12 @@
export const construct = (redisCnx) => {
}
export const methods = {
dispatchArenaMessage(msg, chan) {
if(this.debug) console.log(`[${this.redisId}] Arena message (unhandled):`, msg.eventType, chan)
return(false)
},
}
+5
View File
@@ -0,0 +1,5 @@
export function dispatchMessage(redisCnx, msg, chan) {
if(typeof(redisCnx.dispatchArenaMessage) !== 'function') return
redisCnx.dispatchArenaMessage(msg, chan)
}
+12
View File
@@ -0,0 +1,12 @@
import { methods as arenaMethods, construct as arenaConstruct } from './arenaHandlers.js'
import { dispatchMessage } from './dispatch.js'
export const afterLoginMethods = [
arenaConstruct,
]
export const meshActions = {
...arenaMethods,
}
export { dispatchMessage }
+28
View File
@@ -0,0 +1,28 @@
export function dispatchMessage(redisCnx, msg, chan) {
const observer = redisCnx.config.observer
if(!observer?.observerActionsChannel) return
const actionsChan = redisCnx.fullChan(observer.observerActionsChannel)
if(chan != actionsChan) return
const action = msg.action
if(!action || typeof(action) !== 'string') {
console.warn(`[${redisCnx.redisId}] Ignoring message without action on ${chan}`)
return
}
const handler = redisCnx['action_'+action]
if(typeof(handler) != 'function') {
if(redisCnx.debug) console.warn(`[${redisCnx.redisId}] Unknown action ${action} on ${chan}`)
return
}
const payload = ('payload' in msg) ? msg.payload : null
const reqid = ('reqid' in msg) ? msg.reqid.substr(0, 50) : null
const sender = msg.sender || null
const roles = Array.isArray(msg.roles) ? msg.roles : ['*']
if(redisCnx.debug) console.log(`[${redisCnx.redisId}] Dispatching action ${action} from ${sender}`)
handler.call(redisCnx, action, payload, reqid, sender, roles)
}
+12
View File
@@ -0,0 +1,12 @@
import { methods as utilities, construct as utilitiesConstruct } from './utilities.js'
import { dispatchMessage } from './dispatch.js'
export const afterLoginMethods = [
utilitiesConstruct,
]
export const meshActions = {
...utilities,
}
export { dispatchMessage }
+48
View File
@@ -0,0 +1,48 @@
import { publishActionReply } from '../../actionsHelper.js'
export const construct = (redisCnx) => {
}
export const methods = {
async action_RELOADCONFIG(action, payload, reqid, sender, roles) {
const replyOpts = {
action,
reqid,
sender,
replyChannel: this.config.observer.observerActionsReply,
}
if(!this.accessRights.canDo(roles, action)) {
publishActionReply(this, { ...replyOpts, reply: {
success: false,
err: 'Unauthorized action !',
} })
return
}
this.reloadAccessRights()
publishActionReply(this, { ...replyOpts, reply: {
success: true,
} })
},
async action_GETCONFIG(action, payload, reqid, sender, roles) {
const replyOpts = {
action,
reqid,
sender,
replyChannel: this.config.observer.observerActionsReply,
}
if(!this.accessRights.canDo(roles, action)) {
publishActionReply(this, { ...replyOpts, reply: {
success: false,
err: 'Unauthorized action !',
} })
return
}
publishActionReply(this, { ...replyOpts, reply: {
success: true,
payload: this.getAccessRights(),
} })
},
}
+16
View File
@@ -0,0 +1,16 @@
export function publishActionReply(redisCnx, options) {
const {
action,
reqid,
sender,
reply,
replyChannel,
senderId = 'observer',
} = options
reply.action = action
reply.sender = senderId
if(reqid) reply.reqid = reqid
const chan = replyChannel.replace(/\[UID\]/g, sender)
redisCnx.redisPublish(chan, reply)
}
+55
View File
@@ -0,0 +1,55 @@
import { AccesRights } from '../accesRights.js'
export class observerServer {
constructor(configHelper, allRediscnx, debug) {
this.configHelper = configHelper
this.observerConfig = configHelper.config
this.allRediscnx = allRediscnx
this.debug = debug
this.accessRights = new AccesRights(this.observerConfig, debug)
this.arenaCnx = null
this.arenaCnxs = []
this.systemCnx = null
}
getObserverSettings() {
const observer = this.observerConfig.observer ?? {}
return({
senderId: observer.senderId ?? 'observer',
lifecycle: {
arenaChannel: observer.lifecycle?.arenaChannel ?? 'arena:lifecycle',
godsReadyChannel: observer.lifecycle?.godsReadyChannel ?? 'arena:gods:ready',
},
})
}
wireSystemConnexion(cnx) {
cnx.observerSrv = this
cnx.accessRights = this.accessRights
cnx.reloadAccessRights = () => this.reloadAccessRights()
cnx.getAccessRights = () => this.getAccessRights()
if(!this.systemCnx || cnx.redisConfig.role === 'primary') {
this.systemCnx = cnx
}
}
wireArenaConnexion(cnx) {
cnx.observerSrv = this
this.arenaCnxs.push(cnx)
if(!this.arenaCnx || cnx.redisConfig.role === 'primary') {
this.arenaCnx = cnx
}
}
async reloadAccessRights() {
await this.configHelper.refreshAccessRights()
this.observerConfig.accessRights = this.configHelper.config.accessRights
this.accessRights.refreshAccessRights(this.observerConfig)
}
getAccessRights() {
return(this.observerConfig.accessRights)
}
}
+131
View File
@@ -0,0 +1,131 @@
import yargs from 'yargs/yargs'
import { hideBin } from 'yargs/helpers'
import 'node:process'
import { RedisConnexion } from '../redisConnexion.js'
import { configHelper } from '../configHelper.js'
import { observerServer } from './observerServer.js'
import * as systemMesh from './actions/system/index.js'
import * as arenaMesh from './actions/arena/index.js'
const meshModules = {
system: systemMesh,
arena: arenaMesh,
}
const originalLog = console.log
const originalWarn = console.warn
const originalError = console.error
function logWithTimestamp(originalFn, level, ...args) {
const timestamp = new Date().toISOString()
originalFn(`[${timestamp}] [${level}]`, ...args)
}
console.log = (...args) => logWithTimestamp(originalLog, 'LOG', ...args)
console.warn = (...args) => logWithTimestamp(originalWarn, 'WARN', ...args)
console.error = (...args) => logWithTimestamp(originalError, 'ERROR', ...args)
const argv = yargs(hideBin(process.argv)).command('Observer', 'Simulation observer for P42', {})
.options({
'debug': {
description: 'shows debug info',
alias: 'd',
defaut: false,
type: 'boolean'
},
'config': {
description: 'Points to config file (default: ../config.json)',
alias: 'c',
default: '../config.json',
type: 'string'
},
}).help().version('1.0').argv
const debug = Boolean(process.env.DEBUG) || argv.debug
let cfgh = new configHelper({
localfile: argv.config,
})
function meshRedisConns(mesh, meshName, debug, rootConfig) {
const { redis, ...meshConfig } = mesh
return redis.map(cfg =>
new RedisConnexion({
debug,
config: { ...cfg, ...meshConfig, observer: rootConfig.observer },
redisId: cfg.redisId,
meshName,
meshModule: meshModules[meshName],
})
)
}
async function startAllRedis(rootConfig, cfgh) {
if(debug) console.log('Starting all Redis instances...')
const redisConns = [
...meshRedisConns(rootConfig.systemMesh, 'system', debug, rootConfig),
...meshRedisConns(rootConfig.arenaMesh, 'arena', debug, rootConfig),
]
const srv = new observerServer(cfgh, redisConns, debug)
for(const cnx of redisConns) {
if(cnx.meshName === 'system') srv.wireSystemConnexion(cnx)
else if(cnx.meshName === 'arena') srv.wireArenaConnexion(cnx)
}
const loginResults = await Promise.allSettled(
redisConns.map(async cnx => {
await cnx.redisLogin()
return(cnx.redisId)
})
)
const failedLogin = loginResults.filter(r => r.status !== 'fulfilled')
if(failedLogin.length > 0) {
console.error('Redis login failures:')
failedLogin.forEach((r, i) => {
const id = redisConns[i].redisId
console.error(`login failed for redis:[${id}] → ${r.reason}`)
})
throw new Error(
`Redis login failed for ${failedLogin.length}/${redisConns.length} instances`
)
}
if(debug) console.log('All Redis logins OK')
const chanResults = await Promise.allSettled(
redisConns.map(async cnx => {
await cnx.redisChansStart()
return(cnx.redisId)
})
)
const failedChans = chanResults.filter(r => r.status !== 'fulfilled')
if(failedChans.length > 0) {
console.error('Redis chansStart failures:')
failedChans.forEach((r, i) => {
const id = redisConns[i].redisId
console.error(`chansStart failed for redis:[${id}] → ${r.reason}`)
})
throw new Error(
`Redis chansStart failed for ${failedChans.length}/${redisConns.length} instances`
)
}
if(debug) console.log('All Redis chansStart OK')
return({ redisConns, srv })
}
cfgh.fetchConfig().then(async rootConfig => {
if(!rootConfig) {
console.error('Cannot get a valid configuration ! Aaarrghhh...')
process.exit()
}
console.log(`Debug mode : ${debug ? 'ON' : 'OFF'}`)
await startAllRedis(rootConfig, cfgh)
})
+9
View File
@@ -0,0 +1,9 @@
{
"name": "p42Observer",
"version": "1.0.0",
"description": "Simulation observer God-daemon for P42",
"type": "module",
"dependencies": {
"yargs": "^17.7.2"
}
}
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
. /etc/p42/secrets.env
daemon=p42Observer
logfile=observer.log
pid=$(pgrep -f "$daemon")
if [ -z "$pid" ]
then
node "${daemon}.js" --debug > "$logfile" 2>&1 &
pid=$!
sleep 1
if kill -0 "$pid" 2>/dev/null
then
echo ""
echo "$daemon is now running with PID=$pid"
echo ""
else
echo ""
echo "Failed to start $daemon. Check observer.log"
echo ""
fi
else
echo ""
echo "$daemon is already running with PID=$pid"
echo ""
fi
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
pid=`ps -ef | grep p42Observer.js |grep -v grep | awk '{print $2}'`
if [ -n "$pid" ]
then
echo "killing pid: $pid"
kill -9 $pid
fi