diff --git a/src/pylorax/api/__init__.py b/src/pylorax/api/__init__.py
new file mode 100644
index 00000000..5ee1ec8a
--- /dev/null
+++ b/src/pylorax/api/__init__.py
@@ -0,0 +1,21 @@
+#
+# lorax-composer API server
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# 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, see .
+from pylorax.api.crossdomain import crossdomain
+
+__all__ = ["crossdomain"]
+
diff --git a/src/pylorax/api/crossdomain.py b/src/pylorax/api/crossdomain.py
new file mode 100644
index 00000000..d2da96ff
--- /dev/null
+++ b/src/pylorax/api/crossdomain.py
@@ -0,0 +1,63 @@
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# 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, see .
+#
+
+# crossdomain decorator from - http://flask.pocoo.org/snippets/56/
+from datetime import timedelta
+from flask import make_response, request, current_app
+from functools import update_wrapper
+
+
+def crossdomain(origin, methods=None, headers=None,
+ max_age=21600, attach_to_all=True,
+ automatic_options=True):
+ if methods is not None:
+ methods = ', '.join(sorted(x.upper() for x in methods))
+ if headers is not None and not isinstance(headers, basestring):
+ headers = ', '.join(x.upper() for x in headers)
+ if not isinstance(origin, list):
+ origin = [origin]
+ if isinstance(max_age, timedelta):
+ max_age = max_age.total_seconds()
+
+ def get_methods():
+ if methods is not None:
+ return methods
+
+ options_resp = current_app.make_default_options_response()
+ return options_resp.headers['allow']
+
+ def decorator(f):
+ def wrapped_function(*args, **kwargs):
+ if automatic_options and request.method == 'OPTIONS':
+ resp = current_app.make_default_options_response()
+ else:
+ resp = make_response(f(*args, **kwargs))
+ if not attach_to_all and request.method != 'OPTIONS':
+ return resp
+
+ h = resp.headers
+
+ h.extend([("Access-Control-Allow-Origin", orig) for orig in origin])
+ h['Access-Control-Allow-Methods'] = get_methods()
+ h['Access-Control-Max-Age'] = str(max_age)
+ if headers is not None:
+ h['Access-Control-Allow-Headers'] = headers
+ return resp
+
+ f.provide_automatic_options = False
+ return update_wrapper(wrapped_function, f)
+ return decorator
diff --git a/src/pylorax/api/server.py b/src/pylorax/api/server.py
new file mode 100644
index 00000000..5e1d2a8a
--- /dev/null
+++ b/src/pylorax/api/server.py
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# 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, see .
+#
+
+from flask import Flask, jsonify
+
+from pylorax.api import crossdomain
+from pylorax.api.v0 import v0_api
+
+server = Flask(__name__)
+
+__all__ = ["server"]
+
+@server.route('/')
+@crossdomain(origin="*")
+def hello_world():
+ return 'Hello, World!'
+
+v0_api(server)
diff --git a/src/pylorax/api/v0.py b/src/pylorax/api/v0.py
new file mode 100644
index 00000000..b8a8e7b5
--- /dev/null
+++ b/src/pylorax/api/v0.py
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# 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, see .
+#
+from flask import jsonify
+
+# Use pykickstart to calculate disk image size
+from pykickstart.parser import KickstartParser
+from pykickstart.version import makeVersion, RHEL7
+
+from pylorax.api import crossdomain
+from pylorax.creator import DRACUT_DEFAULT, mount_boot_part_over_root
+from pylorax.creator import make_appliance, make_image, make_livecd, make_live_images
+from pylorax.creator import make_runtime, make_squashfs
+from pylorax.imgutils import copytree
+from pylorax.imgutils import Mount, PartitionMount, umount
+from pylorax.installer import InstallError
+from pylorax.sysutils import joinpaths
+
+
+# no-virt mode doesn't need libvirt, so make it optional
+try:
+ import libvirt
+except ImportError:
+ libvirt = None
+
+def v0_api(api):
+ """ Setup v0 of the API server"""
+ @api.route("/api/v0/status")
+ @crossdomain(origin="*")
+ def v0_status():
+ return jsonify(build="devel", api="0", db_version="0", schema_version="0", db_supported=False)
diff --git a/src/sbin/lorax b/src/sbin/lorax
index b60c7ce7..0984e927 100755
--- a/src/sbin/lorax
+++ b/src/sbin/lorax
@@ -1,5 +1,4 @@
#!/usr/bin/python
-
#
# lorax
#
diff --git a/src/sbin/lorax-composer b/src/sbin/lorax-composer
new file mode 100755
index 00000000..87c438c3
--- /dev/null
+++ b/src/sbin/lorax-composer
@@ -0,0 +1,115 @@
+#!/usr/bin/python
+#
+# lorax-composer
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# 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, see .
+#
+import logging
+log = logging.getLogger("lorax-composer")
+program_log = logging.getLogger("program")
+pylorax_log = logging.getLogger("pylorax")
+
+import argparse
+import os
+import shutil
+import sys
+import tempfile
+from gevent.wsgi import WSGIServer
+
+from pylorax import vernum
+from pylorax.api.server import server
+
+VERSION = "{0}-{1}".format(os.path.basename(sys.argv[0]), vernum)
+
+def get_parser():
+ """ Return the ArgumentParser for lorax-composer"""
+
+ parser = argparse.ArgumentParser( description="Composer API Server",
+ fromfile_prefix_chars="@" )
+
+ parser.add_argument("--host", default="127.0.0.1", metavar="HOST",
+ help="Host or IP to bind to (127.0.0.1)")
+ parser.add_argument("--port", default=4000, metavar="PORT",
+ help="Port to bind to (4000)")
+ parser.add_argument("--log", dest="logfile", default="/var/log/lorax-composer/composer.log", metavar="LOG",
+ help="Path to logfile (/var/log/lorax-composer/composer.log)")
+ parser.add_argument("--mockfiles", default="/var/tmp/bdcs-mockfiles/", metavar="MOCKFILES",
+ help="Path to JSON files used for /api/mock/ paths (/var/tmp/bdcs-mockfiles/)")
+ parser.add_argument("--bdcs", default="/mddb/cs.repo", metavar="BDCS",
+ help="Path to the content store directory (/mddb/cs.repo)")
+ parser.add_argument("-V", action="store_true", dest="showver",
+ help="show program's version number and exit")
+ parser.add_argument("DB", metavar="DATABASE",
+ help="Path to the BDCS sqlite database")
+ parser.add_argument("RECIPES", metavar="RECIPES",
+ help="Path to the recipes")
+
+ return parser
+
+
+def setup_logging(logfile):
+ # Setup logging to console and to logfile
+ log.setLevel(logging.DEBUG)
+ pylorax_log.setLevel(logging.DEBUG)
+
+ sh = logging.StreamHandler()
+ sh.setLevel(logging.INFO)
+ fmt = logging.Formatter("%(asctime)s: %(message)s")
+ sh.setFormatter(fmt)
+ log.addHandler(sh)
+ pylorax_log.addHandler(sh)
+
+ fh = logging.FileHandler(filename=logfile, mode="w")
+ fh.setLevel(logging.DEBUG)
+ fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
+ fh.setFormatter(fmt)
+ log.addHandler(fh)
+ pylorax_log.addHandler(fh)
+
+ # External program output log
+ program_log.setLevel(logging.DEBUG)
+ logfile = os.path.abspath(os.path.dirname(logfile))+"/program.log"
+ fh = logging.FileHandler(filename=logfile, mode="w")
+ fh.setLevel(logging.DEBUG)
+ program_log.addHandler(fh)
+
+
+if __name__ == '__main__':
+ # parse the arguments
+ opts = get_parser().parse_args()
+
+ if opts.showver:
+ print(VERSION)
+ sys.exit(0)
+
+ errors = []
+ if not os.path.exists(opts.DB):
+ errors.append("Database, %s, doesn't exist." % opts.DB)
+ if not os.path.exists(opts.RECIPES):
+ errors.append("Recipe directory, %s, is missing." % opts.RECIPES)
+
+ for e in errors:
+ print("ERROR: " + e)
+ if errors:
+ sys.exit(1)
+
+ setup_logging(opts.logfile)
+ http_server = WSGIServer((opts.host, opts.port), server)
+ # The server writes directly to a file object, so point to our log directory
+ server_logfile = os.path.abspath(os.path.dirname(opts.logfile))+"/server.log"
+ server_log = open(server_logfile, "w")
+ http_server.log = server_log
+ http_server.serve_forever()