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:
Claude 2026-07-08 17:14:57 +00:00
parent cc33aeb355
commit 4544d1b12b
No known key found for this signature in database
34 changed files with 1794 additions and 3 deletions

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View 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()

View File

@ -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",

View File

@ -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"

View File

@ -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)

View File

@ -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))

View 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

View 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

View 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

View 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

View 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

View 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)

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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
View 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
View 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