summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/salt-minion/minion.tmpl10
-rw-r--r--debian/control1
-rw-r--r--debian/vyos-1x.postinst21
-rw-r--r--interface-definitions/salt-minion.xml.in78
-rw-r--r--python/vyos/ifconfig/dhcp.py4
-rwxr-xr-xsrc/conf_mode/salt-minion.py105
-rwxr-xr-xsrc/migration-scripts/salt/0-to-158
-rwxr-xr-xsrc/op_mode/powerctrl.py291
8 files changed, 314 insertions, 254 deletions
diff --git a/data/templates/salt-minion/minion.tmpl b/data/templates/salt-minion/minion.tmpl
index 5e50d588c..9369573a4 100644
--- a/data/templates/salt-minion/minion.tmpl
+++ b/data/templates/salt-minion/minion.tmpl
@@ -12,7 +12,7 @@
#
# Prior to changing this value, the master should be stopped and all Salt
# caches should be cleared.
-hash_type: {{ hash_type }}
+hash_type: {{ hash }}
##### Logging settings #####
##########################################
@@ -21,11 +21,7 @@ hash_type: {{ hash_type }}
# location. Remote logging works best when configured to use rsyslogd(8) (e.g.:
# ``file:///dev/log``), with rsyslogd(8) configured for network logging. The URI
# format is: <file|udp|tcp>://<host|socketpath>:<port-if-required>/<log-facility>
-#log_file: /var/log/salt/minion
-#log_file: file:///dev/log
-#log_file: udp://loghost:10514
-#
-log_file: {{ log_file }}
+log_file: file:///dev/log
# The level of messages to send to the console.
# One of 'garbage', 'trace', 'debug', info', 'warning', 'error', 'critical'.
@@ -58,6 +54,6 @@ id: {{ salt_id }}
# The number of minutes between mine updates.
-mine_interval: {{ mine_interval }}
+mine_interval: {{ interval }}
verify_master_pubkey_sign: {{ verify_master_pubkey_sign }}
diff --git a/debian/control b/debian/control
index 7b95b2c75..5c176f40a 100644
--- a/debian/control
+++ b/debian/control
@@ -89,6 +89,7 @@ Depends: python3,
pmacct (>= 1.6.0),
python3-certbot-nginx,
pppoe,
+ salt-minion,
${shlibs:Depends},
${misc:Depends}
Description: VyOS configuration scripts and data
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
new file mode 100644
index 000000000..a308401ee
--- /dev/null
+++ b/debian/vyos-1x.postinst
@@ -0,0 +1,21 @@
+#!/bin/sh -e
+if ! deb-systemd-helper --quiet was-enabled salt-minion.service; then
+ # Enables the unit on first installation, creates new
+ # symlinks on upgrades if the unit file has changed.
+ deb-systemd-helper disable salt-minion.service >/dev/null || true
+fi
+
+if [ -x "/etc/init.d/salt-minion" ]; then
+ update-rc.d -f salt-minion remove >/dev/null
+fi
+
+# Add minion user for salt-minion
+if ! grep -q '^minion' /etc/passwd; then
+ adduser --quiet --firstuid 100 --system --disabled-login --ingroup vyattacfg --gecos "salt minion user" --shell /bin/vbash minion
+ adduser --quiet minion frrvty
+ adduser --quiet minion sudo
+ adduser --quiet minion adm
+ adduser --quiet minion dip
+ adduser --quiet minion disk
+ adduser --quiet minion users
+fi
diff --git a/interface-definitions/salt-minion.xml.in b/interface-definitions/salt-minion.xml.in
index 9aa60249a..97f882a6a 100644
--- a/interface-definitions/salt-minion.xml.in
+++ b/interface-definitions/salt-minion.xml.in
@@ -1,5 +1,4 @@
<?xml version="1.0"?>
-<!--Salt-minion configuration -->
<interfaceDefinition>
<node name="service">
<children>
@@ -9,73 +8,56 @@
<priority>500</priority>
</properties>
<children>
- <leafNode name="hash_type">
+ <leafNode name="hash">
<properties>
- <help>The hash_type is the hash to use when discovering the hash of a file on the master server.</help>
+ <help>Hash used when discovering file on master server (default: sha256)</help>
+ <completionHelp>
+ <list>md5 sha1 sha224 sha256 sha384 sha512</list>
+ </completionHelp>
+ <constraint>
+ <regex>(md5|sha1|sha224|sha256|sha384|sha512)</regex>
+ </constraint>
</properties>
</leafNode>
- <leafNode name="log_file">
- <properties>
- <help>The location of the minion log file.</help>
- </properties>
- </leafNode>
- <leafNode name="log_level">
+ <leafNode name="master">
<properties>
- <help>Log level</help>
- <valueHelp>
- <format>garbage</format>
- <description>log garbage info</description>
- </valueHelp>
- <valueHelp>
- <format>trace</format>
- <description>log trace info</description>
- </valueHelp>
- <valueHelp>
- <format>debug</format>
- <description>log debug info</description>
- </valueHelp>
- <valueHelp>
- <format>info</format>
- <description>log info</description>
- </valueHelp>
- <valueHelp>
- <format>warning</format>
- <description>log warning info</description>
- </valueHelp>
+ <help>The hostname or IP address of the master.</help>
<valueHelp>
- <format>error</format>
- <description>log error info</description>
+ <format>ipv4</format>
+ <description>Remote syslog server IPv4 address</description>
</valueHelp>
<valueHelp>
- <format>critical</format>
- <description>log critical info</description>
+ <format>hostname</format>
+ <description>Remote syslog server FQDN</description>
</valueHelp>
- </properties>
- </leafNode>
- <leafNode name="master">
- <properties>
- <help>The hostname or IP address of the master.</help>
+ <constraint>
+ <validator name="ip-address"/>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Invalid FQDN or IP address</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
<leafNode name="id">
<properties>
- <help>Explicitly declare the id for this minion to use.</help>
- </properties>
- </leafNode>
- <leafNode name="user">
- <properties>
- <help>The user to run the Salt processes.</help>
+ <help>Explicitly declare ID for this minion to use (default: hostname)</help>
</properties>
</leafNode>
- <leafNode name="mine_interval">
+ <leafNode name="interval">
<properties>
- <help>The number of minutes between mine updates.</help>
+ <help>Interval in minutes between updates (default: 60)</help>
+ <valueHelp>
+ <format>&lt;1-1440&gt;</format>
+ <description>Update interval in minutes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-1440"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="master-key">
<properties>
- <help>Enables verification of the master-public-signature returned by the master in auth-replies.</help>
+ <help>URL with signature of master for auth reply verification</help>
</properties>
</leafNode>
</children>
diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py
index d4ff9c2cd..3122147a3 100644
--- a/python/vyos/ifconfig/dhcp.py
+++ b/python/vyos/ifconfig/dhcp.py
@@ -187,14 +187,14 @@ class _DHCPv6 (_DHCP):
# stop dhclient
cmd = 'start-stop-daemon'
- cmd += ' --start'
+ cmd += ' --stop'
cmd += ' --oknodo'
cmd += ' --quiet'
cmd += ' --pidfile {pid}'
self._cmd(cmd.format(**self.file))
# accept router announcements on this interface
- self._write_sysfs(self.options['accept_ra'], 1)
+ self._write_sysfs(self.file['accept_ra'], 1)
# cleanup old config files
for name in ('conf', 'pid', 'lease'):
diff --git a/src/conf_mode/salt-minion.py b/src/conf_mode/salt-minion.py
index 236480854..dffe7fcd4 100755
--- a/src/conf_mode/salt-minion.py
+++ b/src/conf_mode/salt-minion.py
@@ -17,117 +17,108 @@
import os
from copy import deepcopy
-from pwd import getpwnam
from socket import gethostname
from sys import exit
from urllib3 import PoolManager
from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import call
from vyos.template import render
-
+from vyos.util import call, chown
+from vyos import ConfigError
config_file = r'/etc/salt/minion'
+master_keyfile = r'/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub'
default_config_data = {
- 'hash_type': 'sha256',
- 'log_file': '/var/log/salt/minion',
+ 'hash': 'sha256',
'log_level': 'warning',
'master' : 'salt',
'user': 'minion',
+ 'group': 'vyattacfg',
'salt_id': gethostname(),
'mine_interval': '60',
- 'verify_master_pubkey_sign': 'false'
+ 'verify_master_pubkey_sign': 'false',
+ 'master_key': ''
}
def get_config():
salt = deepcopy(default_config_data)
conf = Config()
- if not conf.exists('service salt-minion'):
+ base = ['service', 'salt-minion']
+
+ if not conf.exists(base):
return None
else:
- conf.set_level('service salt-minion')
-
- if conf.exists('hash_type'):
- salt['hash_type'] = conf.return_value('hash_type')
+ conf.set_level(base)
- if conf.exists('log_file'):
- salt['log_file'] = conf.return_value('log_file')
+ if conf.exists(['hash']):
+ salt['hash'] = conf.return_value(['hash'])
- if conf.exists('log_level'):
- salt['log_level'] = conf.return_value('log_level')
+ if conf.exists(['master']):
+ salt['master'] = conf.return_values(['master'])
- if conf.exists('master'):
- master = conf.return_values('master')
- salt['master'] = master
+ if conf.exists(['id']):
+ salt['salt_id'] = conf.return_value(['id'])
- if conf.exists('id'):
- salt['salt_id'] = conf.return_value('id')
+ if conf.exists(['user']):
+ salt['user'] = conf.return_value(['user'])
- if conf.exists('user'):
- salt['user'] = conf.return_value('user')
+ if conf.exists(['interval']):
+ salt['interval'] = conf.return_value(['interval'])
- if conf.exists('mine_interval'):
- salt['mine_interval'] = conf.return_value('mine_interval')
-
- salt['master-key'] = None
- if conf.exists('master-key'):
- salt['master-key'] = conf.return_value('master-key')
+ if conf.exists(['master-key']):
+ salt['master_key'] = conf.return_value(['master-key'])
salt['verify_master_pubkey_sign'] = 'true'
return salt
-def generate(salt):
- paths = ['/etc/salt/','/var/run/salt','/opt/vyatta/etc/config/salt/']
- directory = '/opt/vyatta/etc/config/salt/pki/minion'
- uid = getpwnam(salt['user']).pw_uid
- http = PoolManager()
+def verify(salt):
+ return None
- if salt is None:
+def generate(salt):
+ if not salt:
return None
- if not os.path.exists(directory):
- os.makedirs(directory)
+ for file in [config_file, master_keyfile]:
+ dirname = os.path.dirname(file)
+ if not os.path.exists(dirname):
+ os.mkdir(dirname)
+ chown(dirname, salt['user'], salt['group'])
render(config_file, 'salt-minion/minion.tmpl', salt)
+ chown(config_file, salt['user'], salt['group'])
- path = "/etc/salt/"
- for path in paths:
- for root, dirs, files in os.walk(path):
- for usgr in dirs:
- os.chown(os.path.join(root, usgr), uid, 100)
- for usgr in files:
- os.chown(os.path.join(root, usgr), uid, 100)
-
- if not os.path.exists('/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub'):
- if not salt['master-key'] is None:
- r = http.request('GET', salt['master-key'], preload_content=False)
+ if not os.path.exists(master_keyfile):
+ if salt['master_key']:
+ req = PoolManager().request('GET', salt['master_key'], preload_content=False)
- with open('/opt/vyatta/etc/config/salt/pki/minion/master_sign.pub', 'wb') as out:
+ with open(master_keyfile, 'wb') as f:
while True:
- data = r.read(1024)
+ data = req.read(1024)
if not data:
break
- out.write(data)
+ f.write(data)
- r.release_conn()
+ req.release_conn()
+ chown(master_keyfile, salt['user'], salt['group'])
return None
def apply(salt):
- if salt is not None:
- call("sudo systemctl restart salt-minion")
+ if not salt:
+ # Salt removed from running config
+ call('systemctl stop salt-minion.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
else:
- # Salt access is removed in the commit
- call("sudo systemctl stop salt-minion")
- os.unlink(config_file)
+ call('systemctl restart salt-minion.service')
return None
if __name__ == '__main__':
try:
c = get_config()
+ verify(c)
generate(c)
apply(c)
except ConfigError as e:
diff --git a/src/migration-scripts/salt/0-to-1 b/src/migration-scripts/salt/0-to-1
new file mode 100755
index 000000000..79053c056
--- /dev/null
+++ b/src/migration-scripts/salt/0-to-1
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+# Delete log_file, log_level and user nodes
+# rename hash_type to hash
+# rename mine_interval to interval
+
+from sys import argv,exit
+
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base = ['service', 'salt-minion']
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+else:
+
+ # delete nodes which are now populated with sane defaults
+ for node in ['log_file', 'log_level', 'user']:
+ if config.exists(base + [node]):
+ config.delete(base + [node])
+
+ if config.exists(base + ['hash_type']):
+ config.rename(base + ['hash_type'], 'hash')
+
+ if config.exists(base + ['mine_interval']):
+ config.rename(base + ['mine_interval'], 'interval')
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/op_mode/powerctrl.py b/src/op_mode/powerctrl.py
index 4ab91384b..b2fb5fe5a 100755
--- a/src/op_mode/powerctrl.py
+++ b/src/op_mode/powerctrl.py
@@ -15,168 +15,179 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import sys
-import argparse
import re
+from argparse import ArgumentParser
from datetime import datetime, timedelta, time as type_time, date as type_date
-from vyos.util import ask_yes_no
-from vyos.util import cmd
-from vyos.util import call
-from vyos.util import run
-from vyos.util import STDOUT
+from sys import exit
+from time import time
+
+from vyos.util import ask_yes_no, cmd, call, run, STDOUT
systemd_sched_file = "/run/systemd/shutdown/scheduled"
-def parse_time(s):
- try:
- if re.match(r'^\d{1,2}$', s):
- return datetime.strptime(s, "%M").time()
- else:
- return datetime.strptime(s, "%H:%M").time()
- except ValueError:
- return None
+def utc2local(datetime):
+ now = time()
+ offs = datetime.fromtimestamp(now) - datetime.utcfromtimestamp(now)
+ return datetime + offs
-def parse_date(s):
- for fmt in ["%d%m%Y", "%d/%m/%Y", "%d.%m.%Y", "%d:%m:%Y", "%Y-%m-%d"]:
+def parse_time(s):
try:
- return datetime.strptime(s, fmt).date()
+ if re.match(r'^\d{1,2}$', s):
+ return datetime.strptime(s, "%M").time()
+ else:
+ return datetime.strptime(s, "%H:%M").time()
except ValueError:
- continue
- # If nothing matched...
- return None
+ return None
+
+
+def parse_date(s):
+ for fmt in ["%d%m%Y", "%d/%m/%Y", "%d.%m.%Y", "%d:%m:%Y", "%Y-%m-%d"]:
+ try:
+ return datetime.strptime(s, fmt).date()
+ except ValueError:
+ continue
+ # If nothing matched...
+ return None
+
def get_shutdown_status():
- if os.path.exists(systemd_sched_file):
- # Get scheduled from systemd file
- with open(systemd_sched_file, 'r') as f:
- data = f.read().rstrip('\n')
- r_data = {}
- for line in data.splitlines():
- tmp_split = line.split("=")
- if tmp_split[0] == "USEC":
- # Convert USEC to human readable format
- r_data['DATETIME'] = datetime.utcfromtimestamp(int(tmp_split[1])/1000000).strftime('%Y-%m-%d %H:%M:%S')
- else:
- r_data[tmp_split[0]] = tmp_split[1]
- return r_data
- return None
+ if os.path.exists(systemd_sched_file):
+ # Get scheduled from systemd file
+ with open(systemd_sched_file, 'r') as f:
+ data = f.read().rstrip('\n')
+ r_data = {}
+ for line in data.splitlines():
+ tmp_split = line.split("=")
+ if tmp_split[0] == "USEC":
+ # Convert USEC to human readable format
+ r_data['DATETIME'] = datetime.utcfromtimestamp(
+ int(tmp_split[1])/1000000).strftime('%Y-%m-%d %H:%M:%S')
+ else:
+ r_data[tmp_split[0]] = tmp_split[1]
+ return r_data
+ return None
+
def check_shutdown():
- output = get_shutdown_status()
- if output and 'MODE' in output:
- if output['MODE'] == 'reboot':
- print("Reboot is scheduled", output['DATETIME'])
- elif output['MODE'] == 'poweroff':
- print("Poweroff is scheduled", output['DATETIME'])
- else:
- print("Reboot or poweroff is not scheduled")
+ output = get_shutdown_status()
+ dt = datetime.strptime(output['DATETIME'], '%Y-%m-%d %H:%M:%S')
+ if output and 'MODE' in output:
+ if output['MODE'] == 'reboot':
+ print("Reboot is scheduled", utc2local(dt))
+ elif output['MODE'] == 'poweroff':
+ print("Poweroff is scheduled", utc2local(dt))
+ else:
+ print("Reboot or poweroff is not scheduled")
+
def cancel_shutdown():
- output = get_shutdown_status()
- if output and 'MODE' in output:
- timenow = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- try:
- cmd('/sbin/shutdown -c --no-wall')
- except OSError as e:
- sys.exit("Could not cancel a reboot or poweroff: %s" % e)
- message = "Scheduled %s has been cancelled %s" % (output['MODE'], timenow)
- run(f'wall {message}')
- else:
- print("Reboot or poweroff is not scheduled")
-
-def execute_shutdown(time, reboot = True, ask=True):
- if not ask:
- action = "reboot" if reboot else "poweroff"
- if not ask_yes_no("Are you sure you want to %s this system?" % action):
- sys.exit(0)
-
- action = "-r" if reboot else "-P"
-
- if len(time) == 0:
- ### T870 legacy reboot job support
- chk_vyatta_based_reboots()
- ###
-
- out = cmd(f'/sbin/shutdown {action} now', stderr=STDOUT)
- print(out.split(",",1)[0])
- return
- elif len(time) == 1:
- # Assume the argument is just time
- ts = parse_time(time[0])
- if ts:
- cmd(f'/sbin/shutdown {action} {time[0]}', stderr=STDOUT)
+ output = get_shutdown_status()
+ if output and 'MODE' in output:
+ timenow = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+ try:
+ run('/sbin/shutdown -c --no-wall')
+ except OSError as e:
+ exit("Could not cancel a reboot or poweroff: %s" % e)
+
+ message = 'Scheduled {} has been cancelled {}'.format(output['MODE'], timenow)
+ run(f'wall {message} > /dev/null 2>&1')
else:
- sys.exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
- elif len(time) == 2:
- # Assume it's date and time
- ts = parse_time(time[0])
- ds = parse_date(time[1])
- if ts and ds:
- t = datetime.combine(ds, ts)
- td = t - datetime.now()
- t2 = 1 + int(td.total_seconds())//60 # Get total minutes
- cmd('/sbin/shutdown {action} {t2}', stderr=STDOUT)
+ print("Reboot or poweroff is not scheduled")
+
+
+def execute_shutdown(time, reboot=True, ask=True):
+ if not ask:
+ action = "reboot" if reboot else "poweroff"
+ if not ask_yes_no("Are you sure you want to %s this system?" % action):
+ exit(0)
+
+ action = "-r" if reboot else "-P"
+
+ if len(time) == 0:
+ # T870 legacy reboot job support
+ chk_vyatta_based_reboots()
+ ###
+
+ out = cmd(f'/sbin/shutdown {action} now', stderr=STDOUT)
+ print(out.split(",", 1)[0])
+ return
+ elif len(time) == 1:
+ # Assume the argument is just time
+ ts = parse_time(time[0])
+ if ts:
+ cmd(f'/sbin/shutdown {action} {time[0]}', stderr=STDOUT)
+ else:
+ exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
+ elif len(time) == 2:
+ # Assume it's date and time
+ ts = parse_time(time[0])
+ ds = parse_date(time[1])
+ if ts and ds:
+ t = datetime.combine(ds, ts)
+ td = t - datetime.now()
+ t2 = 1 + int(td.total_seconds())//60 # Get total minutes
+ cmd('/sbin/shutdown {action} {t2}', stderr=STDOUT)
+ else:
+ if not ts:
+ exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
+ else:
+ exit("Invalid time \"{0}\". A valid format is YYYY-MM-DD [HH:MM]".format(time[1]))
else:
- if not ts:
- sys.exit("Invalid time \"{0}\". The valid format is HH:MM".format(time[0]))
- else:
- sys.exit("Invalid time \"{0}\". A valid format is YYYY-MM-DD [HH:MM]".format(time[1]))
- else:
- sys.exit("Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM")
- check_shutdown()
+ exit("Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM")
+ check_shutdown()
+
def chk_vyatta_based_reboots():
- ### T870 commit-confirm is still using the vyatta code base, once gone, the code below can be removed
- ### legacy scheduled reboot s are using at and store the is as /var/run/<name>.job
- ### name is the node of scheduled the job, commit-confirm checks for that
+ # T870 commit-confirm is still using the vyatta code base, once gone, the code below can be removed
+ # legacy scheduled reboot s are using at and store the is as /var/run/<name>.job
+ # name is the node of scheduled the job, commit-confirm checks for that
+
+ f = r'/var/run/confirm.job'
+ if os.path.exists(f):
+ jid = open(f).read().strip()
+ if jid != 0:
+ call(f'sudo atrm {jid}')
+ os.remove(f)
- f = r'/var/run/confirm.job'
- if os.path.exists(f):
- jid = open(f).read().strip()
- if jid != 0:
- call(f'sudo atrm {jid}')
- os.remove(f)
def main():
- parser = argparse.ArgumentParser()
- parser.add_argument("--yes", "-y",
- help="Do not ask for confirmation",
- action="store_true",
- dest="yes")
- action = parser.add_mutually_exclusive_group(required=True)
- action.add_argument("--reboot", "-r",
- help="Reboot the system",
- nargs="*",
- metavar="Minutes|HH:MM")
-
- action.add_argument("--poweroff", "-p",
- help="Poweroff the system",
- nargs="*",
- metavar="Minutes|HH:MM")
-
- action.add_argument("--cancel", "-c",
- help="Cancel pending shutdown",
- action="store_true")
-
- action.add_argument("--check",
- help="Check pending chutdown",
- action="store_true")
- args = parser.parse_args()
-
- try:
- if args.reboot is not None:
- execute_shutdown(args.reboot, reboot=True, ask=args.yes)
- if args.poweroff is not None:
- execute_shutdown(args.poweroff, reboot=False,ask=args.yes)
- if args.cancel:
- cancel_shutdown()
- if args.check:
- check_shutdown()
- except KeyboardInterrupt:
- sys.exit("Interrupted")
+ parser = ArgumentParser()
+ parser.add_argument("--yes", "-y",
+ help="Do not ask for confirmation",
+ action="store_true",
+ dest="yes")
+ action = parser.add_mutually_exclusive_group(required=True)
+ action.add_argument("--reboot", "-r",
+ help="Reboot the system",
+ nargs="*",
+ metavar="Minutes|HH:MM")
+
+ action.add_argument("--poweroff", "-p",
+ help="Poweroff the system",
+ nargs="*",
+ metavar="Minutes|HH:MM")
+
+ action.add_argument("--cancel", "-c",
+ help="Cancel pending shutdown",
+ action="store_true")
+
+ action.add_argument("--check",
+ help="Check pending chutdown",
+ action="store_true")
+ args = parser.parse_args()
+ try:
+ if args.reboot is not None:
+ execute_shutdown(args.reboot, reboot=True, ask=args.yes)
+ if args.poweroff is not None:
+ execute_shutdown(args.poweroff, reboot=False, ask=args.yes)
+ if args.cancel:
+ cancel_shutdown()
+ if args.check:
+ check_shutdown()
+ except KeyboardInterrupt:
+ exit("Interrupted")
if __name__ == "__main__":
- main()
-
+ main()