10 Commits

Author SHA1 Message Date
100b23793c Merge pull request 'Many changes' (#62) from Kan18/develop-1.8 into develop-1.8
Some checks failed
CI / build (push) Has been cancelled
Reviewed-on: #62
2026-03-22 11:58:40 -04:00
Kan18
9b502553e0 Update json.lua 2023-12-17 10:14:40 -05:00
Kan18
c247097e20 oops 2022-12-28 20:25:52 +04:00
Kan18
2597724dab Fix Util.getVersion 2022-12-28 20:05:16 +04:00
Kan18
5f153721ea oops 2022-12-28 19:26:48 +04:00
Kan18
0a42551ab7 actually set it to 30 2022-12-28 19:22:38 +04:00
Kan18
76b310d873 oops 2022-12-28 19:22:20 +04:00
Kan18
f26f443b9d Increase discovery message interval, distribute messages
Previously on large server(s), there was an issue where because
chunkloaded computers all started up at once, so they all had the same
discovery message sending timer. This prevents that by starting each off
with a random offset. Also increases the interval for sending messages
from 15 s to 30 s, so that messages (which are the same, a lot of the
time) get sent less often.
2022-12-28 18:43:49 +04:00
Kan18
8fbcc7b8bc Sanitize label in samba.lua
Prevent labels from having .., /, *, control characters, or quotes (this
generally messes things up) and limit them to a reasonable length. We
might possibly also want to do this in snmp.lua, I'm not sure if that
will break things though
2022-12-28 18:26:27 +04:00
Kan18
f7ba900930 genotp.lua changes
Make genotp more clearly state that the password can be used once but
allows permanent access (sorry for not making this clear earlier.) Use a
slightly longer password, and allow uppercase characters except for some
that could be ambiguous
2022-12-28 13:22:57 +04:00
22 changed files with 123 additions and 670 deletions

View File

@@ -16,5 +16,5 @@
## Install ## Install
``` ```
wget run https://git.spatulaa.com/MayaTheShy/Opus/raw/branch/main/startup.lua pastebin run UzGHLbNC
``` ```

View File

@@ -16,27 +16,25 @@ local page = UI.Page {
x = 2, ex = 14, y = 2, ey = -6, x = 2, ex = 14, y = 2, ey = -6,
values = { }, values = { },
columns = { columns = {
{ heading = ' Package', key = 'displayName' }, { heading = 'Package', key = 'name' },
}, },
sortColumn = 'name', sortColumn = 'name',
autospace = true, autospace = true,
help = 'Space to select, Enter to toggle', help = 'Select a package',
}, },
installSelected = UI.Button { add = UI.Button {
x = 2, y = -3, x = 2, y = -3,
text = ' + ', text = ' + ',
event = 'batch_action', event = 'action',
operation = 'install', help = 'Install or update',
operationText = 'Install',
help = 'Install or update selected',
}, },
removeSelected = UI.Button { remove = UI.Button {
x = 8, y = -3, x = 8, y = -3,
text = ' - ', text = ' - ',
event = 'batch_action', event = 'action',
operation = 'uninstall', operation = 'uninstall',
operationText = 'Remove', operationText = 'Remove',
help = 'Remove selected', help = 'Remove',
}, },
updateall = UI.Button { updateall = UI.Button {
ex = -2, y = -3, width = 12, ex = -2, y = -3, width = 12,
@@ -91,9 +89,7 @@ function page:loadPackages()
end end
table.insert(self.grid.values, { table.insert(self.grid.values, {
installed = not not Packages:isInstalled(k), installed = not not Packages:isInstalled(k),
selected = false,
name = k, name = k,
displayName = k,
manifest = manifest, manifest = manifest,
}) })
end end
@@ -108,62 +104,12 @@ function page:loadPackages()
end end
function page.grid:getRowTextColor(row, selected) function page.grid:getRowTextColor(row, selected)
if row.selected then
return colors.cyan
end
if row.installed then if row.installed then
return colors.yellow return colors.yellow
end end
return UI.Grid.getRowTextColor(self, row, selected) return UI.Grid.getRowTextColor(self, row, selected)
end end
function page:getSelectedPackages()
local selected = { }
for _, row in pairs(self.grid.values) do
if row.selected then
table.insert(selected, row)
end
end
return selected
end
function page:getSelectedCount()
local count = 0
for _, row in pairs(self.grid.values) do
if row.selected then
count = count + 1
end
end
return count
end
function page:toggleSelection(row)
row.selected = not row.selected
row.displayName = row.selected and ('\187 ' .. row.name) or row.name
self.grid:draw()
self:updateStatus()
end
function page:clearSelection()
for _, row in pairs(self.grid.values) do
row.selected = false
row.displayName = row.name
end
self.grid:draw()
end
function page:updateStatus()
local count = self:getSelectedCount()
if count > 0 then
self.statusBar:setStatus(count .. ' package(s) selected')
else
local focused = self.grid:getSelected()
if focused then
self.statusBar:setStatus(focused.installed and 'Installed' or '')
end
end
end
function page.action:show() function page.action:show()
self.output.win:clear() self.output.win:clear()
UI.SlideOut.show(self) UI.SlideOut.show(self)
@@ -188,7 +134,11 @@ function page:run(operation, name)
end end
function page:updateSelection(selected) function page:updateSelection(selected)
-- no-op: buttons are always active for batch operations self.add.operation = selected.installed and 'update' or 'install'
self.add.operationText = selected.installed and 'Update' or 'Install'
self.remove.inactive = not selected.installed
self.add:draw()
self.remove:draw()
end end
function page:eventHandler(event) function page:eventHandler(event)
@@ -202,14 +152,7 @@ function page:eventHandler(event)
Ansi.yellow, manifest.title, Ansi.yellow, manifest.title,
Ansi.white, manifest.description)) Ansi.white, manifest.description))
self.description:draw() self.description:draw()
self:updateStatus() self:updateSelection(event.selected)
elseif event.type == 'grid_select' then
-- Space or Enter toggles selection
local row = self.grid:getSelected()
if row then
self:toggleSelection(row)
end
elseif event.type == 'checkbox_change' then elseif event.type == 'checkbox_change' then
config.compression = not config.compression config.compression = not config.compression
@@ -217,69 +160,34 @@ function page:eventHandler(event)
elseif event.type == 'updateall' then elseif event.type == 'updateall' then
self.operation = 'updateall' self.operation = 'updateall'
self.operationTargets = { }
self.action.button.text = ' Begin ' self.action.button.text = ' Begin '
self.action.button.event = 'begin' self.action.button.event = 'begin'
self.action.titleBar.title = 'Update All' self.action.titleBar.title = 'Update All'
self.action:show() self.action:show()
elseif event.type == 'batch_action' then elseif event.type == 'action' then
local targets = self:getSelectedPackages() local selected = self.grid:getSelected()
local operation = event.button.operation if selected then
self.operation = event.button.operation
-- fall back to focused row if nothing selected self.action.button.text = event.button.operationText
if #targets == 0 then self.action.titleBar.title = selected.manifest.title
local focused = self.grid:getSelected() self.action.button.text = ' Begin '
if focused then self.action.button.event = 'begin'
targets = { focused } self.action:show()
end
end end
if #targets == 0 then return end
-- for install: update already-installed packages, install new ones
if operation == 'install' then
self.operation = 'install'
else
-- filter to only installed packages for uninstall
local installed = { }
for _, t in ipairs(targets) do
if t.installed then
table.insert(installed, t)
end
end
targets = installed
if #targets == 0 then return end
self.operation = 'uninstall'
end
self.operationTargets = targets
local title = #targets == 1
and targets[1].manifest.title
or (#targets .. ' packages')
self.action.button.text = ' Begin '
self.action.button.event = 'begin'
self.action.titleBar.title = string.format('%s %s',
operation == 'install' and 'Install/Update' or 'Remove', title)
self.action:show()
elseif event.type == 'hide-action' then elseif event.type == 'hide-action' then
self.action:hide() self.action:hide()
elseif event.type == 'begin' then elseif event.type == 'begin' then
if self.operation == 'updateall' then if self.operation == 'updateall' then
self:run('updateall', '') self:run(self.operation, '')
else else
for _, target in ipairs(self.operationTargets) do local selected = self.grid:getSelected()
local op = self.operation self:run(self.operation, selected.name)
if op == 'install' and target.installed then selected.installed = Packages:isInstalled(selected.name)
op = 'update'
end self:updateSelection(selected)
self:run(op, target.name)
target.installed = Packages:isInstalled(target.name)
end
self:clearSelection()
self:updateStatus()
end end
self.action.button.text = ' Done ' self.action.button.text = ' Done '

View File

@@ -2,7 +2,6 @@ local Ansi = require('opus.ansi')
local Security = require('opus.security') local Security = require('opus.security')
local SHA = require('opus.crypto.sha2') local SHA = require('opus.crypto.sha2')
local UI = require('opus.ui') local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors local colors = _G.colors
local os = _G.os local os = _G.os
@@ -17,9 +16,9 @@ local labelIntro = [[Set a friendly name for this computer.
local passwordIntro = [[A password is required for wireless access. local passwordIntro = [[A password is required for wireless access.
%sLeave blank to skip.]] %sLeave blank to skip.]]
local packagesIntro = [[Optional Packages local packagesIntro = [[Setup Complete
%sSelect packages to install with this computer. You can always add more later from the Package Manager.]] %sOpen the package manager to add software to this computer.]]
local contributorsIntro = [[Contributors%s local contributorsIntro = [[Contributors%s
Anavrins: Encryption/security/custom apps Anavrins: Encryption/security/custom apps
@@ -29,7 +28,7 @@ LDDestroier: Art design + custom apps
Lemmmy: Application improvements Lemmmy: Application improvements
%sContribute at:%s %sContribute at:%s
https://git.spatulaa.com/MayaTheShy/Opus]] https://github.com/kepler155c/opus]]
local page = UI.Page { local page = UI.Page {
wizard = UI.Wizard { wizard = UI.Wizard {
@@ -94,54 +93,17 @@ local page = UI.Page {
}, },
packages = UI.WizardPage { packages = UI.WizardPage {
index = 4, index = 4,
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea { intro = UI.TextArea {
textColor = colors.yellow, textColor = colors.yellow,
inactive = true, inactive = true,
x = 3, ex = -3, y = 2, ey = 5, x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white), value = string.format(packagesIntro, Ansi.white),
}, },
chkTurtle = UI.Checkbox {
x = 5, y = 7,
label = 'RemoteTurtle',
textColor = 'yellow',
backgroundColor = 'primary',
value = false,
},
lblTurtle = UI.Text {
x = 22, y = 7,
value = 'Turtle control + web dashboard',
textColor = colors.lightGray,
},
chkInventory = UI.Checkbox {
x = 5, y = 9,
label = 'Inventory Manager',
textColor = 'yellow',
backgroundColor = 'primary',
value = false,
},
lblInventory = UI.Text {
x = 28, y = 9,
value = 'Storage automation system',
textColor = colors.lightGray,
},
button = UI.Button {
x = 3, y = -3,
text = 'More Packages...',
event = 'packages',
},
validate = function(self)
local toInstall = { }
if self.chkTurtle.value then
table.insert(toInstall, 'remoteturtle')
end
if self.chkInventory.value then
table.insert(toInstall, 'inventory-manager')
end
for _, pkg in ipairs(toInstall) do
shell.run('package install ' .. pkg)
end
return true
end,
}, },
contributors = UI.WizardPage { contributors = UI.WizardPage {
index = 5, index = 5,

View File

@@ -1,200 +0,0 @@
-- Memory Profiler for CC:Tweaked / Opus OS
-- Usage: memprofile [--watch] [--interval <seconds>]
--
-- Shows current Lua memory usage with breakdown estimates.
-- Use --watch to continuously monitor.
-- Useful for detecting memory leaks and understanding overhead.
local args = { ... }
local watch = false
local interval = 3
for i, arg in ipairs(args) do
if arg == '--watch' or arg == '-w' then
watch = true
elseif arg == '--interval' or arg == '-i' then
interval = tonumber(args[i + 1]) or 3
elseif arg == '--help' or arg == '-h' then
print('Usage: memprofile [--watch] [--interval <secs>]')
print('')
print('Options:')
print(' --watch, -w Continuously monitor memory')
print(' --interval, -i N Update interval in seconds (default: 3)')
print(' --help, -h Show this help')
return
end
end
local term = _G.term
local os = _G.os
local function formatBytes(bytes)
if bytes < 1024 then
return string.format('%d B', bytes)
elseif bytes < 1024 * 1024 then
return string.format('%.1f KB', bytes / 1024)
else
return string.format('%.2f MB', bytes / (1024 * 1024))
end
end
local function countTable(t, seen)
if type(t) ~= 'table' or seen[t] then return 0, 0 end
seen[t] = true
local entries = 0
local nested = 0
for k, v in pairs(t) do
entries = entries + 1
if type(v) == 'table' then
local e, n = countTable(v, seen)
nested = nested + 1 + e
entries = entries + n
end
if type(k) == 'table' then
local e, n = countTable(k, seen)
nested = nested + 1 + e
entries = entries + n
end
end
return entries, nested
end
local function getSnapshot()
-- Force a full GC cycle to get accurate usage
collectgarbage('collect')
collectgarbage('collect')
local memKB = collectgarbage('count') -- returns KB as float
local snapshot = {
totalKB = memKB,
totalBytes = math.floor(memKB * 1024),
timestamp = os.clock(),
}
-- Count entries in major global tables
local seen = {}
local globals = {}
local interesting = {
{ name = '_G (globals)', tbl = _G },
{ name = 'kernel', tbl = _G.kernel },
{ name = 'network', tbl = _G.network },
{ name = 'device', tbl = _G.device },
}
for _, item in ipairs(interesting) do
if type(item.tbl) == 'table' then
local entries, nested = countTable(item.tbl, seen)
table.insert(globals, {
name = item.name,
entries = entries,
nested = nested,
})
end
end
snapshot.globals = globals
-- Count routines if kernel is available
if _G.kernel and _G.kernel.routines then
snapshot.routines = #_G.kernel.routines
end
-- Count loaded modules
if package and package.loaded then
local count = 0
for _ in pairs(package.loaded) do
count = count + 1
end
snapshot.loadedModules = count
end
return snapshot
end
local function printSnapshot(snap, prev)
term.clear()
term.setCursorPos(1, 1)
local w = term.getSize()
local sep = string.rep('-', w)
term.setTextColor(colors.yellow)
print('=== Memory Profile ===')
term.setTextColor(colors.white)
print('')
-- Total memory
local memStr = formatBytes(snap.totalBytes)
local deltaStr = ''
if prev then
local delta = snap.totalBytes - prev.totalBytes
if delta > 0 then
deltaStr = string.format(' (+%s)', formatBytes(delta))
term.setTextColor(colors.red)
elseif delta < 0 then
deltaStr = string.format(' (-%s)', formatBytes(-delta))
term.setTextColor(colors.green)
end
end
term.setTextColor(colors.white)
print(string.format('Total Memory: %s%s', memStr, deltaStr))
print(string.format('Uptime: %.1fs', snap.timestamp))
print('')
-- Table sizes
term.setTextColor(colors.lightBlue)
print('Global Tables:')
term.setTextColor(colors.white)
print(sep)
print(string.format(' %-20s %8s %8s', 'Name', 'Entries', 'Nested'))
print(sep)
for _, g in ipairs(snap.globals) do
print(string.format(' %-20s %8d %8d', g.name, g.entries, g.nested))
end
print(sep)
print('')
-- Kernel info
if snap.routines then
term.setTextColor(colors.lightBlue)
print('Kernel:')
term.setTextColor(colors.white)
print(string.format(' Active routines: %d', snap.routines))
end
if snap.loadedModules then
print(string.format(' Loaded modules: %d', snap.loadedModules))
end
print('')
-- CC:Tweaked limits
term.setTextColor(colors.gray)
print('Note: CC:Tweaked default memory limit is ~128MB per computer.')
print('High memory usage may cause slowdowns or crashes.')
if watch then
print('')
term.setTextColor(colors.yellow)
print(string.format('Refreshing every %ds... (Ctrl+T to stop)', interval))
end
end
local function run()
local prev = nil
if watch then
while true do
local snap = getSnapshot()
printSnapshot(snap, prev)
prev = snap
os.sleep(interval)
end
else
local snap = getSnapshot()
printSnapshot(snap, nil)
end
end
run()

View File

@@ -7,7 +7,6 @@
local Crypto = require('opus.crypto.chacha20') local Crypto = require('opus.crypto.chacha20')
local Event = require('opus.event') local Event = require('opus.event')
local SHA = require('opus.crypto.sha2')
local network = _G.network local network = _G.network
local os = _G.os local os = _G.os
@@ -36,19 +35,7 @@ function transport.read(socket)
local data = table.remove(socket.messages, 1) local data = table.remove(socket.messages, 1)
if data then if data then
if socket.options.ENCRYPT then if socket.options.ENCRYPT then
local ciphertext = data[1] return table.unpack(Crypto.decrypt(data[1], socket.enckey)), data[2]
-- Verify HMAC if present (new protocol)
if socket.hmackey and type(ciphertext) == 'table' and ciphertext.hmac then
local expected = SHA.hmac(
ciphertext[1] .. ciphertext[2],
socket.hmackey
):toHex()
if expected ~= ciphertext.hmac then
_G._syslog('transport: HMAC verification failed on port ' .. socket.sport)
return nil
end
end
return table.unpack(Crypto.decrypt(ciphertext, socket.enckey)), data[2]
end end
return table.unpack(data) return table.unpack(data)
end end
@@ -91,15 +78,7 @@ Event.on('transport_encrypt', function()
if socket and socket.connected then if socket and socket.connected then
local msg = entry[2] local msg = entry[2]
local encrypted = Crypto.encrypt({ msg.data }, socket.enckey) msg.data = Crypto.encrypt({ msg.data }, socket.enckey)
-- Attach HMAC if key is available
if socket.hmackey then
encrypted.hmac = SHA.hmac(
encrypted[1] .. encrypted[2],
socket.hmackey
):toHex()
end
msg.data = encrypted
socket.transmit(socket.dport, socket.dhost, msg) socket.transmit(socket.dport, socket.dhost, msg)
end end
end end

View File

@@ -25,11 +25,11 @@ end
local function trustConnection(socket) local function trustConnection(socket)
local data = socket:read(2) local data = socket:read(2)
if data then if data then
local trustKey = Security.getTrustKey() local password = Security.getPassword()
if not trustKey then if not password then
socket:write({ msg = 'No password has been set' }) socket:write({ msg = 'No password has been set' })
else else
if validateData(data, trustKey, socket.dhost) then if validateData(data, password, socket.dhost) then
print("Accepted trust from " .. socket.dhost) print("Accepted trust from " .. socket.dhost)
socket:write({ success = true, msg = 'Trust accepted' }) socket:write({ success = true, msg = 'Trust accepted' })
return return

View File

@@ -78,19 +78,6 @@ local function install(name, isUpdate, ignoreDeps)
local packageDir = fs.combine('packages', name) local packageDir = fs.combine('packages', name)
local list = Git.list(manifest.repository) local list = Git.list(manifest.repository)
-- apply exclude filters from manifest
if manifest.exclude then
for path in pairs(list) do
for _, pattern in ipairs(manifest.exclude) do
if path:match(pattern) then
list[path] = nil
break
end
end
end
end
-- clear out contents before install/update -- clear out contents before install/update
-- TODO: figure out whether to run -- TODO: figure out whether to run
-- install/uninstall for the package -- install/uninstall for the package

View File

@@ -1,9 +1,10 @@
local Security = require('opus.security') local Security = require('opus.security')
local SHA = require('opus.crypto.sha2')
local Terminal = require('opus.terminal') local Terminal = require('opus.terminal')
local password = Terminal.readPassword('Enter new password: ') local password = Terminal.readPassword('Enter new password: ')
if password then if password then
Security.updatePassword(password) Security.updatePassword(SHA.compute(password))
print('Password updated') print('Password updated')
end end

View File

@@ -4,7 +4,7 @@ local Util = require('opus.util')
local fs = _G.fs local fs = _G.fs
local shell = _ENV.shell local shell = _ENV.shell
local URL = 'https://git.spatulaa.com/MayaTheShy/Opus/raw/branch/%s/.opus_version' local URL = 'https://raw.githubusercontent.com/kepler155c/opus/%s/.opus_version'
if fs.exists('.opus_version') then if fs.exists('.opus_version') then
local f = fs.open('.opus_version', 'r') local f = fs.open('.opus_version', 'r')

View File

@@ -1,5 +1,5 @@
sys/apps/pain.lua urlfs https://github.com/LDDestroier/CC/raw/master/pain.lua sys/apps/pain.lua urlfs https://github.com/LDDestroier/CC/raw/master/pain.lua
sys/apps/update.lua urlfs https://pastebin.com/raw/UzGHLbNC sys/apps/update.lua urlfs http://pastebin.com/raw/UzGHLbNC
sys/apps/Enchat.lua urlfs https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua sys/apps/Enchat.lua urlfs https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua
sys/apps/cloud.lua urlfs https://cloud-catcher.squiddev.cc/cloud.lua sys/apps/cloud.lua urlfs https://cloud-catcher.squiddev.cc/cloud.lua
rom/modules/main/opus linkfs sys/modules/opus rom/modules/main/opus linkfs sys/modules/opus

View File

@@ -1,4 +0,0 @@
{
["remoteturtle"] = "https://git.spatulaa.com/MayaTheShy/remoteturtle/raw/branch/master/.package",
["inventory-manager"] = "https://git.spatulaa.com/MayaTheShy/Inventory-Manager-CC/raw/branch/main/.package",
}

View File

@@ -5,7 +5,7 @@ local cbor = require('opus.cbor')
local sha2 = require('opus.crypto.sha2') local sha2 = require('opus.crypto.sha2')
local Util = require('opus.util') local Util = require('opus.util')
local ROUNDS = 20 -- Standard ChaCha20 (was 8, upgraded for security) local ROUNDS = 8 -- Adjust this for speed tradeoff
local bxor = bit32.bxor local bxor = bit32.bxor
local band = bit32.band local band = bit32.band

View File

@@ -15,11 +15,7 @@ for _,m in pairs(methods) do
end end
function linkfs.resolve(node, dir) function linkfs.resolve(node, dir)
local mp = node.mountPoint return dir:gsub(node.mountPoint, node.source, 1)
if dir:sub(1, #mp) == mp then
return node.source .. dir:sub(#mp + 1)
end
return dir
end end
function linkfs.mount(path, source) function linkfs.mount(path, source)
@@ -45,8 +41,8 @@ function linkfs.mount(path, source)
end end
function linkfs.copy(node, s, t) function linkfs.copy(node, s, t)
s = linkfs.resolve(node, s) s = s:gsub(node.mountPoint, node.source, 1)
t = linkfs.resolve(node, t) t = t:gsub(node.mountPoint, node.source, 1)
return fs.copy(s, t) return fs.copy(s, t)
end end
@@ -54,29 +50,25 @@ function linkfs.delete(node, dir)
if dir == node.mountPoint then if dir == node.mountPoint then
fs.unmount(node.mountPoint) fs.unmount(node.mountPoint)
else else
dir = linkfs.resolve(node, dir) dir = dir:gsub(node.mountPoint, node.source, 1)
return fs.delete(dir) return fs.delete(dir)
end end
end end
function linkfs.find(node, spec) function linkfs.find(node, spec)
spec = linkfs.resolve(node, spec) spec = spec:gsub(node.mountPoint, node.source, 1)
local list = fs.find(spec) local list = fs.find(spec)
local src = node.source
local mp = node.mountPoint
for k,f in ipairs(list) do for k,f in ipairs(list) do
if f:sub(1, #src) == src then list[k] = f:gsub(node.source, node.mountPoint, 1)
list[k] = mp .. f:sub(#src + 1)
end
end end
return list return list
end end
function linkfs.move(node, s, t) function linkfs.move(node, s, t)
s = linkfs.resolve(node, s) s = s:gsub(node.mountPoint, node.source, 1)
t = linkfs.resolve(node, t) t = t:gsub(node.mountPoint, node.source, 1)
return fs.move(s, t) return fs.move(s, t)
end end

View File

@@ -35,10 +35,8 @@ end
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx', 'attributes' } local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx', 'attributes' }
local function resolve(node, dir) local function resolve(node, dir)
local mp = node.mountPoint -- TODO: Wrong ! (does not support names with dashes)
if dir:sub(1, #mp) == mp then dir = dir:gsub(node.mountPoint, '', 1)
dir = dir:sub(#mp + 1)
end
return fs.combine(node.source, dir) return fs.combine(node.source, dir)
end end
@@ -55,7 +53,7 @@ end
function netfs.mount(_, id, source) function netfs.mount(_, id, source)
if not id or not tonumber(id) then if not id or not tonumber(id) then
error('netfs syntax: computerId [directory]') error('ramfs syntax: computerId [directory]')
end end
return { return {
id = tonumber(id), id = tonumber(id),

View File

@@ -1,90 +1,67 @@
local json = require('opus.json') local json = require('opus.json')
local Util = require('opus.util') local Util = require('opus.util')
local GITHUB_TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1' local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'
local GITHUB_FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s' local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
local GITEA_TREE_URL = 'https://%s/api/v1/repos/%s/%s/git/trees/%s?recursive=1'
local GITEA_FILE_URL = 'https://%s/%s/%s/raw/branch/%s/%s'
local TREE_HEADERS = {} local TREE_HEADERS = {}
local git = { } local git = { }
if _G._GIT_API_KEY then if _G._GIT_API_KEY then
TREE_HEADERS.Authorization = 'token ' .. _G._GIT_API_KEY TREE_HEADERS.Authorization = 'token ' .. _G._GIT_API_KEY
end end
local function parseTree(data, path, fileUrlFn) function git.list(repository)
if data.message then local t = Util.split(repository, '(.-)/')
if data.message:find("API rate limit exceeded") then
error("Out of API calls, try again later") local user = table.remove(t, 1)
end local repo = table.remove(t, 1)
if data.message == "Not found" or data.message == "Not Found" then local branch = table.remove(t, 1) or 'master'
error("Invalid repository") local path
if not Util.empty(t) then
path = table.concat(t, '/') .. '/'
end
local function getContents()
local dataUrl = string.format(TREE_URL, user, repo, branch)
local contents, msg = Util.httpGet(dataUrl, TREE_HEADERS)
if not contents then
error(string.format('Failed to download %s\n%s', dataUrl, msg), 2)
else
return json.decode(contents)
end end
end end
local data = getContents() or error('Invalid repository')
if data.message and data.message:find("API rate limit exceeded") then
error("Out of API calls, try again later")
end
if data.message and data.message == "Not found" then
error("Invalid repository")
end
local list = { } local list = { }
for _, v in pairs(data.tree) do for _,v in pairs(data.tree) do
if v.type == "blob" then if v.type == "blob" then
v.path = v.path:gsub("%s", "%%20") v.path = v.path:gsub("%s","%%20")
if not path then if not path then
list[v.path] = { list[v.path] = {
url = fileUrlFn(v.path), url = string.format(FILE_URL, user, repo, branch, v.path),
size = v.size, size = v.size,
} }
elseif Util.startsWith(v.path, path) then elseif Util.startsWith(v.path, path) then
local p = string.sub(v.path, #path) local p = string.sub(v.path, #path)
list[p] = { list[p] = {
url = fileUrlFn(path .. p), url = string.format(FILE_URL, user, repo, branch, path .. p),
size = v.size, size = v.size,
} }
end end
end end
end end
return list return list
end end
local function fetchTree(url)
local contents, msg = Util.httpGet(url, TREE_HEADERS)
if not contents then
error(string.format('Failed to download %s\n%s', url, msg), 2)
end
return json.decode(contents) or error('Invalid repository')
end
-- GitHub: user/repo/branch/subdir/
local function listGithub(repository)
local t = Util.split(repository, '(.-)/')
local user = table.remove(t, 1)
local repo = table.remove(t, 1)
local branch = table.remove(t, 1) or 'main'
local path = not Util.empty(t) and (table.concat(t, '/') .. '/') or nil
local data = fetchTree(string.format(GITHUB_TREE_URL, user, repo, branch))
return parseTree(data, path, function(p)
return string.format(GITHUB_FILE_URL, user, repo, branch, p)
end)
end
-- Gitea: gitea://host/user/repo/branch/subdir/
local function listGitea(host, remainder)
local t = Util.split(remainder, '(.-)/')
local user = table.remove(t, 1)
local repo = table.remove(t, 1)
local branch = table.remove(t, 1) or 'main'
local path = not Util.empty(t) and (table.concat(t, '/') .. '/') or nil
local data = fetchTree(string.format(GITEA_TREE_URL, host, user, repo, branch))
return parseTree(data, path, function(p)
return string.format(GITEA_FILE_URL, host, user, repo, branch, p)
end)
end
function git.list(repository)
local host_type, host, rest = repository:match('^(%w+)://(.-)/(.*)')
if host_type == 'gitea' then
return listGitea(host, rest)
end
return listGithub(repository)
end
return git return git

View File

@@ -39,8 +39,8 @@ if register_global_module_table then
_G[global_module_name] = json _G[global_module_name] = json
end end
local fs = fs -- this was incompatible because we use fs later
local _ENV = nil -- blocking globals in Lua 5.2 --local _ENV = nil -- blocking globals in Lua 5.2
pcall (function() pcall (function()
-- Enable access to blocked metatables. -- Enable access to blocked metatables.

View File

@@ -1,5 +1,4 @@
local Config = require('opus.config') local Util = require('opus.util')
local Util = require('opus.util')
local fs = _G.fs local fs = _G.fs
local textutils = _G.textutils local textutils = _G.textutils
@@ -8,21 +7,6 @@ local PACKAGE_DIR = 'packages'
local Packages = { } local Packages = { }
-- Default package sources (upstream GitHub + self-hosted Gitea)
local DEFAULT_SOURCES = {
{
name = 'opus-apps',
branches = {
[ 'develop-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/packages.list',
[ 'master-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/packages.list',
},
},
{
name = 'spatulaa',
url = 'https://git.spatulaa.com/MayaTheShy/Opus/raw/branch/main/sys/etc/packages.list',
},
}
function Packages:installed() function Packages:installed()
local list = { } local list = { }
@@ -71,30 +55,13 @@ function Packages:isInstalled(package)
end end
function Packages:downloadList() function Packages:downloadList()
local sources = Config.load('package').sources or DEFAULT_SOURCES local packages = {
local packages = { } [ 'develop-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/packages.list',
[ 'master-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/packages.list',
}
for _, source in ipairs(sources) do if packages[_G.OPUS_BRANCH] then
local url = source.url Util.download(packages[_G.OPUS_BRANCH], 'usr/config/packages')
if source.branches then
url = source.branches[_G.OPUS_BRANCH]
end
if url then
local content = Util.httpGet(url)
if content then
local list = textutils.unserialize(content)
if list then
for k, v in pairs(list) do
packages[k] = v
end
end
end
end
end
if next(packages) then
Util.writeTable('usr/config/packages', packages)
end end
end end

View File

@@ -1,38 +1,10 @@
local Config = require('opus.config') local Config = require('opus.config')
local SHA = require('opus.crypto.sha2')
local Util = require('opus.util')
local PBKDF2_ITERATIONS = 100
local Security = { } local Security = { }
local function generateSalt()
local salt = { }
for _ = 1, 16 do
salt[#salt + 1] = math.random(0, 0xFF)
end
return setmetatable(salt, Util.byteArrayMT):toHex()
end
function Security.verifyPassword(password) function Security.verifyPassword(password)
local stored = Security.getPassword() local current = Security.getPassword()
if not stored then return current and password == current
return false
end
-- New format: { hash = hex, salt = hex, iter = N }
if type(stored) == 'table' and stored.hash and stored.salt then
local iter = stored.iter or PBKDF2_ITERATIONS
local derived = SHA.pbkdf2(password, Util.hexToByteArray(stored.salt), iter)
return derived:toHex() == stored.hash
end
-- Legacy format: plain SHA-256 hex string
if type(stored) == 'string' then
return SHA.compute(password) == stored
end
return false
end end
function Security.hasPassword() function Security.hasPassword()
@@ -56,16 +28,8 @@ function Security.getIdentifier()
end end
function Security.updatePassword(password) function Security.updatePassword(password)
local salt = generateSalt()
local derived = SHA.pbkdf2(password, Util.hexToByteArray(salt), PBKDF2_ITERATIONS)
local config = Config.load('os') local config = Config.load('os')
config.password = { config.password = password
hash = derived:toHex(),
salt = salt,
iter = PBKDF2_ITERATIONS,
trustKey = SHA.compute(password),
}
Config.update('os', config) Config.update('os', config)
end end
@@ -73,15 +37,4 @@ function Security.getPassword()
return Config.load('os').password return Config.load('os').password
end end
-- Returns the trust key for ChaCha20-based trust protocol.
-- Compatible with both new (PBKDF2 table) and legacy (SHA-256 string) formats.
function Security.getTrustKey()
local stored = Security.getPassword()
if type(stored) == 'table' then
return stored.trustKey
end
-- Legacy: the stored string IS the SHA-256 hex
return stored
end
return Security return Security

View File

@@ -107,7 +107,7 @@ end
local function setupCrypto(socket, isClient) local function setupCrypto(socket, isClient)
socket.sharedKey = ECC.exchange(socket.privKey, socket.remotePubKey) socket.sharedKey = ECC.exchange(socket.privKey, socket.remotePubKey)
socket.enckey = SHA.pbkdf2(socket.sharedKey, "1enc", 1) socket.enckey = SHA.pbkdf2(socket.sharedKey, "1enc", 1)
socket.hmackey = SHA.pbkdf2(socket.sharedKey, "2hmac", 1) --self.hmackey = SHA.pbkdf2(self.sharedKey, "2hmac", 1)
socket.rrng = Crypto.newRNG( socket.rrng = Crypto.newRNG(
SHA.pbkdf2(socket.sharedKey, isClient and "3rseed" or "4sseed", 1)) SHA.pbkdf2(socket.sharedKey, isClient and "3rseed" or "4sseed", 1))

View File

@@ -115,16 +115,12 @@ function UI:init()
local ie = Input:translate('mouse_up', button, x, y) local ie = Input:translate('mouse_up', button, x, y)
local currentPage = self:getActivePage() local currentPage = self:getActivePage()
if ie.code == 'control-shift-mouse_click' then -- debug inspector if ie.code == 'control-shift-mouse_click' then -- hack
local Config = require('opus.config') local event = currentPage:pointToChild(x, y)
local debugCfg = Config.load('os', { debug_inspector = false }) _ENV.multishell.openTab(_ENV, {
if debugCfg.debug_inspector then path = 'sys/apps/Lua.lua',
local event = currentPage:pointToChild(x, y) args = { event.element, self, _ENV },
_ENV.multishell.openTab(_ENV, { focused = true })
path = 'sys/apps/Lua.lua',
args = { event.element, self, _ENV },
focused = true })
end
elseif ie and currentPage and currentPage.parent.device.side == side then elseif ie and currentPage and currentPage.parent.device.side == side then
self:click(currentPage, ie) self:click(currentPage, ie)

View File

@@ -26,28 +26,6 @@ function Writer:write(s, width, align, bg, fg)
self.x = self.x + width self.x = self.x + width
end end
function Writer:writeBar(width, ratio, barColor, emptyColor, text, textColor)
local filled = math.floor(ratio * width)
local empty = width - filled
if filled > 0 then
self.element:write(self.x, self.y, _rep(' ', filled), barColor)
end
if empty > 0 then
self.element:write(self.x + filled, self.y, _rep(' ', empty), emptyColor)
end
if text and #text > 0 and textColor then
local tx = self.x + math.floor((width - #text) / 2)
for i = 1, #text do
local cx = tx + i - 1
if cx >= self.x and cx < self.x + width then
local bg = (cx - self.x) < filled and barColor or emptyColor
self.element:write(cx, self.y, text:sub(i, i), bg, textColor)
end
end
end
self.x = self.x + width
end
function Writer:finish(bg) function Writer:finish(bg)
if self.x <= self.element.width then if self.x <= self.element.width then
self.element:write(self.x, self.y, _rep(' ', self.element.width - self.x + 1), bg) self.element:write(self.x, self.y, _rep(' ', self.element.width - self.x + 1), bg)
@@ -69,7 +47,6 @@ UI.Grid.defaults = {
textSelectedColor = 'white', textSelectedColor = 'white',
backgroundColor = 'black', backgroundColor = 'black',
backgroundSelectedColor = 'gray', backgroundSelectedColor = 'gray',
alternateRowColor = nil,
headerBackgroundColor = 'primary', headerBackgroundColor = 'primary',
headerTextColor = 'white', headerTextColor = 'white',
headerSortColor = 'yellow', headerSortColor = 'yellow',
@@ -340,7 +317,7 @@ function UI.Grid:drawRows()
local row = self:getDisplayValues(rawRow, key) local row = self:getDisplayValues(rawRow, key)
local selected = index == self.index and not self.inactive local selected = index == self.index and not self.inactive
local bg = self:getRowBackgroundColor(rawRow, selected, index) local bg = self:getRowBackgroundColor(rawRow, selected)
local fg = self:getRowTextColor(rawRow, selected) local fg = self:getRowTextColor(rawRow, selected)
local focused = self.focused and selected local focused = self.focused and selected
@@ -358,25 +335,11 @@ function UI.Grid:drawRow(sb, row, focused, bg, fg)
local ind = focused and self.focusIndicator or ' ' local ind = focused and self.focusIndicator or ' '
for _,col in pairs(self.columns) do for _,col in pairs(self.columns) do
if col.barColumn then sb:write(ind .. safeValue(row[col.key] or ''),
-- Bar column: render a colored fill bar col.cw + 1,
local ratio = tonumber(row[col.key]) or 0 col.align,
ratio = math.max(0, math.min(1, ratio)) col.backgroundColor or bg,
local barColor = col.barColor or colors.lime col.textColor or fg)
local emptyColor = col.barEmptyColor or bg
if type(col.barColor) == 'function' then
barColor = col.barColor(row, ratio)
end
local barText = col.barText and (type(col.barText) == 'function' and col.barText(row, ratio) or col.barText) or nil
sb:write(ind, 1, nil, bg, fg)
sb:writeBar(col.cw, ratio, barColor, emptyColor, barText, col.barTextColor or colors.white)
else
sb:write(ind .. safeValue(row[col.key] or ''),
col.cw + 1,
col.align,
col.backgroundColor or bg,
col.textColor or fg)
end
ind = ' ' ind = ' '
end end
end end
@@ -391,16 +354,13 @@ function UI.Grid:getRowTextColor(row, selected)
return self.textColor return self.textColor
end end
function UI.Grid:getRowBackgroundColor(row, selected, index) function UI.Grid:getRowBackgroundColor(row, selected)
if selected then if selected then
if self.focused then if self.focused then
return self.backgroundSelectedColor return self.backgroundSelectedColor
end end
return self.unfocusedBackgroundSelectedColor return self.unfocusedBackgroundSelectedColor
end end
if self.alternateRowColor and index then
return index % 2 == 0 and self.alternateRowColor or self.backgroundColor
end
return self.backgroundColor return self.backgroundColor
end end

View File

@@ -12,35 +12,12 @@ UI.ProgressBar.defaults = {
fillColor = 'gray', fillColor = 'gray',
textColor = 'green', textColor = 'green',
value = 0, value = 0,
showText = false, -- display value text overlay centered on bar
textOverlay = nil, -- custom overlay text (string or function(value) -> string)
textOverlayColor = 'white', -- text overlay foreground color
} }
function UI.ProgressBar:draw() function UI.ProgressBar:draw()
local width = math.ceil(self.value / 100 * self.width) local width = math.ceil(self.value / 100 * self.width)
self:fillArea(width + 1, 1, self.width - width, self.height, self.fillChar, nil, self.fillColor) self:fillArea(width + 1, 1, self.width - width, self.height, self.fillChar, nil, self.fillColor)
self:fillArea(1, 1, width, self.height, self.progressChar, self.progressColor) self:fillArea(1, 1, width, self.height, self.progressChar, self.progressColor)
if self.showText then
local text
if self.textOverlay then
text = type(self.textOverlay) == 'function'
and self.textOverlay(self.value)
or tostring(self.textOverlay)
else
text = math.floor(self.value) .. '%'
end
local midY = math.ceil(self.height / 2)
local x = math.floor((self.width - #text) / 2) + 1
for i = 1, #text do
local cx = x + i - 1
if cx >= 1 and cx <= self.width then
local bg = cx <= width and self.progressColor or self.fillColor
self:write(cx, midY, text:sub(i, i), bg, self.textOverlayColor)
end
end
end
end end
function UI.ProgressBar.example() function UI.ProgressBar.example()