diff --git a/container-compose-dev.yml b/container-compose-dev.yml
index 9b48fd88..b0307857 100644
--- a/container-compose-dev.yml
+++ b/container-compose-dev.yml
@@ -17,5 +17,5 @@ services:
     volumes:
       - type: bind
         source: ./kdlp.underground.software
-        target: /orbit/docs
+        target: /usr/local/share/orbit/docs
         read_only: true
diff --git a/orbit/.dockerignore b/orbit/.dockerignore
deleted file mode 100644
index 1b30444a..00000000
--- a/orbit/.dockerignore
+++ /dev/null
@@ -1 +0,0 @@
-orbit.db
diff --git a/orbit/Containerfile b/orbit/Containerfile
index 856ff967..a31ef9ff 100644
--- a/orbit/Containerfile
+++ b/orbit/Containerfile
@@ -1,24 +1,14 @@
 FROM alpine:3.19 AS build
 
 RUN apk update && apk upgrade && apk add \
-	python3-dev \
-	py3-pip \
-	build-base \
-	libffi-dev \
+	py3-peewee \
 	envsubst \
 	;
 
-COPY requirements.txt /requirements.txt
-RUN python3 -m venv /radius-venv && \
-	source /radius-venv/bin/activate && \
-	pip install -r requirements.txt && \
-	:
-
-COPY . /orbit
-WORKDIR /orbit
+COPY . /usr/local/share/orbit
+WORKDIR /usr/local/share/orbit
 
 RUN mkdir -p /var/orbit/ && \
-	source /radius-venv/bin/activate && \
 	./db.py \
 	:
 
@@ -29,35 +19,31 @@ RUN test -n "$orbit_version_info" || (echo 'version info is not set' && false) &
 	rm config.py.template \
 	;
 
-RUN mkdir \
-	/var/git \
-	/orbit/docs \
-	/etc/cgit \
-	;
-
-COPY --from=orbit_singularity_git_dir . /var/git/singularity
-COPY --from=orbit_docs_source . /orbit/docs
-COPY --from=orbit_repos_source . /etc/cgit
-
 FROM alpine:3.19 AS orbit
 
 RUN apk update && apk upgrade && apk add \
-	python3 \
+	py3-bcrypt \
+	py3-peewee \
+	py3-markdown \
+	uwsgi-python3 \
+	uwsgi-http \
 	cgit \
 	;
 
-COPY --from=build /orbit /orbit
-COPY --from=build /radius-venv /radius-venv
+WORKDIR /usr/local/share/orbit
+
+COPY --from=build /usr/local/share/orbit /usr/local/share/orbit
+COPY --from=orbit_docs_source . ./docs
 COPY --from=build /var/orbit /var/orbit
-COPY --from=build /var/git /var/git
-COPY --from=build /etc/cgit /etc/cgit
+COPY --from=orbit_singularity_git_dir . /var/git/singularity
+COPY --from=orbit_repos_source . /etc/cgit
 
 COPY cgitrc /etc/cgitrc
 
-RUN chown -R 100:100 /orbit /radius-venv /var/orbit /var/git
+RUN chown -R 100:100 /var/orbit
 
 USER 100:100
 
 EXPOSE 9098
 
-CMD /bin/sh -c "source /radius-venv/bin/activate && uwsgi /orbit/radius.ini"
+CMD ["uwsgi", "--plugin", "python,http", "./radius.ini"]
diff --git a/orbit/README.md b/orbit/README.md
deleted file mode 100644
index 8ebcc009..00000000
--- a/orbit/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# orbit
-
-For development, setup and enter a python virtualenv for
-(e.g. `python -m venv orbit-dev && source orbit-dev/bin/activate`)
-and install the development dependencies: `pip install -r dev-requirements.txt`
-to enable style checking via `test-style.sh` utilizing flake8.
diff --git a/orbit/config.py b/orbit/config.py
index 67b781d1..07fe92a3 100644
--- a/orbit/config.py
+++ b/orbit/config.py
@@ -1,9 +1,8 @@
 version_info = '${orbit_version_info}'
 
 # read these documents from a filesystem path
-orbit_root = '/orbit'
-doc_root = f'{orbit_root}/docs'
-doc_header = f'{orbit_root}/header.html'
+doc_root = './docs'
+doc_header = './header.html'
 database = '/var/orbit/orbit.db'
 
 # duration of authentication token validity period
diff --git a/orbit/dev-requirements.txt b/orbit/dev-requirements.txt
deleted file mode 100644
index 2c9e2a97..00000000
--- a/orbit/dev-requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-flake8 ~=7.0.0
-mccabe ~= 0.7.0
-pycodestyle ~= 2.11.1
-pyflakes ~= 3.2.0
diff --git a/orbit/hyperspace.py b/orbit/hyperspace.py
index f8f39260..6c750901 100755
--- a/orbit/hyperspace.py
+++ b/orbit/hyperspace.py
@@ -8,7 +8,6 @@
 
 # internal imports
 import config
-from radius import Session
 
 
 def errx(msg):
@@ -16,77 +15,38 @@ def errx(msg):
     exit(1)
 
 
-def need(a, u=False, p=False, t=False):
+def need(a, u=False, p=False):
+    needed = []
     if u and a.username is None:
-        errx("Need username. Bye.")
+        needed.append('username')
     if p and a.password is None:
-        errx("Need password. Bye.")
-    if t and a.token is None:
-        errx("Need token. Bye.")
+        needed.append('password')
+    if needed:
+        errx(f"Need {' and '.join(needed)}. Bye.")
 
 
 def nou(u):
     errx(f'no such user "{u}". Bye.')
 
 
-def do_query_username(args):
-    need(args, u=True)
-    if not (user := db.User.get_or_none(db.User.username == args.username)):
-        nou(args.username)
-    print(f'Username        : {user.username}\n'
-          f'Hashed Password : {user.pwdhash}\n'
-          f'Student ID      : {user.student_id}')
-
-
-def do_validate_token(args):
-    need(args, t=True)
-
-    ses = db.Session.get_or_none(db.Session.token == args.token)
-    if ses:
-        print(ses.username)
-    else:
-        print('null')
-
-
 def do_drop_session(args):
     need(args, u=True)
     query = (db.Session
              .delete()
-             .where(db.Session.username == args.username)
-             .returning(db.Session))
-
-    if ses := next(iter(query.execute()), None):
-        print(ses.username)
-    else:
-        print('null')
-
-
-def do_create_session(args):
-    need(args, u=True)
-    ses = Session(username=args.username)
-    print(ses.token)
-
+             .where(db.Session.username == args.username))
 
-def do_validate_creds(args):
-    need(args, u=True, p=True)
-    if not (user := db.User.get_or_none(db.User.username == args.username)):
-        nou(args.username)
-    if not bcrypt.checkpw(args.password.encode('utf-8'),
-                          user.pwdhash.encode('utf-8')):
-        print('null')
-        return
-    print(f'credentials(username: {args.username}, password:{args.password})')
+    if query.execute() < 1:
+        errx('No session belonging to that user found')
 
 
 def do_change_password(args):
     need(args, u=True, p=True)
-    new_hash = do_bcrypt_hash(args, get=True)
+    new_hash = do_bcrypt_hash(args)
     query = (db.User
              .update({db.User.pwdhash: new_hash})
              .where(db.User.username == args.username))
     if query.execute() < 1:
         nou(args.username)
-    print(f'credentials(username: {args.username}, password:{args.password})')
 
 
 def do_delete_user(args):
@@ -96,22 +56,17 @@ def do_delete_user(args):
              .where(db.User.username == args.username))
     if query.execute() < 1:
         nou(args.username)
-    print(args.username)
 
 
-def do_bcrypt_hash(args, get=False):
+def do_bcrypt_hash(args):
     need(args, p=True)
-    res = str(bcrypt.hashpw(bytes(args.password, "UTF-8"),
-                            bcrypt.gensalt()), "UTF-8")
-    if get:
-        return res
-    else:
-        print(res)
+    return bcrypt.hashpw(args.password.encode('utf-8'),
+                         bcrypt.gensalt()).decode('utf-8')
 
 
 def do_newuser(args):
     need(args, u=True, p=True)
-    new_hash = do_bcrypt_hash(args, get=True)
+    new_hash = do_bcrypt_hash(args)
     try:
         db.User.create(username=args.username, pwdhash=new_hash,
                        student_id=args.studentid)
@@ -119,7 +74,6 @@ def do_newuser(args):
             db.Registration.create(username=args.username,
                                    password=args.password,
                                    student_id=args.studentid)
-        do_validate_creds(args)
     except db.peewee.IntegrityError as e:
         errx(f'cannot create user with duplicate field: "{e}"')
 
@@ -145,9 +99,6 @@ def hyperspace_main(raw_args):
     parser.add_argument('-u', '--username', help='Username to operate with')
     parser.add_argument('-p', '--password', help='Password to operate with')
     parser.add_argument('-i', '--studentid', help='Student ID to operate with')
-    parser.add_argument('-t', '--token', help='Token to operate with')
-    parser.add_argument('-e', '--exercise',
-                        help='Assignment/Exercise to operate with')
 
     actions = parser.add_mutually_exclusive_group()
     actions.add_argument('-r', '--roster', action='store_const',
@@ -156,39 +107,24 @@ def hyperspace_main(raw_args):
     actions.add_argument('-n', '--newuser', action='store_const',
                          help='Create a new user from supplied credentials',
                          dest='do', const=do_newuser)
-    actions.add_argument('-s', '--session', action='store_const',
-                         help='Check valitity of supplied token',
-                         dest='do', const=do_validate_token)
-    actions.add_argument('-d', '--dropsession', action='store_const',
-                         help='Drop any existing valid session for supplied username',  # NOQA: E501
-                         dest='do', const=do_drop_session)
-    actions.add_argument('-c', '--createsession', action='store_const',
-                         help='Create session for supplied username',
-                         dest='do', const=do_create_session)
-    actions.add_argument('-v', '--validatecreds', action='store_const',
-                         help='Create session for supplied username',
-                         dest='do', const=do_validate_creds)
     actions.add_argument('-m', '--mutatepassword', action='store_const',
                          help='Change password for supplied username to supplied password',  # NOQA: E501
                          dest='do', const=do_change_password)
     actions.add_argument('-w', '--withdrawuser', action='store_const',
                          help='Delete ("withdraw") the supplied username',
                          dest='do', const=do_delete_user)
-    actions.add_argument('-b', '--bcrypthash', action='store_const',
-                         help='Generate bcrypt hash from supplied password',
-                         dest='do', const=do_bcrypt_hash)
     actions.add_argument('-l', '--listsessions', action='store_const',
                          help='List of all known sessions (some could be invalid)',  # NOQA: E501
                          dest='do', const=do_list_sessions)
-    actions.add_argument('-q', '--queryuser', action='store_const',
-                         help='Get information about supplied username if valid',  # NOQA: E501
-                         dest='do', const=do_query_username)
+    actions.add_argument('-d', '--dropsession', action='store_const',
+                         help='Drop any existing valid session for supplied username',  # NOQA: E501
+                         dest='do', const=do_drop_session)
 
     args = parser.parse_args(raw_args)
     if (args.do):
         args.do(args)
     else:
-        print("Nothing to do. Tip: -h")
+        parser.print_help()
 
 
 if __name__ == "__main__":
diff --git a/orbit/radius.ini b/orbit/radius.ini
index bb303375..5d4696c0 100644
--- a/orbit/radius.ini
+++ b/orbit/radius.ini
@@ -1,6 +1,5 @@
 [uwsgi]
 http = 0.0.0.0:9098
-chdir = /orbit
 wsgi-file = radius.py
 
 disable-logging
diff --git a/orbit/radius.py b/orbit/radius.py
index 5c54cb8f..1de2f79b 100644
--- a/orbit/radius.py
+++ b/orbit/radius.py
@@ -3,13 +3,13 @@
 # it's all one things now
 
 import bcrypt
-import hashlib
 import html
 import markdown
 import os
 import re
 import subprocess
 import sys
+import secrets
 from http import HTTPStatus, cookies
 from datetime import datetime, timedelta
 from urllib.parse import parse_qs, urlparse
@@ -27,34 +27,10 @@
 # === utilities ===
 
 
-def encode(dat): return bytes(dat, "UTF-8")
-def decode(dat): return str(dat, "UTF-8")
-
-
-def mk_table(row_list, indentation_level=0):
-    # Create <th> elements in first row, and <td> elements afterwards
-    first_row = True
-    def indenter(adjustment): return '\t' * (indentation_level + adjustment)
-
-    output = f'{indenter(0)}<table>'
-    for row in row_list:
-        output += f'{indenter(1)}<tr>'
-        for column in row:
-            if first_row:
-                output += f'{indenter(2)}<th>{column}</th>'
-                first_row = False
-            else:
-                output += f'{indenter(2)}<td>{column}</td>'
-        output += f'{indenter(1)}</tr>'
-    output += f'{indenter(0)}</table>'
-
-    return output
-
-
 def check_credentials(username, password):
     if not (user := db.User.get_or_none(db.User.username == username)):
         return False
-    return bcrypt.checkpw(encode(password), encode(user.pwdhash))
+    return bcrypt.checkpw(password.encode(), user.pwdhash.encode())
 
 
 # === user session handling ===
@@ -141,8 +117,7 @@ def valid(self):
             return self.token
 
     def mk_hash(self, username):
-        hash_input = username + str(datetime.now())
-        return hashlib.sha256(encode(hash_input)).hexdigest()
+        return secrets.token_hex()
 
     def expired(self):
         if (expiry := self.expiry) is None or datetime.utcnow() > expiry:
@@ -166,12 +141,6 @@ def mk_cookie_header(self):
 
         return [('Set-Cookie', cookie_val)]
 
-    def __repr__(self):
-        return f'Session({self.token},{self.username},{self.expiry})'
-
-    def __str__(self):
-        return repr(self)
-
 
 class Rocket:
     """
@@ -226,16 +195,6 @@ def __init__(self, env, start_response):
         self.headers = []
         self.body_args = self.read_body_args_wsgi()
 
-    def __repr__(self):
-        return (
-            f'Rocket({self.method},{self.path_info},{self.queries},'
-            f'{str(self.headers)},{self._msg},{str(self.session)},'
-            f'{self.body_args})'
-        )
-
-    def __str__(self):
-        return repr(self)
-
     def msg(self, msg):
         self._msg = msg
 
@@ -264,19 +223,9 @@ def username(self):
         if session := self.session:
             return session.username
 
-    @property
-    def token(self):
-        if session := self.session:
-            return session.token
-
-    @property
-    def expiry(self):
-        if session := self.session:
-            return session.expiry
-
     def body_args_query(self, key):
         return html.escape(
-            decode(self.body_args.get(encode(key), [b''])[0]))
+            self.body_args.get(key.encode(), [b''])[0].decode())
 
     def queries_query(self, key):
         return self.queries.get(key, [''])[0]
@@ -320,31 +269,38 @@ def raw_respond(self, response_code, body=b''):
     def respond(self, response_document):
         self.headers += [('Content-Type', 'text/html')]
         response_document = self.format_html(response_document)
-        return self.raw_respond(HTTPStatus.OK, encode(response_document))
+        return self.raw_respond(HTTPStatus.OK, response_document.encode())
 
 
-form_welcome_template = """
+def mk_form_welcome(session):
+    return f'''
     <div class="logout_info">
         <div class="logout_left">
-        {}
+            <table>
+                <tr><th>Cookie Key</th><th>Value</th></tr>
+                <tr><td>Token</td><td>{session.token}</td></tr>
+                <tr><td>User</td><td>{session.username}</td></tr>
+                <tr><td>Expiry</td><td>{session.expiry_fmt()}</td></tr>
+            </table>
         </div>
         <div class="logout_right">
             <h5> Welcome!</h5>
          </div>
     </div>
     <div class="logout_buttons">
-    {}
-    </div>
-""".strip()
+        <form id="logout">
+            <input class="logout" type="button" onclick="location.href='/logout';" value="Logout" />
+        </form>
+    </div>'''
 
-form_welcome_buttons = """
-    <form id="logout">
-        <input class="logout" type="button" onclick="location.href='/logout';" value="Logout" />
-    </form>
-""".strip()  # NOQA: E501
 
-form_login = """
-    <form id="login" method="post" action="/login%(target_redir)s">
+def login_form(target_location=None):
+    if target_location is not None:
+        target_redir = f'?target={target_location}'
+    else:
+        target_redir = ''
+    return f'''
+    <form id="login" method="post" action="/login{target_redir}">
         <label for="username">Username:<br /></label>
         <input name="username" type="text" id="username" />
     <br />
@@ -353,22 +309,7 @@ def respond(self, response_document):
     <br />
         <button type="submit">Submit</button>
     </form>
-    <h3>Need an account? Register <a href="/register">here</a></h3><br>
-""".strip()
-
-
-def cookie_info_table(session):
-    return mk_table([
-        ('Cookie Key', 'Value'),
-        ('Token', session.token),
-        ('User', session.username),
-        ('Expiry', session.expiry_fmt()),
-        ('Remaining Validity', str(session.expiry - datetime.utcnow()))])
-
-
-def mk_form_welcome(session):
-    return form_welcome_template.format(cookie_info_table(session),
-                                        form_welcome_buttons)
+    <h3>Need an account? Register <a href="/register">here</a></h3><br>'''
 
 
 def handle_login(rocket):
@@ -384,12 +325,11 @@ def respond(welcome):
             rocket.headers += [('Location', target)]
             return rocket.raw_respond(HTTPStatus.SEE_OTHER)
         elif target:
-            return rocket.respond(form_login % ({'target_redir':
-                                                 f'?target={target}'}))
+            return rocket.respond(login_form(target_location=target))
         elif welcome:
             return rocket.respond(mk_form_welcome(rocket.session))
         else:
-            return rocket.respond(form_login % ({'target_redir': ''}))
+            return rocket.respond(login_form())
 
     if rocket.session:
         rocket.msg(f'{rocket.username} authenticated by token')
@@ -442,25 +382,15 @@ def handle_dashboard(rocket):
     return handle_stub(rocket, ['dashboard in development, check back later'])
 
 
-form_register = """
+def handle_register(rocket):
+    def form_respond():
+        return rocket.respond('''
     <form id="register" method="post" action="/register">
         <label for="student_id">Student ID:</label>
         <input name="student_id" type="text" id="student_id" /><br />
         <button type="submit">Submit</button>
-    </form>
-""".strip()
-
+    </form>''')
 
-register_response = """
-<h1>Save these credentials, you will not be able to access them again</h1><br>
-<h3>Username: %(username)s</h1><br>
-<h3>Password: %(password)s</h1><br>
-""".strip()
-
-
-def handle_register(rocket):
-    def form_respond():
-        return rocket.respond(form_register)
     if rocket.method != 'POST':
         return form_respond()
     if not (student_id := rocket.body_args_query('student_id')):
@@ -474,10 +404,10 @@ def form_respond():
         rocket.msg('no such student')
         return form_respond()
     rocket.msg('welcome to the classroom')
-    return rocket.respond((register_response % {
-        'username': registration.username,
-        'password': registration.password,
-    }))
+    return rocket.respond(f'''
+    <h1>Save these credentials, you will not be able to access them again</h1><br>
+    <h3>Username: {registration.username}</h3><br>
+    <h3>Password: {registration.password}</h3><br>''')
 
 
 def handle_cgit(rocket):
@@ -497,7 +427,7 @@ def handle_cgit(rocket):
                             env=cgit_env)
     so, se = proc.communicate()
     try:
-        outstring = str(so, 'UTF-8')
+        outstring = so.decode()
         begin = outstring.index('\n\n')
         return rocket.respond(outstring[begin+2:])
     except (UnicodeDecodeError, ValueError) as ex:
diff --git a/orbit/requirements.txt b/orbit/requirements.txt
deleted file mode 100644
index a1543266..00000000
--- a/orbit/requirements.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-bcrypt ~= 3.1.6
-certbot ~= 2.6.0
-Markdown ~= 3.3.7
-urllib3 ~= 1.26.16
-uWSGI ~= 2.0.21
-uwsgi-tools ~= 1.1.1
-peewee ~= 3.17.1
diff --git a/orbit/warpdrive.sh b/orbit/warpdrive.sh
index ed4ef082..19344730 100755
--- a/orbit/warpdrive.sh
+++ b/orbit/warpdrive.sh
@@ -12,7 +12,4 @@ require "${DOCKER}"
 
 CONTAINER=${CONTAINER:-singularity_orbit_1}
 
-cat <<EOF | $DOCKER exec -i "$CONTAINER" /bin/sh
-. /radius-venv/bin/activate
-/orbit/hyperspace.py $@
-EOF
+$DOCKER exec -i "$CONTAINER" ./hyperspace.py "$@"
diff --git a/submatrix/Containerfile b/submatrix/Containerfile
index 903c8301..2fdecfcc 100644
--- a/submatrix/Containerfile
+++ b/submatrix/Containerfile
@@ -16,5 +16,5 @@ VOLUME /var/synapse
 
 USER 100:100
 
-CMD /usr/bin/synapse_homeserver -c /etc/synapse/homeserver.yaml
+CMD ["synapse_homeserver", "-c", "/etc/synapse/homeserver.yaml"]
 
diff --git a/test.sh b/test.sh
index 7fb34037..eb951888 100755
--- a/test.sh
+++ b/test.sh
@@ -146,15 +146,9 @@ curl --url "https://$SINGULARITY_HOSTNAME/login" \
   | grep "msg = authentication failure"
 
 # Check that we can create a user
-orbit/warpdrive.sh \
-  -u user -p pass -i 1234 -n \
-  | tee test/create_user \
-  | grep "credentials(username: user, password:pass)"
-
-add_cleanup "orbit/warpdrive.sh \
-  -u user -w \
-  | tee test/delete_user \
-  | grep 'user'"
+orbit/warpdrive.sh -u user -p pass -i 1234 -n
+
+add_cleanup "orbit/warpdrive.sh -u user -w"
 
 # Check that registration fails with incorrect student id
 curl --url "https://$SINGULARITY_HOSTNAME/register" \
