Merge pull request #303 from rmkraus/daemon

Updating Daemon Code
This commit is contained in:
Paulus Schoutsen 2015-09-01 00:58:03 -07:00
commit c1f172f33a
7 changed files with 221 additions and 198 deletions

View File

@ -77,8 +77,70 @@ def get_arguments():
'--open-ui', '--open-ui',
action='store_true', action='store_true',
help='Open the webinterface in a browser') help='Open the webinterface in a browser')
parser.add_argument(
'-v', '--verbose',
action='store_true',
help="Enable verbose logging to file.")
parser.add_argument(
'--pid-file',
metavar='path_to_pid_file',
default=None,
help='Path to PID file useful for running as daemon')
if os.name != "nt":
parser.add_argument(
'--daemon',
action='store_true',
help='Run Home Assistant as daemon')
return parser.parse_args() arguments = parser.parse_args()
if os.name == "nt":
arguments.daemon = False
return arguments
def daemonize():
""" Move current process to daemon process """
# create first fork
pid = os.fork()
if pid > 0:
sys.exit(0)
# decouple fork
os.setsid()
os.umask(0)
# create second fork
pid = os.fork()
if pid > 0:
sys.exit(0)
def check_pid(pid_file):
""" Check that HA is not already running """
# check pid file
try:
pid = int(open(pid_file, 'r').readline())
except IOError:
# PID File does not exist
return
try:
os.kill(pid, 0)
except OSError:
# PID does not exist
return
print('Fatal Error: HomeAssistant is already running.')
sys.exit(1)
def write_pid(pid_file):
""" Create PID File """
pid = os.getpid()
try:
open(pid_file, 'w').write(str(pid))
except IOError:
print('Fatal Error: Unable to write pid file {}'.format(pid_file))
sys.exit(1)
def main(): def main():
@ -90,15 +152,24 @@ def main():
config_dir = os.path.join(os.getcwd(), args.config) config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir) ensure_config_path(config_dir)
# daemon functions
if args.pid_file:
check_pid(args.pid_file)
if args.daemon:
daemonize()
if args.pid_file:
write_pid(args.pid_file)
if args.demo_mode: if args.demo_mode:
hass = bootstrap.from_config_dict({ hass = bootstrap.from_config_dict({
'frontend': {}, 'frontend': {},
'demo': {} 'demo': {}
}, config_dir=config_dir) }, config_dir=config_dir, daemon=args.daemon, verbose=args.verbose)
else: else:
config_file = ensure_config_file(config_dir) config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir) print('Config directory:', config_dir)
hass = bootstrap.from_config_file(config_file) hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose)
if args.open_ui: if args.open_ui:
def open_browser(event): def open_browser(event):

View File

@ -149,8 +149,9 @@ def mount_local_lib_path(config_dir):
sys.path.insert(0, os.path.join(config_dir, 'lib')) sys.path.insert(0, os.path.join(config_dir, 'lib'))
# pylint: disable=too-many-branches, too-many-statements # pylint: disable=too-many-branches, too-many-statements, too-many-arguments
def from_config_dict(config, hass=None, config_dir=None, enable_log=True): def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False):
""" """
Tries to configure Home Assistant from a config dict. Tries to configure Home Assistant from a config dict.
@ -166,7 +167,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
process_ha_core_config(hass, config.get(core.DOMAIN, {})) process_ha_core_config(hass, config.get(core.DOMAIN, {}))
if enable_log: if enable_log:
enable_logging(hass) enable_logging(hass, verbose, daemon)
_ensure_loader_prepared(hass) _ensure_loader_prepared(hass)
@ -195,7 +196,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
return hass return hass
def from_config_file(config_path, hass=None): def from_config_file(config_path, hass=None, verbose=False, daemon=False):
""" """
Reads the configuration file and tries to start all the required Reads the configuration file and tries to start all the required
functionality. Will add functionality to 'hass' parameter if given, functionality. Will add functionality to 'hass' parameter if given,
@ -209,15 +210,16 @@ def from_config_file(config_path, hass=None):
hass.config.config_dir = config_dir hass.config.config_dir = config_dir
mount_local_lib_path(config_dir) mount_local_lib_path(config_dir)
enable_logging(hass) enable_logging(hass, verbose, daemon)
config_dict = config_util.load_config_file(config_path) config_dict = config_util.load_config_file(config_path)
return from_config_dict(config_dict, hass, enable_log=False) return from_config_dict(config_dict, hass, enable_log=False)
def enable_logging(hass): def enable_logging(hass, verbose=False, daemon=False):
""" Setup the logging for home assistant. """ """ Setup the logging for home assistant. """
if not daemon:
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s") "[%(name)s] %(message)s%(reset)s")
@ -251,11 +253,13 @@ def enable_logging(hass):
err_handler = logging.FileHandler( err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True) err_log_path, mode='w', delay=True)
err_handler.setLevel(logging.WARNING) err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter( err_handler.setFormatter(
logging.Formatter('%(asctime)s %(name)s: %(message)s', logging.Formatter('%(asctime)s %(name)s: %(message)s',
datefmt='%y-%m-%d %H:%M:%S')) datefmt='%y-%m-%d %H:%M:%S'))
logging.getLogger('').addHandler(err_handler) logger = logging.getLogger('')
logger.addHandler(err_handler)
logger.setLevel(logging.INFO) # this sets the minimum log level
else: else:
_LOGGER.error( _LOGGER.error(

View File

@ -208,6 +208,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
""" Registers a path wit the server. """ """ Registers a path wit the server. """
self.paths.append((method, url, callback, require_auth)) self.paths.append((method, url, callback, require_auth))
def log_message(self, fmt, *args):
""" Redirect built-in log to HA logging """
# pylint: disable=no-self-use
_LOGGER.info(fmt, *args)
# pylint: disable=too-many-public-methods,too-many-locals # pylint: disable=too-many-public-methods,too-many-locals
class RequestHandler(SimpleHTTPRequestHandler): class RequestHandler(SimpleHTTPRequestHandler):
@ -225,6 +230,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
self._session = None self._session = None
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server) SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """
_LOGGER.info(fmt, *arguments)
def _handle_request(self, method): # pylint: disable=too-many-branches def _handle_request(self, method): # pylint: disable=too-many-branches
""" Does some common checks and calls appropriate method. """ """ Does some common checks and calls appropriate method. """
url = urlparse(self.path) url = urlparse(self.path)

View File

@ -9,6 +9,7 @@ of entities and react to changes.
import os import os
import time import time
import logging import logging
import signal
import threading import threading
import enum import enum
import re import re
@ -73,13 +74,16 @@ class HomeAssistant(object):
will block until called. """ will block until called. """
request_shutdown = threading.Event() request_shutdown = threading.Event()
def stop_homeassistant(service): def stop_homeassistant(*args):
""" Stops Home Assistant. """ """ Stops Home Assistant. """
request_shutdown.set() request_shutdown.set()
self.services.register( self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant) DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
if os.name != "nt":
signal.signal(signal.SIGQUIT, stop_homeassistant)
while not request_shutdown.isSet(): while not request_shutdown.isSet():
try: try:
time.sleep(1) time.sleep(1)

101
scripts/hass-daemon Normal file
View File

@ -0,0 +1,101 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: hass
# Required-Start: $local_fs $network $named $time $syslog
# Required-Stop: $local_fs $network $named $time $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Home\ Assistant
### END INIT INFO
# /etc/init.d Service Script for Home Assistant
# Created with: https://gist.github.com/naholyr/4275302#file-new-service-sh
#
# Installation:
# 1) If any commands need to run before executing hass (like loading a
# virutal environment), put them in PRE_EXEC. This command must end with
# a semicolon.
# 2) Set RUN_AS to the username that should be used to execute hass.
# 3) Copy this script to /etc/init.d/
# sudo cp hass-daemon /etc/init.d/hass-daemon
# sudo chmod +x /etc/init.d/hass-daemon
# 4) Register the daemon with Linux
# sudo update-rc.d hass-daemon defaults
# 5) Install this service
# sudo service hass-daemon install
# 6) Restart Machine
#
# After installation, HA should start automatically. If HA does not start,
# check the log file output for errors.
# /var/opt/homeassistant/home-assistant.log
PRE_EXEC=""
RUN_AS="USER"
PID_FILE="/var/run/hass.pid"
CONFIG_DIR="/var/opt/homeassistant"
FLAGS="-v --config $CONFIG_DIR --pid-file $PID_FILE --daemon"
start() {
if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE); then
echo 'Service already running' >&2
return 1
fi
echo 'Starting service…' >&2
local CMD="$PRE_EXEC hass $FLAGS;"
su -c "$CMD" $RUN_AS
echo 'Service started' >&2
}
stop() {
if [ ! -f "$PID_FILE" ] || ! kill -0 $(cat "$PID_FILE"); then
echo 'Service not running' >&2
return 1
fi
echo 'Stopping service…' >&2
kill -3 $(cat "$PID_FILE")
echo 'Service stopped' >&2
}
install() {
echo "Installing Home Assistant Daemon (hass-daemon)"
echo "999999" > $PID_FILE
chown $RUN_AS $PID_FILE
mkdir -p $CONFIG_DIR
chown $RUN_AS $CONFIG_DIR
}
uninstall() {
echo -n "Are you really sure you want to uninstall this service? That cannot be undone. [yes|No] "
local SURE
read SURE
if [ "$SURE" = "yes" ]; then
stop
rm -fv "$PID_FILE"
echo "Notice: The config directory has not been removed"
echo $CONFIG_DIR
update-rc.d -f hass-daemon remove
rm -fv "$0"
echo "Home Assistant Daemon has been removed. Home Assistant is still installed."
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
install)
install
;;
uninstall)
uninstall
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart|install|uninstall}"
esac

View File

@ -1,78 +0,0 @@
#!/bin/sh
# To script is for running Home Assistant as a service and automatically starting it on boot.
# Assuming you have cloned the HA repo into /home/pi/Apps/home-assistant adjust this path if necessary
# This also assumes you installed HA on your raspberry pi using the instructions here:
# https://home-assistant.io/getting-started/
#
# To install to the following:
# sudo cp /home/pi/Apps/home-assistant/scripts/homeassistant-pi.sh /etc/init.d/homeassistant.sh
# sudo chmod +x /etc/init.d/homeassistant.sh
# sudo chown root:root /etc/init.d/homeassistant.sh
#
# If you want HA to start on boot also run the following:
# sudo update-rc.d homeassistant.sh defaults
# sudo update-rc.d homeassistant.sh enable
#
# You should now be able to start HA by running
# sudo /etc/init.d/homeassistant.sh start
### BEGIN INIT INFO
# Provides: myservice
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Put a short description of the service here
# Description: Put a long description of the service here
### END INIT INFO
# Change the next 3 lines to suit where you install your script and what you want to call it
DIR=/home/pi/Apps/home-assistant
DAEMON="/home/pi/.pyenv/shims/python3 -m homeassistant"
DAEMON_NAME=homeassistant
# Add any command line options for your daemon here
DAEMON_OPTS=""
# This next line determines what user the script runs as.
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
DAEMON_USER=pi
# The process ID of the script when it runs is stored here:
PIDFILE=/var/run/$DAEMON_NAME.pid
. /lib/lsb/init-functions
do_start () {
log_daemon_msg "Starting system $DAEMON_NAME daemon"
start-stop-daemon --start --background --chdir $DIR --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS
log_end_msg $?
}
do_stop () {
log_daemon_msg "Stopping system $DAEMON_NAME daemon"
start-stop-daemon --stop --pidfile $PIDFILE --retry 10
log_end_msg $?
}
case "$1" in
start|stop)
do_${1}
;;
restart|reload|force-reload)
do_stop
do_start
;;
status)
status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
;;
*)
echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
exit 1
;;
esac
exit 0

View File

@ -1,88 +0,0 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: homeassistant
# Required-Start: $local_fs $network $named $time $syslog
# Required-Stop: $local_fs $network $named $time $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Home\ Assistant
### END INIT INFO
# /etc/init.d Service Script for Home Assistant
# Created with: https://gist.github.com/naholyr/4275302#file-new-service-sh
#
# Installation:
# 1) Populate RUNAS and RUNDIR variables
# 2) Create Log -- sudo touch /var/log/homeassistant.log
# 3) Set Log Ownership -- sudo chown USER:GROUP /var/log/homeassistant.log
# 4) Create PID File -- sudo touch /var/run/homeassistant.pid
# 5) Set PID File Ownership -- sudo chown USER:GROUP /var/run/homeassistant.pid
# 6) Install init.d script -- cp homeassistant.daemon /etc/init.d/homeassistant
# 7) Setup HA for Auto Start -- sudo update-rc.d homeassistant defaults
# 8) Run HA -- sudo service homeassistant start
#
# After installation, HA should start automatically. If HA does not start,
# check the log file output for errors. (/var/log/homeassistant.log)
#
# For this script, it is assumed that you are using a local Virtual Environment
# per the install directions as http://home-assistant.io
# If you are not, the SCRIPT variable must be modified to point to the correct
# Python environment.
SCRIPT="source bin/activate; ./bin/python -m homeassistant"
RUNAS=<USER TO RUN SERVER AS>
RUNDIR=<LOCATION OF home-assistant DIR>
PIDFILE=/var/run/homeassistant.pid
LOGFILE=/var/log/homeassistant.log
start() {
if [ -f /var/run/$PIDNAME ] && kill -0 $(cat /var/run/$PIDNAME); then
echo 'Service already running' >&2
return 1
fi
echo 'Starting service…' >&2
local CMD="cd $RUNDIR; $SCRIPT &> \"$LOGFILE\" & echo \$!"
su -c "$CMD" $RUNAS > "$PIDFILE"
echo 'Service started' >&2
}
stop() {
if [ ! -f "$PIDFILE" ] || ! kill -0 $(cat "$PIDFILE"); then
echo 'Service not running' >&2
return 1
fi
echo 'Stopping service…' >&2
kill -15 $(cat "$PIDFILE") && rm -f "$PIDFILE"
echo 'Service stopped' >&2
}
uninstall() {
echo -n "Are you really sure you want to uninstall this service? That cannot be undone. [yes|No] "
local SURE
read SURE
if [ "$SURE" = "yes" ]; then
stop
rm -f "$PIDFILE"
echo "Notice: log file is not be removed: '$LOGFILE'" >&2
update-rc.d -f homeassistant remove
rm -fv "$0"
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
uninstall)
uninstall
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start|stop|restart|uninstall}"
esac