Orion09

CC-SSH Client

Mar 24th, 2026 (edited)
28
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.00 KB | None | 0 0
  1. -- ============================================================
  2. -- cct_remote.lua – Pastebin + Remote CLI client
  3. -- Place this file on your ComputerCraft: Tweaked computer.
  4. --
  5. -- Usage:
  6. -- cct_remote daemon Connect & listen for commands
  7. -- cct_remote put <file> Upload file → get a code
  8. -- cct_remote get <code> [file] Download paste (print or save)
  9. -- cct_remote run <code> Download + run a paste
  10. -- cct_remote del <code> Delete a paste from the server
  11. -- ============================================================
  12.  
  13. -- ┌─ CONFIG – change these to match your server ──────────────
  14. local SERVER_HTTP = "http://94.6.73.247:3000"
  15. local SERVER_WS = "ws://94.6.73.247:3000"
  16. -- └───────────────────────────────────────────────────────────
  17.  
  18. local args = { ... }
  19. local cmd = args[1]
  20.  
  21. -- ── Colour helpers ──────────────────────────────────────────
  22.  
  23. local function colour(c)
  24. if not term.isColour() then return end
  25. term.setTextColour(c)
  26. end
  27. local function reset() colour(colours.white) end
  28.  
  29. local function printC(c, ...)
  30. colour(c)
  31. print(...)
  32. reset()
  33. end
  34.  
  35. -- ── Output capture ──────────────────────────────────────────
  36. -- Uses term.redirect to intercept ALL terminal output, including
  37. -- text written by shell programs (which bypass _G.print).
  38.  
  39. local function captureOutput(fn)
  40. local lines = {}
  41. local curLine = ""
  42.  
  43. -- Build a dummy terminal that records every character written
  44. local fakeTerm = {}
  45. for k, v in pairs(term.current()) do fakeTerm[k] = v end
  46.  
  47. fakeTerm.write = function(text)
  48. curLine = curLine .. tostring(text)
  49. end
  50.  
  51. fakeTerm.blit = function(text)
  52. curLine = curLine .. tostring(text)
  53. end
  54.  
  55. fakeTerm.setCursorPos = function(x, y)
  56. -- Moving to a new line flushes the current buffer
  57. if curLine ~= "" then
  58. lines[#lines + 1] = curLine
  59. curLine = ""
  60. end
  61. end
  62.  
  63. fakeTerm.scroll = function()
  64. if curLine ~= "" then
  65. lines[#lines + 1] = curLine
  66. curLine = ""
  67. end
  68. end
  69.  
  70. fakeTerm.clear = function() end
  71. fakeTerm.clearLine = function() end
  72. fakeTerm.setTextColour = function() end
  73. fakeTerm.setTextColor = function() end
  74. fakeTerm.setBackgroundColour = function() end
  75. fakeTerm.setBackgroundColor = function() end
  76.  
  77. local prev = term.redirect(fakeTerm)
  78. local ok, err = pcall(fn)
  79. term.redirect(prev)
  80.  
  81. -- Flush any remaining partial line
  82. if curLine ~= "" then lines[#lines + 1] = curLine end
  83.  
  84. return ok, table.concat(lines, "\n"), tostring(err)
  85. end
  86.  
  87. -- ── HTTP helpers ────────────────────────────────────────────
  88.  
  89. local function http_post(url, body, headers)
  90. local h = headers or {}
  91. h["Content-Type"] = h["Content-Type"] or "text/plain"
  92. local resp, err = http.post(url, body, h)
  93. if not resp then return nil, err or "HTTP error" end
  94. local data = resp.readAll()
  95. resp.close()
  96. return data
  97. end
  98.  
  99. local function http_get(url)
  100. local resp, err = http.get(url)
  101. if not resp then return nil, err or "HTTP error" end
  102. local data = resp.readAll()
  103. resp.close()
  104. return data
  105. end
  106.  
  107. local function http_delete(url)
  108. -- CC:T http.request supports DELETE
  109. local resp, err = http.request({ url = url, method = "DELETE" })
  110. if not resp then return nil, err or "HTTP error" end
  111. local data = resp.readAll()
  112. resp.close()
  113. return data
  114. end
  115.  
  116. -- ── Paste API ───────────────────────────────────────────────
  117.  
  118. local function paste_upload(content, filename)
  119. local headers = { ["Content-Type"] = "text/plain" }
  120. if filename then headers["X-Filename"] = filename end
  121. local data, err = http_post(SERVER_HTTP .. "/paste", content, headers)
  122. if not data then return nil, err end
  123. local decoded = textutils.unserialiseJSON(data)
  124. if decoded and decoded.code then return decoded.code end
  125. return nil, "Server error: " .. tostring(data)
  126. end
  127.  
  128. local function paste_get(code)
  129. return http_get(SERVER_HTTP .. "/paste/" .. code)
  130. end
  131.  
  132. local function paste_delete(code)
  133. local data, err = http_delete(SERVER_HTTP .. "/paste/" .. code)
  134. if not data then return false, err end
  135. return true
  136. end
  137.  
  138. -- ── Daemon (WebSocket listener) ─────────────────────────────
  139.  
  140. local function daemon()
  141. printC(colours.yellow, "Connecting to " .. SERVER_WS .. " …")
  142.  
  143. local ws, err = http.websocket(SERVER_WS)
  144. if not ws then
  145. printC(colours.red, "Failed to connect: " .. (err or "unknown"))
  146. return
  147. end
  148.  
  149. -- Register
  150. local label = os.getComputerLabel() or ("Computer " .. os.getComputerID())
  151. ws.send(textutils.serialiseJSON({
  152. type = "register",
  153. id = tostring(os.getComputerID()),
  154. label = label,
  155. }))
  156.  
  157. printC(colours.lime, "Connected as: " .. label)
  158. printC(colours.yellow, "Listening for remote commands… (hold Ctrl+T to stop)")
  159.  
  160. -- ── Receive loop ──────────────────────────────────────────
  161. while true do
  162. local event, _, data = os.pullEvent()
  163.  
  164. if event == "websocket_message" then
  165. local ok, msg = pcall(textutils.unserialiseJSON, data)
  166. if not (ok and msg) then goto continue end
  167.  
  168. if msg.type == "run" and msg.code then
  169. -- Try compiling as Lua first
  170. local fn, loadErr = load(msg.code, "remote", "t", _ENV)
  171. local resultOk, output, errMsg
  172.  
  173. if fn then
  174. resultOk, output, errMsg = captureOutput(fn)
  175. else
  176. -- Fall back to shell.run (handles programs like "ls", "edit foo", etc.)
  177. resultOk, output, errMsg = captureOutput(function()
  178. local success = shell.run(msg.code)
  179. if not success then error("shell.run failed") end
  180. end)
  181. end
  182.  
  183. -- Reply to server
  184. ws.send(textutils.serialiseJSON({
  185. type = "response",
  186. msgId = msg.msgId or "",
  187. ok = resultOk,
  188. data = resultOk and (output ~= "" and output or "OK") or errMsg,
  189. }))
  190.  
  191. -- Also show locally
  192. if resultOk then
  193. printC(colours.lime, "[remote] " .. (output ~= "" and output or "OK"))
  194. else
  195. printC(colours.red, "[remote ERROR] " .. errMsg)
  196. end
  197. end
  198.  
  199. ::continue::
  200.  
  201. elseif event == "websocket_closed" then
  202. printC(colours.orange, "Connection closed. Retrying in 5 s…")
  203. sleep(5)
  204. daemon() -- reconnect
  205. return
  206.  
  207. elseif event == "terminate" then
  208. ws.close()
  209. printC(colours.yellow, "Daemon stopped.")
  210. return
  211. end
  212. end
  213. end
  214.  
  215. -- ── Subcommand dispatch ─────────────────────────────────────
  216.  
  217. if cmd == "daemon" or cmd == "d" then
  218. -- ── daemon ────────────────────────────────────────────────
  219. daemon()
  220.  
  221. elseif cmd == "put" or cmd == "upload" then
  222. -- ── put <filename> ────────────────────────────────────────
  223. local filename = args[2]
  224. if not filename then
  225. printError("Usage: cct_remote put <filename>")
  226. return
  227. end
  228. if not fs.exists(filename) then
  229. printError("File not found: " .. filename)
  230. return
  231. end
  232.  
  233. local f = fs.open(filename, "r")
  234. local content = f.readAll()
  235. f.close()
  236.  
  237. printC(colours.yellow, "Uploading " .. filename .. " …")
  238. local code, err = paste_upload(content, fs.getName(filename))
  239. if code then
  240. printC(colours.lime, "Uploaded! Code: " .. code)
  241. print(" Get: cct_remote get " .. code)
  242. print(" Run: cct_remote run " .. code)
  243. else
  244. printError("Upload failed: " .. (err or "unknown"))
  245. end
  246.  
  247. elseif cmd == "get" or cmd == "download" then
  248. -- ── get <code> [saveas] ───────────────────────────────────
  249. local code = args[2]
  250. local saveas = args[3]
  251. if not code then
  252. printError("Usage: cct_remote get <code> [filename]")
  253. return
  254. end
  255.  
  256. printC(colours.yellow, "Fetching " .. code .. " …")
  257. local content, err = paste_get(code)
  258. if not content then
  259. printError("Failed: " .. (err or "unknown"))
  260. return
  261. end
  262.  
  263. if saveas then
  264. local f = fs.open(saveas, "w")
  265. f.write(content)
  266. f.close()
  267. printC(colours.lime, "Saved to: " .. saveas)
  268. else
  269. print(content)
  270. end
  271.  
  272. elseif cmd == "run" or cmd == "execute" then
  273. -- ── run <code> ────────────────────────────────────────────
  274. local code = args[2]
  275. if not code then
  276. printError("Usage: cct_remote run <code>")
  277. return
  278. end
  279.  
  280. printC(colours.yellow, "Fetching " .. code .. " …")
  281. local content, err = paste_get(code)
  282. if not content then
  283. printError("Failed to fetch: " .. (err or "unknown"))
  284. return
  285. end
  286.  
  287. local tmpPath = "/.cct_remote_run.lua"
  288. local f = fs.open(tmpPath, "w")
  289. f.write(content)
  290. f.close()
  291.  
  292. printC(colours.lime, "Running…")
  293. shell.run(tmpPath, table.unpack(args, 3))
  294. fs.delete(tmpPath)
  295.  
  296. elseif cmd == "del" or cmd == "delete" then
  297. -- ── del <code> ────────────────────────────────────────────
  298. local code = args[2]
  299. if not code then
  300. printError("Usage: cct_remote del <code>")
  301. return
  302. end
  303.  
  304. printC(colours.yellow, "Deleting " .. code .. " …")
  305. local ok, err = paste_delete(code)
  306. if ok then
  307. printC(colours.lime, "Deleted: " .. code)
  308. else
  309. printError("Failed: " .. (err or "unknown"))
  310. end
  311.  
  312. else
  313. -- ── help ──────────────────────────────────────────────────
  314. colour(colours.yellow)
  315. print("CC:T Remote – Pastebin & Remote CLI")
  316. reset()
  317. print("")
  318. colour(colours.white)
  319. print(" cct_remote daemon Connect & listen for commands")
  320. print(" cct_remote put <file> Upload file → get a code")
  321. print(" cct_remote get <code> [file] Download paste (print or save)")
  322. print(" cct_remote run <code> Download and run a paste")
  323. print(" cct_remote del <code> Delete a paste from the server")
  324. print("")
  325. colour(colours.lightGrey)
  326. print(" Set SERVER_HTTP / SERVER_WS at the top of this file.")
  327. reset()
  328. end
Advertisement
Add Comment
Please, Sign In to add comment