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