Persist config files across Opus package updates

The Opus package manager deletes the entire package directory on
update (fs.delete(packageDir)) before re-downloading files. This
wiped all config files (.manager_config, .client_config, etc.) that
were stored inside packages/inventory-manager/.

Fix: add _configPath() helper to every program that resolves config
file paths to usr/config/inventory-manager/ when running under Opus,
which lives outside the package directory and survives updates.
Falls back to the local _path() for standalone (non-Opus) use.

Updated files:
- inventoryManager.lua, manager/config.lua, inventoryClient.lua,
  inventoryWebBridge.lua, dropperController.lua, craftingTurtle.lua
- .package install script: saves configs to usr/config/inventory-manager/
- autorun/startup.lua: checks both persistent and package dirs
- startup/client.lua: uses persistent dir for .client_setup/.dropper_config
- web/server/Dockerfile: switch health check to wget (from prior fix)
This commit is contained in:
MayaTheShy
2026-03-22 20:51:31 -04:00
parent 01eef4eead
commit 173a0a9f95
10 changed files with 94 additions and 20 deletions

View File

@@ -8,7 +8,8 @@
"%.bak$", "^listDevicesByType%.lua$", "%.bak$", "^listDevicesByType%.lua$",
}, },
install = [[ install = [[
local pkgDir = fs.combine("packages", "inventory-manager") local cfgDir = "usr/config/inventory-manager"
if not fs.isDir(cfgDir) then fs.makeDir(cfgDir) end
local function ask(prompt, default) local function ask(prompt, default)
if default and #default > 0 then if default and #default > 0 then
write(prompt .. " [" .. default .. "]: ") write(prompt .. " [" .. default .. "]: ")
@@ -47,14 +48,14 @@
dropperName = dropperName, dropperName = dropperName,
barrelName = barrelName, barrelName = barrelName,
} }
local f = fs.open(fs.combine(pkgDir, ".manager_config"), "w") local f = fs.open(fs.combine(cfgDir, ".manager_config"), "w")
f.write(textutils.serialiseJSON(cfg)) f.write(textutils.serialiseJSON(cfg))
f.close() f.close()
print("Saved manager config.") print("Saved manager config.")
if serverUrl and #serverUrl > 0 then if serverUrl and #serverUrl > 0 then
local bcfg = { serverUrl = serverUrl } local bcfg = { serverUrl = serverUrl }
local bf = fs.open(fs.combine(pkgDir, ".webbridge_config"), "w") local bf = fs.open(fs.combine(cfgDir, ".webbridge_config"), "w")
bf.write(textutils.serialiseJSON(bcfg)) bf.write(textutils.serialiseJSON(bcfg))
bf.close() bf.close()
print("Saved web bridge config.") print("Saved web bridge config.")
@@ -75,7 +76,7 @@
if dropperName and #dropperName > 0 then cfg.dropperName = dropperName end if dropperName and #dropperName > 0 then cfg.dropperName = dropperName end
if barrelName and #barrelName > 0 then cfg.barrelName = barrelName end if barrelName and #barrelName > 0 then cfg.barrelName = barrelName end
local f = fs.open(fs.combine(pkgDir, ".client_config"), "w") local f = fs.open(fs.combine(cfgDir, ".client_config"), "w")
f.write(textutils.serialiseJSON(cfg)) f.write(textutils.serialiseJSON(cfg))
f.close() f.close()
print("Saved client config.") print("Saved client config.")
@@ -86,7 +87,7 @@
local serverUrl = ask("Web server URL", "http://localhost") local serverUrl = ask("Web server URL", "http://localhost")
local cfg = { serverUrl = serverUrl } local cfg = { serverUrl = serverUrl }
local f = fs.open(fs.combine(pkgDir, ".webbridge_config"), "w") local f = fs.open(fs.combine(cfgDir, ".webbridge_config"), "w")
f.write(textutils.serialiseJSON(cfg)) f.write(textutils.serialiseJSON(cfg))
f.close() f.close()
print("Saved web bridge config.") print("Saved web bridge config.")

View File

@@ -8,17 +8,23 @@ local peripheral = _G.peripheral
local shell = _ENV.shell local shell = _ENV.shell
local BASE = 'packages/inventory-manager' local BASE = 'packages/inventory-manager'
local CFG = 'usr/config/inventory-manager'
------------------------------------------------- -------------------------------------------------
-- Determine role from config files written during install -- Determine role from config files written during install
------------------------------------------------- -------------------------------------------------
local function cfgExists(name)
return fs.exists(fs.combine(CFG, name))
or fs.exists(fs.combine(BASE, name))
end
local role local role
if fs.exists(fs.combine(BASE, '.manager_config')) then if cfgExists('.manager_config') then
role = 'manager' role = 'manager'
elseif fs.exists(fs.combine(BASE, '.client_config')) then elseif cfgExists('.client_config') then
role = 'client' role = 'client'
elseif fs.exists(fs.combine(BASE, '.webbridge_config')) then elseif cfgExists('.webbridge_config') then
role = 'bridge' role = 'bridge'
elseif _G.turtle then elseif _G.turtle then
role = 'turtle' role = 'turtle'

View File

@@ -31,7 +31,17 @@ local CRAFT_SLOTS = {1, 2, 3, 5, 6, 7, 9, 10, 11}
local _baseDir = fs.getDir(shell.getRunningProgram()) local _baseDir = fs.getDir(shell.getRunningProgram())
local function _path(rel) return fs.combine(_baseDir, rel) end local function _path(rel) return fs.combine(_baseDir, rel) end
local TURTLE_CONFIG_FILE = _path(".turtle_config") -- Persistent config path (survives Opus package updates)
local _PERSIST_DIR = "usr/config/inventory-manager"
local function _configPath(rel)
if fs.isDir(_PERSIST_DIR) or fs.isDir("packages/inventory-manager") then
if not fs.isDir(_PERSIST_DIR) then fs.makeDir(_PERSIST_DIR) end
return fs.combine(_PERSIST_DIR, rel)
end
return _path(rel)
end
local TURTLE_CONFIG_FILE = _configPath(".turtle_config")
local function loadConfig() local function loadConfig()
if not fs.exists(TURTLE_CONFIG_FILE) then return end if not fs.exists(TURTLE_CONFIG_FILE) then return end

View File

@@ -11,7 +11,18 @@ local POLL_INTERVAL = 0.5 -- how often to check the dropper
------------------------------------------------- -------------------------------------------------
local _baseDir = fs.getDir(shell.getRunningProgram()) local _baseDir = fs.getDir(shell.getRunningProgram())
local CONFIG_FILE = fs.combine(_baseDir, ".dropper_config")
-- Persistent config path: survives Opus package updates
local _PERSIST_DIR = "usr/config/inventory-manager"
local function _configPath(rel)
if fs.isDir(_PERSIST_DIR) or fs.isDir("packages/inventory-manager") then
if not fs.isDir(_PERSIST_DIR) then fs.makeDir(_PERSIST_DIR) end
return fs.combine(_PERSIST_DIR, rel)
end
return fs.combine(_baseDir, rel)
end
local CONFIG_FILE = _configPath(".dropper_config")
if fs.exists(CONFIG_FILE) then if fs.exists(CONFIG_FILE) then
local f = fs.open(CONFIG_FILE, "r") local f = fs.open(CONFIG_FILE, "r")

View File

@@ -30,6 +30,16 @@ local DROPPER_ANNOUNCE_INTERVAL = 30 -- seconds between dropper announcements
local _baseDir = fs.getDir(shell.getRunningProgram()) local _baseDir = fs.getDir(shell.getRunningProgram())
local function _path(rel) return fs.combine(_baseDir, rel) end local function _path(rel) return fs.combine(_baseDir, rel) end
-- Persistent config path: survives Opus package updates
local _PERSIST_DIR = "usr/config/inventory-manager"
local function _configPath(rel)
if fs.isDir(_PERSIST_DIR) or fs.isDir("packages/inventory-manager") then
if not fs.isDir(_PERSIST_DIR) then fs.makeDir(_PERSIST_DIR) end
return fs.combine(_PERSIST_DIR, rel)
end
return _path(rel)
end
-- Override dofile to load modules into our _ENV so they inherit -- Override dofile to load modules into our _ENV so they inherit
-- Opus's require/package (CC:Tweaked dofile uses _G instead). -- Opus's require/package (CC:Tweaked dofile uses _G instead).
local _ccDofile = dofile local _ccDofile = dofile
@@ -39,7 +49,7 @@ local function dofile(path) -- luacheck: ignore
else error(err, 2) end else error(err, 2) end
end end
local CLIENT_CONFIG_FILE = _path(".client_config") local CLIENT_CONFIG_FILE = _configPath(".client_config")
------------------------------------------------- -------------------------------------------------
-- Modules -- Modules

View File

@@ -15,6 +15,17 @@
local _baseDir = fs.getDir(shell.getRunningProgram()) local _baseDir = fs.getDir(shell.getRunningProgram())
local function _path(rel) return fs.combine(_baseDir, rel) end local function _path(rel) return fs.combine(_baseDir, rel) end
-- Persistent config path: survives Opus package updates by storing
-- user data in usr/config/inventory-manager/ instead of the package dir.
local _PERSIST_DIR = "usr/config/inventory-manager"
local function _configPath(rel)
if fs.isDir(_PERSIST_DIR) or fs.isDir("packages/inventory-manager") then
if not fs.isDir(_PERSIST_DIR) then fs.makeDir(_PERSIST_DIR) end
return fs.combine(_PERSIST_DIR, rel)
end
return _path(rel)
end
-- Write crash info to a file so we can always read it -- Write crash info to a file so we can always read it
local function _crashLog(err) local function _crashLog(err)
local f = fs.open(_path(".crash.log"), "w") local f = fs.open(_path(".crash.log"), "w")
@@ -42,7 +53,7 @@ end
local log = dofile(_path("lib/log.lua")) local log = dofile(_path("lib/log.lua"))
local ui = dofile(_path("lib/ui.lua")) local ui = dofile(_path("lib/ui.lua"))
local itemDB = dofile(_path("lib/itemDB.lua")) local itemDB = dofile(_path("lib/itemDB.lua"))
itemDB.init(_path(".item_names.db")) itemDB.init(_configPath(".item_names.db"))
------------------------------------------------- -------------------------------------------------
-- Load modules (factory pattern → shared context) -- Load modules (factory pattern → shared context)

View File

@@ -24,7 +24,17 @@ local ORDER_CHANNEL = 4201
local _baseDir = fs.getDir(shell.getRunningProgram()) local _baseDir = fs.getDir(shell.getRunningProgram())
local function _path(rel) return fs.combine(_baseDir, rel) end local function _path(rel) return fs.combine(_baseDir, rel) end
local CONFIG_FILE = _path(".webbridge_config") -- Persistent config path: survives Opus package updates
local _PERSIST_DIR = "usr/config/inventory-manager"
local function _configPath(rel)
if fs.isDir(_PERSIST_DIR) or fs.isDir("packages/inventory-manager") then
if not fs.isDir(_PERSIST_DIR) then fs.makeDir(_PERSIST_DIR) end
return fs.combine(_PERSIST_DIR, rel)
end
return _path(rel)
end
local CONFIG_FILE = _configPath(".webbridge_config")
local API_KEY = nil -- optional API key for server auth local API_KEY = nil -- optional API key for server auth
local function loadConfig() local function loadConfig()

View File

@@ -22,9 +22,9 @@ C.SMELT_RESERVE = 128
C.DEFRAG_INTERVAL = 600 C.DEFRAG_INTERVAL = 600
C.COMPOST_INTERVAL = 3 C.COMPOST_INTERVAL = 3
C.ALERT_INTERVAL = 15 C.ALERT_INTERVAL = 15
C.CACHE_FILE = _path(".inventory_cache") C.CACHE_FILE = _configPath(".inventory_cache")
C.SMELTER_MONITOR_SIDE = "top" C.SMELTER_MONITOR_SIDE = "top"
C.DISABLED_RECIPES_FILE = _path(".disabled_recipes") C.DISABLED_RECIPES_FILE = _configPath(".disabled_recipes")
-- Network -- Network
C.BROADCAST_CHANNEL = 4200 C.BROADCAST_CHANNEL = 4200
@@ -71,7 +71,17 @@ C.SLOT_OUTPUT = 3
-- Config file loader -- Config file loader
------------------------------------------------- -------------------------------------------------
local CONFIG_FILE = _path(".manager_config") -- Persistent config path: survives Opus package updates
local _PERSIST_DIR = "usr/config/inventory-manager"
local function _configPath(rel)
if fs.isDir(_PERSIST_DIR) or fs.isDir("packages/inventory-manager") then
if not fs.isDir(_PERSIST_DIR) then fs.makeDir(_PERSIST_DIR) end
return fs.combine(_PERSIST_DIR, rel)
end
return _path(rel)
end
local CONFIG_FILE = _configPath(".manager_config")
function C.loadConfig() function C.loadConfig()
if not fs.exists(CONFIG_FILE) then return end if not fs.exists(CONFIG_FILE) then return end
@@ -124,7 +134,7 @@ C.LOW_STOCK_ALERTS = dofile(_path("data/alerts.lua"))
-- Recipe book: merges built-in recipes + user-learned recipes -- Recipe book: merges built-in recipes + user-learned recipes
local recipeBook = dofile(_path("lib/recipeBook.lua")) local recipeBook = dofile(_path("lib/recipeBook.lua"))
recipeBook.init(_path(".recipes.db")) recipeBook.init(_configPath(".recipes.db"))
recipeBook.loadLegacyCrafting(dofile(_path("data/craftable.lua"))) recipeBook.loadLegacyCrafting(dofile(_path("data/craftable.lua")))
recipeBook.loadLegacySmelting(dofile(_path("data/smeltable.lua"))) recipeBook.loadLegacySmelting(dofile(_path("data/smeltable.lua")))
C.recipeBook = recipeBook C.recipeBook = recipeBook

View File

@@ -2,8 +2,13 @@
-- Auto-updates from git then launches inventoryClient + dropperController in parallel -- Auto-updates from git then launches inventoryClient + dropperController in parallel
local REPO_RAW = "https://git.spatulaa.com/MayaTheShy/Inventory-Manager-CC/raw/branch/main" local REPO_RAW = "https://git.spatulaa.com/MayaTheShy/Inventory-Manager-CC/raw/branch/main"
local SETUP_FILE = ".client_setup" -- marks first-run complete
local DROPPER_CONFIG = ".dropper_config" -- Persistent config directory (survives Opus package updates)
local PERSIST_DIR = "usr/config/inventory-manager"
if not fs.isDir(PERSIST_DIR) then fs.makeDir(PERSIST_DIR) end
local SETUP_FILE = fs.combine(PERSIST_DIR, ".client_setup")
local DROPPER_CONFIG = fs.combine(PERSIST_DIR, ".dropper_config")
-- Files to download (destination -> repo path) -- Files to download (destination -> repo path)
local FILES = { local FILES = {

View File

@@ -27,7 +27,7 @@ RUN chmod +x /usr/local/bin/docker-entrypoint.sh
EXPOSE 3001 EXPOSE 3001
HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \ HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \
CMD wget -qO- http://localhost:3001/api/health || exit 1 CMD wget --spider -q http://localhost:3001/api/health || exit 1
ENTRYPOINT ["docker-entrypoint.sh"] ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "server.js"] CMD ["node", "server.js"]