diff --git a/apa b/apa
new file mode 100755
index 0000000000000000000000000000000000000000..f9fcfa3a1741491807ceee1baa8c290989c7501b
--- /dev/null
+++ b/apa
@@ -0,0 +1,251 @@
+#!/usr/bin/python3
+"""
+Anders Python Archiver
+
+Copyright (C) 2004-2023 Anders Blomdell <anders.blomdell@control.lth.se>
+
+  A small utility program to join a number of python modules
+  into a single executable python program.
+
+  http://www.control.lth.se/user/andersb/software/apa
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+"""
+from __future__ import print_function
+
+import optparse
+import os
+import re
+import string
+import sys
+
+def getModuleName(s):
+    m = re.match("^(.*)/__init__\.py$", s)
+    if m:
+        return m.group(1).replace("/", ".")
+    m = re.match("^(.*)\.py$", s)
+    if m:
+        return m.group(1).replace("/", ".")
+    return None
+
+def emitCode(f, module, filename, code):
+    print("EMIT:", module, file=sys.stderr)
+    print('"%s" : ( "%s", """%s""")' % (quote(module),
+                                        quote(filename),
+                                        quote(code)), file=f)
+    
+def quote(s):
+    return s.replace('\\', '\\\\').replace('"', '\\"')
+
+if __name__ == '__main__':
+    usage = "%prog [options] <main module> <additional modules>*"
+    optParser = optparse.OptionParser(usage=usage)
+    optParser.add_option('-d','--documentation',
+                         action='store_true', 
+                         help='Show documentation')
+    optParser.add_option('--interpreter',
+                         default=None,
+                         action='store', 
+                         help='Interpreter to use')
+    optParser.add_option('-o','--output',
+                         action='store', 
+                         help='Name of archive')
+    optParser.add_option('-s','--strip',
+                         action='append',
+                         default=[],
+                         help='Strip this part from filenames')
+    (options, args) = optParser.parse_args(sys.argv[1:])
+
+    if options.documentation:
+        print(__doc__)
+        sys.exit(0)
+
+    # Read all python files
+    code = []
+    processed = set()
+    for filename in args:
+        archivename = filename
+        for strip in options.strip:
+            m = re.match("^%s/*(.*)" % strip, filename)
+            if m:
+                archivename = m.group(1)
+                break
+            
+        if archivename in processed:
+            continue
+        processed.add(archivename)
+
+        name = getModuleName(archivename)
+        if name:
+            f = open(filename)
+            code.append((name, archivename, f.read()))
+            f.close()
+        else:
+            print("Illegal filename '%s'" % filename)
+            sys.exit(1)
+
+    if options.output:
+        apa = open(options.output, 'w')
+    else:
+        apa = None
+
+    # Emit code
+    interpreter = code[0][2].splitlines()[0]
+    if options.interpreter != None:
+        print("#!%s" % options.interpreter, file=apa)
+    elif interpreter.find("python") >= 0:
+        # Take interpreter version from main file
+        print(interpreter, file=apa)
+    else:
+        # Default interpreter
+        print("Interpreter defaulting to '#!/usr/bin/python3'", file=sys.stderr)
+        print("#!/usr/bin/python3", file=apa)
+    print("\"\"\"", file=apa)
+    print("Executable archive of '%s'" % code[0][0], file=apa)
+    print("\nThe following modules are archived:", file=apa)
+    print("  %-20s %s" % ('__main__', code[0][1]), file=apa)
+    for (n, f, c) in code[1:]:
+        print("  %-20s %s" % (n, f), file=apa)
+    print("""
+Archive created by 'apa' (http://www.control.lth.se/user/andersb/software/apa)
+
+To extract as individual files:
+
+  * Add a .py suffix to the archive (if needed)
+  * Start a python interpreter
+  * Run the following commands:
+       import <archive name>
+       <archive name>.extract(<optional destination dir>)
+\"\"\"
+""", file=apa)
+    print("code = {", file=apa)
+    (n, f, c) = code[0]
+    emitCode(apa, '__main__', f, c)
+    for (n, f, c) in code[1:]:
+        print(",", file=apa)
+        emitCode(apa, n, f, c)
+    print("}", file=apa)
+    print("""
+
+import sys
+import os
+import importlib.abc
+import importlib.util
+
+class Finder(importlib.abc.MetaPathFinder):
+
+    def __init__(self, loader, code):
+        self.loader = loader
+        self.code = code
+        pass
+
+    def find_spec(self, fullname, path=None, target=None):
+        if fullname in self.code:
+            return importlib.util.spec_from_loader(fullname, self.loader)
+        return None
+
+    pass
+
+class Loader(importlib.abc.Loader):
+
+    def __init__(self, code):
+        self.code = code;
+        self._path = "APA[%s]" % os.path.realpath(sys.argv[0])
+        pass
+    
+    def create_module(self, spec):
+        return None
+
+    def exec_module(self, module):
+        filename,src = self.code[module.__name__]
+        mod = sys.modules.setdefault(module.__name__, module)
+        mod.__file__ = filename
+        mod.__loader__ = self
+        mod.__path__ = [ self._path ]
+        global BAD
+        BAD = None
+        try:
+            code = compile(src, "%s/%s" % (self._path, filename), 'exec')
+            exec(code, mod.__dict__)
+        except SystemExit as e:
+            # Silently propagate exit
+            raise e
+        except Exception as e:
+            # Silently propagate other exceptions as well
+            BAD = sys.exc_info()
+            raise e
+        return mod
+
+    def is_package(self, fullname):
+        (filename, src) = self.code[fullname]
+        (name, extension) = os.path.splitext(os.path.basename(filename))
+        return name == '__init__'
+
+    def get_code(self, fullname):
+        (filename, src) = self.code[fullname]
+        code = compile(src, "%s/%s" % (self.path, filename), 'exec')
+        return code
+
+    def get_source(self, fullname):
+        (filename, src) = self.code[fullname]
+        return src
+
+    def get_filename(self, fullname):
+        (filename, src) = self.code[fullname]
+        return filename
+
+    pass
+
+def extract(dest='.'):
+    \"\"\"Extracts archived files in dest directory ('.' is default)\"\"\"
+    import os
+    import os.path
+    for (m, (f, c)) in list(code.items()):
+        filepath = '%s/%s' % (dest, f)
+        dirpath = os.path.dirname(filepath)
+        if os.path.exists(filepath):
+            raise Exception('File already exists: %s' % filepath)
+        if not os.path.exists(dirpath):
+            os.makedirs(dirpath)
+        f = file(filepath, 'w')
+        f.write(c)
+        f.close()
+        os.chmod(filepath, 0o755)
+    
+if __name__ == '__main__':
+    import sys
+    loader = Loader(code)
+    finder = Finder(loader, code)
+    sys.meta_path.append(finder)
+    try:
+        # This will start actual execution
+        spec = finder.find_spec("__main__")
+        m = importlib.util.module_from_spec(spec)
+        loader.exec_module(m)
+    except SystemExit as e:
+        raise e
+    except:
+        if BAD:
+            import traceback
+            traceback.print_exception(*BAD)
+            pass
+        sys.exit(1)
+""", file=apa)
+    if apa:
+        apa.close()
+        try:
+            os.chmod(options.output, 0o755)
+        except:
+            pass