Added working installer workflow

This commit is contained in:
Andrew Lalis 2022-05-29 22:12:24 +02:00
parent d9a4ec31c4
commit 637dee747d
4 changed files with 244 additions and 6 deletions

View File

@ -2,7 +2,7 @@ package nl.andrewl.railsignalapi.rest.dto.link_token;
import nl.andrewl.railsignalapi.model.LinkToken; import nl.andrewl.railsignalapi.model.LinkToken;
import nl.andrewl.railsignalapi.model.component.Component; import nl.andrewl.railsignalapi.model.component.Component;
import nl.andrewl.railsignalapi.rest.dto.component.out.SimpleComponentResponse; import nl.andrewl.railsignalapi.rest.dto.component.out.ComponentResponse;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -19,7 +19,7 @@ import java.util.List;
public record StandaloneLinkTokenResponse ( public record StandaloneLinkTokenResponse (
long id, long id,
String label, String label,
List<SimpleComponentResponse> components, List<ComponentResponse> components,
long rsId, long rsId,
String rsName String rsName
) { ) {
@ -29,7 +29,7 @@ public record StandaloneLinkTokenResponse (
token.getLabel(), token.getLabel(),
token.getComponents().stream() token.getComponents().stream()
.sorted(Comparator.comparing(Component::getName)) .sorted(Comparator.comparing(Component::getName))
.map(SimpleComponentResponse::new).toList(), .map(ComponentResponse::of).toList(),
token.getRailSystem().getId(), token.getRailSystem().getId(),
token.getRailSystem().getName() token.getRailSystem().getName()
); );

View File

@ -59,6 +59,7 @@ public class LinkTokenService {
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Optional<LinkToken> validateToken(String rawToken) { public Optional<LinkToken> validateToken(String rawToken) {
if (rawToken.length() < LinkToken.PREFIX_SIZE) return Optional.empty();
for (var token : tokenRepository.findAllByTokenPrefix(rawToken.substring(0, LinkToken.PREFIX_SIZE))) { for (var token : tokenRepository.findAllByTokenPrefix(rawToken.substring(0, LinkToken.PREFIX_SIZE))) {
if (passwordEncoder.matches(rawToken, token.getTokenHash())) { if (passwordEncoder.matches(rawToken, token.getTokenHash())) {
return Optional.of(token); return Optional.of(token);

View File

@ -1,13 +1,16 @@
-- Rail Signal CC:Tweaked Driver -- Rail Signal CC:Tweaked Driver
local VERSION = "1.0.0" local VERSION = "1.0.0"
local args = {...}
-- Global config. Will be loaded at start of script. -- Global config. Will be loaded at start of script.
local config = {} local config = {}
-- Global websocket reference -- Global websocket reference
local ws = nil local ws = nil
local function loadConfig() -- Loads config from a given filename and returns the table with data.
local configFile = io.open("rs_config.tbl", "r") local function loadConfig(filename)
local configFile = io.open(filename, "r")
if not configFile then if not configFile then
return nil return nil
end end
@ -17,6 +20,7 @@ local function loadConfig()
return cfg return cfg
end end
-- Fetches JSON data from the given endpoint, using the config's API url.
local function fetchJson(endpoint) local function fetchJson(endpoint)
local response, msg, r = http.get({ local response, msg, r = http.get({
url = config.apiUrl .. endpoint, url = config.apiUrl .. endpoint,
@ -207,9 +211,20 @@ local function initApiData()
end end
-- MAIN SCRIPT -- MAIN SCRIPT
term.clear()
print("Rail Signal Device Driver " .. VERSION .. " for CC:Tweaked computers") print("Rail Signal Device Driver " .. VERSION .. " for CC:Tweaked computers")
print(" By Andrew Lalis <andrewlalisofficial@gmail.com>") print(" By Andrew Lalis <andrewlalisofficial@gmail.com>")
config = loadConfig() print("-------------------------------------------------")
if #args < 1 then
print("Missing required config filename argument.")
return
end
local configFilename = args[1]
config = loadConfig(configFilename)
if config == nil then
print("Error: Could not load config from file.")
return
end
print("Loaded config.") print("Loaded config.")
if initApiData() then if initApiData() then

View File

@ -0,0 +1,222 @@
local function tableLength(t)
local c = 0
for _ in pairs(t) do
c = c + 1
end
return c
end
local function startsWith(str, s)
return str:find(s, 1, true) == 1
end
local function readNum(validationFunction)
local func = validationFunction or function (n)
return n ~= nil, "Please enter a valid number."
end
local num = nil
while true do
local s = io.read()
if s ~= nil then num = tonumber(s) end
local valid, msg = func(num)
if valid then return num else print(msg) end
end
end
local function readNumInRange(s, e)
return readNum(function (n)
return n ~= nil and n >= s and n <= e, "Please enter a number between " .. s .. " and " .. e .. "."
end)
end
local function readStr(validationFunction)
local func = validationFunction or function (s)
return s ~= nil and string.len(s) > 0, "Please enter a non-empty string."
end
while true do
local str = io.read()
local valid, msg = func(str)
if valid then return str else print(msg) end
end
end
local function readUrl()
return readStr(function (s)
return s ~= nil and (startsWith(s, "http://") or startsWith(s, "https://")), "Please enter a valid URL."
end)
end
local function choice(options, required)
local req = required or false
local maxChoices = tableLength(options)
for i = 1, maxChoices do
local text = options[i].text or options[i]
print("[" .. i .. "] " .. text)
end
if not req then
maxChoices = maxChoices + 1
print("[" .. maxChoices .. "] None")
end
local c = readNumInRange(1, maxChoices)
if not req and c == maxChoices then
return nil
else
local value = options[c].value or options[c]
return value
end
end
local function chooseBoolean()
return choice({"true", "false"}, true) == "true"
end
local function fetchJson(url)
local response = http.get(url)
if response then
local text = response.readAll()
response.close()
return textutils.unserialiseJSON(text)
else
return nil
end
end
local function chooseRsSide()
return choice({"front", "back", "left", "right", "top", "bottom"}, true)
end
local function choosePeripheral(prefix, blacklist)
local ps = peripheral.getNames()
local choices = {}
for _, name in pairs(ps) do
if startsWith(name, prefix) then
local isBanned = false
for _, bannedName in pairs(blacklist) do
if name == bannedName then
isBanned = true
break
end
end
if not isBanned then table.insert(choices, name) end
end
end
if tableLength(choices) < 1 then
return nil
end
return choice(choices, true)
end
local function configSegmentBoundary(data, sb)
print("What side is the redstone input for this segment boundary?")
data.rsSide = chooseRsSide()
print("Select the detector augment that this segment boundary will use.")
data.augmentId = choosePeripheral("ir_augment_detector", {})
data.segments = {}
for _, segment in pairs(sb.segments) do
local segmentData = {id = segment.id}
print("In what direction would a train travel towards the segment \"" .. segment.name .. "\"?")
segmentData.direction = string.upper(choice({"North", "South", "East", "West"}, true))
table.insert(data.segments, segmentData)
end
end
local function configSignal(data, signal)
print("Select the monitor that this signal will use.")
local monitorId = choosePeripheral("monitor", {})
data.segment = {
id = signal.segment.id,
monitorId = monitorId
}
end
local function configSwitch(data, switch)
print("What side is the redstone input/output for this switch?")
data.rsSide = chooseRsSide()
data.possibleConfigurations = {}
for _, cfg in pairs(switch.possibleConfigurations) do
local cfgData = {id = cfg.id}
local names = {}
for _, node in pairs(cfg.nodes) do table.insert(names, node.name) end
local routeName = table.concat(names, ", ")
print("What is the redstone output to configure the switch to send traffic via " .. routeName)
cfgData.rsOutput = chooseBoolean()
table.insert(data.possibleConfigurations, cfgData)
end
end
-- SCRIPT START
local config = {}
print("Rail Signal Driver Installer for CC:Tweaked")
print("-------------------------------------------")
print("Please enter the base URL for your Rail System site.")
print(" For example: http://localhost:8080")
local baseUrl = readUrl()
config.apiUrl = baseUrl .. "/api"
if startsWith(baseUrl, "https") then
config.wsUrl = "wss" .. string.sub(baseUrl, 6) .. "/api/ws/component"
else
config.wsUrl = "ws" .. string.sub(baseUrl, 5) .. "/api/ws/component"
end
print("Please enter this device's link token.")
config.linkToken = readStr()
local tokenData = fetchJson(config.apiUrl .. "/lt/" .. config.linkToken)
if tokenData == nil then
print("Error: Could not fetch data for this token. Please make sure your token and URL is correct.")
return
end
print("The token you entered is for the \"" .. tokenData.rsName .. "\" rail system, and controls " .. tableLength(tokenData.components) .. " components:")
for _, component in pairs(tokenData.components) do
print("\t" .. component.name .. "\n\t\tID: " .. component.id .. ", Type: " .. component.type)
end
print("Are you sure you want to continue?\n[1] Continue\n[2] Exit and try again")
local c = readNumInRange(1, 2)
if c == 2 then
return
end
config.components = {}
for _, component in pairs(tokenData.components) do
print("Configuring component " .. component.name .. ":")
print("-----------------------------------------------")
local componentData = {
id = component.id,
type = component.type
}
if component.type == "SEGMENT_BOUNDARY" then
configSegmentBoundary(componentData, component)
elseif component.type == "SIGNAL" then
configSignal(componentData, component)
elseif component.type == "SWITCH" then
configSwitch(componentData, component)
end
table.insert(config.components, componentData)
end
print("How often should segment boundaries perform train scans? Give an interval between 0.1 and 3 seconds.")
config.trainScanInterval = readNumInRange(0.1, 3)
print("Configuration complete!")
local configFile = io.open("__rs_config.tbl", "w")
configFile:write(textutils.serialize(config))
configFile:close()
print("Saved config.")
print("Downloading driver...")
local resp = http.get(baseUrl .. "/driver/cc/driver.lua")
local driverScript = resp.readAll()
resp.close()
local driverFile = io.open("rs_driver.lua", "w")
driverFile:write(driverScript)
driverFile:close()
print("Downloaded driver file.")
local startupFile = io.open("startup.lua", "w")
startupFile:write("shell.execute(\"rs_driver.lua\", \"__rs_config.tbl\")")
startupFile:close()
print("Created startup file.")
print("Rebooting to start the device...")
os.sleep(1)
os.reboot()