mirror of
https://github.com/IgorTimofeev/MineOS.git
synced 2026-09-12 19:51:02 +05:00
SecSite OS : apps de sécurité préinstallées + login carte + installeur repointé
- Applications/ : 12 apps SecSite (SecurityConsole, Access, Accounts, Badges, Logs, Messages,
Fleet, Protocols, Reactor, Defense, Config, Map)
- Libraries/shared + Libraries/mineos/{lib,login-fork} : nos libs (résolues via /Libraries/?.lua,
requires en notation slash)
- Libraries/System.lua : auth par carte OpenSecurity ajoutée dans system.authorize (protégée par
pcall -> login mot de passe intact si pas de carte réseau)
- Installer/Files.cfg : nos fichiers ajoutés à la section required (installés de base)
- Installer/Main.lua + OpenOS.lua : repositoryURL repointé sur quentinthierry12/mineos
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UD7cRL4GJdermphho5rVXM
This commit is contained in:
parent
cc33aeb355
commit
4544d1b12b
69
Applications/Access.app/Main.lua
Normal file
69
Applications/Access.app/Main.lua
Normal file
@ -0,0 +1,69 @@
|
||||
-- Access.app/Main.lua
|
||||
-- Application MineOS — contrôle des portes typées + LOCKDOWN.
|
||||
-- UI par type : simple/bunker/shelter -> Ouvrir/Fermer ; airlock -> battants Intérieur/Extérieur ;
|
||||
-- silo -> armement (réservé admin côté serveur). Toutes les actions passent par le serveur (RPC signé).
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 96, 30, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "CONTRÔLE D'ACCÈS"))
|
||||
|
||||
local list = window:addChild(GUI.layout(3, 5, window.width - 4, window.height - 8, 1, 1))
|
||||
|
||||
local function cmd(id, action)
|
||||
local resp = net.request(protocol.request(protocol.REQ.DOOR_CMD,
|
||||
{ token = session.token(), id = id, action = action }))
|
||||
if not (resp and resp.ok) then
|
||||
GUI.alert("Échec: " .. tostring(resp and resp.error or "réseau"))
|
||||
end
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
list:removeChildren()
|
||||
local resp = net.request(protocol.request(protocol.REQ.DOOR_LIST, { token = session.token() }))
|
||||
if not (resp and resp.ok) then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "Serveur injoignable ou non autorisé"))
|
||||
workspace:draw(); return
|
||||
end
|
||||
if resp.data.locked then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "⚠ LOCKDOWN ACTIF"))
|
||||
end
|
||||
for _, d in ipairs(resp.data.doors) do
|
||||
local row = list:addChild(GUI.container(1, 1, list.width, 3))
|
||||
row:addChild(GUI.text(1, 2, 0xCCCCCC, d.name .. " [" .. d.type .. "]"))
|
||||
if d.type == "airlock" then
|
||||
row:addChild(GUI.button(40, 1, 14, 3, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Intérieur")).onTouch = function()
|
||||
cmd(d.id, "inner_open")
|
||||
end
|
||||
row:addChild(GUI.button(56, 1, 14, 3, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Extérieur")).onTouch = function()
|
||||
cmd(d.id, "outer_open")
|
||||
end
|
||||
else
|
||||
row:addChild(GUI.button(40, 1, 12, 3, 0x2E7D32, 0xFFF, 0x2D2D2D, 0xFFF, "Ouvrir")).onTouch = function()
|
||||
cmd(d.id, "open")
|
||||
end
|
||||
row:addChild(GUI.button(54, 1, 12, 3, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "Fermer")).onTouch = function()
|
||||
cmd(d.id, "close")
|
||||
end
|
||||
end
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, window.height - 2, 18, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "LOCKDOWN")).onTouch = function()
|
||||
cmd(nil, "lockdown"); refresh()
|
||||
end
|
||||
window:addChild(GUI.button(23, window.height - 2, 18, 1, 0x2E7D32, 0xFFF, 0x2D2D2D, 0xFFF, "Lever lockdown")).onTouch = function()
|
||||
cmd(nil, "release"); refresh()
|
||||
end
|
||||
window:addChild(GUI.button(43, window.height - 2, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
|
||||
refresh()
|
||||
60
Applications/Accounts.app/Main.lua
Normal file
60
Applications/Accounts.app/Main.lua
Normal file
@ -0,0 +1,60 @@
|
||||
-- Accounts.app/Main.lua
|
||||
-- Application MineOS (admin) — gestion des comptes : liste, création, rôle, suppression,
|
||||
-- et sessions actives. Toutes les opérations passent par le serveur (permission manage_accounts).
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
local roles = require("shared/roles")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 104, 32, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "COMPTES & RÔLES"))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}
|
||||
payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local list = window:addChild(GUI.layout(3, 9, window.width - 4, window.height - 11, 1, 1))
|
||||
|
||||
local function refresh()
|
||||
list:removeChildren()
|
||||
local resp = rpc(protocol.REQ.ACCOUNT_LIST)
|
||||
if not (resp and resp.ok) then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "Non autorisé ou serveur injoignable."))
|
||||
workspace:draw(); return
|
||||
end
|
||||
for _, a in ipairs(resp.data.accounts) do
|
||||
local row = list:addChild(GUI.container(1, 1, list.width, 1))
|
||||
local flags = (a.hasCard and "🪪 " or "") .. (a.hasPassword and "🔑" or "")
|
||||
row:addChild(GUI.text(1, 1, 0xCCCCCC, string.format("%-16s %-8s %s", a.name, a.role, flags)))
|
||||
row:addChild(GUI.button(50, 1, 12, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "Supprimer")).onTouch = function()
|
||||
rpc(protocol.REQ.ACCOUNT_DELETE, { id = a.id }); refresh()
|
||||
end
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
-- Formulaire de création.
|
||||
local nameInput = window:addChild(GUI.input(3, 5, 24, 1, 0x262626, 0x999, 0x262626, 0xFFF, 0xFFF, "", "nom"))
|
||||
local roleCombo = window:addChild(GUI.comboBox(29, 5, 16, 1, 0x262626, 0xFFF, 0x333, 0x999))
|
||||
for _, r in ipairs(roles.LIST) do roleCombo:addItem(r) end
|
||||
local pwInput = window:addChild(GUI.input(47, 5, 20, 1, 0x262626, 0x999, 0x262626, 0xFFF, 0xFFF, "", "mot de passe"))
|
||||
window:addChild(GUI.button(69, 5, 14, 1, 0x2E7D32, 0xFFF, 0x2D2D2D, 0xFFF, "Créer")).onTouch = function()
|
||||
local resp = rpc(protocol.REQ.ACCOUNT_CREATE, {
|
||||
name = nameInput.text, role = roleCombo:getItem(roleCombo.selectedItem).text,
|
||||
password = pwInput.text ~= "" and pwInput.text or nil,
|
||||
})
|
||||
if not (resp and resp.ok) then GUI.alert("Échec: " .. tostring(resp and resp.error)) end
|
||||
refresh()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(85, 5, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
refresh()
|
||||
64
Applications/Badges.app/Main.lua
Normal file
64
Applications/Badges.app/Main.lua
Normal file
@ -0,0 +1,64 @@
|
||||
-- Badges.app/Main.lua
|
||||
-- Application MineOS (admin) — émission/révocation de badges.
|
||||
-- Émettre : génère un cardId, le GRAVE sur la carte physique (os_cardwriter) et l'associe au
|
||||
-- compte côté serveur (ACCOUNT_SETCARD). Révoquer : détache la carte du compte.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local writer = require("mineos/lib/writer")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 96, 30, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "ÉMISSION DE BADGES"))
|
||||
|
||||
local status = window:addChild(GUI.text(3, 5, 0xAAAAAA,
|
||||
writer.available() and "Graveur détecté." or "⚠ Aucun graveur (os_cardwriter)."))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}
|
||||
payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local list = window:addChild(GUI.layout(3, 7, window.width - 4, window.height - 9, 1, 1))
|
||||
|
||||
local function issue(acc)
|
||||
local cardId = writer.newCardId()
|
||||
local okWrite, wReason = writer.write(cardId, "SecSite: " .. acc.name)
|
||||
if not okWrite then
|
||||
GUI.alert("Gravure impossible: " .. tostring(wReason)); return
|
||||
end
|
||||
local resp = rpc(protocol.REQ.ACCOUNT_SETCARD, { id = acc.id, cardId = cardId })
|
||||
if resp and resp.ok then status.text = "Badge émis pour " .. acc.name
|
||||
else GUI.alert("Enregistrement échoué: " .. tostring(resp and resp.error)) end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
local function revoke(acc)
|
||||
local resp = rpc(protocol.REQ.ACCOUNT_SETCARD, { id = acc.id }) -- cardId nil = révocation
|
||||
if resp and resp.ok then status.text = "Badge révoqué pour " .. acc.name end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
list:removeChildren()
|
||||
local resp = rpc(protocol.REQ.ACCOUNT_LIST)
|
||||
if not (resp and resp.ok) then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "Non autorisé.")); workspace:draw(); return
|
||||
end
|
||||
for _, a in ipairs(resp.data.accounts) do
|
||||
local row = list:addChild(GUI.container(1, 1, list.width, 1))
|
||||
row:addChild(GUI.text(1, 1, 0xCCCCCC, string.format("%-16s %s", a.name, a.hasCard and "🪪 badge" or "—")))
|
||||
row:addChild(GUI.button(40, 1, 14, 1, 0x2E7D32, 0xFFF, 0x2D2D2D, 0xFFF, "Émettre")).onTouch = function() issue(a) end
|
||||
row:addChild(GUI.button(56, 1, 14, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "Révoquer")).onTouch = function() revoke(a) end
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
refresh()
|
||||
46
Applications/Config.app/Main.lua
Normal file
46
Applications/Config.app/Main.lua
Normal file
@ -0,0 +1,46 @@
|
||||
-- Config.app/Main.lua
|
||||
-- Application MineOS (admin) — édite en jeu les réglages runtime (site, radar, défense),
|
||||
-- appliqués à chaud côté serveur, sans toucher aux fichiers de code.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 90, 28, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "CONFIGURATION (runtime)"))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}; payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local list = window:addChild(GUI.layout(3, 5, window.width - 4, window.height - 6, 1, 1))
|
||||
|
||||
local function refresh()
|
||||
list:removeChildren()
|
||||
local resp = rpc(protocol.REQ.SETTINGS_GET)
|
||||
if not (resp and resp.ok) then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "Rôle admin requis ou serveur injoignable."))
|
||||
workspace:draw(); return
|
||||
end
|
||||
for _, setting in ipairs(resp.data.settings) do
|
||||
local row = list:addChild(GUI.container(1, 1, list.width, 1))
|
||||
row:addChild(GUI.text(1, 1, 0xCCCCCC, string.format("%-24s (%s)", setting.key, setting.type)))
|
||||
local input = row:addChild(GUI.input(40, 1, 22, 1, 0x262626, 0x999, 0x262626, 0xFFF, 0xFFF,
|
||||
setting.value ~= nil and tostring(setting.value) or "", "valeur"))
|
||||
row:addChild(GUI.button(64, 1, 12, 1, 0x2E7D32, 0xFFF, 0x2D2D2D, 0xFFF, "Appliquer")).onTouch = function()
|
||||
local r = rpc(protocol.REQ.SETTINGS_SET, { key = setting.key, value = input.text })
|
||||
if not (r and r.ok) then GUI.alert("Échec: " .. tostring(r and r.error)) else refresh() end
|
||||
end
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, window.height - 1, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
refresh()
|
||||
53
Applications/Defense.app/Main.lua
Normal file
53
Applications/Defense.app/Main.lua
Normal file
@ -0,0 +1,53 @@
|
||||
-- Defense.app/Main.lua
|
||||
-- Application MineOS (admin) — contre-mesures anti-missile : mode off/manual/auto + tir manuel.
|
||||
-- En mode auto, le serveur engage automatiquement les contre-mesures sur escalade DEFCON.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 80, 24, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "CONTRE-MESURES ANTI-MISSILE"))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}; payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local modeLabel = window:addChild(GUI.text(3, 5, 0xAAAAAA, "Mode : —"))
|
||||
|
||||
local function refresh()
|
||||
local resp = rpc(protocol.REQ.DEFENSE_STATE)
|
||||
if resp and resp.ok then
|
||||
modeLabel.text = "Mode : " .. tostring(resp.data.mode) ..
|
||||
(resp.data.lastFire and (" dernier tir: x" .. resp.data.lastFire.count) or "")
|
||||
else
|
||||
modeLabel.text = "Mode : (non autorisé / injoignable)"
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
local function setMode(m)
|
||||
local r = rpc(protocol.REQ.DEFENSE_MODE, { mode = m })
|
||||
if not (r and r.ok) then GUI.alert("Échec: " .. tostring(r and r.error)) end
|
||||
refresh()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, 7, 14, 3, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "OFF")).onTouch = function() setMode("off") end
|
||||
window:addChild(GUI.button(19, 7, 14, 3, 0x2E7D32, 0xFFF, 0x2D2D2D, 0xFFF, "MANUEL")).onTouch = function() setMode("manual") end
|
||||
window:addChild(GUI.button(35, 7, 14, 3, 0xEF6C00, 0xFFF, 0x2D2D2D, 0xFFF, "AUTO")).onTouch = function() setMode("auto") end
|
||||
|
||||
window:addChild(GUI.button(3, 12, 30, 3, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "TIR MANUEL")).onTouch = function()
|
||||
local r = rpc(protocol.REQ.DEFENSE_FIRE)
|
||||
GUI.alert((r and r.ok) and ("Tir: x" .. r.data.fired) or ("Échec: " .. tostring(r and r.error)))
|
||||
refresh()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, window.height - 1, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
refresh()
|
||||
48
Applications/Fleet.app/Main.lua
Normal file
48
Applications/Fleet.app/Main.lua
Normal file
@ -0,0 +1,48 @@
|
||||
-- Fleet.app/Main.lua
|
||||
-- Application MineOS (admin) — gestion de la flotte : lister les machines et envoyer
|
||||
-- reboot / shutdown / lock à distance. Commandes réservées au rôle Admin (vérifié serveur).
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 100, 30, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "FLOTTE — Gestion à distance"))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}; payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local list = window:addChild(GUI.layout(3, 5, window.width - 4, window.height - 7, 1, 1))
|
||||
|
||||
local function send(address, command)
|
||||
local r = rpc(protocol.REQ.NODE_CMD, { address = address, command = command })
|
||||
if not (r and r.ok) then GUI.alert("Échec: " .. tostring(r and r.error)) end
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
list:removeChildren()
|
||||
local resp = rpc(protocol.REQ.NODE_LIST)
|
||||
if not (resp and resp.ok) then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "Rôle Admin requis ou serveur injoignable."))
|
||||
workspace:draw(); return
|
||||
end
|
||||
for _, n in ipairs(resp.data.nodes) do
|
||||
local row = list:addChild(GUI.container(1, 1, list.width, 1))
|
||||
row:addChild(GUI.text(1, 1, 0xCCCCCC, string.format("%s %-10s %s", n.address:sub(1, 8), n.kind, n.status)))
|
||||
row:addChild(GUI.button(40, 1, 12, 1, 0xEF6C00, 0xFFF, 0x2D2D2D, 0xFFF, "Reboot")).onTouch = function() send(n.address, "reboot") end
|
||||
row:addChild(GUI.button(54, 1, 12, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "Éteindre")).onTouch = function() send(n.address, "shutdown") end
|
||||
row:addChild(GUI.button(68, 1, 12, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Verrouiller")).onTouch = function() send(n.address, "lock") end
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, window.height - 1, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
refresh()
|
||||
34
Applications/Logs.app/Main.lua
Normal file
34
Applications/Logs.app/Main.lua
Normal file
@ -0,0 +1,34 @@
|
||||
-- Logs.app/Main.lua
|
||||
-- Application MineOS — consultation du journal d'audit (connexions, portes, radar, sécurité).
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 100, 30, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "JOURNAL D'AUDIT"))
|
||||
|
||||
local view = window:addChild(GUI.textBox(3, 5, window.width - 4, window.height - 7, 0x161616, 0xBBBBBB, {}, 1, 0, 0))
|
||||
|
||||
local function refresh()
|
||||
local lines = {}
|
||||
local resp = net.request(protocol.request(protocol.REQ.LOG_QUERY, { token = session.token(), limit = 100 }))
|
||||
if resp and resp.ok then
|
||||
for _, e in ipairs(resp.data.entries) do
|
||||
lines[#lines + 1] = string.format("%s [%s] %s — %s", e.stamp or "", e.kind or "?", e.actor or "?", e.message or "")
|
||||
end
|
||||
else
|
||||
lines = { "Serveur injoignable ou non autorisé." }
|
||||
end
|
||||
view.lines = lines
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, window.height - 1, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
refresh()
|
||||
78
Applications/Map.app/Main.lua
Normal file
78
Applications/Map.app/Main.lua
Normal file
@ -0,0 +1,78 @@
|
||||
-- Map.app/Main.lua
|
||||
-- Application MineOS — carte 2D (vue du dessus) des contacts radar autour de l'installation.
|
||||
-- Le centre/rayon viennent de shared/site.lua ; les contacts (x,z) de SITUATION_GET
|
||||
-- (nécessite le radar OC HBM pour des positions ; en repli redstone, pas de blips).
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
local site = require("shared/site")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 92, 30, 0x0A0A0A))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "CARTE TACTIQUE"))
|
||||
|
||||
local MAP_X, MAP_Y, MAP_W, MAP_H = 3, 5, 60, 22
|
||||
local mapArea = window:addChild(GUI.panel(MAP_X, MAP_Y, MAP_W, MAP_H, 0x111111))
|
||||
local markers = window:addChild(GUI.container(MAP_X, MAP_Y, MAP_W, MAP_H))
|
||||
local sidebar = window:addChild(GUI.layout(MAP_X + MAP_W + 2, MAP_Y, window.width - MAP_W - MAP_X - 3, MAP_H, 1, 1))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}; payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
markers:removeChildren()
|
||||
sidebar:removeChildren()
|
||||
|
||||
local cx, cz = site.CONFIG.center.x, site.CONFIG.center.z
|
||||
local range = math.max(16, (site.CONFIG.radius or 64) * 3) -- portée affichée
|
||||
local mcx, mcy = math.floor(MAP_W / 2), math.floor(MAP_H / 2)
|
||||
local sx, sy = mcx / range, mcy / range
|
||||
|
||||
-- Centre du site.
|
||||
markers:addChild(GUI.text(mcx, mcy, 0x2E7D32, "◉"))
|
||||
|
||||
local resp = rpc(protocol.REQ.SITUATION_GET)
|
||||
if not (resp and resp.ok) then
|
||||
sidebar:addChild(GUI.text(1, 1, 0xDD4444, "Injoignable / non autorisé"))
|
||||
workspace:draw(); return
|
||||
end
|
||||
local sit = resp.data
|
||||
|
||||
sidebar:addChild(GUI.text(1, 1, 0xFFFFFF, "DEFCON " .. tostring(sit.defcon)))
|
||||
sidebar:addChild(GUI.text(1, 1, sit.alert and 0xEF5350 or 0x9E9E9E, sit.alert and "ALERTE" or "calme"))
|
||||
sidebar:addChild(GUI.text(1, 1, 0x9E9E9E, "Contacts: " .. tostring(sit.contacts or 0)))
|
||||
if sit.impactETA then sidebar:addChild(GUI.text(1, 1, 0xF9A825, "ETA ~" .. math.floor(sit.impactETA) .. "s")) end
|
||||
|
||||
-- Blips des contacts (positions relatives au centre).
|
||||
local rc = resp.data and select(2, pcall(function() return rpc(protocol.REQ.RADAR_STATE) end))
|
||||
local radar = rc and rc.ok and rc.data or nil
|
||||
for _, k in ipairs((radar and radar.contacts) or {}) do
|
||||
if k.x and k.z then
|
||||
local mx = math.floor(mcx + (k.x - cx) * sx)
|
||||
local my = math.floor(mcy + (k.z - cz) * sy)
|
||||
if mx >= 1 and mx <= MAP_W and my >= 1 and my <= MAP_H then
|
||||
markers:addChild(GUI.text(mx, my, 0xB71C1C, "▲"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Portes importantes en légende.
|
||||
sidebar:addChild(GUI.text(1, 1, 0x666666, "— Portes —"))
|
||||
for _, d in ipairs(sit.importantDoors or {}) do
|
||||
local st = d.state or {}
|
||||
local open = st.open or st.inner or st.outer
|
||||
sidebar:addChild(GUI.text(1, 1, open and 0xEF5350 or 0x66BB6A, (open and "▮ " or "▯ ") .. d.name))
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, window.height - 1, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
refresh()
|
||||
57
Applications/Messages.app/Main.lua
Normal file
57
Applications/Messages.app/Main.lua
Normal file
@ -0,0 +1,57 @@
|
||||
-- Messages.app/Main.lua
|
||||
-- Application MineOS — collaboration : bulletin d'annonces + boîte de réception.
|
||||
-- Publier une annonce la diffuse à tous les terminaux et dans le chat du serveur (Computronics).
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 100, 32, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "MESSAGERIE & ANNONCES"))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}; payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
window:addChild(GUI.text(3, 5, 0xAAAAAA, "Tableau d'annonces"))
|
||||
local board = window:addChild(GUI.textBox(3, 6, window.width - 4, 10, 0x161616, 0xBBBBBB, {}, 1, 0, 0))
|
||||
|
||||
window:addChild(GUI.text(3, 18, 0xAAAAAA, "Boîte de réception"))
|
||||
local inbox = window:addChild(GUI.textBox(3, 19, window.width - 4, 8, 0x161616, 0xBBBBBB, {}, 1, 0, 0))
|
||||
|
||||
local function refresh()
|
||||
local b = rpc(protocol.REQ.BOARD_GET, { limit = 20 })
|
||||
local blines = {}
|
||||
if b and b.ok then
|
||||
for _, a in ipairs(b.data.board) do blines[#blines + 1] = (a.stamp or "") .. " " .. a.actor .. ": " .. a.text end
|
||||
end
|
||||
board.lines = #blines > 0 and blines or { "(aucune annonce)" }
|
||||
|
||||
local m = rpc(protocol.REQ.MSG_INBOX)
|
||||
local mlines = {}
|
||||
if m and m.ok then
|
||||
for _, e in ipairs(m.data.messages) do mlines[#mlines + 1] = (e.stamp or "") .. " " .. e.from .. ": " .. e.text end
|
||||
end
|
||||
inbox.lines = #mlines > 0 and mlines or { "(aucun message)" }
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
-- Zone de publication d'annonce.
|
||||
local annInput = window:addChild(GUI.input(3, window.height - 2, 60, 1, 0x262626, 0x999, 0x262626, 0xFFF, 0xFFF, "", "annonce…"))
|
||||
window:addChild(GUI.button(65, window.height - 2, 14, 1, 0x2E7D32, 0xFFF, 0x2D2D2D, 0xFFF, "Publier")).onTouch = function()
|
||||
if annInput.text ~= "" then
|
||||
rpc(protocol.REQ.ANNOUNCE_POST, { text = annInput.text })
|
||||
annInput.text = ""
|
||||
refresh()
|
||||
end
|
||||
end
|
||||
window:addChild(GUI.button(81, window.height - 2, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
|
||||
refresh()
|
||||
62
Applications/Protocols.app/Main.lua
Normal file
62
Applications/Protocols.app/Main.lua
Normal file
@ -0,0 +1,62 @@
|
||||
-- Protocols.app/Main.lua
|
||||
-- Application MineOS — protocoles : lister, saisir un code et lancer en DRILL (simulation) ou RÉEL.
|
||||
-- Réel = permission "protocol" (admin) ; drill = permission "drill" (agent+admin), vérifié serveur.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 96, 30, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "PROTOCOLES & OVERRIDES"))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}; payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local list = window:addChild(GUI.layout(3, 8, window.width - 4, window.height - 10, 1, 1))
|
||||
|
||||
local function run(code, drill)
|
||||
local r = rpc(protocol.REQ.PROTOCOL_RUN, { code = code, drill = drill })
|
||||
if r and r.ok then
|
||||
GUI.alert((drill and "DRILL lancé: " or "Protocole exécuté: ") .. r.data.name)
|
||||
else
|
||||
GUI.alert("Échec: " .. tostring(r and r.error))
|
||||
end
|
||||
end
|
||||
|
||||
local function refresh()
|
||||
list:removeChildren()
|
||||
local resp = rpc(protocol.REQ.PROTOCOL_LIST)
|
||||
if not (resp and resp.ok) then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "Non autorisé ou serveur injoignable."))
|
||||
workspace:draw(); return
|
||||
end
|
||||
for _, p in ipairs(resp.data.protocols) do
|
||||
local row = list:addChild(GUI.container(1, 1, list.width, 1))
|
||||
row:addChild(GUI.text(1, 1, 0xCCCCCC, string.format("%-28s code:%-6s %s", p.name, p.code, p.role)))
|
||||
if p.drillable then
|
||||
row:addChild(GUI.button(48, 1, 10, 1, 0x9E9D24, 0xFFF, 0x2D2D2D, 0xFFF, "Drill")).onTouch = function() run(p.code, true) end
|
||||
end
|
||||
row:addChild(GUI.button(60, 1, 10, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "RÉEL")).onTouch = function() run(p.code, false) end
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
-- Saisie directe par code.
|
||||
local codeInput = window:addChild(GUI.input(3, 5, 16, 1, 0x262626, 0x999, 0x262626, 0xFFF, 0xFFF, "", "code"))
|
||||
window:addChild(GUI.button(21, 5, 12, 1, 0x9E9D24, 0xFFF, 0x2D2D2D, 0xFFF, "Drill")).onTouch = function()
|
||||
if codeInput.text ~= "" then run(codeInput.text, true) end
|
||||
end
|
||||
window:addChild(GUI.button(35, 5, 12, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "RÉEL")).onTouch = function()
|
||||
if codeInput.text ~= "" then run(codeInput.text, false) end
|
||||
end
|
||||
window:addChild(GUI.button(49, 5, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
|
||||
refresh()
|
||||
52
Applications/Reactor.app/Main.lua
Normal file
52
Applications/Reactor.app/Main.lua
Normal file
@ -0,0 +1,52 @@
|
||||
-- Reactor.app/Main.lua
|
||||
-- Application MineOS — supervision réacteurs / énergie (température, combustible, puissance),
|
||||
-- avec arrêt d'urgence (SCRAM) réservé au rôle admin.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
local STATUS_COLOR = { ok = 0x66BB6A, warn = 0xF9A825, crit = 0xB71C1C, unknown = 0x757575 }
|
||||
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 96, 30, 0x1E1E1E))
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "SUPERVISION RÉACTEURS / ÉNERGIE"))
|
||||
|
||||
local function rpc(rtype, payload)
|
||||
payload = payload or {}; payload.token = session.token()
|
||||
return net.request(protocol.request(rtype, payload))
|
||||
end
|
||||
|
||||
local list = window:addChild(GUI.layout(3, 5, window.width - 4, window.height - 6, 1, 1))
|
||||
|
||||
local function refresh()
|
||||
list:removeChildren()
|
||||
local resp = rpc(protocol.REQ.POWER_STATE)
|
||||
if not (resp and resp.ok) then
|
||||
list:addChild(GUI.text(1, 1, 0xDD4444, "Non autorisé ou serveur injoignable."))
|
||||
workspace:draw(); return
|
||||
end
|
||||
for _, r in ipairs(resp.data.reactors) do
|
||||
local row = list:addChild(GUI.container(1, 1, list.width, 2))
|
||||
row:addChild(GUI.text(1, 1, STATUS_COLOR[r.status] or 0xCCCCCC, "● " .. r.name .. " [" .. (r.status or "?") .. "]"))
|
||||
local info = {}
|
||||
if r.temp then info[#info + 1] = "T=" .. math.floor(r.temp) end
|
||||
if r.fuel then info[#info + 1] = "Fuel=" .. math.floor(r.fuel) end
|
||||
if r.power then info[#info + 1] = "P=" .. math.floor(r.power) end
|
||||
if r.energy then info[#info + 1] = "E=" .. math.floor(r.energy) end
|
||||
row:addChild(GUI.text(30, 1, 0x999999, table.concat(info, " ")))
|
||||
row:addChild(GUI.button(70, 1, 12, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "SCRAM")).onTouch = function()
|
||||
local sc = rpc(protocol.REQ.REACTOR_SCRAM, { id = r.id })
|
||||
GUI.alert((sc and sc.ok) and "SCRAM envoyé" or ("Échec: " .. tostring(sc and sc.error)))
|
||||
end
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, window.height - 1, 14, 1, 0x3C3C3C, 0xFFF, 0x2D2D2D, 0xFFF, "Rafraîchir")).onTouch = refresh
|
||||
refresh()
|
||||
64
Applications/SecurityConsole.app/Main.lua
Normal file
64
Applications/SecurityConsole.app/Main.lua
Normal file
@ -0,0 +1,64 @@
|
||||
-- SecurityConsole.app/Main.lua
|
||||
-- Application MineOS — coquille du tableau de bord de sécurité (lot 1).
|
||||
-- Utilise le framework GUI de MineOS (workspaces / conteneurs / widgets).
|
||||
-- Les widgets DEFCON / portes / alertes seront ajoutés au lot 2.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local GUI = require("GUI")
|
||||
local system = require("System")
|
||||
local net = require("mineos/lib/net")
|
||||
local session = require("mineos/lib/session")
|
||||
local protocol = require("shared/protocol")
|
||||
|
||||
-- Couleur d'affichage selon le niveau DEFCON (5 calme -> 1 imminent).
|
||||
local DEFCON_COLOR = { [5] = 0x2E7D32, [4] = 0x9E9D24, [3] = 0xF9A825, [2] = 0xEF6C00, [1] = 0xB71C1C }
|
||||
|
||||
-- Fenêtre principale.
|
||||
local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 88, 26, 0x1E1E1E))
|
||||
|
||||
window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
||||
window:addChild(GUI.text(3, 2, 0xFFFFFF, "SECURITY CONSOLE — Intranet"))
|
||||
|
||||
local statusLabel = window:addChild(GUI.text(3, 5, 0xAAAAAA, "Serveur : vérification…"))
|
||||
local defconPanel = window:addChild(GUI.panel(3, 7, 40, 3, 0x333333))
|
||||
local defconLabel = window:addChild(GUI.text(5, 8, 0xFFFFFF, "DEFCON : —"))
|
||||
|
||||
-- Interroge serveur (ping) + état radar (DEFCON).
|
||||
local function refresh()
|
||||
local resp = net.ping()
|
||||
if resp and resp.ok then
|
||||
statusLabel.text = "Serveur : EN LIGNE (protocole v" .. tostring(resp.data.v) .. ")"
|
||||
statusLabel.color = 0x44DD44
|
||||
else
|
||||
statusLabel.text = "Serveur : INJOIGNABLE"
|
||||
statusLabel.color = 0xDD4444
|
||||
end
|
||||
|
||||
local rs = net.request(protocol.request(protocol.REQ.RADAR_STATE, { token = session.token() }))
|
||||
if rs and rs.ok then
|
||||
local d = rs.data.defcon or 5
|
||||
defconLabel.text = "DEFCON : " .. d .. (rs.data.alert and " ⚠ ALERTE" or "")
|
||||
defconPanel.color = DEFCON_COLOR[d] or 0x333333
|
||||
end
|
||||
workspace:draw()
|
||||
end
|
||||
|
||||
window:addChild(GUI.button(3, 11, 24, 3, 0x3C3C3C, 0xFFFFFF, 0x2D2D2D, 0xFFFFFF, "Rafraîchir")).onTouch = function()
|
||||
refresh()
|
||||
end
|
||||
|
||||
-- Saisie rapide d'un code de protocole (drill/réel).
|
||||
window:addChild(GUI.text(3, 16, 0xAAAAAA, "Code protocole :"))
|
||||
local codeInput = window:addChild(GUI.input(19, 16, 12, 1, 0x262626, 0x999999, 0x262626, 0xFFFFFF, 0xFFFFFF, "", "code"))
|
||||
local function runProto(drill)
|
||||
if codeInput.text == "" then return end
|
||||
local r = net.request(protocol.request(protocol.REQ.PROTOCOL_RUN, { token = session.token(), code = codeInput.text, drill = drill }))
|
||||
GUI.alert((r and r.ok) and ((drill and "DRILL: " or "Exécuté: ") .. r.data.name) or ("Échec: " .. tostring(r and r.error)))
|
||||
end
|
||||
window:addChild(GUI.button(33, 16, 10, 1, 0x9E9D24, 0xFFF, 0x2D2D2D, 0xFFF, "Drill")).onTouch = function() runProto(true) end
|
||||
window:addChild(GUI.button(45, 16, 10, 1, 0xB71C1C, 0xFFF, 0x2D2D2D, 0xFFF, "RÉEL")).onTouch = function() runProto(false) end
|
||||
|
||||
refresh()
|
||||
workspace:draw()
|
||||
@ -45,6 +45,37 @@
|
||||
"Localizations/Polish.lang"
|
||||
},
|
||||
required = {
|
||||
-- === SecSite OS : bibliothèques + applications préinstallées ===
|
||||
"Libraries/shared/protocol.lua",
|
||||
"Libraries/shared/netsec.lua",
|
||||
"Libraries/shared/sha2.lua",
|
||||
"Libraries/shared/util.lua",
|
||||
"Libraries/shared/roles.lua",
|
||||
"Libraries/shared/doors.lua",
|
||||
"Libraries/shared/site.lua",
|
||||
"Libraries/shared/protocols.lua",
|
||||
"Libraries/shared/reactors.lua",
|
||||
"Libraries/shared/defense.lua",
|
||||
"Libraries/shared/branding.lua",
|
||||
"Libraries/mineos/lib/net.lua",
|
||||
"Libraries/mineos/lib/card.lua",
|
||||
"Libraries/mineos/lib/session.lua",
|
||||
"Libraries/mineos/lib/writer.lua",
|
||||
"Libraries/mineos/lib/blackout.lua",
|
||||
"Libraries/mineos/login-fork/patch.lua",
|
||||
"Libraries/mineos/login-fork/autorun.lua",
|
||||
"Applications/SecurityConsole.app/Main.lua",
|
||||
"Applications/Access.app/Main.lua",
|
||||
"Applications/Accounts.app/Main.lua",
|
||||
"Applications/Badges.app/Main.lua",
|
||||
"Applications/Logs.app/Main.lua",
|
||||
"Applications/Messages.app/Main.lua",
|
||||
"Applications/Fleet.app/Main.lua",
|
||||
"Applications/Protocols.app/Main.lua",
|
||||
"Applications/Reactor.app/Main.lua",
|
||||
"Applications/Defense.app/Main.lua",
|
||||
"Applications/Config.app/Main.lua",
|
||||
"Applications/Map.app/Main.lua",
|
||||
-- Libraries
|
||||
{
|
||||
path = "Libraries/Bit32.lua",
|
||||
|
||||
@ -13,7 +13,7 @@ local EEPROMAddress, internetAddress, GPUAddress =
|
||||
component.invoke(GPUAddress, "bind", getComponentAddress("screen"))
|
||||
local screenWidth, screenHeight = component.invoke(GPUAddress, "getResolution")
|
||||
|
||||
local repositoryURL = "https://raw.githubusercontent.com/IgorTimofeev/MineOS/master/"
|
||||
local repositoryURL = "https://raw.githubusercontent.com/quentinthierry12/mineos/master/"
|
||||
local installerURL = "Installer/"
|
||||
local EFIURL = "EFI/Minified.lua"
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ end
|
||||
|
||||
-- Checking if installer can be downloaded from GitHub, because of PKIX errors, server blacklists, etc
|
||||
do
|
||||
local success, result = pcall(component.internet.request, "https://raw.githubusercontent.com/IgorTimofeev/MineOS/master/Installer/Main.lua")
|
||||
local success, result = pcall(component.internet.request, "https://raw.githubusercontent.com/quentinthierry12/mineos/master/Installer/Main.lua")
|
||||
|
||||
if not success then
|
||||
if result then
|
||||
@ -102,7 +102,7 @@ end
|
||||
-- Flashing EEPROM with tiny script that will run installer itself after reboot.
|
||||
-- It's necessary, because we need clean computer without OpenOS hooks to computer.pullSignal()
|
||||
component.eeprom.set([[
|
||||
local connection, data, chunk = component.proxy(component.list("internet")()).request("https://raw.githubusercontent.com/IgorTimofeev/MineOS/master/Installer/Main.lua"), ""
|
||||
local connection, data, chunk = component.proxy(component.list("internet")()).request("https://raw.githubusercontent.com/quentinthierry12/mineos/master/Installer/Main.lua"), ""
|
||||
|
||||
while true do
|
||||
chunk = connection.read(math.huge)
|
||||
|
||||
@ -3263,6 +3263,23 @@ function system.authorize()
|
||||
end
|
||||
end
|
||||
|
||||
-- === SecSite : authentification par carte OpenSecurity (en plus du mot de passe) ===
|
||||
local secStop
|
||||
local secOk, secPatch = pcall(require, "mineos/login-fork/patch")
|
||||
if secOk then
|
||||
secStop = secPatch.cardListener(function(userName)
|
||||
for _, u in ipairs(userList) do
|
||||
if u:sub(1, -2) == userName then
|
||||
if secStop then secStop() end
|
||||
container:remove()
|
||||
updateUser(userName)
|
||||
workspace:draw()
|
||||
return
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
selectUser()
|
||||
else
|
||||
updateUser(userList[1]:sub(1, -2))
|
||||
|
||||
50
Libraries/mineos/lib/blackout.lua
Normal file
50
Libraries/mineos/lib/blackout.lua
Normal file
@ -0,0 +1,50 @@
|
||||
-- mineos/lib/blackout.lua
|
||||
-- Watcher « poste inaccessible » : quand l'agent a reçu la commande `blackout`, un drapeau existe ;
|
||||
-- ce module affiche alors une page plein écran bloquante jusqu'à la levée (`release`).
|
||||
-- À appeler depuis le hook de login/kiosque du terminal (les écrans du mur en sont exemptés).
|
||||
|
||||
local component = require("component")
|
||||
|
||||
local blackout = {}
|
||||
|
||||
local FLAG = "/tmp/secsite.blackout"
|
||||
|
||||
function blackout.active()
|
||||
local f = io.open(FLAG, "r")
|
||||
if f then f:close(); return true end
|
||||
return false
|
||||
end
|
||||
|
||||
function blackout.screen()
|
||||
if not component.isAvailable("gpu") then return end
|
||||
local gpu = component.gpu
|
||||
local w, h = gpu.getResolution()
|
||||
gpu.setBackground(0x330000)
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
gpu.fill(1, 1, w, h, " ")
|
||||
local msg = "ORDINATEUR INACCESSIBLE"
|
||||
local sub = "Acces suspendu par la securite"
|
||||
gpu.set(math.max(1, math.floor((w - #msg) / 2)), math.floor(h / 2), msg)
|
||||
gpu.set(math.max(1, math.floor((w - #sub) / 2)), math.floor(h / 2) + 2, sub)
|
||||
end
|
||||
|
||||
-- Boucle bloquante tant que le poste est verrouillé.
|
||||
function blackout.guard()
|
||||
local event = require("event")
|
||||
while blackout.active() do
|
||||
blackout.screen()
|
||||
event.pull(1)
|
||||
end
|
||||
end
|
||||
|
||||
-- À appeler depuis la boucle de login/kiosque : prend l'écran si le poste est en blackout.
|
||||
-- Renvoie true si le blackout a été appliqué (et vient d'être levé), false sinon.
|
||||
function blackout.enforce()
|
||||
if blackout.active() then
|
||||
blackout.guard()
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return blackout
|
||||
65
Libraries/mineos/lib/card.lua
Normal file
65
Libraries/mineos/lib/card.lua
Normal file
@ -0,0 +1,65 @@
|
||||
-- mineos/lib/card.lua
|
||||
-- Adaptateur lecteur de carte OpenSecurity (magnétique ou RFID).
|
||||
-- API confirmées (wiki PC-Logix/OpenSecurity) :
|
||||
-- * Magreader : événement `magData` = (address, playerName, cardData, cardUniqueId, isCardLocked, side)
|
||||
-- -> identifiant de badge = cardData (repli cardUniqueId). Passif (swipe joueur).
|
||||
-- * RFID : `scan([1-64])` déclenche `rfidData` = (uuid, playerName, distance, data)
|
||||
-- -> identifiant = data. ACTIF : il faut appeler scan().
|
||||
|
||||
local component = require("component")
|
||||
local computer = require("computer")
|
||||
local event = require("event")
|
||||
|
||||
local card = {}
|
||||
|
||||
card.EVENTS = { magData = true, rfidData = true }
|
||||
|
||||
-- Détecte un lecteur disponible. Renvoie (proxy, type) ou nil.
|
||||
function card.reader()
|
||||
if component.isAvailable("os_magreader") then return component.os_magreader, "mag" end
|
||||
if component.isAvailable("os_rfidreader") then return component.os_rfidreader, "rfid" end
|
||||
return nil
|
||||
end
|
||||
|
||||
function card.available()
|
||||
return card.reader() ~= nil
|
||||
end
|
||||
|
||||
-- Extrait l'identifiant de badge selon l'événement OpenSecurity.
|
||||
-- ev[1] = nom de l'événement ; les champs suivants dépendent du type.
|
||||
local function extract(ev)
|
||||
local name = ev[1]
|
||||
if name == "magData" then
|
||||
-- magData: address, playerName, cardData, cardUniqueId, isCardLocked, side
|
||||
return ev[4] or ev[5]
|
||||
elseif name == "rfidData" then
|
||||
-- rfidData: uuid, playerName, distance, data
|
||||
return ev[5]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Attend une carte. Renvoie le cardId (string) ou nil au timeout.
|
||||
-- Pour un lecteur RFID, on déclenche activement un scan à chaque itération.
|
||||
function card.await(timeout)
|
||||
local reader, kind = card.reader()
|
||||
local deadline = timeout and (computer.uptime() + timeout) or nil
|
||||
while true do
|
||||
local remaining = deadline and (deadline - computer.uptime()) or nil
|
||||
if remaining and remaining <= 0 then return nil end
|
||||
if kind == "rfid" and reader then pcall(reader.scan) end
|
||||
-- RFID : scans rapprochés ; borne l'attente pour re-scanner régulièrement.
|
||||
local wait = remaining
|
||||
if kind == "rfid" then wait = math.min(remaining or 1, 1) end
|
||||
local ev = { event.pull(wait) }
|
||||
local name = ev[1]
|
||||
if name and card.EVENTS[name] then
|
||||
local id = extract(ev)
|
||||
if id and id ~= "" then return id end
|
||||
elseif name == nil and kind ~= "rfid" then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return card
|
||||
56
Libraries/mineos/lib/net.lua
Normal file
56
Libraries/mineos/lib/net.lua
Normal file
@ -0,0 +1,56 @@
|
||||
-- mineos/lib/net.lua
|
||||
-- Client RPC vers le serveur de sécurité. Utilisé par les apps MineOS, la tablette et l'agent.
|
||||
-- Diffuse une requête signée sur le port privé et attend la réponse corrélée (par id).
|
||||
-- Le serveur est découvert par broadcast : sur le réseau privé (HMAC + liste blanche),
|
||||
-- seul le vrai serveur peut produire une réponse valide.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local component = require("component")
|
||||
local computer = require("computer")
|
||||
local event = require("event")
|
||||
|
||||
local netsec = require("shared/netsec")
|
||||
local protocol = require("shared/protocol")
|
||||
local util = require("shared/util")
|
||||
|
||||
local net = {}
|
||||
|
||||
local modem = component.modem
|
||||
if not modem.isOpen(protocol.PORT) then modem.open(protocol.PORT) end
|
||||
|
||||
-- net.request(table, timeout?) -> (réponse, nil) ou (nil, raison)
|
||||
function net.request(tbl, timeout)
|
||||
timeout = timeout or 3
|
||||
tbl.id = util.uuid()
|
||||
modem.broadcast(protocol.PORT, netsec.encode(tbl))
|
||||
local deadline = computer.uptime() + timeout
|
||||
while true do
|
||||
local remaining = deadline - computer.uptime()
|
||||
if remaining <= 0 then return nil, "timeout" end
|
||||
local name, _, _, port, _, msg = event.pull(remaining, "modem_message")
|
||||
if not name then return nil, "timeout" end
|
||||
if port == protocol.PORT and msg then
|
||||
local resp = netsec.decode(msg)
|
||||
if resp and resp.id == tbl.id then
|
||||
return resp
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Raccourcis d'authentification.
|
||||
function net.loginCard(cardId)
|
||||
return net.request(protocol.request(protocol.REQ.AUTH_CARD, { cardId = cardId }))
|
||||
end
|
||||
|
||||
function net.loginPassword(name, password)
|
||||
return net.request(protocol.request(protocol.REQ.AUTH_PASSWORD, { name = name, password = password }))
|
||||
end
|
||||
|
||||
function net.ping()
|
||||
return net.request(protocol.request(protocol.REQ.PING), 2)
|
||||
end
|
||||
|
||||
return net
|
||||
33
Libraries/mineos/lib/session.lua
Normal file
33
Libraries/mineos/lib/session.lua
Normal file
@ -0,0 +1,33 @@
|
||||
-- mineos/lib/session.lua
|
||||
-- Détient la session courante du terminal (token/rôle/nom), établie par le login forké
|
||||
-- (mineos/login-fork) et lue par les applications de sécurité.
|
||||
|
||||
local session = {}
|
||||
|
||||
local current = nil
|
||||
|
||||
function session.set(s)
|
||||
current = s -- { token, name, role }
|
||||
end
|
||||
|
||||
function session.clear()
|
||||
current = nil
|
||||
end
|
||||
|
||||
function session.get()
|
||||
return current
|
||||
end
|
||||
|
||||
function session.token()
|
||||
return current and current.token or nil
|
||||
end
|
||||
|
||||
function session.role()
|
||||
return current and current.role or nil
|
||||
end
|
||||
|
||||
function session.name()
|
||||
return current and current.name or nil
|
||||
end
|
||||
|
||||
return session
|
||||
32
Libraries/mineos/lib/writer.lua
Normal file
32
Libraries/mineos/lib/writer.lua
Normal file
@ -0,0 +1,32 @@
|
||||
-- mineos/lib/writer.lua
|
||||
-- Adaptateur du graveur de carte OpenSecurity (os_cardwriter) pour l'émission de badges.
|
||||
-- La signature exacte de write(...) est À CONFIRMER en jeu ; isolée ici.
|
||||
|
||||
local component = require("component")
|
||||
local util = require("shared/util")
|
||||
|
||||
local writer = {}
|
||||
|
||||
function writer.available()
|
||||
return component.isAvailable("os_cardwriter")
|
||||
end
|
||||
|
||||
-- Génère un identifiant de badge unique.
|
||||
function writer.newCardId()
|
||||
return "SEC-" .. util.uuid(24)
|
||||
end
|
||||
|
||||
-- Grave un cardId sur la carte physique posée dans le graveur.
|
||||
-- Renvoie true, ou (nil, raison).
|
||||
function writer.write(cardId, label)
|
||||
if not writer.available() then return nil, "no_writer" end
|
||||
local w = component.os_cardwriter
|
||||
local ok = pcall(function()
|
||||
-- Ordre des arguments probable : (data, label, isRewritable) — à ajuster une fois testé.
|
||||
w.write(cardId, label or "SecSite Badge", true)
|
||||
end)
|
||||
if not ok then return nil, "write_failed" end
|
||||
return true
|
||||
end
|
||||
|
||||
return writer
|
||||
70
Libraries/mineos/login-fork/autorun.lua
Normal file
70
Libraries/mineos/login-fork/autorun.lua
Normal file
@ -0,0 +1,70 @@
|
||||
-- mineos/login-fork/autorun.lua
|
||||
-- Enrobage NON INVASIF du login MineOS (pas d'édition de System.lua) :
|
||||
-- * ajoute l'auth par carte à l'écran de connexion (en plus du mot de passe MineOS)
|
||||
-- * applique le branding SecSite (bannière)
|
||||
-- * mode kiosque optionnel (lance SecurityConsole après login)
|
||||
-- À exécuter au démarrage de MineOS (installé comme autorun par install/secsite_os.lua).
|
||||
--
|
||||
-- ⚠️ S'appuie sur des fonctions publiques de l'API System de MineOS (setUser/updateDesktop) ;
|
||||
-- les noms exacts sont À CONFIRMER en jeu. Tout est protégé par pcall : en cas d'échec, le
|
||||
-- login mot de passe MineOS reste pleinement fonctionnel.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local ok, system = pcall(require, "System")
|
||||
if not ok then return end
|
||||
|
||||
local patch = require("mineos/login-fork/patch")
|
||||
local session = require("mineos/lib/session")
|
||||
local branding = require("shared/branding")
|
||||
|
||||
-- Lecture du drapeau kiosque depuis secsite.cfg.
|
||||
local function kioskEnabled()
|
||||
local f = io.open(ROOT .. "/secsite.cfg", "r")
|
||||
if not f then return false end
|
||||
local data = f:read("*a"); f:close()
|
||||
local chunk = load("return " .. (data or ""), "=cfg", "t", {})
|
||||
if not chunk then return false end
|
||||
local okc, cfg = pcall(chunk)
|
||||
return okc and type(cfg) == "table" and cfg.kiosk == true
|
||||
end
|
||||
|
||||
-- Bascule vers le bureau pour un profil donné (via API publique MineOS).
|
||||
local function loginAs(userName)
|
||||
local done = false
|
||||
if system.setUser then done = pcall(system.setUser, userName) end
|
||||
if system.updateDesktop then pcall(system.updateDesktop) end
|
||||
return done
|
||||
end
|
||||
|
||||
-- Après connexion : en mode kiosque, lance SecurityConsole en avant-plan.
|
||||
local function postLogin()
|
||||
if not kioskEnabled() then return end
|
||||
pcall(function()
|
||||
dofile(ROOT .. "/mineos/apps/SecurityConsole.app/Main.lua")
|
||||
end)
|
||||
end
|
||||
|
||||
-- Enrobe system.authorize : démarre l'écoute carte pendant l'écran de login.
|
||||
if not system.__secsitePatched and system.authorize then
|
||||
system.__secsitePatched = true
|
||||
local realAuthorize = system.authorize
|
||||
function system.authorize(...)
|
||||
local stop = patch.cardListener(function(userName, sess)
|
||||
session.set(sess)
|
||||
if loginAs(userName) then
|
||||
if stop then stop() end
|
||||
postLogin()
|
||||
end
|
||||
end)
|
||||
local res = { pcall(realAuthorize, ...) }
|
||||
if stop then stop() end
|
||||
return table.unpack(res, 2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Bannière SecSite (visible si un terminal texte est disponible au boot).
|
||||
pcall(function()
|
||||
for _, line in ipairs(branding.BANNER) do print(line) end
|
||||
end)
|
||||
87
Libraries/mineos/login-fork/patch.lua
Normal file
87
Libraries/mineos/login-fork/patch.lua
Normal file
@ -0,0 +1,87 @@
|
||||
-- mineos/login-fork/patch.lua
|
||||
-- Voie d'authentification « carte » ajoutée à l'écran de connexion MineOS.
|
||||
-- Conçu pour être appelé DEPUIS le login MineOS forké (voir install.lua) : il écoute un swipe,
|
||||
-- résout la carte auprès du serveur, et si un compte correspond, autorise l'ouverture de session.
|
||||
--
|
||||
-- La voie « login + mot de passe » native de MineOS reste inchangée : ce module ajoute la carte
|
||||
-- À CÔTÉ, il ne remplace rien.
|
||||
|
||||
local ROOT = (os.getenv and os.getenv("SECSITE_ROOT")) or "/home/secsite"
|
||||
package.path = ROOT .. "/?.lua;" .. ROOT .. "/?/init.lua;" .. package.path
|
||||
|
||||
local net = require("mineos/lib/net")
|
||||
local card = require("mineos/lib/card")
|
||||
local session = require("mineos/lib/session")
|
||||
local blackout = require("mineos/lib/blackout")
|
||||
|
||||
local patch = {}
|
||||
|
||||
-- Tente une connexion par carte (bloquant jusqu'au swipe ou timeout).
|
||||
-- Renvoie une session serveur { token, name, role } ou (nil, raison).
|
||||
function patch.tryCardLogin(timeout)
|
||||
if not card.available() then return nil, "no_reader" end
|
||||
local cardId = card.await(timeout)
|
||||
if not cardId then return nil, "timeout" end
|
||||
local resp, err = net.loginCard(cardId)
|
||||
if not resp then return nil, err or "no_server" end
|
||||
if not resp.ok then return nil, resp.error end
|
||||
return resp.data
|
||||
end
|
||||
|
||||
-- Boucle d'écoute non bloquante à brancher dans le workspace du login MineOS.
|
||||
-- `onSuccess(session)` est appelé quand une carte valide est présentée.
|
||||
-- Le login MineOS choisit ensuite le profil (même nom) et ouvre la session.
|
||||
function patch.attach(onSuccess, onReject)
|
||||
return function()
|
||||
-- Override « poste inaccessible » : si un blackout est actif, on prend l'écran d'abord.
|
||||
if blackout.enforce() then return end
|
||||
local s, reason = patch.tryCardLogin(0.5)
|
||||
if s then
|
||||
session.set(s) -- rend le token disponible aux apps de sécurité
|
||||
if onSuccess then onSuccess(s) end
|
||||
elseif reason and reason ~= "timeout" and reason ~= "no_reader" then
|
||||
if onReject then onReject(reason) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Écouteur NON bloquant pour l'écran de login MineOS forké (system.authorize).
|
||||
-- Enregistre les événements OpenSecurity (magData/rfidData) — et déclenche les scans RFID —
|
||||
-- puis, sur carte valide, appelle onUser(nomDeCompte, session). À insérer DANS system.authorize
|
||||
-- (voir login-fork/install.lua) : le nom de compte doit correspondre à un profil MineOS.
|
||||
-- Renvoie une fonction stop() à appeler quand on quitte l'écran de login.
|
||||
function patch.cardListener(onUser, onReject)
|
||||
local event = require("event")
|
||||
local reader, kind = card.reader()
|
||||
|
||||
local function handle(cardId)
|
||||
if not cardId or cardId == "" then return end
|
||||
local resp = net.loginCard(cardId)
|
||||
if resp and resp.ok then
|
||||
session.set(resp.data)
|
||||
if onUser then onUser(resp.data.name, resp.data) end
|
||||
elseif onReject then
|
||||
onReject(resp and resp.error or "no_server")
|
||||
end
|
||||
end
|
||||
|
||||
-- magData: (_, address, playerName, cardData, cardUniqueId, isCardLocked, side)
|
||||
local onMag = function(_, _, _, cardData, cardUniqueId) handle(cardData or cardUniqueId) end
|
||||
-- rfidData: (_, uuid, playerName, distance, data)
|
||||
local onRfid = function(_, _, _, _, data) handle(data) end
|
||||
event.listen("magData", onMag)
|
||||
event.listen("rfidData", onRfid)
|
||||
|
||||
local timer
|
||||
if kind == "rfid" and reader then
|
||||
timer = event.timer(1.5, function() pcall(reader.scan) end, math.huge)
|
||||
end
|
||||
|
||||
return function()
|
||||
event.ignore("magData", onMag)
|
||||
event.ignore("rfidData", onRfid)
|
||||
if timer then event.cancel(timer) end
|
||||
end
|
||||
end
|
||||
|
||||
return patch
|
||||
24
Libraries/shared/branding.lua
Normal file
24
Libraries/shared/branding.lua
Normal file
@ -0,0 +1,24 @@
|
||||
-- shared/branding.lua
|
||||
-- Identité visuelle « SecSite OS » (nom, couleurs, bannière). Utilisée par l'enrobage de login,
|
||||
-- le kiosque et (optionnellement) l'en-tête des apps.
|
||||
|
||||
local branding = {}
|
||||
|
||||
branding.NAME = "SecSite OS"
|
||||
branding.TAGLINE = "Terminal de sécurité"
|
||||
branding.COLORS = {
|
||||
bg = 0x0E0E12,
|
||||
panel = 0x1C1C24,
|
||||
accent = 0x2E7D32,
|
||||
alert = 0xB71C1C,
|
||||
text = 0xFFFFFF,
|
||||
}
|
||||
|
||||
branding.BANNER = {
|
||||
"==============================",
|
||||
" S E C S I T E ",
|
||||
" Terminal de sécurité OC ",
|
||||
"==============================",
|
||||
}
|
||||
|
||||
return branding
|
||||
21
Libraries/shared/defense.lua
Normal file
21
Libraries/shared/defense.lua
Normal file
@ -0,0 +1,21 @@
|
||||
-- shared/defense.lua
|
||||
-- Emplacements de contre-mesures anti-missile (CIWS, silo intercepteur…).
|
||||
-- Actionnés en redstone (tourelle/CIWS) ou via composant (silo HBM launch). Mode par défaut au boot.
|
||||
|
||||
local defense = {}
|
||||
|
||||
defense.DEFAULT_MODE = "manual" -- "off" | "manual" | "auto"
|
||||
defense.ENGAGE_LEVEL = 2 -- en mode auto : engage si DEFCON <= ce niveau
|
||||
|
||||
defense.EMPLACEMENTS = {
|
||||
{
|
||||
id = "ciws_1", name = "CIWS Nord", kind = "redstone",
|
||||
side = "up", channel = 6, -- impulsion redstone d'activation
|
||||
},
|
||||
{
|
||||
id = "interceptor_1", name = "Silo intercepteur", kind = "component",
|
||||
address = "REPLACE_WITH_HBM_SILO_ADDRESS", -- composant hbm_silo (launch)
|
||||
},
|
||||
}
|
||||
|
||||
return defense
|
||||
81
Libraries/shared/doors.lua
Normal file
81
Libraries/shared/doors.lua
Normal file
@ -0,0 +1,81 @@
|
||||
-- shared/doors.lua
|
||||
-- Configuration DÉCLARATIVE des portes de l'installation.
|
||||
-- Ajouter une porte = ajouter une entrée ici (aucun code à écrire).
|
||||
-- Le moteur de portes (server/services/doors.lua, lot 2) interprète `type` et `driver`.
|
||||
--
|
||||
-- driver.kind :
|
||||
-- "redstone" -> contrôleur HBM / porte blast / trappe silo, via redstone
|
||||
-- (side + éventuellement channel bundled Project Red)
|
||||
-- "os_secdoor"-> composant OpenSecurity adressé (address)
|
||||
-- type : "simple" | "bunker" | "shelter" | "airlock" | "silo"
|
||||
|
||||
local doors = {}
|
||||
|
||||
doors.CONFIG = {
|
||||
{
|
||||
id = "lobby",
|
||||
name = "Porte d'accueil",
|
||||
type = "simple",
|
||||
zone = "lobby",
|
||||
roles = { "admin", "agent", "invite" },
|
||||
driver = { kind = "os_secdoor", address = "REPLACE_WITH_SECDOOR_ADDRESS" },
|
||||
},
|
||||
{
|
||||
id = "bunker_main",
|
||||
name = "Entrée bunker",
|
||||
type = "bunker",
|
||||
zone = "A",
|
||||
roles = { "admin", "agent" },
|
||||
driver = { kind = "redstone", side = "north", channel = 1 },
|
||||
},
|
||||
{
|
||||
id = "airlock_A",
|
||||
name = "Sas arrivée/sortie A",
|
||||
type = "airlock",
|
||||
zone = "A",
|
||||
roles = { "admin", "agent" },
|
||||
-- Deux battants interverrouillés : un canal par battant.
|
||||
driver = { kind = "redstone", side = "north", channelInner = 2, channelOuter = 3 },
|
||||
cycleSeconds = 3, -- délai de sécurité entre battants
|
||||
},
|
||||
{
|
||||
id = "shelter_1",
|
||||
name = "Porte abri",
|
||||
type = "shelter",
|
||||
zone = "B",
|
||||
roles = { "admin", "agent" },
|
||||
driver = { kind = "redstone", side = "up", channel = 4 },
|
||||
},
|
||||
{
|
||||
id = "silo_hatch",
|
||||
name = "Trappe silo",
|
||||
type = "silo",
|
||||
zone = "silo",
|
||||
roles = { "admin" }, -- + permission restreinte "door:silo"
|
||||
driver = { kind = "redstone", side = "south", channel = 5 },
|
||||
},
|
||||
{
|
||||
id = "bunker_oc",
|
||||
name = "Porte bunker (via addon OC HBM)",
|
||||
type = "bunker",
|
||||
zone = "A",
|
||||
roles = { "admin", "agent" },
|
||||
-- Pilotage direct par composant HBM exposé par hbm-oc-addon (au lieu du redstone).
|
||||
driver = { kind = "hbm_oc", address = "REPLACE_WITH_HBM_DOOR_ADDRESS" },
|
||||
},
|
||||
}
|
||||
|
||||
function doors.get(id)
|
||||
for _, d in ipairs(doors.CONFIG) do
|
||||
if d.id == id then return d end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Permission associée à une porte (ex. "door:A", "door:silo").
|
||||
function doors.permission(door)
|
||||
if door.type == "silo" then return "door:silo" end
|
||||
return "door:" .. (door.zone or door.id)
|
||||
end
|
||||
|
||||
return doors
|
||||
83
Libraries/shared/netsec.lua
Normal file
83
Libraries/shared/netsec.lua
Normal file
@ -0,0 +1,83 @@
|
||||
-- shared/netsec.lua
|
||||
-- Sécurité du réseau privé de l'intranet :
|
||||
-- * secret partagé (déployé uniquement sur les machines admises)
|
||||
-- * signature HMAC-SHA256 de chaque message (authenticité + intégrité)
|
||||
-- * nonce + horodatage -> fenêtre anti-rejeu
|
||||
-- * liste blanche d'adresses de composants
|
||||
-- Un ordinateur qui ne connaît pas le secret ne peut ni forger ni lire un message valide.
|
||||
|
||||
local util = require("shared/util")
|
||||
local sha2 = require("shared/sha2")
|
||||
|
||||
local netsec = {}
|
||||
|
||||
netsec.secret = "CHANGE_ME_DEFAULT_SECRET" -- à remplacer par un fichier de config déployé
|
||||
netsec.window = 30 -- tolérance d'horloge / anti-rejeu (secondes)
|
||||
netsec.whitelist = nil -- nil = pas de filtrage ; table[addr]=true sinon
|
||||
|
||||
function netsec.setSecret(s)
|
||||
assert(type(s) == "string" and #s >= 8, "netsec: secret trop court")
|
||||
netsec.secret = s
|
||||
end
|
||||
|
||||
-- Liste blanche : table {address = true}. nil pour désactiver.
|
||||
function netsec.setWhitelist(set)
|
||||
netsec.whitelist = set
|
||||
end
|
||||
|
||||
function netsec.allow(addr)
|
||||
netsec.whitelist = netsec.whitelist or {}
|
||||
netsec.whitelist[addr] = true
|
||||
end
|
||||
|
||||
function netsec.isAllowed(addr)
|
||||
if not netsec.whitelist then return true end
|
||||
return netsec.whitelist[addr] == true
|
||||
end
|
||||
|
||||
function netsec.mac(data)
|
||||
return sha2.hmac(netsec.secret, data)
|
||||
end
|
||||
|
||||
-- Emballe une charge utile (string déjà sérialisée) dans une enveloppe signée.
|
||||
function netsec.wrap(payload)
|
||||
assert(type(payload) == "string", "netsec.wrap attend une string")
|
||||
local body = tostring(util.now()) .. "|" .. util.uuid(16) .. "|" .. payload
|
||||
return { b = body, m = netsec.mac(body) }
|
||||
end
|
||||
|
||||
-- Vérifie et déballe. Renvoie (payload, nonce) ou (nil, raison).
|
||||
function netsec.unwrap(env)
|
||||
if type(env) ~= "table" or type(env.b) ~= "string" or type(env.m) ~= "string" then
|
||||
return nil, "malformed"
|
||||
end
|
||||
if netsec.mac(env.b) ~= env.m then
|
||||
return nil, "bad_mac"
|
||||
end
|
||||
local ts, nonce, payload = env.b:match("^(%d+)|([^|]+)|(.*)$")
|
||||
if not ts then
|
||||
return nil, "bad_body"
|
||||
end
|
||||
if netsec.window and math.abs(util.now() - tonumber(ts)) > netsec.window then
|
||||
return nil, "expired"
|
||||
end
|
||||
return payload, nonce
|
||||
end
|
||||
|
||||
-- Format « fil » : table -> string signée prête à envoyer sur le modem.
|
||||
function netsec.encode(tbl)
|
||||
return util.serialize(netsec.wrap(util.serialize(tbl)))
|
||||
end
|
||||
|
||||
-- string reçue -> table, ou (nil, raison) si signature/format invalide.
|
||||
function netsec.decode(str)
|
||||
local env = util.deserialize(str)
|
||||
if type(env) ~= "table" then return nil, "decode" end
|
||||
local payload, reason = netsec.unwrap(env)
|
||||
if not payload then return nil, reason end
|
||||
local tbl = util.deserialize(payload)
|
||||
if type(tbl) ~= "table" then return nil, "payload" end
|
||||
return tbl
|
||||
end
|
||||
|
||||
return netsec
|
||||
94
Libraries/shared/protocol.lua
Normal file
94
Libraries/shared/protocol.lua
Normal file
@ -0,0 +1,94 @@
|
||||
-- shared/protocol.lua
|
||||
-- Contrat de communication terminal/agent/tablette <-> serveur.
|
||||
-- Les messages sont des tables sérialisées (util.serialize) puis emballées (netsec.wrap).
|
||||
|
||||
local protocol = {}
|
||||
|
||||
protocol.VERSION = 1
|
||||
protocol.PORT = 2412 -- port modem dédié à l'intranet de sécurité
|
||||
|
||||
-- Types de requêtes (client -> serveur).
|
||||
protocol.REQ = {
|
||||
PING = "ping",
|
||||
AUTH_CARD = "auth.card", -- { cardId } -> résout un compte via badge
|
||||
AUTH_PASSWORD = "auth.password", -- { name, password } -> login classique
|
||||
SESSION_CHECK = "session.check", -- { token }
|
||||
LOGOUT = "auth.logout", -- { token }
|
||||
ACCOUNT_LIST = "account.list", -- { token }
|
||||
ACCOUNT_CREATE = "account.create", -- { token, name, role, cardId?, password? }
|
||||
ACCOUNT_DELETE = "account.delete", -- { token, id }
|
||||
LOG_QUERY = "log.query", -- { token, limit? }
|
||||
DOOR_LIST = "door.list", -- { token }
|
||||
DOOR_CMD = "door.cmd", -- { token, id, action }
|
||||
RADAR_STATE = "radar.state", -- { token }
|
||||
SESSION_LIST = "session.list", -- { token }
|
||||
ACCOUNT_SETROLE = "account.setrole", -- { token, id, role }
|
||||
ACCOUNT_SETCARD = "account.setcard", -- { token, id, cardId? }
|
||||
-- Flotte (lot 4) :
|
||||
NODE_REGISTER = "node.register", -- { token?, address, kind }
|
||||
NODE_CMD = "node.cmd", -- { token, address, command }
|
||||
NODE_LIST = "node.list", -- { token }
|
||||
-- Collaboration (lot 5) :
|
||||
ANNOUNCE_POST = "announce.post", -- { token, text }
|
||||
BOARD_GET = "board.get", -- { token }
|
||||
MSG_SEND = "msg.send", -- { token, to, text }
|
||||
MSG_INBOX = "msg.inbox", -- { token }
|
||||
-- Salle de contrôle & protocoles (lot 7) :
|
||||
SITUATION_GET = "situation.get", -- { token }
|
||||
PROTOCOL_LIST = "protocol.list", -- { token }
|
||||
PROTOCOL_RUN = "protocol.run", -- { token, code, drill }
|
||||
-- Supervision réacteur & contre-mesures :
|
||||
POWER_STATE = "power.state", -- { token }
|
||||
REACTOR_SCRAM = "reactor.scram", -- { token, id }
|
||||
DEFENSE_STATE = "defense.state", -- { token }
|
||||
DEFENSE_MODE = "defense.mode", -- { token, mode }
|
||||
DEFENSE_FIRE = "defense.fire", -- { token }
|
||||
-- Kiosque public (sans token) & configuration :
|
||||
KIOSK_GET = "kiosk.get", -- {} (aucun token requis : info publique)
|
||||
SETTINGS_GET = "settings.get", -- { token }
|
||||
SETTINGS_SET = "settings.set", -- { token, key, value }
|
||||
}
|
||||
|
||||
-- Actions de porte acceptées par DOOR_CMD.
|
||||
protocol.DOOR_ACTIONS = {
|
||||
open = true, close = true,
|
||||
inner_open = true, inner_close = true,
|
||||
outer_open = true, outer_close = true,
|
||||
lockdown = true, release = true,
|
||||
}
|
||||
|
||||
-- Types d'événements diffusés (serveur -> clients).
|
||||
protocol.EVT = {
|
||||
ALERT = "evt.alert", -- montée DEFCON / missile
|
||||
LOCKDOWN = "evt.lockdown",
|
||||
ANNOUNCE = "evt.announce", -- annonce / bulletin
|
||||
}
|
||||
|
||||
-- Messages serveur -> agent de nœud (gestion à distance).
|
||||
protocol.AGENT = {
|
||||
EXEC = "agent.exec", -- { command } où command ∈ NODE_COMMANDS
|
||||
}
|
||||
|
||||
protocol.NODE_COMMANDS = {
|
||||
reboot = true, shutdown = true, lock = true, status = true,
|
||||
blackout = true, release = true, -- override « poste inaccessible » + levée
|
||||
}
|
||||
|
||||
-- Construit une requête.
|
||||
function protocol.request(rtype, payload)
|
||||
local req = payload or {}
|
||||
req.v = protocol.VERSION
|
||||
req.t = rtype
|
||||
return req
|
||||
end
|
||||
|
||||
-- Réponses normalisées.
|
||||
function protocol.ok(data)
|
||||
return { ok = true, data = data }
|
||||
end
|
||||
|
||||
function protocol.err(reason)
|
||||
return { ok = false, error = reason }
|
||||
end
|
||||
|
||||
return protocol
|
||||
58
Libraries/shared/protocols.lua
Normal file
58
Libraries/shared/protocols.lua
Normal file
@ -0,0 +1,58 @@
|
||||
-- shared/protocols.lua
|
||||
-- Protocoles déclaratifs : séquences d'actions lançables par CODE (drill ou override réel).
|
||||
-- Une step = { type=..., ... }. Types gérés par server/services/protocols.lua :
|
||||
-- announce{text} · alarm{on} · lockdown · release · disable_nodes{exclude} · wait{s}
|
||||
-- drillable=true : le protocole peut être joué en simulation (mode drill = non destructif).
|
||||
|
||||
local protocols = {}
|
||||
|
||||
protocols.LIST = {
|
||||
{
|
||||
id = "drill_evac", name = "Drill évacuation", code = "1111", role = "agent", drillable = true,
|
||||
steps = {
|
||||
{ type = "announce", text = "DRILL: procédure d'évacuation" },
|
||||
{ type = "alarm", on = true },
|
||||
{ type = "wait", s = 3 },
|
||||
{ type = "alarm", on = false },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "lockdown_full", name = "Confinement total", code = "2222", role = "admin", drillable = true,
|
||||
steps = {
|
||||
{ type = "announce", text = "CONFINEMENT TOTAL EN COURS" },
|
||||
{ type = "alarm", on = true },
|
||||
{ type = "lockdown" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "release_all", name = "Levée du confinement", code = "0000", role = "admin", drillable = false,
|
||||
steps = {
|
||||
{ type = "release" },
|
||||
{ type = "alarm", on = false },
|
||||
{ type = "announce", text = "Fin de confinement" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "blackout", name = "Coupure des postes (override)", code = "9999", role = "admin", drillable = false,
|
||||
steps = {
|
||||
{ type = "announce", text = "OVERRIDE: coupure des postes de travail" },
|
||||
{ type = "disable_nodes", exclude = "display" }, -- épargne les écrans du mur
|
||||
},
|
||||
},
|
||||
{
|
||||
id = "restore_nodes", name = "Rétablir les postes", code = "9990", role = "admin", drillable = false,
|
||||
steps = {
|
||||
{ type = "restore_nodes", exclude = "display" },
|
||||
{ type = "announce", text = "Postes rétablis" },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function protocols.get(idOrCode)
|
||||
for _, p in ipairs(protocols.LIST) do
|
||||
if p.id == idOrCode or p.code == idOrCode then return p end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return protocols
|
||||
31
Libraries/shared/reactors.lua
Normal file
31
Libraries/shared/reactors.lua
Normal file
@ -0,0 +1,31 @@
|
||||
-- shared/reactors.lua
|
||||
-- Machines HBM à superviser (réacteurs / gros consommateurs d'énergie).
|
||||
-- Lues via l'addon OC HBM (composants hbm_reactor / hbm_machine). Ajouter une machine = une entrée.
|
||||
|
||||
local reactors = {}
|
||||
|
||||
reactors.CONFIG = {
|
||||
{
|
||||
id = "reactor_1",
|
||||
name = "Réacteur principal",
|
||||
address = "REPLACE_WITH_HBM_REACTOR_ADDRESS", -- composant hbm_reactor
|
||||
tempWarn = 800, -- seuil d'avertissement
|
||||
tempCrit = 1000, -- seuil critique (déclenche alarme + SCRAM auto si activé)
|
||||
autoScram = true, -- arrêt d'urgence automatique au seuil critique
|
||||
},
|
||||
{
|
||||
id = "grid",
|
||||
name = "Réseau énergie",
|
||||
address = "REPLACE_WITH_HBM_MACHINE_ADDRESS", -- composant hbm_machine (getEnergy)
|
||||
energyOnly = true, -- pas de température : supervision d'énergie seule
|
||||
},
|
||||
}
|
||||
|
||||
function reactors.get(id)
|
||||
for _, r in ipairs(reactors.CONFIG) do
|
||||
if r.id == id then return r end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return reactors
|
||||
64
Libraries/shared/roles.lua
Normal file
64
Libraries/shared/roles.lua
Normal file
@ -0,0 +1,64 @@
|
||||
-- shared/roles.lua
|
||||
-- Rôles et permissions. Vérifiés CÔTÉ SERVEUR (jamais faire confiance au client).
|
||||
-- Une permission est une chaîne ; le suffixe "*" agit comme joker (ex. "door:*").
|
||||
|
||||
local roles = {}
|
||||
|
||||
roles.LIST = { "admin", "agent", "invite" }
|
||||
|
||||
roles.PERMISSIONS = {
|
||||
admin = { "*" }, -- tout
|
||||
agent = {
|
||||
"view_dashboard",
|
||||
"view_logs",
|
||||
"ack_alarm",
|
||||
"lockdown",
|
||||
"announce", -- publier une annonce / bulletin
|
||||
"drill", -- lancer un protocole en mode simulation
|
||||
"door:*", -- toutes les portes de zone...
|
||||
-- (le silo reste réservé à admin : "door:silo" n'est PAS couvert, voir ci-dessous)
|
||||
},
|
||||
invite = {
|
||||
"view_dashboard",
|
||||
"door:lobby",
|
||||
},
|
||||
}
|
||||
|
||||
-- Permissions sensibles qui exigent un rôle explicite même si un joker pourrait matcher.
|
||||
-- Ex : la trappe de silo ne doit jamais tomber sous "door:*".
|
||||
roles.RESTRICTED = {
|
||||
["door:silo"] = { admin = true },
|
||||
}
|
||||
|
||||
local function matches(pattern, perm)
|
||||
if pattern == perm then return true end
|
||||
local prefix = pattern:match("^(.-)%*$")
|
||||
if prefix then
|
||||
return perm:sub(1, #prefix) == prefix
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- roles.can(role, permission) -> booléen
|
||||
function roles.can(role, permission)
|
||||
if type(role) ~= "string" or type(permission) ~= "string" then return false end
|
||||
|
||||
-- Restriction explicite : seul un rôle listé passe, joker ignoré.
|
||||
local restricted = roles.RESTRICTED[permission]
|
||||
if restricted then
|
||||
return restricted[role] == true
|
||||
end
|
||||
|
||||
local perms = roles.PERMISSIONS[role]
|
||||
if not perms then return false end
|
||||
for _, p in ipairs(perms) do
|
||||
if matches(p, permission) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function roles.isRole(role)
|
||||
return roles.PERMISSIONS[role] ~= nil
|
||||
end
|
||||
|
||||
return roles
|
||||
99
Libraries/shared/sha2.lua
Normal file
99
Libraries/shared/sha2.lua
Normal file
@ -0,0 +1,99 @@
|
||||
-- shared/sha2.lua
|
||||
-- SHA-256 + HMAC-SHA256 en Lua 5.3 pur (entiers 64 bits + opérateurs bit à bit + string.pack).
|
||||
-- Aucune dépendance matérielle (pas besoin d'une Data Card OpenComputers).
|
||||
-- Vérifié contre les vecteurs de test standard (voir tools/test/run.lua).
|
||||
|
||||
local sha2 = {}
|
||||
|
||||
local MASK = 0xFFFFFFFF
|
||||
|
||||
local function rotr(x, n)
|
||||
return ((x >> n) | (x << (32 - n))) & MASK
|
||||
end
|
||||
|
||||
local function shr(x, n)
|
||||
return (x >> n) & MASK
|
||||
end
|
||||
|
||||
local function bnot32(x)
|
||||
return (~x) & MASK
|
||||
end
|
||||
|
||||
local K = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
}
|
||||
|
||||
-- Renvoie les 8 mots d'état (h0..h7) après hachage de msg.
|
||||
local function core(msg)
|
||||
local h0, h1, h2, h3 = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a
|
||||
local h4, h5, h6, h7 = 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
|
||||
|
||||
local bitlen = #msg * 8
|
||||
msg = msg .. "\128"
|
||||
while (#msg % 64) ~= 56 do msg = msg .. "\0" end
|
||||
msg = msg .. string.pack(">I4", (bitlen >> 32) & MASK) .. string.pack(">I4", bitlen & MASK)
|
||||
|
||||
local w = {}
|
||||
for chunk = 1, #msg, 64 do
|
||||
for j = 0, 15 do
|
||||
w[j] = (string.unpack(">I4", msg, chunk + j * 4))
|
||||
end
|
||||
for j = 16, 63 do
|
||||
local a15, a2 = w[j - 15], w[j - 2]
|
||||
local s0 = rotr(a15, 7) ~ rotr(a15, 18) ~ shr(a15, 3)
|
||||
local s1 = rotr(a2, 17) ~ rotr(a2, 19) ~ shr(a2, 10)
|
||||
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) & MASK
|
||||
end
|
||||
|
||||
local a, b, c, d = h0, h1, h2, h3
|
||||
local e, f, g, h = h4, h5, h6, h7
|
||||
for j = 0, 63 do
|
||||
local S1 = rotr(e, 6) ~ rotr(e, 11) ~ rotr(e, 25)
|
||||
local ch = (e & f) ~ (bnot32(e) & g)
|
||||
local t1 = (h + S1 + ch + K[j + 1] + w[j]) & MASK
|
||||
local S0 = rotr(a, 2) ~ rotr(a, 13) ~ rotr(a, 22)
|
||||
local maj = (a & b) ~ (a & c) ~ (b & c)
|
||||
local t2 = (S0 + maj) & MASK
|
||||
h = g; g = f; f = e; e = (d + t1) & MASK
|
||||
d = c; c = b; b = a; a = (t1 + t2) & MASK
|
||||
end
|
||||
|
||||
h0 = (h0 + a) & MASK; h1 = (h1 + b) & MASK; h2 = (h2 + c) & MASK; h3 = (h3 + d) & MASK
|
||||
h4 = (h4 + e) & MASK; h5 = (h5 + f) & MASK; h6 = (h6 + g) & MASK; h7 = (h7 + h) & MASK
|
||||
end
|
||||
|
||||
return h0, h1, h2, h3, h4, h5, h6, h7
|
||||
end
|
||||
|
||||
-- Digest binaire (32 octets).
|
||||
function sha2.digest(msg)
|
||||
return string.pack(">I4I4I4I4I4I4I4I4", core(msg))
|
||||
end
|
||||
|
||||
-- Digest hexadécimal (64 caractères).
|
||||
function sha2.sha256(msg)
|
||||
return (string.format("%08x%08x%08x%08x%08x%08x%08x%08x", core(msg)))
|
||||
end
|
||||
|
||||
-- HMAC-SHA256 -> hex.
|
||||
function sha2.hmac(key, msg)
|
||||
local B = 64
|
||||
if #key > B then key = sha2.digest(key) end
|
||||
key = key .. string.rep("\0", B - #key)
|
||||
local ipad, opad = {}, {}
|
||||
for n = 1, B do
|
||||
local kb = string.byte(key, n)
|
||||
ipad[n] = string.char(kb ~ 0x36)
|
||||
opad[n] = string.char(kb ~ 0x5c)
|
||||
end
|
||||
return sha2.sha256(table.concat(opad) .. sha2.digest(table.concat(ipad) .. msg))
|
||||
end
|
||||
|
||||
return sha2
|
||||
17
Libraries/shared/site.lua
Normal file
17
Libraries/shared/site.lua
Normal file
@ -0,0 +1,17 @@
|
||||
-- shared/site.lua
|
||||
-- Paramètres physiques de l'installation, utilisés par l'agrégateur de situation
|
||||
-- (ETA impact, risque, temps de lockdown complet). À ajuster aux coordonnées réelles en jeu.
|
||||
|
||||
local site = {}
|
||||
|
||||
site.CONFIG = {
|
||||
name = "Installation Alpha",
|
||||
center = { x = 0, y = 64, z = 0 }, -- centre du site (coordonnées monde)
|
||||
radius = 64, -- rayon considéré « installation » (blocs)
|
||||
lockdown = {
|
||||
perDoorSeconds = 2, -- durée de fermeture d'une porte simple/bunker/shelter
|
||||
airlockSeconds = 3, -- durée de cycle d'un sas
|
||||
},
|
||||
}
|
||||
|
||||
return site
|
||||
91
Libraries/shared/util.lua
Normal file
91
Libraries/shared/util.lua
Normal file
@ -0,0 +1,91 @@
|
||||
-- shared/util.lua
|
||||
-- Petits utilitaires portables (OpenComputers / Lua 5.3 standard).
|
||||
-- Pas de dépendance à un composant : utilisable côté serveur, terminal, agent et en test.
|
||||
|
||||
local util = {}
|
||||
|
||||
-- Horloge : uptime monotone si dispo (OC expose `computer`), sinon os.clock.
|
||||
function util.uptime()
|
||||
if type(_G.computer) == "table" and computer.uptime then
|
||||
return computer.uptime()
|
||||
end
|
||||
return os.clock()
|
||||
end
|
||||
|
||||
-- Timestamp epoch (secondes). os.time existe sur OpenOS et en Lua standard.
|
||||
function util.now()
|
||||
return os.time()
|
||||
end
|
||||
|
||||
-- Horodatage lisible pour les logs.
|
||||
function util.stamp(t)
|
||||
return os.date("!%Y-%m-%d %H:%M:%S", t or util.now())
|
||||
end
|
||||
|
||||
-- Identifiant court (8 hex). Suffisant pour des ids internes / nonces.
|
||||
local seeded = false
|
||||
local function seed()
|
||||
if seeded then return end
|
||||
seeded = true
|
||||
local s = (util.now() * 1000) + math.floor((util.uptime() * 1000) % 1000000)
|
||||
math.randomseed(s)
|
||||
end
|
||||
|
||||
function util.uuid(len)
|
||||
seed()
|
||||
len = len or 8
|
||||
local out = {}
|
||||
for i = 1, len do
|
||||
out[i] = string.format("%x", math.random(0, 15))
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
-- Jeton de session plus long.
|
||||
function util.token()
|
||||
return util.uuid(32)
|
||||
end
|
||||
|
||||
-- Sérialisation Lua minimale (tables de string/number/boolean, imbriquées).
|
||||
function util.serialize(v)
|
||||
local t = type(v)
|
||||
if t == "nil" then
|
||||
return "nil"
|
||||
elseif t == "number" or t == "boolean" then
|
||||
return tostring(v)
|
||||
elseif t == "string" then
|
||||
return string.format("%q", v)
|
||||
elseif t == "table" then
|
||||
local parts = {}
|
||||
for k, val in pairs(v) do
|
||||
local key
|
||||
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
|
||||
key = k
|
||||
else
|
||||
key = "[" .. util.serialize(k) .. "]"
|
||||
end
|
||||
parts[#parts + 1] = key .. "=" .. util.serialize(val)
|
||||
end
|
||||
return "{" .. table.concat(parts, ",") .. "}"
|
||||
end
|
||||
error("util.serialize: type non supporté: " .. t)
|
||||
end
|
||||
|
||||
-- Désérialisation en environnement vide (aucun accès aux globales -> pas d'exécution).
|
||||
function util.deserialize(s)
|
||||
if type(s) ~= "string" then return nil end
|
||||
local f = load("return " .. s, "=data", "t", {})
|
||||
if not f then return nil end
|
||||
local ok, res = pcall(f)
|
||||
if ok then return res end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Copie superficielle (pratique pour renvoyer un état sans exposer la table interne).
|
||||
function util.shallow(t)
|
||||
local out = {}
|
||||
for k, v in pairs(t) do out[k] = v end
|
||||
return out
|
||||
end
|
||||
|
||||
return util
|
||||
Loading…
Reference in New Issue
Block a user