robotpy / robotpy-installer

RobotPy installer program
MIT License
5 stars 11 forks source link

Change installation to use a HTTP server via SSH tunnel #31

Closed virtuald closed 4 years ago

virtuald commented 4 years ago

One annoying problem when running installs is that we have this complex logic for copying files over , ensuring that pip/opkg find our packages, and then cleaning up the files (which we don't do currently).

Instead, when we SSH to the roborio, we should open up a custom HTTP server on the local host that serves the opkg cache and the pip cache, that way pip/opkg can install from a 'network' source instead of copying the files over. I'd be willing to bet this would be faster to install also.

To do this we would need to move to paramiko -- but binary wheels are now available for this for all major platforms (unlike in 2015), so I think this would be an excellent time to switch. Additionally, by moving to paramiko, we should be able to execute commands in a much simpler and streamlined way than what we currently do.

One thing to be careful of is to never open up an actual HTTP server on the user's machine (eg, it needs to be a virtual server). In some school environments, users won't have admin privileges, so the local firewall may block such things.

connorworley commented 4 years ago

I took a stab at a quick proof of concept:

import http.server
import threading

import paramiko

client = paramiko.SSHClient()
client.load_system_host_keys()

client.connect(
    'roborio-1337-frc.local',
    22,
    username='lvuser',
    password='',
)

transport = client.get_transport()
transport.request_port_forward('', 12345)

def handle_request(request):
    client_address = request.getpeername()
    try:
        http.server.SimpleHTTPRequestHandler(
            request=request,
            client_address=client_address,
            server=None,
        ).handle()
    except OSError as e:
        # Don't die if the remote hangs up
        if str(e) == 'File is closed':
            return
        raise
    finally:
        request.close()

while True:
    request = transport.accept()
    t = threading.Thread(target=handle_request, args=[request,])
    t.setDaemon(True)
    t.run()

SimpleHTTPRequestHandler can be pointed at a different directory to serve caches.

virtuald commented 4 years ago

That's awesome, much simpler than I thought it would be. Now we just need to integrate it.

auscompgeek commented 4 years ago

if str(e) == 'File is closed':

Surely we'd want to catch ConnectionError or similar instead?

M1sterShad0w commented 4 years ago

http.server only raises OSError so it's the only one we can catch to know when the remote disconnects ("File is closed") - as it treats the remote similar to a file system.