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
parent
8570f73794
commit
363f00775b
69
README.md
69
README.md
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
70
run
|
@ -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()
|
||||
|
|
|
@ -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()
|
Loading…
Reference in New Issue