Had a quick glance and your code is littered with unchecked function calls and potential overflows.
Also: Cookie:../../../<filename>
Where <filename> is a file starting with a value that's interpreted as a valid uid by atoi(). You're saved by a NULL pointer deref when the unchecked getpwuid() fails if the resulting uid is >0 but invalid (unless you're running it on a system where NULL is mapped to readable memory).
The reality is that I only wrote as much as I needed to go back to working on the project I needed it for. It works for the 'Everything is fine' case, which is what I needed to go back to developing the client side. Even a hint of malicious intent could probably bring it to it's knees.
But therein lies the rub. Is it worth hardening it or should I just go to Rust where most of those things just won't pop up?
So... did you use HTTP because the client side required it or it made the client side much easier, or was that just what came to mind? Because this seems to be exactly the type of thing I would generally use the default OpenSSH installaction on the box for, pre-shared keys, and possibly even setting a specific shell on the specified public key on the user side on the server to prevent random shell access.
There's some really interesting advanced features of OpenSSH that most people will never have need for, but you can come up with some really interesting solutions. For example, you could also use a single remote account that allows SSH access and has a separate public key for each user that sets an environment var for the target desires user, and restrict the command run to sudo with that environment variable defining to run as that specific user, and make sure sudo is configured for the users allowed.
A microservice isn't a bad idea, it's just interesting how many ways there usually are to accomplish what seems like odd, specific custom workflow in most UNIX environments.
Client side was all browser. https://github.com/Lerc/notanos also incomplete. I go back and add things to it from time to time. It's my long term toy project.
Also: Cookie:../../../<filename>
Where <filename> is a file starting with a value that's interpreted as a valid uid by atoi(). You're saved by a NULL pointer deref when the unchecked getpwuid() fails if the resulting uid is >0 but invalid (unless you're running it on a system where NULL is mapped to readable memory).