WIP trying to figure out git
This commit is contained in:
parent
27891d657e
commit
babf2f2ed5
13
documentation/git_notes.md
Normal file
13
documentation/git_notes.md
Normal file
@ -0,0 +1,13 @@
|
||||
The relevant ulities for implementing a git server are:
|
||||
* git-upload-pack
|
||||
* git-recieve-pack
|
||||
* git-fetch-pack
|
||||
* git-send-pack
|
||||
|
||||
Not all of these are installed by git packages,
|
||||
but they are very much real binaries produced by standard builds.
|
||||
|
||||
Communication model:
|
||||
(client) (server)
|
||||
git-fetch-pack => git-upload-pack # pull
|
||||
git-send-pack => git-recieve-pack # push
|
||||
111
gn-daemon
Executable file
111
gn-daemon
Executable file
@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
gn-daemon: Gorillanest daemon accepting commands over SSH
|
||||
|
||||
Commands:
|
||||
git-upload-pack
|
||||
git-receive-pack
|
||||
new-repo [--migrate URL] [--pull-mirror]
|
||||
git config remote.$1.$2 $3
|
||||
/* NOTE:
|
||||
* git actually allows adding arbitrary bullshit,
|
||||
* therefor the maximum number of remotes must be limited
|
||||
* and the fields must be on a whitelist;
|
||||
* a special field should be added called sync,
|
||||
* which determines mirror frequency
|
||||
*/
|
||||
"""
|
||||
import asyncio
|
||||
import os
|
||||
import asyncssh
|
||||
import subprocess
|
||||
import logging
|
||||
from sys import exit
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log = logging.getLogger("gn-daemon")
|
||||
|
||||
HOST_KEY_PATH = "gn_host_key" # XXX
|
||||
LISTEN_ADDR = "0.0.0.0"
|
||||
LISTEN_PORT = 2222
|
||||
|
||||
def connection_made(conn):
|
||||
log.info("Connection from %s", conn.get_extra_info("peername"))
|
||||
|
||||
def connection_lost(exc):
|
||||
if exc: log.warning("Connection error: %s", exc)
|
||||
else: log.info("Connection closed")
|
||||
|
||||
def validate_password(username, password):
|
||||
return username == "anon" and password == "passwd"
|
||||
|
||||
# Implementing AsyncSSH functionality is done though primarily polymorphism.
|
||||
# This is the class it expects.
|
||||
# Complete function definitions are moved out so that
|
||||
# this class doesn't dominate the script,
|
||||
# and its never in question what should and should not be inside it.
|
||||
class GNServer(asyncssh.SSHServer):
|
||||
def connection_made(self, conn): connection_made(conn)
|
||||
def connection_lost(self, exc): connection_lost(exc)
|
||||
|
||||
def begin_auth(self, username): return True
|
||||
def password_auth_supported(self): return True
|
||||
|
||||
def validate_password(self, username, password):
|
||||
return validate_password(username, password)
|
||||
# ---
|
||||
|
||||
def ensure_host_key(path: str):
|
||||
if os.path.exists(path): return path
|
||||
|
||||
log.info("Generating ephemeral host key (saved to %s)", path)
|
||||
key = asyncssh.generate_private_key('ssh-rsa')
|
||||
with open(path, "wb") as f: f.write(key.export_private_key())
|
||||
os.chmod(path, 0o600)
|
||||
|
||||
return path
|
||||
|
||||
async def gn_process(proc : asyncssh.SSHServerProcess):
|
||||
cmd = proc.command
|
||||
|
||||
async def pump(reader, writer):
|
||||
while True:
|
||||
data = await reader.read(4096)
|
||||
if not data: break
|
||||
log.info(data)
|
||||
writer.write(str(data))
|
||||
await writer.drain()
|
||||
|
||||
sub = await asyncio.create_subprocess_exec(
|
||||
'/bin/sh', '-c', cmd, # XXX
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
await asyncio.gather(
|
||||
pump(proc.stdin, sub.stdin),
|
||||
pump(sub.stdout, proc.stdout),
|
||||
pump(sub.stderr, proc.stderr)
|
||||
)
|
||||
|
||||
rc = await sub.wait()
|
||||
proc.exit(rc)
|
||||
|
||||
async def start_server():
|
||||
host_key_path = ensure_host_key(HOST_KEY_PATH)
|
||||
|
||||
await asyncssh.create_server(
|
||||
GNServer,
|
||||
LISTEN_ADDR,
|
||||
LISTEN_PORT,
|
||||
server_host_keys=[host_key_path],
|
||||
process_factory=gn_process,
|
||||
)
|
||||
|
||||
await asyncio.Future()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try: asyncio.run(start_server())
|
||||
except (OSError, asyncssh.Error) as exc:
|
||||
log.error("Error starting server: %s", exc)
|
||||
Loading…
Reference in New Issue
Block a user