commit 3b56f81c2a7ba1f40a6b2f6a057b17501957e549 from: Izzy Blacklock date: Mon Oct 02 17:54:03 2023 UTC added command line params for bin/configNow.pl Initial work for staging Initial work to create staging repo and merge config. Seems to be a bug I haven't had a chance to fix yet. usage for bin/configNow.pl added. bin/configNow.pl account commit - 5413554083a9dca6bbfb3e043e73e6325c3d53f6 commit + 3b56f81c2a7ba1f40a6b2f6a057b17501957e549 blob - af3e377b932594e963ec7aec92c47e3902eded4d blob + 438b2c1c652543466e89365170c4e90dc7180947 --- bin/configNow.pl +++ bin/configNow.pl @@ -7,17 +7,21 @@ use IRCNOW::ConfigNow; use File::Basename; use File::Path qw(make_path); -my $account = shift || 'blacklock'; -my @users = qw( izzyb nathan ashley ); + +my $account = shift || die "Usage: $0 <...>\n"; +my $users = \@ARGV; +# Temporary hack to support custom Domains +my $custDomain; +$custDomain = 'bnsnet.ca' if ($account eq 'blacklock'); + my $domain = 'user.planetofnix.com'; -my $custDomain = 'bnsnet.ca'; my %config=( type=>'shell', account => $account, - users => \@users, + users => $users, gitAuthor => $account, gitEmail => $account . "@" . $domain, - gitWorkDir => './configNow', + gitWorkDir => "./configNow/$account", ipv4 => '38.87.162.191', ipv6 => '2602:fccf:1:1191::', domain => $domain, @@ -25,7 +29,6 @@ my %config=( StageDir => './stageNow', DeployDir => './deployNow/', Accounts => [ $account ], # List of accounts to deploy - ); my $shellConfig = new IRCNOW::ConfigNow( %config ); @@ -45,15 +48,16 @@ print "\n" . $shellConfig->repo_log(); if ($shellConfig->stage_ready()) { # Verify stage repo is ready +} print "Deploying config Change.\n"; - $shellConfig->stage_pull($account => '../configNow'); + $shellConfig->stage_pull($account => "../configNow/$account"); $shellConfig->stage_merge(); $shellConfig->stage_commit(); $shellConfig->deploy_system(); -} + # Get list of files changed in a diff #$r->run(qw(git diff 0cd562e --name-only)); blob - /dev/null blob + de69b88350a3ff90f95af40634cc1bc914793005 (mode 644) --- /dev/null +++ Templates/etc/acme-client.conf_HEAD @@ -0,0 +1,26 @@ +# +# $OpenBSD: acme-client.conf,v 1.1 2019/01/08 07:14:10 florian Exp $ +# +authority letsencrypt { + api url "https://acme-v02.api.letsencrypt.org/directory" + account key "/etc/acme/letsencrypt-privkey.pem" +} + +authority letsencrypt-staging { + api url "https://acme-staging-v02.api.letsencrypt.org/directory" + account key "/etc/acme/letsencrypt-staging-privkey.pem" +} + +authority buypass { + api url "https://api.buypass.com/acme/directory" + account key "/etc/acme/buypass-privkey.pem" + contact "mailto:support@planetofnix.com" +} + +authority buypass-test { + api url "https://api.test4.buypass.no/acme/directory" + account key "/etc/acme/buypass-test-privkey.pem" + contact "mailto:support@planetofnix.com" +} + + blob - /dev/null blob + 27ffe761684ec54b98e8d08fbe80cdf5486d38df (mode 644) --- /dev/null +++ Templates/etc/prosody/prosody.cfg.lua_HEAD @@ -0,0 +1,308 @@ +-- Prosody Example Configuration File +-- +-- Information on configuring Prosody can be found on our +-- website at https://prosody.im/doc/configure +-- +-- Tip: You can check that the syntax of this file is correct +-- when you have finished by running this command: +-- prosodyctl check config +-- If there are any errors, it will let you know what and where +-- they are, otherwise it will keep quiet. +-- +-- The only thing left to do is rename this file to remove the .dist ending, and fill in the +-- blanks. Good luck, and happy Jabbering! + + +---------- Server-wide settings ---------- +-- Settings in this section apply to the whole server and are the default settings +-- for any virtual hosts + +-- This is a (by default, empty) list of accounts that are admins +-- for the server. Note that you must create the accounts separately +-- (see https://prosody.im/doc/creating_accounts for info) +-- Example: admins = { "user1@example.com", "user2@example.net" } +admins = {"izzyb@user.planetofnix.com"} +support_contact = "izzyb@user.planetofnix.com" +contact_info = { + abuse = { "mailto:abuse@planetofnix.com", "xmpp:izzyb@user.planetofnix.com" }; + support = { "mailto:support@planetofnix.com", "xmpp:izzyb@user.planetofnix.com" }; +} +-- Drop privileges +prosody_user = "_prosody" +prosody_group = "_prosody" + +-- Enable POSIX-only options +pidfile = "/var/prosody/prosody.pid" + +-- This option allows you to specify additional locations where Prosody +-- will search first for modules. For additional modules you can install, see +-- the community module repository at https://modules.prosody.im/ +--plugin_paths = {} +plugin_paths = { "/usr/local/lib/prosody/extras/" } +-- This is the list of modules Prosody will load on startup. +-- Documentation for bundled modules can be found at: https://prosody.im/doc/modules +modules_enabled = { + + -- Generally required + "disco"; -- Service discovery + "roster"; -- Allow users to have a roster. Recommended ;) + "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in. + "tls"; -- Add support for secure TLS on c2s/s2s connections + + -- Not essential, but recommended + "blocklist"; -- Allow users to block communications with other users + "carbons"; -- Keep multiple online clients in sync + "dialback"; -- Support for verifying remote servers using DNS + "limits"; -- Enable bandwidth limiting for XMPP connections + "pep"; -- Allow users to store public and private data in their account + "private"; -- Legacy account storage mechanism (XEP-0049) + "smacks"; -- Stream management and resumption (XEP-0198) + "vcard4"; -- User profiles (stored in PEP) + "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard + + -- Nice to have + "csi_simple"; -- Simple but effective traffic optimizations for mobile devices + "invites"; -- Create and manage invites + "invites_adhoc"; -- Allow admins/users to create invitations via their client + "invites_register"; -- Allows invited users to create accounts + "ping"; -- Replies to XMPP pings with pongs + "time"; -- Let others know the time here on this server + "uptime"; -- Report how long server has been running + "version"; -- Replies to server version requests + "mam"; -- Store recent messages to allow multi-device synchronization + --"turn_external"; -- Provide external STUN/TURN service for e.g. audio/video calls + + -- Admin interfaces + "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands + "admin_shell"; -- Allow secure administration via 'prosodyctl shell' + + -- HTTP modules + "bosh"; -- Enable BOSH clients, aka "Jabber over HTTP" + --IB Found on ircnow + --"http_files"; -- Serve static files from a directory over HTTP + --IB not on ircnow XXX + "http_openmetrics"; -- for exposing metrics to stats collectors + "websocket"; -- XMPP over WebSockets + + -- Other specific functionality + "announce"; -- Send announcement to all online users + "groups"; -- Shared roster support + --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots. + --"mimicking"; -- Prevent address spoofing + --"motd"; -- Send a message to users when they log in + "proxy65"; -- Enables a file transfer proxy service which clients behind NAT can use + --"s2s_bidi"; -- Bi-directional server-to-server (XEP-0288) + "server_contact_info"; -- Publish contact information for this service + --"tombstones"; -- Prevent registration of deleted accounts + "watchregistrations"; -- Alert admins of registrations + "welcome"; -- Welcome users who register accounts + + -- Found on ircnow.org + "bookmarks2"; -- required for compliance + "cloud_notify"; -- Support push notifications + "csi"; -- Reduce network usage + "filter_chatstates"; -- filter chat states during inactivity + "smacks"; -- Improve experience on unstable networks + "filter_chatstates"; -- filter chat states during inactivity + "auto_accept_subscriptions"; -- Auto accept subscriptions + --"group_bookmarks"; -- Group bookmarks + --"deny_omemo"; -- Deny OMEMO + --"roster_allinall"; -- Add everyone in roster + --"block_registrations"; -- block registrations using regex + "support_contact"; -- support contact + --"captcha_registration"; + --"throttle_unsolicited"; -- throttle unsolicited messages + --"bookmarks"; + "lastactivity"; -- handy, mkf + --"firewall"; + + +} + +-- These modules are auto-loaded, but should you want +-- to disable them then uncomment them here: +modules_disabled = { + -- "offline"; -- Store offline messages + -- "c2s"; -- Handle client connections + -- "s2s"; -- Handle server-to-server connections + -- "posix"; -- POSIX functionality, sends server to background, etc. +} + + +-- Server-to-server authentication +-- Require valid certificates for server-to-server connections? +-- If false, other methods such as dialback (DNS) may be used instead. +--IB changed to false to match ircnow.org +s2s_secure_auth = false + + + +-- Some servers have invalid or self-signed certificates. You can list +-- remote domains here that will not be required to authenticate using +-- certificates. They will be authenticated using other methods instead, +-- even when s2s_secure_auth is enabled. + +--s2s_insecure_domains = { "insecure.example" } + +-- Even if you disable s2s_secure_auth, you can still require valid +-- certificates for some domains by specifying a list here. + +--s2s_secure_domains = { "jabber.org" } + +--IB Found on ircnow.org +-- Force clients to use encrypted connections? This option will +-- prevent clients from authenticating unless they are using encryption. + +c2s_require_encryption = true + +-- Force servers to use encrypted connections? This option will +-- prevent servers from authenticating unless they are using encryption. + +s2s_require_encryption = true + +-- required for compliance, enables support in legacy clients +legacy_ssl_ports = { 5223 } +legacy_ssl_ssl = { + key = "/etc/prosody/certs/xmpp.user.planetofnix.com.key"; + certificate = "/etc/prosody/certs/xmpp.user.planetofnix.com.fullchain.pem"; +} + +--IB ---------------- + + +-- Rate limits +-- Enable rate limits for incoming client and server connections. These help +-- protect from excessive resource consumption and denial-of-service attacks. + +limits = { + c2s = { + rate = "10kb/s"; + }; + s2sin = { + rate = "30kb/s"; + }; +} + +-- Authentication +-- Select the authentication backend to use. The 'internal' providers +-- use Prosody's configured data storage to store the authentication data. +-- For more information see https://prosody.im/doc/authentication + +authentication = "internal_hashed" + +-- Many authentication providers, including the default one, allow you to +-- create user accounts via Prosody's admin interfaces. For details, see the +-- documentation at https://prosody.im/doc/creating_accounts + + +-- Storage +-- Select the storage backend to use. By default Prosody uses flat files +-- in its configured data directory, but it also supports more backends +-- through modules. An "sql" backend is included by default, but requires +-- additional dependencies. See https://prosody.im/doc/storage for more info. + +--storage = "sql" -- Default is "internal" + +-- For the "sql" backend, you can uncomment *one* of the below to configure: +--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename. +--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } +--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" } + + +-- Archiving configuration +-- If mod_mam is enabled, Prosody will store a copy of every message. This +-- is used to synchronize conversations between multiple clients, even if +-- they are offline. This setting controls how long Prosody will keep +-- messages in the archive before removing them. + +archive_expires_after = "4w" -- Remove archived messages after 1 week + +-- You can also configure messages to be stored in-memory only. For more +-- archiving options, see https://prosody.im/doc/modules/mod_mam + + +-- Audio/video call relay (STUN/TURN) +-- To ensure clients connected to the server can establish connections for +-- low-latency media streaming (such as audio and video calls), it is +-- recommended to run a STUN/TURN server for clients to use. If you do this, +-- specify the details here so clients can discover it. +-- Find more information at https://prosody.im/doc/turn + +-- Specify the address of the TURN service (you may use the same domain as XMPP) +--turn_external_host = "turn.example.com" + +-- This secret must be set to the same value in both Prosody and the TURN server +--turn_external_secret = "your-secret-turn-access-token" + + +-- Logging configuration +-- For advanced logging see https://prosody.im/doc/logging +log = { + info = "/var/prosody/prosody.log"; -- Change 'info' to 'debug' for verbose logging + error = "/var/prosody/prosody.err"; + -- "*syslog"; -- Uncomment this for logging to syslog + -- "*console"; -- Log to the console, useful for debugging when running in the foreground +} +-- Uncomment to enable statistics +-- For more info see https://prosody.im/doc/statistics +-- statistics = "internal" + + +-- Certificates +-- Every virtual host and component needs a certificate so that clients and +-- servers can securely verify its identity. Prosody will automatically load +-- certificates/keys from the directory specified here. +-- For more information, including how to use 'prosodyctl' to auto-import certificates +-- (from e.g. Let's Encrypt) see https://prosody.im/doc/certificates + +-- Location of directory to find certificates in (relative to main config file): +certificates = "certs" + +--IB Found on ircnow.org +-- HTTPS currently only supports a single certificate, specify it here: +-- Expects to find the key in same name .key +https_certificate = "/etc/prosody/certs/xmpp.user.planetofnix.com.crt" + +----------- Virtual hosts ----------- +-- You need to add a VirtualHost entry for each domain you wish Prosody to serve. +-- Settings under each VirtualHost entry apply *only* to that host. +--IB for shellname@user.plantofnix.com addresses +VirtualHost "user.planetofnix.com" + +key = "/etc/prosody/certs/xmpp.user.planetofnix.com.key" +certificate = "/etc/prosody/certs/xmpp.user.planetofnix.com.fullchain.pem" +--IB Found on ircnow.org +disco_items = { + { "xmpp.user.planetofnix.com" }, +} +http_host = "xmpp.user.planetofnix.com" +--http_default_host = "ircnow.org" +http_upload_access = {"xmpp.user.planetofnix.com"}; +http_upload_file_size_limit = 200*1024*1024 -- 200MB +http_upload_expire_after = 60 * 60 * 24 * 60 -- 60 days in sec + +----- Components ------ +-- You can specify components to add hosts that provide special services, +-- like multi-user conferences, and transports. +-- For more information on components, see https://prosody.im/doc/components + +---Set up a MUC (multi-user chat) room server on conference.example.com: +Component "xmpp.user.planetofnix.com" "muc" +--- Store MUC messages in an archive and allow users to access it +modules_enabled = { + "muc_mam", + "vcard_muc", -- profile photos for groups + } + +---Set up an external component (default component port is 5347) +-- +-- External components allow adding various services, such as gateways/ +-- transports to other networks like ICQ, MSN and Yahoo. For more info +-- see: https://prosody.im/doc/components#adding_an_external_component +-- +Component "xmpp.user.planetofnix.com" + component_secret = "redacted" + +Component "xmpp.user.planetofnix.com" "http_upload" + + blob - /dev/null blob + 28ad757f1207e3869b63b7f8cb1b2991bcb475ed (mode 644) --- /dev/null +++ Templates/var/nsd/zones/master/user.planetofnix.com_HEAD @@ -0,0 +1,33 @@ +$ORIGIN user.planetofnix.com. +user.planetofnix.com. 3600 SOA ns1.user.planetofnix.com. root.planetofnix.com. ( + 2023083101 ; serial YYYYMMDDnn + 1800 ; refresh + 3600 ; retry + 86400 ; expire + 3600 ) ; minimum TTL + 3600 IN A 38.87.162.191 + 3600 IN AAAA 2602:fccf:1:1191:: + 3600 IN NS ns1 + 3600 IN NS ns2 + 3600 IN MX 10 mail +ns1 3600 IN A 198.251.81.44 + 3600 IN AAAA 2605:6400:10:69d:: +ns2 3600 IN A 198.251.81.44 + 3600 IN AAAA 2605:6400:10:69d:: +mail 3600 IN A 38.87.162.191 + 3600 IN AAAA 2602:fccf:1:1191:: +imap 3600 IN A 38.87.162.191 + 3600 IN AAAA 2602:fccf:1:1191:: +pop 3600 IN A 38.87.162.191 + 3600 IN AAAA 2602:fccf:1:1191:: +pop3 3600 IN A 38.87.162.191 + 3600 IN AAAA 2602:fccf:1:1191:: +smtp 3600 IN A 38.87.162.191 + 3600 IN AAAA 2602:fccf:1:1191:: +_adsp._domainkey 3600 IN TXT "dkim=discardable;" +_mail._domainkey 3600 IN TXT "k=rsa; t-s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJkGACi9cbYFFUYZ8Kn/UfVuveop1v9FD1jQGwWkIO3s5DMo+1uhOwsMtHfpOFzfIZrQAwNP8g/X4Gi7QRt/OIPRgPM6qV1vhABCXFzisYd/3hiMpPigK1n+0iagKJdFbvSw8RDroPk4G/63nJsg4vbda1R+zNEKeZeUv3xgu/eQIDAQAB" +xmpp 3600 IN A 38.87.162.191 + 3600 IN AAAA 2602:fccf:1:1191:: +_xmpp-client._tcp 3600 IN SRV 0 5 5222 xmpp +_xmpp-server._tcp 3600 IN SRV 0 5 5269 xmpp + blob - 2deb07066ae0be4666ae274b8a37d42ec46dad7b blob + 44aa8bccf65653533ff64d8936004738a8f50322 --- lib/IRCNOW/ConfigNow.pm +++ lib/IRCNOW/ConfigNow.pm @@ -44,7 +44,9 @@ sub new { $self->mod_load('mail','IRCNOW::ConfigNow::Module::SmtpDove'); $self->mod_load('xmpp','IRCNOW::ConfigNow::Module::Prosody'); $self->mod_load('acme','IRCNOW::ConfigNow::Module::AcmeClient'); - $self->mod_load('custDomain','IRCNOW::ConfigNow::Module::CustDomain'); + if (defined $vars->{custDomain}) { + $self->mod_load('custDomain','IRCNOW::ConfigNow::Module::CustDomain'); + } } return $self; } @@ -100,26 +102,36 @@ sub vars { sub write_file { my $self = shift; my $filename = shift; - my $workDir=$self->{vars}->{gitWorkDir}; - $filename = "$workDir/$filename"; my $output = shift; + my $workDir = shift || $self->{vars}->{gitWorkDir} || die 'No work directory specified'; + $filename = "$workDir/$filename"; my $path = dirname($filename); -#print "Making Directory: $path\n"; make_path($path); -print "Writing: $filename\n"; - #XXX add proper error handling - open my $FH, ">>$filename"; + open my $FH, ">>$filename" || die "failed to open $filename for appending"; print $FH $output; close $FH; } +sub read_file { + my $self = shift; + my $filename = shift; + my $workDir = shift || $self->{vars}->{gitWorkDir} || die 'No work directory specified'; + $filename = "$workDir/$filename"; + my $output=""; + open (my $FH, "<$filename") || die "failed to open $filename for reading"; + while (<$FH>) { + $output.= $_; + } + close $FH; + return $output; +} + # Delete specified file sub delete_file { my $self = shift; my $filename = shift; - my $workDir=$self->{vars}->{gitWorkDir}; + my $workDir= shift || $self->{vars}->{gitWorkDir}; $filename = "$workDir/$filename"; -print "Deleting $filename\n"; unlink $filename; } @@ -151,7 +163,6 @@ sub write_config { } # Output for $type = "custDomain" if (exists $self->{vars}->{custDomain}) { -warn "custDomain: $filename"; # generate output for type = 'custDomain' my $out = $obj->output($filename, 'custDomain'); $output .= $out if defined $out; @@ -221,7 +232,7 @@ sub repo_connect { my $config=$self->{vars}; my $dir = shift || $config->{gitWorkDir}; return $self->{$repo} if defined $self->{$repo}; - $self->{$repo} = Git::Repository->new( + my $r = Git::Repository->new( work_tree => $dir, { env => { @@ -230,14 +241,16 @@ sub repo_connect { }, }, ); - # Ready for changes. - return $self->{$repo}; + # don't save temp repos. + $self->{$repo} = $r unless ($repo eq 'tmp'); + return $r; } sub repo_commit { my $self=shift; - my $r = $self->{repo}; - my $workDir = $self->{vars}->{gitWorkDir}; + my $repo = shift || 'repo'; + my $workDir = shift || $self->{vars}->{gitWorkDir}; + my $r = $self->{$repo} || $self->repo_connect($repo,$workDir); # Check status of untracked files. my $update=0; # Flag for changes to commit my @output = $r->run(qw(status -su)); @@ -251,7 +264,7 @@ sub repo_commit { } else { print "... ignoring $1\n"; } - } elsif ($file =~ /^\s+M\s+/) { + } elsif ($file =~ /^\s?[MA]\s+/) { # File modified or added thats already tracked $update = 1; } } @@ -271,7 +284,10 @@ sub stage_ready { # Initialize the config repo if it hasn't been created yet. make_path($workDir); make_path("$workDir/Accounts"); - make_path("$workDir/Stage"); + make_path("$workDir/Templates"); + make_path("$workDir/Staged"); + # XXX cheap hack to copy template files - should do this without the system() + system("cp -av ./Templates/* $workDir/Templates/"); Git::Repository->run(init => $workDir); } $r = $self->repo_connect('stage_repo', $workDir); @@ -292,22 +308,103 @@ sub stage_ready { sub stage_pull { my $self=shift; my ($account, $upstream) = @_; - my $r = $self->{stage_repo}; my $account_repo="Accounts/$account"; - if (not -d $account_repo) { + if (not -d $self->{vars}->{StageDir} . "/$account_repo") { # Don't have this account so clone it - my @status = $r->run("submodule","add", $upstream,$account_repo); - use Data::Dumper; - print Dumper(@status); - return 1; + my $r = $self->{stage_repo}; + my @results = $r->run("submodule","add", $upstream, $account_repo); + } else { + my $r = $self->repo_connect('tmp',$self->{vars}->{StageDir} . "/$account_repo"); + my @results = $r->run("pull"); } } +# Find any _HEAD templates for given file +sub template_head { + my $self = shift; + my $filename=shift; + my $templates=$self->{vars}->{StageDir} . '/Templates'; + my $output = ""; + if (-e "$templates/$filename" . "_HEAD") { +print "Reading: $templates/$filename" . "_HEAD\n"; + open (my $FH, "<$templates/$filename" . "_HEAD"); + while (<$FH>) { + $output.=$_; + } + close $FH; + } + return $output; +} + + + + sub stage_merge { + my $self = shift; + # # abort of the repo isn't ready + # return 0 unless $self->repo_ready(); + # get the output for each module for this filename. + my $stagedDir=$self->{vars}->{StageDir} . "/Staged"; + my $accountsDir = $self->{vars}->{StageDir} . "/Accounts"; + for my $filename ($self->filenames()) { + #purge existing files before generation. + $self->delete_file($filename,$stagedDir); + # Output template header if one exists + my $output=$self->template_head($filename); + # Find configNow repos with content for this filename + opendir(my $DH, $accountsDir) || die "can't open directory $accountsDir: $!"; + while (readdir $DH) { + next if $_ =~ /^\./; + if (-d "$accountsDir/$_" and -e "$accountsDir/$_/$filename") { + $output .= $self->read_file($filename, "$accountsDir/$_"); + } + } + closedir $DH; + $self->write_file($filename, $output, $stagedDir) + } + +# my $out = $obj->output($filename,$type); +# $output .= $out if defined $out; +# # Output for $type . "users" if we have a user list +# if (exists $self->{vars}->{users}) { +# for my $user (@{$self->{vars}->{users}}) { +# # set the username var to this user. +# $self->{vars}->{username} = $user; +# # generate output for $type . 'user' +# my $out = $obj->output($filename,$type . "user"); +# $output .= $out if defined $out; +# # delete the username so it doesn't bleed anywhere +# delete $self->{vars}->{username}; +# } +# } +# # Output for $type = "custDomain" +# if (exists $self->{vars}->{custDomain}) { +#warn "custDomain: $filename"; +# # generate output for type = 'custDomain' +# my $out = $obj->output($filename, 'custDomain'); +# $output .= $out if defined $out; +# } +# } +# if (length($output)>0) { # Should we support empty files? +# $self->write_file($filename,$output); +# } +# } +# return 1; } + + + + sub stage_commit { + my $self = shift; +print "stage_commit(): " . $self->{vars}->{StageDir} . "\n"; + return $self->repo_commit('stage_repo',$self->{vars}->{StageDir}); + + } + + sub deploy_system { }