Added working installer workflow
This commit is contained in:
parent
d9a4ec31c4
commit
637dee747d
|
@ -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()
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue