diff options
| -rw-r--r-- | op-mode-definitions/bandwidth-test.xml | 29 | ||||
| -rw-r--r-- | op-mode-definitions/disks.xml | 50 | ||||
| -rw-r--r-- | op-mode-definitions/generate-ssh-server-key.xml | 16 | ||||
| -rw-r--r-- | op-mode-definitions/show-disk.xml | 23 | ||||
| -rw-r--r-- | op-mode-definitions/show-history.xml | 31 | ||||
| -rw-r--r-- | op-mode-definitions/show-host.xml | 12 | ||||
| -rw-r--r-- | op-mode-definitions/telnet.xml | 8 | ||||
| -rw-r--r-- | op-mode-definitions/terminal.xml | 93 | ||||
| -rw-r--r-- | python/vyos/util.py | 8 | ||||
| -rwxr-xr-x | src/completion/list_disks.py | 36 | ||||
| -rwxr-xr-x | src/completion/list_disks.sh | 5 | ||||
| -rwxr-xr-x | src/helpers/vyos-sudo.py | 9 | ||||
| -rwxr-xr-x | src/op_mode/format_disk.py | 148 | ||||
| -rwxr-xr-x | src/op_mode/generate_ssh_server_key.py | 27 | ||||
| -rwxr-xr-x | src/op_mode/toggle_help_binding.sh | 25 | 
15 files changed, 480 insertions, 40 deletions
| diff --git a/op-mode-definitions/bandwidth-test.xml b/op-mode-definitions/bandwidth-test.xml new file mode 100644 index 000000000..d1e459b17 --- /dev/null +++ b/op-mode-definitions/bandwidth-test.xml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<interfaceDefinition> +  <node name="monitor"> +    <children> +      <node name="bandwidth-test"> +        <properties> +          <help>Initiate or wait for bandwidth test</help> +        </properties> +        <children> +          <leafNode name="accept"> +            <properties> +              <help>Wait for bandwidth test connections (port TCP/5001)</help> +            </properties> +            <command>iperf -s</command> +          </leafNode> +          <tagNode name="initiate"> +            <properties> +              <help>Initiate a bandwidth test to specified host (port TCP/5001)</help> +              <completionHelp> +                <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> +              </completionHelp> +            </properties> +            <command>iperf -c $4</command> +          </tagNode> +        </children> +      </node> +    </children> +  </node> +</interfaceDefinition> diff --git a/op-mode-definitions/disks.xml b/op-mode-definitions/disks.xml new file mode 100644 index 000000000..fb39c4f3c --- /dev/null +++ b/op-mode-definitions/disks.xml @@ -0,0 +1,50 @@ +<?xml version="1.0"?> +<interfaceDefinition> +  <node name="format"> +    <properties> +      <help>Format a device</help> +    </properties> +    <children> +      <tagNode name="disk"> +        <properties> +          <help>Format a disk drive</help> +          <completionHelp> +            <script>${vyos_completion_dir}/list_disks.py</script> +          </completionHelp> +        </properties> +        <children> +          <tagNode name="like"> +            <properties> +              <help>Format this disk the same as another disk</help> +              <completionHelp> +                <script>${vyos_completion_dir}/list_disks.py --exclude ${COMP_WORDS[2]}</script> +              </completionHelp> +            </properties> +            <command>${vyos_op_scripts_dir}/format_disk.py --target $3 --proto $5</command> +          </tagNode> +        </children> +      </tagNode> +    </children> +  </node> + +  <node name="show"> +    <children> +      <tagNode name="disk"> +        <properties> +          <help>Show status of disk device</help> +          <completionHelp> +            <script>${vyos_completion_dir}/list_disks.py</script> +          </completionHelp> +        </properties> +        <children> +          <leafNode name="format"> +            <properties> +              <help>Show disk drive formatting</help> +            </properties> +            <command>${vyos_op_scripts_dir}/show_disk_format.sh $3</command> +          </leafNode> +        </children> +      </tagNode> +    </children> +  </node> +</interfaceDefinition> diff --git a/op-mode-definitions/generate-ssh-server-key.xml b/op-mode-definitions/generate-ssh-server-key.xml new file mode 100644 index 000000000..a6ebf1b78 --- /dev/null +++ b/op-mode-definitions/generate-ssh-server-key.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<interfaceDefinition> +  <node name="generate"> +    <properties> +      <help>Generate an object</help> +    </properties> +    <children> +      <node name="ssh-server-key"> +        <properties> +          <help>Regenerate the host SSH keys and restart the SSH server</help> +        </properties> +        <command>${vyos_op_scripts_dir}/generate_ssh_server_key.py</command> +      </node> +    </children> +  </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-disk.xml b/op-mode-definitions/show-disk.xml deleted file mode 100644 index 37da07fbe..000000000 --- a/op-mode-definitions/show-disk.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0"?> -<interfaceDefinition> -  <node name="show"> -    <children> -      <tagNode name="disk"> -        <properties> -          <help>Show status of disk device</help> -          <completionHelp> -            <script>${vyos_completion_dir}/list_disks.sh</script> -          </completionHelp> -        </properties> -        <children> -          <leafNode name="format"> -            <properties> -              <help>Show disk drive formatting</help> -            </properties> -            <command>${vyos_op_scripts_dir}/show_disk_format.sh $3</command> -          </leafNode> -        </children> -      </tagNode> -    </children> -  </node> -</interfaceDefinition> diff --git a/op-mode-definitions/show-history.xml b/op-mode-definitions/show-history.xml new file mode 100644 index 000000000..7fb286264 --- /dev/null +++ b/op-mode-definitions/show-history.xml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<interfaceDefinition> +  <node name="show"> +    <children> +      <node name="history"> +        <properties> +          <help>Show command history</help> +        </properties> +        <command>HISTTIMEFORMAT='%FT%T%z ' HISTFILE="$HOME/.bash_history" \set -o history; history</command> +        <children> +          <leafNode name="brief"> +            <properties> +              <help>Show recent command history</help> +            </properties> +            <command>HISTTIMEFORMAT='%FT%T%z ' HISTFILE="$HOME/.bash_history" \set -o history; history 20</command> +          </leafNode> +        </children> +      </node> + +      <tagNode name="history"> +        <properties> +          <help>Show last N commands in history</help> +          <completionHelp> +            <list><NUMBER></list> +          </completionHelp> +        </properties> +        <command>HISTTIMEFORMAT='%FT%T%z ' HISTFILE="$HOME/.bash_history" \set -o history; history $3</command> +      </tagNode> +    </children> +  </node> +</interfaceDefinition> diff --git a/op-mode-definitions/show-host.xml b/op-mode-definitions/show-host.xml index d7f8104aa..eee1288a1 100644 --- a/op-mode-definitions/show-host.xml +++ b/op-mode-definitions/show-host.xml @@ -7,6 +7,12 @@            <help>Show host information</help>          </properties>          <children> +          <leafNode name="date"> +            <properties> +              <help>Show host current date</help> +            </properties> +            <command>/bin/date</command> +          </leafNode>            <leafNode name="domain">              <properties>                <help>Show domain name</help> @@ -25,6 +31,12 @@              </properties>              <command>/usr/bin/host $4</command>            </tagNode> +          <leafNode name="os"> +            <properties> +              <help>Show host operating system details</help> +            </properties> +            <command>/bin/uname -a</command> +          </leafNode>          </children>        </node>      </children> diff --git a/op-mode-definitions/telnet.xml b/op-mode-definitions/telnet.xml index 7ec75be88..c5bb6d283 100644 --- a/op-mode-definitions/telnet.xml +++ b/op-mode-definitions/telnet.xml @@ -5,23 +5,23 @@        <help>Telnet to a node</help>      </properties>      <children> -      <tagNode name=""> +      <tagNode name="to">          <properties>            <help>Telnet to a host</help>            <completionHelp>              <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list>            </completionHelp>          </properties> -        <command>/usr/bin/telnet $2</command> +        <command>/usr/bin/telnet $3</command>          <children> -          <tagNode name=""> +          <tagNode name="port">              <properties>                <help>Telnet to a host:port</help>                <completionHelp>                  <list><0-65535></list>                </completionHelp>              </properties> -            <command>/usr/bin/telnet $2 $3</command> +            <command>/usr/bin/telnet $3 $5</command>            </tagNode>          </children>        </tagNode> diff --git a/op-mode-definitions/terminal.xml b/op-mode-definitions/terminal.xml index db74f867e..9c4e629cb 100644 --- a/op-mode-definitions/terminal.xml +++ b/op-mode-definitions/terminal.xml @@ -26,4 +26,97 @@        </node>      </children>    </node> +  <node name="set"> +    <properties> +      <help>Set operational options</help> +    </properties> +    <children> +      <tagNode name="builtin"> +        <properties> +          <help>Bash builtin set command</help> +          <completionHelp> +            <list><OPTION></list> +          </completionHelp> +        </properties> +        <command>builtin $3</command> +      </tagNode> + +      <node name="console"> +        <properties> +          <help>Control console behaviors</help> +        </properties> +        <children> +          <leafNode name="keymap"> +            <properties> +              <help>Reconfigure console keyboard layout</help> +            </properties> +            <command>sudo dpkg-reconfigure -f dialog keyboard-configuration && sudo systemctl restart keyboard-setup</command> +          </leafNode> +        </children> +      </node> + +      <node name="terminal"> +        <properties> +          <help>Control terminal behaviors</help> +        </properties> +        <children> + +          <node name="key"> +            <properties> +              <help>Set key behaviors</help> +            </properties> +            <children> +              <tagNode name="query-help"> +                <properties> +                  <help>Enable/disable getting help using question mark (default enabled)</help> +                  <completionHelp> +                    <list>enable disable</list> +                  </completionHelp> +                </properties> +                <command>${vyos_op_scripts_dir}/toggle_help_binding.sh $5</command> +              </tagNode> +            </children> +          </node> + +          <node name="pager"> +            <properties> +              <help>Set terminal pager to default (less)</help> +            </properties> +            <command>VYATTA_PAGER=${_vyatta_default_pager}</command> +          </node> +          <tagNode name="pager"> +            <properties> +              <help>Set terminal pager</help> +              <completionHelp> +                <list><PROGRAM></list> +              </completionHelp> +            </properties> +            <command>VYATTA_PAGER=$4</command> +          </tagNode> + +          <tagNode name="length"> +            <properties> +              <help>Set terminal to given number of rows (0 disables paging)</help> +              <completionHelp> +                <list><NUMBER></list> +              </completionHelp> +            </properties> +            <command>if [ "$4" -eq 0 ]; then VYATTA_PAGER=cat; else VYATTA_PAGER=${_vyatta_default_pager}; stty rows $4; fi</command> +          </tagNode> + +          <tagNode name="width"> +            <properties> +              <help>Set terminal to given number of columns</help> +              <completionHelp> +                <list><NUMBER></list> +              </completionHelp> +            </properties> +            <command>stty columns $4</command> +          </tagNode> +        </children> +      </node> +    </children> +  </node> + +  </interfaceDefinition> diff --git a/python/vyos/util.py b/python/vyos/util.py index 88bb0c8f4..67a602f7a 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -15,6 +15,7 @@  import os  import re +import getpass  import grp  import time  import subprocess @@ -191,3 +192,10 @@ def ask_yes_no(question, default=False) -> bool:              return False          else:              sys.stdout.write("Please respond with yes/y or no/n\n") + + +def is_admin() -> bool: +    """Look if current user is in sudo group""" +    current_user = getpass.getuser() +    (_, _, _, admin_group_members) = grp.getgrnam('sudo') +    return current_user in admin_group_members diff --git a/src/completion/list_disks.py b/src/completion/list_disks.py new file mode 100755 index 000000000..ff1135e23 --- /dev/null +++ b/src/completion/list_disks.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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/>. + +# Completion script used by show disks to collect physical disk + +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("-e", "--exclude", type=str, help="Exclude specified device from the result list") +args = parser.parse_args() + +disks = set() +with open('/proc/partitions') as partitions_file: +    for line in partitions_file: +        fields = line.strip().split() +        if len(fields) == 4 and fields[3].isalpha() and fields[3] != 'name': +            disks.add(fields[3]) + +if args.exclude: +    disks.remove(args.exclude) + +for disk in disks: +    print(disk) diff --git a/src/completion/list_disks.sh b/src/completion/list_disks.sh deleted file mode 100755 index f32e558fd..000000000 --- a/src/completion/list_disks.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Completion script used by show disks to collect physical disk - -awk 'NR > 2 && $4 !~ /[0-9]$/ { print $4 }' </proc/partitions diff --git a/src/helpers/vyos-sudo.py b/src/helpers/vyos-sudo.py index 0101a0c95..3e4c196d9 100755 --- a/src/helpers/vyos-sudo.py +++ b/src/helpers/vyos-sudo.py @@ -15,17 +15,10 @@  # You should have received a copy of the GNU Lesser General Public  # License along with this library.  If not, see <http://www.gnu.org/licenses/>. -import getpass -import grp  import os  import sys - -def is_admin() -> bool: -    """Look if current user is in sudo group""" -    current_user = getpass.getuser() -    (_, _, _, admin_group_members) = grp.getgrnam('sudo') -    return current_user in admin_group_members +from vyos.util import is_admin  if __name__ == '__main__': diff --git a/src/op_mode/format_disk.py b/src/op_mode/format_disk.py new file mode 100755 index 000000000..5a3b250ee --- /dev/null +++ b/src/op_mode/format_disk.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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/>. + +import argparse +import os +import re +import subprocess +import sys +from datetime import datetime +from time import sleep + +from vyos.util import is_admin, ask_yes_no + + +def list_disks(): +    disks = set() +    with open('/proc/partitions') as partitions_file: +        for line in partitions_file: +            fields = line.strip().split() +            if len(fields) == 4 and fields[3].isalpha() and fields[3] != 'name': +                disks.add(fields[3]) +    return disks + + +def is_busy(disk: str): +    """Check if given disk device is busy by re-reading it's partition table""" + +    cmd = 'sudo blockdev --rereadpt /dev/{}'.format(disk) +    status = subprocess.call([cmd], shell=True, stderr=subprocess.DEVNULL) +    return status != 0 + + +def backup_partitions(disk: str): +    """Save sfdisk partitions output to a backup file""" + +    device_path = '/dev/' + disk +    backup_ts = datetime.now().strftime('%Y-%m-%d-%H:%M') +    backup_file = '/var/tmp/backup_{}.{}'.format(disk, backup_ts) +    cmd = 'sudo /sbin/sfdisk -d {} > {}'.format(device_path, backup_file) +    subprocess.check_call([cmd], shell=True) + + +def list_partitions(disk: str): +    """List partition numbers of a given disk""" + +    parts = set() +    part_num_expr = re.compile(disk + '([0-9]+)') +    with open('/proc/partitions') as partitions_file: +        for line in partitions_file: +            fields = line.strip().split() +            if len(fields) == 4 and fields[3] != 'name' and part_num_expr.match(fields[3]): +                part_idx = part_num_expr.match(fields[3]).group(1) +                parts.add(int(part_idx)) +    return parts + + +def delete_partition(disk: str, partition_idx: int): +    cmd = 'sudo /sbin/parted /dev/{} rm {}'.format(disk, partition_idx) +    subprocess.check_call([cmd], shell=True) + + +def format_disk_like(target: str, proto: str): +    cmd = 'sudo /sbin/sfdisk -d /dev/{} | sudo /sbin/sfdisk --force /dev/{}'.format(proto, target) +    subprocess.check_call([cmd], shell=True) + + +if __name__ == '__main__': +    parser = argparse.ArgumentParser() +    group = parser.add_argument_group() +    group.add_argument('-t', '--target', type=str, required=True, help='Target device to format') +    group.add_argument('-p', '--proto', type=str, required=True, help='Prototype device to use as reference') +    args = parser.parse_args() + +    if not is_admin(): +        print('Must be admin or root to format disk') +        sys.exit(1) + +    target_disk = args.target +    eligible_target_disks = list_disks() + +    proto_disk = args.proto +    eligible_proto_disks = eligible_target_disks.copy() +    eligible_proto_disks.remove(target_disk) + +    fmt = { +        'target_disk': target_disk, +        'proto_disk': proto_disk, +    } + +    if proto_disk == target_disk: +        print('The two disk drives must be different.') +        sys.exit(1) + +    if not os.path.exists('/dev/' + proto_disk): +        print('Device /dev/{proto_disk} does not exist'.format_map(fmt)) +        sys.exit(1) + +    if not os.path.exists('/dev/' + target_disk): +        print('Device /dev/{target_disk} does not exist'.format_map(fmt)) +        sys.exit(1) + +    if target_disk not in eligible_target_disks: +        print('Device {target_disk} can not be formatted'.format_map(fmt)) +        sys.exit(1) + +    if proto_disk not in eligible_proto_disks: +        print('Device {proto_disk} can not be used as a prototype for {target_disk}'.format_map(fmt)) +        sys.exit(1) + +    if is_busy(target_disk): +        print("Disk device {target_disk} is busy. Can't format it now".format_map(fmt)) +        sys.exit(1) + +    print('This will re-format disk {target_disk} so that it has the same disk\n' +          'partion sizes and offsets as {proto_disk}. This will not copy\n' +          'data from {proto_disk} to {target_disk}. But this will erase all\n' +          'data on {target_disk}.\n'.format_map(fmt)) + +    if not ask_yes_no("Do you wish to proceed?"): +        print('OK. Disk drive {target_disk} will not be re-formated'.format_map(fmt)) +        sys.exit(0) + +    print('OK. Re-formating disk drive {target_disk}...'.format_map(fmt)) + +    print('Making backup copy of partitions...') +    backup_partitions(target_disk) +    sleep(1) + +    print('Deleting old partitions...') +    for p in list_partitions(target_disk): +        delete_partition(disk=target_disk, partition_idx=p) + +    print('Creating new partitions on {target_disk} based on {proto_disk}...'.format_map(fmt)) +    format_disk_like(target=target_disk, proto=proto_disk) +    print('Done.') diff --git a/src/op_mode/generate_ssh_server_key.py b/src/op_mode/generate_ssh_server_key.py new file mode 100755 index 000000000..f205919b8 --- /dev/null +++ b/src/op_mode/generate_ssh_server_key.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 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/>. + +import subprocess +import sys + +from vyos.util import ask_yes_no + +if not ask_yes_no('Do you really want to remove the existing SSH host keys?'): +    sys.exit(0) +else: +    subprocess.check_call(['sudo rm -v /etc/ssh/ssh_host_*'], shell=True) +    subprocess.check_call(['sudo dpkg-reconfigure openssh-server'], shell=True) +    subprocess.check_call(['sudo systemctl restart ssh'], shell=True) diff --git a/src/op_mode/toggle_help_binding.sh b/src/op_mode/toggle_help_binding.sh new file mode 100755 index 000000000..a8708f3da --- /dev/null +++ b/src/op_mode/toggle_help_binding.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Copyright (C) 2019 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/>. + +# Script for [un-]binding the question mark key for getting help +if [ "$1" == 'disable' ]; then +  sed -i "/^bind '\"?\": .* # vyatta key binding$/d" $HOME/.bashrc +  echo "bind '\"?\": self-insert' # vyatta key binding" >> $HOME/.bashrc +  bind '"?": self-insert' +else +  sed -i "/^bind '\"?\": .* # vyatta key binding$/d" $HOME/.bashrc +  bind '"?": possible-completions' +fi | 
