From 72eb2432cc507208c88a44bd5aacc3444e22e7e3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Feb 2023 12:54:02 -0500 Subject: [PATCH] #117 installation files, first pass --- .vscode/settings.json | 3 +- imgen.py | 95 ++++++++++++ install_manifest.json | 189 ++++++++++++++++++++++++ installer.lua | 326 ++++++++++++++++++++++++++++++++++++++++++ pocket/startup.lua | 11 ++ 5 files changed, 623 insertions(+), 1 deletion(-) create mode 100644 imgen.py create mode 100644 install_manifest.json create mode 100644 installer.lua diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f2ebaa..70230fe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,8 @@ "read", "periphemu", "mekanismEnergyHelper", - "_HOST" + "_HOST", + "http" ], "Lua.diagnostics.disable": [ "duplicate-set-field" diff --git a/imgen.py b/imgen.py new file mode 100644 index 0000000..56193ca --- /dev/null +++ b/imgen.py @@ -0,0 +1,95 @@ +import json +import os + +# list files in a directory +def list_files(path): + list = [] + + for (root, dirs, files) in os.walk(path): + for f in files: + list.append(root[2:] + "/" + f) + + return list + +# get size of all files in a directory +def dir_size(path): + total = 0 + + for (root, dirs, files) in os.walk(path): + for f in files: + total += os.path.getsize(root + "/" + f) + + return total + +# get the version of an application at the provided path +def get_version(path, is_comms = False): + ver = "" + string = "comms.version = \"" + + if not is_comms: + path = path + "/startup.lua" + string = "_VERSION = \"" + + f = open(path, "r") + + for line in f: + pos = line.find(string) + if pos >= 0: + ver = line[(pos + len(string)):(len(line) - 2)] + break + + f.close() + + return ver + +# installation manifest +manifest = { + "versions" : { + "bootloader" : get_version("."), + "comms" : get_version("./scada-common/comms.lua", True), + "reactor-plc" : get_version("./reactor-plc"), + "rtu" : get_version("./rtu"), + "supervisor" : get_version("./supervisor"), + "coordinator" : get_version("./coordinator"), + "pocket" : get_version("./pocket") + }, + "files" : { + # common files + "system" : [ "initenv.lua", "startup.lua" ], + "common" : list_files("./scada-common"), + "graphics" : list_files("./graphics"), + "lockbox" : list_files("./lockbox"), + # platform files + "reactor-plc" : list_files("./reactor-plc"), + "rtu" : list_files("./rtu"), + "supervisor" : list_files("./supervisor"), + "coordinator" : list_files("./coordinator"), + "pocket" : list_files("./pocket"), + }, + "depends" : { + "reactor-plc" : [ "system", "common" ], + "rtu" : [ "system", "common" ], + "supervisor" : [ "system", "common" ], + "coordinator" : [ "system", "common", "graphics" ], + "pocket" : [ "system", "common", "graphics" ] + }, + "sizes" : { + # common files + "system" : os.path.getsize("initenv.lua") + os.path.getsize("startup.lua"), + "common" : dir_size("./scada-common"), + "graphics" : dir_size("./graphics"), + "lockbox" : dir_size("./lockbox"), + # platform files + "reactor-plc" : dir_size("./reactor-plc"), + "rtu" : dir_size("./rtu"), + "supervisor" : dir_size("./supervisor"), + "coordinator" : dir_size("./coordinator"), + "pocket" : dir_size("./pocket"), + } +} + +f = open("install_manifest.json", "w") + +json.dump(manifest, f) + +f.close() diff --git a/install_manifest.json b/install_manifest.json new file mode 100644 index 0000000..58131e8 --- /dev/null +++ b/install_manifest.json @@ -0,0 +1,189 @@ +{ + "versions": { + "bootloader": "0.2", + "comms": "1.3.3", + "reactor-plc": "beta-v0.10.11", + "rtu": "beta-v0.11.0", + "supervisor": "beta-v0.11.13", + "coordinator": "beta-v0.9.16", + "pocket": "alpha-v0.0.0" + }, + "files": { + "system": [ + "initenv.lua", + "startup.lua" + ], + "common": [ + "scada-common/crypto.lua", + "scada-common/ppm.lua", + "scada-common/comms.lua", + "scada-common/psil.lua", + "scada-common/tcallbackdsp.lua", + "scada-common/rsio.lua", + "scada-common/mqueue.lua", + "scada-common/crash.lua", + "scada-common/log.lua", + "scada-common/types.lua", + "scada-common/util.lua" + ], + "graphics": [ + "graphics/element.lua", + "graphics/flasher.lua", + "graphics/core.lua", + "graphics/elements/textbox.lua", + "graphics/elements/displaybox.lua", + "graphics/elements/pipenet.lua", + "graphics/elements/rectangle.lua", + "graphics/elements/div.lua", + "graphics/elements/tiling.lua", + "graphics/elements/colormap.lua", + "graphics/elements/indicators/alight.lua", + "graphics/elements/indicators/icon.lua", + "graphics/elements/indicators/power.lua", + "graphics/elements/indicators/rad.lua", + "graphics/elements/indicators/state.lua", + "graphics/elements/indicators/light.lua", + "graphics/elements/indicators/vbar.lua", + "graphics/elements/indicators/coremap.lua", + "graphics/elements/indicators/data.lua", + "graphics/elements/indicators/hbar.lua", + "graphics/elements/indicators/trilight.lua", + "graphics/elements/controls/switch_button.lua", + "graphics/elements/controls/spinbox_numeric.lua", + "graphics/elements/controls/hazard_button.lua", + "graphics/elements/controls/push_button.lua", + "graphics/elements/controls/radio_button.lua", + "graphics/elements/controls/multi_button.lua", + "graphics/elements/animations/waiting.lua" + ], + "lockbox": [ + "lockbox/init.lua", + "lockbox/LICENSE", + "lockbox/kdf/pbkdf2.lua", + "lockbox/util/bit.lua", + "lockbox/util/array.lua", + "lockbox/util/stream.lua", + "lockbox/util/queue.lua", + "lockbox/digest/sha2_224.lua", + "lockbox/digest/sha1.lua", + "lockbox/digest/sha2_256.lua", + "lockbox/cipher/aes128.lua", + "lockbox/cipher/aes256.lua", + "lockbox/cipher/aes192.lua", + "lockbox/cipher/mode/ofb.lua", + "lockbox/cipher/mode/cbc.lua", + "lockbox/cipher/mode/ctr.lua", + "lockbox/cipher/mode/cfb.lua", + "lockbox/mac/hmac.lua", + "lockbox/padding/ansix923.lua", + "lockbox/padding/pkcs7.lua", + "lockbox/padding/zero.lua", + "lockbox/padding/isoiec7816.lua" + ], + "reactor-plc": [ + "reactor-plc/threads.lua", + "reactor-plc/plc.lua", + "reactor-plc/config.lua", + "reactor-plc/startup.lua" + ], + "rtu": [ + "rtu/threads.lua", + "rtu/rtu.lua", + "rtu/modbus.lua", + "rtu/config.lua", + "rtu/startup.lua", + "rtu/dev/sps_rtu.lua", + "rtu/dev/envd_rtu.lua", + "rtu/dev/boilerv_rtu.lua", + "rtu/dev/redstone_rtu.lua", + "rtu/dev/sna_rtu.lua", + "rtu/dev/imatrix_rtu.lua", + "rtu/dev/turbinev_rtu.lua" + ], + "supervisor": [ + "supervisor/supervisor.lua", + "supervisor/unit.lua", + "supervisor/config.lua", + "supervisor/startup.lua", + "supervisor/unitlogic.lua", + "supervisor/facility.lua", + "supervisor/session/coordinator.lua", + "supervisor/session/svqtypes.lua", + "supervisor/session/svsessions.lua", + "supervisor/session/rtu.lua", + "supervisor/session/plc.lua", + "supervisor/session/rsctl.lua", + "supervisor/session/rtu/boilerv.lua", + "supervisor/session/rtu/txnctrl.lua", + "supervisor/session/rtu/unit_session.lua", + "supervisor/session/rtu/turbinev.lua", + "supervisor/session/rtu/envd.lua", + "supervisor/session/rtu/imatrix.lua", + "supervisor/session/rtu/sps.lua", + "supervisor/session/rtu/qtypes.lua", + "supervisor/session/rtu/sna.lua", + "supervisor/session/rtu/redstone.lua" + ], + "coordinator": [ + "coordinator/coordinator.lua", + "coordinator/renderer.lua", + "coordinator/iocontrol.lua", + "coordinator/sounder.lua", + "coordinator/config.lua", + "coordinator/startup.lua", + "coordinator/apisessions.lua", + "coordinator/process.lua", + "coordinator/ui/dialog.lua", + "coordinator/ui/style.lua", + "coordinator/ui/layout/main_view.lua", + "coordinator/ui/layout/unit_view.lua", + "coordinator/ui/components/reactor.lua", + "coordinator/ui/components/processctl.lua", + "coordinator/ui/components/unit_overview.lua", + "coordinator/ui/components/boiler.lua", + "coordinator/ui/components/unit_detail.lua", + "coordinator/ui/components/imatrix.lua", + "coordinator/ui/components/unit_waiting.lua", + "coordinator/ui/components/turbine.lua" + ], + "pocket": [ + "pocket/config.lua", + "pocket/startup.lua" + ] + }, + "depends": { + "reactor-plc": [ + "system", + "common" + ], + "rtu": [ + "system", + "common" + ], + "supervisor": [ + "system", + "common" + ], + "coordinator": [ + "system", + "common", + "graphics" + ], + "pocket": [ + "system", + "common", + "graphics" + ] + }, + "sizes": { + "system": 1982, + "common": 88049, + "graphics": 99360, + "lockbox": 100797, + "reactor-plc": 75956, + "rtu": 81511, + "supervisor": 261745, + "coordinator": 180622, + "pocket": 335 + } +} \ No newline at end of file diff --git a/installer.lua b/installer.lua new file mode 100644 index 0000000..fa863e1 --- /dev/null +++ b/installer.lua @@ -0,0 +1,326 @@ +-- +-- ComputerCraft Mekanism SCADA System Installer Utility +-- + +--[[ + +Copyright © 2023 Mikayla Fischler + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the “Software”), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +]]-- + +local util = require("scada-common.util") + +local print = util.print +local println = util.println + +local VERSION = "v0.1" + +local install_dir = "/.install-cache" +local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/devel/" +local install_manifest = repo_path .. "install_manifest.json" + +local opts = { ... } +local mode = nil +local app = nil + +-- +-- get and validate command line options +-- + +println("-- CC Mekanism SCADA Installer " .. VERSION .. " --") + +if #opts == 0 or opts[1] == "help" or #opts ~= 2 then + println("note: only modifies files that are part of the device application") + println("usage: installer ") + println("") + println(" install - fresh install, overwrites config") + println(" update - update files EXCEPT for config/logs") + println(" remove - delete files EXCEPT for config/logs") + println(" purge - delete files INCLUDING config/logs") + println("") + println(" reactor-plc - reactor PLC firmware") + println(" rtu - RTU firmware") + println(" supervisor - supervisor server application") + println(" coordinator - coordinator application") + println(" pocket - pocket application") + return +else + for _, v in pairs({ "install", "update", "remove", "purge" }) do + if opts[1] == v then + mode = v + break + end + end + + if mode == nil then + println("unrecognized mode") + return + end + + for _, v in pairs({ "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" }) do + if opts[2] == v then + app = v + break + end + end + + if app == nil then + println("unrecognized application") + return + end +end + +-- +-- run selected mode +-- + +if mode == "install" or mode == "update" then + ------------------------- + -- GET REMOTE MANIFEST -- + ------------------------- + + local response, error = http.get(install_manifest) + + if response == nil then + println("failed to get installation manifest from GitHub, cannot update or install") + println(util.c("http error ", error)) + return + end + + local ok, manifest = pcall(function () return textutils.unserializeJSON(response.readAll()) end) + + if not ok then + println("error parsing remote version manifest") + return + end + + ------------------------ + -- GET LOCAL MANIFEST -- + ------------------------ + + local imfile = fs.open("install_manifest.json") + local local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + + local local_app_version = nil + local local_comms_version = nil + local local_boot_version = nil + + if not local_ok and mode == "update" then + println("warning: failed to load local installation information") + local_app_version = local_manifest.versions[app] + local_comms_version = local_manifest.versions.comms + local_boot_version = local_manifest.versions.bootloader + end + + local remote_app_version = manifest.versions[app] + local remote_comms_version = manifest.versions.comms + local remote_boot_version = manifest.versions.bootloader + + if mode == "install" then + println("installing " .. app .. " files...") + elseif mode == "update" then + println("updating " .. app .. " files... (keeping old config.lua)") + end + + if local_boot_version ~= nil then + if local_boot_version ~= remote_boot_version then + println("[bootldr] updating " .. local_boot_version .. " => " .. remote_boot_version) + end + else + println("[bootldr] fresh install of " .. remote_boot_version) + end + + if local_comms_version ~= nil then + if local_comms_version ~= remote_comms_version then + println("[comms] updating " .. local_comms_version .. " => " .. remote_comms_version) + println("[comms] other devices on the network will require an update") + end + else + println("[comms] fresh install of " .. remote_comms_version) + end + + if local_app_version ~= nil then + if local_app_version ~= remote_app_version then + println("[" .. app .. "] updating " .. local_app_version .. " => " .. remote_app_version) + end + else + println("[" .. app .. "] fresh install of " .. remote_app_version) + end + + -------------------------- + -- START INSTALL/UPDATE -- + -------------------------- + + local space_required = 0 + local space_available = fs.getFreeSpace("/") + + local single_file_mode = false + local file_list = manifest.files + local size_list = manifest.sizes + local dependencies = manifest.depends[app] + local config_file = app .. "/config.lua" + + for _, dependency in pairs(dependencies) do + local size = size_list[dependency] + space_required = space_required + size + end + + if space_available < space_required then + single_file_mode = true + println("WARNING: Insuffienct space available for a full download!") + println("Files will be downloaded one by one, so if you are replacing a current install this will not be a problem unless installation fails.") + println("Do you wish to continue? (y/N)") + + local confirm = read() + if confirm ~= "y" or confirm ~= "Y" then + println("installation cancelled") + return + end + end + +---@diagnostic disable-next-line: undefined-field + os.sleep(2) + + local success = true + + if not single_file_mode then + if fs.exists(install_dir) then + fs.delete(install_dir) + fs.makeDir(install_dir) + end + + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + println("get: " .. file) + local dl, err_c = http.get(repo_path .. file) + + if dl == nil then + println("get: error " .. err_c) + success = false + break + else + local handle = fs.open(install_dir .. "/" .. file, "w") + handle.write(dl.readAll()) + handle.close() + end + end + end + + if success then + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + if mode == "install" or file ~= config_file then + fs.move(install_dir .. "/" .. file, file) + end + end + end + end + + fs.delete(install_dir) + + if success then + -- if we made it here, then none of the file system functions threw exceptions + -- that means everything is OK + if mode == "install" then + println("installation completed successfully") + else + println("update completed successfully") + end + else + if mode == "install" then + println("installation failed") + else + println("update failed, existing files unmodified") + end + end + else + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + println("get: " .. file) + local dl, err_c = http.get(repo_path .. file) + + if dl == nil then + println("get: error " .. err_c) + success = false + break + else + local handle = fs.open("/" .. file, "w") + handle.write(dl.readAll()) + handle.close() + end + end + end + + if success then + -- if we made it here, then none of the file system functions threw exceptions + -- that means everything is OK + if mode == "install" then + println("installation completed successfully") + else + println("update completed successfully") + end + else + if mode == "install" then + println("installation failed, files may have been skipped") + else + println("update failed, files may have been skipped") + end + end + end +elseif mode == "remove" or mode == "purge" then + local imfile = fs.open("install_manifest.json") + local ok, manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + + if not ok then + println("error parsing local version manifest") + return + elseif mode == "remove" then + println("removing all " .. app .. " files except for config.lua and log.txt...") + elseif mode == "purge" then + println("purging all " .. app .. " files including config.lua and log.txt...") + end + +---@diagnostic disable-next-line: undefined-field + os.sleep(2) + + local file_list = manifest.files + local dependencies = manifest.depends[app] + local config_file = app .. "/config.lua" + + -- delete all files except config unless purging + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + if mode == "purge" or file ~= config_file then + fs.delete(file) + println("deleted " .. file) + end + end + end + + -- delete log file if purging + if mode == "purge" then + println("deleting log file '" .. config_file .. "'...") + local config = require(config_file) + fs.delete(config.LOG_PATH) + println("deleted " .. config.LOG_PATH) + end + + println("done!") +end diff --git a/pocket/startup.lua b/pocket/startup.lua index 40a0777..032bba9 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -3,3 +3,14 @@ -- require("/initenv").init_env() + +local util = require("scada-common.util") + +local POCKET_VERSION = "alpha-v0.0.0" + +local print = util.print +local println = util.println +local print_ts = util.print_ts +local println_ts = util.println_ts + +println("Sorry, this isn't written yet :(")