New config options for daemonization, configfile location and basedir location

Using --daemon {start|stop|restart} OctoPrint can now be daemonized/controlled in daemon mode. Via --pidfile it's possible to set the pidfile to use, --configfile allows specification of the config.yaml to use, --basedir specifies the location of the basedir from which to base off the upload, timelapse and log folders. I also updated the README to include some config file settings which were previously undocumented.
master
Gina Häußge 2013-03-11 21:00:43 +01:00
parent 8570f73794
commit 363f00775b
5 changed files with 177 additions and 139 deletions

View File

@ -39,39 +39,39 @@ Usage
Just start the server via
python -m octoprint.server
or alternatively
./run
By default it binds to all interfaces on port 5000 (so pointing your browser to `http://127.0.0.1:5000`
will do the trick). If you want to change that, use the additional command line parameters `host` and `port`,
which accept the host ip to bind to and the numeric port number respectively. If for example you want to the server
which accept the host ip to bind to and the numeric port number respectively. If for example you want the server
to only listen on the local interface on port 8080, the command line would be
python -m octoprint.server --host=127.0.0.1 --port=8080
or
./run --host=127.0.0.1 --port=8080
Alternatively, the host and port on which to bind can be defined via the configuration.
If you want to run OctoPrint as a daemon, there's another script for that:
If you want to run OctoPrint as a daemon (only supported on Linux), use
./run-as-daemon [start|stop|restart]
./run --daemon {start|stop|restart} [--pidfile PIDFILE]
It will create a pid file at `/tmp/octoprint.pid` for now. Further commandline arguments will not be evaluated,
so you'll need to define host and port in the configuration file if you want something different there than the default.
If you do not supply a custom pidfile location via `--pidfile PIDFILE`, it will be created at `/tmp/octoprint.pid`.
You can also specify the configfile or the base directory (for basing off the `uploads`, `timelapse` and `logs` folders),
e.g.:
./run --config /path/to/another/config.yaml --basedir /path/to/my/basedir
See `run --help` for further information.
Configuration
-------------
The config-file `config.yaml` for OctoPrint is expected in its settings folder, which is located at `~/.octoprint`
on Linux, at `%APPDATA%/OctoPrint` on Windows and at `~/Library/Application Support/OctoPrint` on MacOS.
If not specified via the commandline, the configfile `config.yaml` for OctoPrint is expected in its settings folder,
which is located at `~/.octoprint` on Linux, at `%APPDATA%/OctoPrint` on Windows and at
`~/Library/Application Support/OctoPrint` on MacOS.
The following example config should explain the available options:
The following example config should explain the available options, most of which can also be configured via the
settings dialog within OctoPrint:
# Use the following settings to configure the serial connection to the printer
serial:
@ -111,6 +111,10 @@ The following example config should explain the available options:
# Whether to enable the gcode viewer in the UI or not
gCodeVisualizer: true
# Specified whether OctoPrint should wait for the start response from the printer before trying to send commands
# during connect
waitForStartOnConnect: false
# Use the following settings to set custom paths for folders used by OctoPrint
folder:
# Absolute path where to store gcode uploads. Defaults to the uploads folder in the OctoPrint settings folder
@ -122,7 +126,38 @@ The following example config should explain the available options:
# Absolute path where to store temporary timelapse files. Defaults to the timelapse/tmp folder in the OctoPrint
# settings dir
timelapse_tmp: /path/timelapse/tmp/folder
timelapse_tmp: /path/to/timelapse/tmp/folder
# Absolute path where to store log files. Defaults to the logs folder in the OctoPrint settings dir
logs: /path/to/logs/folder
# Use the following settings to configure temperature profiles which will be displayed in the temperature tab.
temperature:
profiles:
- name: ABS
extruder: 210
bed: 100
- name: PLA
extruder: 180
bed: 60
# Use the following settings to configure printer parameters
printerParameters:
# Use this to define the movement speed on X, Y, Z and E to use for the controls on the controls tab
movementSpeed:
x: 6000
y: 6000
z: 200
e: 300
# Use the following settings to tweak OctoPrint's appearance a bit to better distinguish multiple instances/printers
appearance:
# Use this to give your printer a name. It will be displayed in the title bar (as "<Name> [OctoPrint]") and in the
# navigation bar (as "OctoPrint: <Name>")
name: My Printer Model
# Use this to color the navigation bar. Supported colors are red, orange, yellow, green, blue, violet and default.
color: blue
# Use the following settings to add custom controls to the "Controls" tab within OctoPrint
#

View File

@ -18,7 +18,6 @@ import octoprint.gcodefiles as gcodefiles
import octoprint.util as util
SUCCESS = {}
UPLOAD_FOLDER = settings().getBaseFolder("uploads")
BASEURL = "/ajax/"
app = Flask("octoprint")
# Only instantiated by the Server().run() method
@ -249,7 +248,7 @@ def readGcodeFiles():
@app.route(BASEURL + "gcodefiles/<path:filename>", methods=["GET"])
def readGcodeFile(filename):
return send_from_directory(UPLOAD_FOLDER, filename, as_attachment=True)
return send_from_directory(settings().getBaseFolder("uploads"), filename, as_attachment=True)
@app.route(BASEURL + "gcodefiles/upload", methods=["POST"])
def uploadGcodeFile():
@ -442,7 +441,14 @@ def performSystemAction():
#~~ startup code
class Server():
def run(self, host = "0.0.0.0", port = 5000, debug = False):
def __init__(self, configfile=None, basedir=None, host="0.0.0.0", port=5000, debug=False):
self._configfile = configfile
self._basedir = basedir
self._host = host
self._port = port
self._debug = debug
def run(self):
# Global as I can't work out a way to get it into PrinterStateConnection
global printer
global gcodeManager
@ -452,11 +458,22 @@ class Server():
from tornado.ioloop import IOLoop
from tornado.web import Application, FallbackHandler
# first initialize the settings singleton and make sure it uses given configfile and basedir if available
self._initSettings(self._configfile, self._basedir)
# then initialize logging
self._initLogging(self._debug)
gcodeManager = gcodefiles.GcodeManager()
printer = Printer(gcodeManager)
logging.getLogger(__name__).info("Listening on http://%s:%d" % (host, port))
app.debug = debug
if self._host is None:
self._host = settings().get(["server", "host"])
if self._port is None:
self._port = settings().getInt(["server", "port"])
logging.getLogger(__name__).info("Listening on http://%s:%d" % (self._host, self._port))
app.debug = self._debug
self._router = tornadio2.TornadioRouter(PrinterStateConnection)
@ -464,10 +481,13 @@ class Server():
(".*", FallbackHandler, {"fallback": WSGIContainer(app)})
])
self._server = HTTPServer(self._tornado_app)
self._server.listen(port, address=host)
self._server.listen(self._port, address=self._host)
IOLoop.instance().start()
def initLogging(self):
def _initSettings(self, configfile, basedir):
s = settings(init=True, basedir=basedir, configfile=configfile)
def _initLogging(self, debug):
self._config = {
"version": 1,
"formatters": {
@ -503,24 +523,6 @@ class Server():
}
logging.config.dictConfig(self._config)
def start(self):
from optparse import OptionParser
self._defaultHost = settings().get(["server", "host"])
self._defaultPort = settings().get(["server", "port"])
self._parser = OptionParser(usage="usage: %prog [options]")
self._parser.add_option("-d", "--debug", action="store_true", dest="debug",
help="Enable debug mode")
self._parser.add_option("--host", action="store", type="string", default=self._defaultHost, dest="host",
help="Specify the host on which to bind the server, defaults to %s if not set" % (self._defaultHost))
self._parser.add_option("--port", action="store", type="int", default=self._defaultPort, dest="port",
help="Specify the port on which to bind the server, defaults to %s if not set" % (self._defaultPort))
(options, args) = self._parser.parse_args()
self.initLogging()
self.run(host=options.host, port=options.port, debug=options.debug)
if __name__ == "__main__":
octoprint = Server()
octoprint.start()
octoprint.run()

View File

@ -9,17 +9,19 @@ import yaml
import logging
APPNAME="OctoPrint"
OLD_APPNAME="PrinterWebUI"
instance = None
def settings():
def settings(init=False, configfile=None, basedir=None):
global instance
if instance is None:
instance = Settings()
if init:
instance = Settings(configfile, basedir)
else:
raise ValueError("Settings not initialized yet")
return instance
old_default_settings = {
default_settings = {
"serial": {
"port": None,
"baudrate": None
@ -35,33 +37,16 @@ old_default_settings = {
"bitrate": "5000k",
"watermark": True
},
"feature": {
"gCodeVisualizer": True,
"waitForStartOnConnect": False
},
"folder": {
"uploads": None,
"timelapse": None,
"timelapse_tmp": None,
"logs": None
},
"feature": {
"gCodeVisualizer": True,
"waitForStartOnConnect": False
},
}
default_settings = old_default_settings.copy()
default_settings.update({
"appearance": {
"name": "",
"color": "default"
},
"controls": [],
"printerParameters": {
"movementSpeed": {
"x": 6000,
"y": 6000,
"z": 200,
"e": 300
}
},
"temperature": {
"profiles":
[
@ -69,16 +54,29 @@ default_settings.update({
{"name": "PLA", "extruder" : 180, "bed" : 60 }
]
},
"printerParameters": {
"movementSpeed": {
"x": 6000,
"y": 6000,
"z": 200,
"e": 300
}
},
"appearance": {
"name": "",
"color": "default"
},
"controls": [],
"system": {
"actions": []
}
})
}
valid_boolean_trues = ["true", "yes", "y", "1"]
class Settings(object):
def __init__(self):
def __init__(self, configfile=None, basedir=None):
self._logger = logging.getLogger(__name__)
self.settings_dir = None
@ -86,16 +84,14 @@ class Settings(object):
self._config = None
self._dirty = False
self._init_settings_dir()
self.load()
self._init_settings_dir(basedir)
self.load(configfile)
def _init_settings_dir(self):
self.settings_dir = _resolveSettingsDir(APPNAME)
# migration due to rename
old_settings_dir = _resolveSettingsDir(OLD_APPNAME)
if os.path.exists(old_settings_dir) and os.path.isdir(old_settings_dir) and not os.path.exists(self.settings_dir):
os.rename(old_settings_dir, self.settings_dir)
def _init_settings_dir(self, basedir):
if basedir is not None:
self.settings_dir = basedir
else:
self.settings_dir = _resolveSettingsDir(APPNAME)
def _getDefaultFolder(self, type):
folder = default_settings["folder"][type]
@ -105,29 +101,15 @@ class Settings(object):
#~~ load and save
def load(self):
filename = os.path.join(self.settings_dir, "config.yaml")
oldFilename = os.path.join(self.settings_dir, "config.ini")
def load(self, configfile):
if configfile is not None:
filename = configfile
else:
filename = os.path.join(self.settings_dir, "config.yaml")
if os.path.exists(filename) and os.path.isfile(filename):
with open(filename, "r") as f:
self._config = yaml.safe_load(f)
elif os.path.exists(oldFilename) and os.path.isfile(oldFilename):
config = ConfigParser.ConfigParser(allow_no_value=True)
config.read(oldFilename)
self._config = {}
for section in old_default_settings.keys():
if not config.has_section(section):
continue
self._config[section] = {}
for option in old_default_settings[section].keys():
if not config.has_option(section, option):
continue
self._config[section][option] = config.get(section, option)
self._dirty = True
self.save(force=True)
os.rename(oldFilename, oldFilename + ".bck")
else:
self._config = {}

70
run
View File

@ -1,14 +1,62 @@
#!/bin/bash
#!/usr/bin/python
import sys
from octoprint.daemon import Daemon
from octoprint.server import Server
PYTHON=`which python`
class Main(Daemon):
def __init__(self, pidfile, configfile, basedir, host, port, debug):
Daemon.__init__(self, pidfile)
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
self._configfile = configfile
self._basedir = basedir
self._host = host
self._port = port
self._debug = debug
cd $DIR
$PYTHON -m octoprint.server $@
def run(self):
octoprint = Server(self._configfile, self._basedir, self._host, self._port, self._debug)
octoprint.run()
def main():
import argparse
parser = argparse.ArgumentParser(prog="run")
parser.add_argument("-d", "--debug", action="store_true", dest="debug",
help="Enable debug mode")
parser.add_argument("--host", action="store", type=str, dest="host",
help="Specify the host on which to bind the server")
parser.add_argument("--port", action="store", type=int, dest="port",
help="Specify the port on which to bind the server")
parser.add_argument("-c", "--config", action="store", dest="config",
help="Specify the config file to use. OctoPrint needs to have write access for the settings dialog to work. Defaults to ~/.octoprint/config.yaml")
parser.add_argument("-b", "--basedir", action="store", dest="basedir",
help="Specify the basedir to use for uploads, timelapses etc. OctoPrint needs to have write access. Defaults to ~/.octoprint")
parser.add_argument("--daemon", action="store", type=str, choices=["start", "stop", "restart"],
help="Daemonize/control daemonized OctoPrint instance (only supported under Linux right now)")
parser.add_argument("--pid", action="store", type=str, dest="pidfile", default="/tmp/octoprint.pid",
help="Pidfile to use for daemonizing, defaults to /tmp/octoprint.pid")
args = parser.parse_args()
if args.daemon:
if sys.platform == "darwin" or sys.platform == "win32":
print >> sys.stderr, "Sorry, daemon mode is only supported under Linux right now"
sys.exit(2)
daemon = Main(args.pidfile, args.config, args.basedir, args.host, args.port, args.debug)
if args.command is not None:
if "start" == args.command:
daemon.start()
elif "stop" == args.command:
daemon.stop()
elif "restart" == args.command:
daemon.restart()
else:
octoprint = Server(args.config, args.basedir, args.host, args.port, args.debug)
octoprint.run()
if __name__ == "__main__":
main()

View File

@ -1,29 +0,0 @@
#!/usr/bin/python
import sys
from octoprint.daemon import Daemon
from octoprint.server import Server
class Main(Daemon):
def run(self):
octoprint = Server()
octoprint.start()
def main():
daemon = Main('/tmp/octoprint.pid')
if len(sys.argv) == 2:
if 'start' == sys.argv[1]:
daemon.start()
elif 'stop' == sys.argv[1]:
daemon.stop()
elif 'restart' == sys.argv[1]:
daemon.restart()
else:
print "Unknown command"
sys.exit(2)
sys.exit(0)
else:
print "usage: %s start|stop|restart" % sys.argv[0]
sys.exit(2)
if __name__ == "__main__":
main()