diff --git a/sandbox/cli.py b/sandbox/cli.py new file mode 100755 index 0000000..c87096b --- /dev/null +++ b/sandbox/cli.py @@ -0,0 +1,234 @@ +#! /usr/bin/env python + +from argparse import ArgumentParser, RawDescriptionHelpFormatter +from commonkit import highlight_code, smart_cast +from commonkit.logging import LoggingHelper +from commonkit.shell import EXIT +import sys + +sys.path.insert(0, "../") + +from scripttease.constants import LOGGER_NAME +from scripttease.lib.contexts import load_variables, Context +from scripttease.lib.loaders.ini import INILoader +from scripttease.lib.loaders.yaml import YMLLoader +from scripttease.version import DATE as VERSION_DATE, VERSION + +DEBUG = 10 + +logging = LoggingHelper(colorize=True, name=LOGGER_NAME) +log = logging.setup() + + +def execute(): + """Process script configurations.""" + + __author__ = "Shawn Davis " + __date__ = VERSION_DATE + __help__ = """NOTES + +This command is used to parse configuration files and output the commands. + +""" + __version__ = VERSION + "+new" + + # Main argument parser from which sub-commands are created. + parser = ArgumentParser(description=__doc__, epilog=__help__, formatter_class=RawDescriptionHelpFormatter) + + parser.add_argument( + "path", + default="steps.ini", + nargs="?", + help="The path to the configuration file." + ) + + parser.add_argument( + "-c", + "--color", + action="store_true", + dest="color_enabled", + help="Enable code highlighting for terminal output." + ) + + parser.add_argument( + "-C=", + "--context=", + action="append", + dest="variables", + help="Context variables for use in pre-parsing the config and templates. In the form of: name:value" + ) + + parser.add_argument( + "-d", + "--docs", + action="store_true", + dest="docs_enabled", + help="Output documentation instead of code." + ) + + parser.add_argument( + "-D", + "--debug", + action="store_true", + dest="debug_enabled", + help="Enable debug output." + ) + + parser.add_argument( + "-f=", + "--filter=", + action="append", + dest="filters", + help="Filter the commands in the form of: attribute:value" + ) + + parser.add_argument( + "-o=", + "--option=", + action="append", + dest="options", + help="Common command options in the form of: name:value" + ) + + parser.add_argument( + "-P=", + "--profile=", + choices=["centos", "ubuntu"], + default="ubuntu", + dest="profile", + help="The OS profile to use." + ) + + parser.add_argument( + "-T=", + "--template-path=", + action="append", + dest="template_locations", + help="The location of template files that may be used with the template command." + ) + + parser.add_argument( + "-w=", + "--write=", + dest="output_file", + help="Write the output to disk." + ) + + parser.add_argument( + "-V=", + "--variables-file=", + dest="variables_file", + help="Load variables from a file." + ) + + + + # Access to the version number requires special consideration, especially + # when using sub parsers. The Python 3.3 behavior is different. See this + # answer: http://stackoverflow.com/questions/8521612/argparse-optional-subparser-for-version + parser.add_argument( + "-v", + action="version", + help="Show version number and exit.", + version=__version__ + ) + + parser.add_argument( + "--version", + action="version", + help="Show verbose version information and exit.", + version="%(prog)s" + " %s %s by %s" % (__version__, __date__, __author__) + ) + + # Parse arguments. + args = parser.parse_args() + + if args.debug_enabled: + log.setLevel(DEBUG) + + log.debug("Namespace: %s" % args) + + # Create the global context. + context = Context() + + if args.variables_file: + variables = load_variables(args.variables_file) + for v in variables: + context.variables[v.name] = v + + if args.variables: + for token in args.variables: + try: + key, value = token.split(":") + context.add(key, smart_cast(value)) + except ValueError: + context.add(token, True) + + # Capture filters. + if args.filters: + filters = dict() + for token in args.filters: + key, value = token.split(":") + if key not in filters: + filters[key] = list() + + filters[key].append(value) + + # Handle options. + options = dict() + if args.options: + for token in args.options: + try: + key, value = token.split(":") + options[key] = smart_cast(value) + except ValueError: + options[token] = True + + # Load the commands. + if args.path.endswith(".ini"): + loader = INILoader( + args.path, + context=context, + locations=args.template_locations, + profile=args.profile, + **options + ) + elif args.path.endswith(".yml"): + loader = YMLLoader( + args.path, + context=context, + locations=args.template_locations, + profile=args.profile, + **options + ) + else: + log.error("Unsupported file format: %s" % args.path) + exit(EXIT.ERROR) + + # noinspection PyUnboundLocalVariable + if not loader.load(): + exit(EXIT.ERROR) + + # Generate output. + if args.docs_enabled: + pass + else: + commands = list() + for snippet in loader.get_snippets(): + statement = snippet.get_statement() + if statement is not None: + commands.append(statement) + commands.append("") + + if args.color_enabled: + print(highlight_code("\n".join(commands), language="bash")) + else: + print("\n".join(commands)) + + exit(EXIT.OK) + + +if __name__ == '__main__': + execute() + + diff --git a/scripttease/data/inventory/nextcloud/steps.ini b/scripttease/data/inventory/nextcloud/steps.ini new file mode 100644 index 0000000..a569658 --- /dev/null +++ b/scripttease/data/inventory/nextcloud/steps.ini @@ -0,0 +1,180 @@ +[update system repos] +system.update: + +[install apache] +install: apache2 + +[install certbot] +install: certbot + +[make sure a maintenance root exists] +mkdir: /var/www/maint/www +group: {{ apache_group }} +owner: {{ apache_user }} +recursive: yes + +[disable the default site] +apache.disable: 000-default + +[install postgres] +install: postgresql + +[install php and related resources] +install: $item +items: php, libapache2-mod-php, php-pgsql + +[install php modules] +install: $item +items: php-curl, php-dom, php-gd, php-json, php-mbstring, php-pdo-pgsql, php-zip + +[create the document root for the domain] +dir: /var/www/{{ domain_tld }}/www +group: {{ apache_group }} +owner: {{ apache_user }} +recursive: yes + +[prevent browsing of document root] +file: /var/www/{{ domain_tld }}/www/index.html +group: {{ apache_group }} +owner: {{ apache_user }} + +[create the initial apache config file] +template: httpd.conf /etc/apache2/sites-available/{{ domain_name }}.conf + +[enable the site] +apache.enable: {{ domain_name }} + +[enable mod rewrite] +apache.enable_module: rewrite + +[enable SSL engine] +apache.enable_module: ssl + +[enable php ctype] +run: "phpenmod ctype" + +[enable php curl] +run: "phpenmod curl" + +[enable php dom] +run: "phpenmod dom" + +[enable php GD] +run: "phpenmod gd" + +[enable php JSON] +run: "phpenmod json" + +[enable php PGSQL] +run: "phpenmod pdo_pgsql" + +[enable php SimpleXML] +run: "phpenmod simplexml" + +[enable php posix] +run: "phpenmod posix" + +[enable php XMLReader] +run: "phpenmod xmlreader" + +[enable php XMLWriter] +run: "phpenmod xmlwriter" + +[enable php zip] +run: "phpenmod zip" + +;PHP module libxml (Linux package libxml2 must be >=2.7.0) +;php -i | grep -i libxml + +; https://askubuntu.com/questions/323005/php-openssl-extension-has-a-package +; php -i | grep -i openssl +;PHP module openssl + +;php -i | grep -i openssl +;PHP module session + +;php -i | grep -i zlib +;PHP module zlib + +[reload apache] +apache.reload: + +[get an SSL cert] +ssl: {{ domain_name }} +email: {{ webmaster_email }} + +[create the SSL version of the apache config file] +template: httpd.conf /etc/apache2/sites-available/{{ domain_name }}.conf +ssl_enabled: yes + +[restart apache] +apache.restart: + +[create the install path for nextcloud] +dir: /var/www/{{ domain_tld }}/www{{ install_path }} +group: {{ apache_group }} +owner: {{ apache_user }} +recursive: yes + +[get the nextcloud installer] +run: "wget https://download.nextcloud.com/server/installer/setup-nextcloud.php" +cd: /var/www/{{ domain_tld }}/www{{ install_path }} + +; createuser -U postgres -DRS {{ install_path }}_nextcloud +; createdb -U postgres -O diff6_nextcloud diff6_nextcloud +; psql -U postgres -c "ALTER USER diff6_nextcloud WITH ENCRYPTED PASSWORD '******'" + +; https://docs.nextcloud.com/server/latest/admin_manual/installation/source_installation.html +; Recommended packages: +; +;PHP module fileinfo (highly recommended, enhances file analysis performance) +; +;PHP module bz2 (recommended, required for extraction of apps) +; +;PHP module intl (increases language translation performance and fixes sorting of non-ASCII characters) +; +;Required for specific apps: +; +;PHP module ldap (for LDAP integration) +; +;PHP module smbclient (SMB/CIFS integration, see SMB/CIFS) +; +;PHP module ftp (for FTP storage / external user authentication) +; +;PHP module imap (for external user authentication) +; +;PHP module bcmath (for passwordless login) +; +;PHP module gmp (for passwordless login) +; +;Recommended for specific apps (optional): +; +;PHP module gmp (for SFTP storage) +; +;PHP module exif (for image rotation in pictures app) +; +;For enhanced server performance (optional) select one of the following memcaches: +; +;PHP module apcu (>= 4.0.6) +; +;PHP module memcached +; +;PHP module redis (>= 2.2.6, required for Transactional File Locking) +; +;See Memory caching to learn how to select and configure a memcache. +; +;For preview generation (optional): +; +;PHP module imagick +; +;avconv or ffmpeg +; +;OpenOffice or LibreOffice +; +;For command line processing (optional): +; +;PHP module pcntl (enables command interruption by pressing ctrl-c) +; +;For command line updater (optional): +; +;PHP module phar (upgrades Nextcloud by running sudo -u www-data php /var/www/nextcloud/updater/updater.phar) \ No newline at end of file diff --git a/scripttease/data/inventory/nextcloud/templates/httpd.conf b/scripttease/data/inventory/nextcloud/templates/httpd.conf new file mode 100644 index 0000000..bbc601f --- /dev/null +++ b/scripttease/data/inventory/nextcloud/templates/httpd.conf @@ -0,0 +1,28 @@ +# The port 80 host is required for renewing Let's Encrypt certificates. + + ServerName {{ domain_name }} + ServerAlias *.{{ domain_name }} + ServerAdmin {{ webmaster_email }} + RewriteEngine On + RewriteCond %{HTTPS} off + RewriteCond %{REQUEST_URI} !^/.well-known [NC] + RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L] + DocumentRoot /var/www/maint/www + + +{% if ssl_enabled %} +# The 443 host is where the project is actually served. + + ServerName {{ domain_name }} + ServerAdmin {{ webmaster_email }} + DocumentRoot /var/www/{{ domain_tld }}/www + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + SSLEngine on + SSLCertificateKeyFile /etc/letsencrypt/live/{{ domain_name }}/privkey.pem + SSLCertificateFile /etc/letsencrypt/live/{{ domain_name }}/fullchain.pem + + +{% endif %} \ No newline at end of file diff --git a/scripttease/data/inventory/nextcloud/variables.ini b/scripttease/data/inventory/nextcloud/variables.ini new file mode 100644 index 0000000..9e980b1 --- /dev/null +++ b/scripttease/data/inventory/nextcloud/variables.ini @@ -0,0 +1,23 @@ +[apache_user] +comment = The name of the user that runs Apache. +value = www-data + +[apache_group] +comment = The name of the group to which the Apache user is assigned. +value = www-data + +[domain_name] +comment = The domain name to use for the NextCloud host. +value = cloud.example.com + +[domain_tld] +comment = The domain name as a directory. +value = cloud_example_com + +[webmaster_email] +comment = The webmaster's email address. Used when setting up SSL. +value = webmaster@example.com + +[install_path] +comment = The path relative to document root where NextCloud will be installed. +value = / diff --git a/scripttease/data/inventory/pgsql/steps.ini b/scripttease/data/inventory/pgsql/steps.ini new file mode 100644 index 0000000..45c4144 --- /dev/null +++ b/scripttease/data/inventory/pgsql/steps.ini @@ -0,0 +1,2 @@ +[create pg_hba.conf file] +template: pg_hba.conf /etc/postgresql/{{ pgsql_version}}/main/pg_hba.conf diff --git a/scripttease/data/inventory/pgsql/templates/pg_hba.conf b/scripttease/data/inventory/pgsql/templates/pg_hba.conf new file mode 100644 index 0000000..689736f --- /dev/null +++ b/scripttease/data/inventory/pgsql/templates/pg_hba.conf @@ -0,0 +1,8 @@ +# TYPE DATABASE USER ADDRESS METHOD +local all postgres trust +local sameuser all md5 +host all postgres ::1/128 trust +host all postgres localhost trust +host sameuser all ::1/128 md5 +host sameuser all localhost md5 +host sameuser all 127.0.0.1/32 md5 diff --git a/scripttease/data/inventory/pgsql/variables.ini b/scripttease/data/inventory/pgsql/variables.ini new file mode 100644 index 0000000..5f78578 --- /dev/null +++ b/scripttease/data/inventory/pgsql/variables.ini @@ -0,0 +1,3 @@ +[pgsql_version] +comment = The current version of PostgreSQL. +value = 12 diff --git a/scripttease/data/inventory/radicale/steps.ini b/scripttease/data/inventory/radicale/steps.ini index d78ddb6..d4f5e2e 100644 --- a/scripttease/data/inventory/radicale/steps.ini +++ b/scripttease/data/inventory/radicale/steps.ini @@ -28,6 +28,9 @@ template: radicale.service /etc/systemd/system/radicale.service [start the radicale service] start: radicale +[disable the default site] +apache.disable: default + [create the initial apache config file] template: httpd.conf /etc/apache2/sites-available/{{ domain_name }}.conf diff --git a/scripttease/data/inventory/ubuntu/steps.ini b/scripttease/data/inventory/ubuntu/steps.ini new file mode 100644 index 0000000..191c770 --- /dev/null +++ b/scripttease/data/inventory/ubuntu/steps.ini @@ -0,0 +1,2 @@ +[setup restricted ssh] +template: sshd_config /etc/ssh/sshd_config diff --git a/scripttease/data/inventory/ubuntu/templates/sshd_config b/scripttease/data/inventory/ubuntu/templates/sshd_config new file mode 100644 index 0000000..bbed446 --- /dev/null +++ b/scripttease/data/inventory/ubuntu/templates/sshd_config @@ -0,0 +1,10 @@ +Port 4894 +AddressFamily inet +PermitRootLogin no +PasswordAuthentication no +ChallengeResponseAuthentication no +UsePAM yes +X11Forwarding yes +PrintMotd no +AcceptEnv LANG LC_* +Subsystem sftp /usr/lib/openssh/sftp-server \ No newline at end of file diff --git a/scripttease/lib/contexts.py b/scripttease/lib/contexts.py index 5a8b78d..116ec7c 100644 --- a/scripttease/lib/contexts.py +++ b/scripttease/lib/contexts.py @@ -23,21 +23,21 @@ def load_variables(path): :param path: The path to the INI file. :type path: str - :rtype: scripttease.lib.contexts.Context + :rtype: list[scripttease.lib.contexts.Variable] """ if not os.path.exists(path): log.warning("Variables file does not exist: %s" % path) - return Context() + return list() ini = RawConfigParser() try: ini.read(path) except ParsingError as e: log.warning("Failed to parse %s variables file: %s" % (path, str(e))) - return Context() + return list() - context = Context() + variables = list() for variable_name in ini.sections(): _value = None kwargs = dict() @@ -48,11 +48,9 @@ def load_variables(path): kwargs[key] = smart_cast(value) - context.add(variable_name, _value, **kwargs) + variables.append(Variable(variable_name, _value, **kwargs)) - context.is_loaded = True - - return context + return variables # Classes @@ -100,6 +98,7 @@ class Context(object): return d + class Variable(object): """An individual variable.""" diff --git a/scripttease/lib/snippets/posix.py b/scripttease/lib/snippets/posix.py index 4142347..678c866 100644 --- a/scripttease/lib/snippets/posix.py +++ b/scripttease/lib/snippets/posix.py @@ -33,9 +33,9 @@ posix = { ], 'file': [ "{% if content %}cat > {{ args[0] }} << EOF\n{{ content }}\nEOF{% else %}touch {{ args[0] }}{% endif %}", - "{% if mode %}&& chmod {{ mode }} {{ args[0 }}{% endif %}", + "{% if mode %}&& chmod {{ mode }} {{ args[0] }}{% endif %}", "{% if group %}&& chgrp {{ group }} {{ args[0] }}{% endif %}", - "{% if owner %}&& chown{{ owner }} {{ args[0] }}{% endif %}" + "{% if owner %}&& chown {{ owner }} {{ args[0] }}{% endif %}" ], 'link': [ "ln -s", diff --git a/scripttease/lib/snippets/ubuntu.py b/scripttease/lib/snippets/ubuntu.py index 942fd8e..e2a65cc 100644 --- a/scripttease/lib/snippets/ubuntu.py +++ b/scripttease/lib/snippets/ubuntu.py @@ -3,7 +3,7 @@ ubuntu = { 'disable': '{% if args[0].startswith("mod_") %}a2dismod{% else %}a2dissite{% endif %} {{ args[0] }}', 'disable_module': "a2dissite {{ args[0] }}", 'disable_site': "a2dismod {{ args[0] }}", - 'enable': '{% if args[0].startswith("mod_") %}a2denmod{% else %}a2ensite{% endif %} {{ args[0] }}', + 'enable': '{% if args[0].startswith("mod_") %}a2enmod{% else %}a2ensite{% endif %} {{ args[0] }}', 'enable_module': "a2enmod {{ args[0] }}", 'enable_site': "a2ensite {{ args[0] }}", 'reload': "service apache2 reload", @@ -12,7 +12,7 @@ ubuntu = { 'stop': "service apache2 stop", 'test': "apachectl configtest", }, - 'install': "apt-get install -y {{ args[0] }}", + 'install': "apt install -y {{ args[0] }}", 'reload': "service {{ args[0] }} reload", 'restart': "service {{ args[0] }} restart", 'run': "{{ args[0] }}",