├── debian ├── compat ├── source │ └── format ├── watch ├── rules ├── copyright ├── control └── changelog ├── .travis.yml ├── .gitignore ├── lib └── Virtualmin │ ├── Config │ ├── Dummy.pm │ ├── LAMP.pm │ ├── LEMP.pm │ ├── MiniLAMP.pm │ ├── MiniLEMP.pm │ ├── Plugin │ │ ├── Test2.pm │ │ ├── Etckeeper.pm │ │ ├── SELinux.pm │ │ ├── Shells.pm │ │ ├── MySQL.pm │ │ ├── SpamAssassin.pm │ │ ├── PostgreSQL.pm │ │ ├── Mailman.pm │ │ ├── Webalizer.pm │ │ ├── Test.pm │ │ ├── NTP.pm │ │ ├── Firewalld.pm │ │ ├── AWStats.pm │ │ ├── Procmail.pm │ │ ├── Nginx.pm │ │ ├── Limits.pm │ │ ├── Firewall.pm │ │ ├── Webmin.pm │ │ ├── Dovecot.pm │ │ ├── Net.pm │ │ ├── ClamAV.pm │ │ ├── Bind.pm │ │ ├── Apache.pm │ │ ├── Fail2ban.pm │ │ ├── Fail2banFirewalld.pm │ │ ├── SASL.pm │ │ ├── ProFTPd.pm │ │ ├── Quotas.pm │ │ ├── Usermin.pm │ │ ├── Postfix.pm │ │ └── Virtualmin.pm │ ├── Stack.pm │ └── Plugin.pm │ └── Config.pm ├── t ├── 00-options.t ├── 01-plugins.t ├── data │ └── etc │ │ └── webmin │ │ └── miniserv.conf.orig └── 02-actions.t ├── .perltidyrc ├── dist.ini ├── Makefile.PL ├── README.pod ├── bin └── virtualmin-config-system ├── virtualmin-config.spec └── .github └── workflows └── virtualmin.dev+virtualmin-config.yml /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=3 2 | http://search.cpan.org/dist/Virtualmin-Config/ .*/Virtualmin-Config-v?(\d[\d.-]+)\.(?:tar(?:\.gz|\.bz2)?|tgz|zip)$ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: perl 3 | perl: 4 | - "5.24" 5 | - "5.16" 6 | - "5.10" 7 | before_install: 8 | cpanm -n Devel::Cover::Report::Coveralls 9 | script: 10 | perl Makefile.PL && make && cover -test -report coveralls 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /blib/ 2 | /.build/ 3 | _build/ 4 | cover_db/ 5 | inc/ 6 | Build 7 | !Build/ 8 | Build.bat 9 | .last_cover_stats 10 | /Makefile 11 | /Makefile.old 12 | /MANIFEST.bak 13 | /META.yml 14 | /META.json 15 | /MYMETA.* 16 | nytprof.out 17 | /pm_to_blib 18 | *.o 19 | *.bs 20 | /_eumm/ 21 | .perl-version 22 | Virtualmin-Config-* 23 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Dummy.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Dummy; 2 | 3 | # A list of plugins for testing 4 | use strict; 5 | use warnings; 6 | use 5.010; 7 | 8 | sub new { 9 | my ($class, %args) = @_; 10 | my $self = {}; 11 | 12 | return bless $self, $class; 13 | } 14 | 15 | sub plugins { 16 | return ["Test", "Test2",]; 17 | } 18 | 19 | 1; 20 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/LAMP.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::LAMP; 2 | use strict; 3 | use warnings; 4 | use 5.010_001; 5 | 6 | # A list of plugins for configuring a LAMP stack 7 | 8 | sub new { 9 | my ($class, %args) = @_; 10 | my $self = {}; 11 | 12 | return bless $self, $class; 13 | } 14 | 15 | sub plugins { 16 | my ($self, $stack) = @_; 17 | return $stack->list('lamp', 'full'); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/LEMP.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::LEMP; 2 | use strict; 3 | use warnings; 4 | use 5.010_001; 5 | 6 | # A list of plugins for configuring a LAMP stack 7 | 8 | sub new { 9 | my ($class, %args) = @_; 10 | my $self = {}; 11 | 12 | return bless $self, $class; 13 | } 14 | 15 | sub plugins { 16 | my ($self, $stack) = @_; 17 | return $stack->list('lemp', 'full'); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/MiniLAMP.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::MiniLAMP; 2 | use strict; 3 | use warnings; 4 | use 5.010_001; 5 | 6 | # A list of plugins for configuring a mini LAMP stack 7 | 8 | sub new { 9 | my ($class, %args) = @_; 10 | my $self = {}; 11 | 12 | return bless $self, $class; 13 | } 14 | 15 | sub plugins { 16 | my ($self, $stack) = @_; 17 | return $stack->list('lamp', 'mini'); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/MiniLEMP.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::MiniLEMP; 2 | use strict; 3 | use warnings; 4 | use 5.010_001; 5 | 6 | # A list of plugins for configuring a mini LEMP stack 7 | 8 | sub new { 9 | my ($class, %args) = @_; 10 | my $self = {}; 11 | 12 | return bless $self, $class; 13 | } 14 | 15 | sub plugins { 16 | my ($self, $stack) = @_; 17 | return $stack->list('lemp', 'mini'); 18 | } 19 | 20 | 1; 21 | -------------------------------------------------------------------------------- /t/00-options.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | 3 | require_ok('Virtualmin::Config'); 4 | 5 | my $bundle = Virtualmin::Config->new(bundle => 'Dummy',); 6 | ok($bundle->{bundle} eq 'Dummy'); 7 | 8 | my $bundle2 = Virtualmin::Config->new(include => 'Test',); 9 | ok($bundle2->{include} eq 'Test'); 10 | 11 | my $bundle3 = Virtualmin::Config->new(bundle => 'Dummy', exclude => 'Test',); 12 | ok($bundle3->{exclude} eq 'Test'); 13 | 14 | done_testing(); 15 | -------------------------------------------------------------------------------- /.perltidyrc: -------------------------------------------------------------------------------- 1 | -pbp # Start with Perl Best Practices 2 | -nst # Non-standard output (allow operate in-place) 3 | -w # Show all warnings 4 | -iob # Ignore old breakpoints 5 | -l=80 # 80 characters per line 6 | -mbl=2 # No more than 2 blank lines 7 | -i=2 # Indentation is 2 columns 8 | -ci=2 # Continuation indentation is 2 columns 9 | -vt=0 # Less vertical tightness 10 | -pt=2 # High parenthesis tightness 11 | -bt=2 # High brace tightness 12 | -sbt=2 # High square bracket tightness 13 | -isbc # Don't indent comments without leading space 14 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | 6 | override_dh_auto_configure: 7 | dh_auto_configure -- NO_PERLLOCAL=1 8 | 9 | override_dh_auto_install: 10 | sed -i 's/\/vendor_perl//' Makefile 11 | dh_auto_install 12 | find debian/virtualmin-config -type f -name .packlist -delete 13 | rm -rf debian/virtualmin-config/usr/lib 14 | mkdir -p debian/virtualmin-config/usr/share/webmin/virtual-server 15 | ln -s /usr/bin/virtualmin-config-system debian/virtualmin-config/usr/share/webmin/virtual-server/config-system.pl 16 | 17 | override_dh_strip_nondeterminism: 18 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format-Specification: http://anonscm.debian.org/viewvc/dep/web/deps/dep5.mdwn?view=markup&pathrev=135 2 | Maintainer: Joe Cooper 3 | Source: http://search.cpan.org/dist/Virtualmin-Config/ 4 | Name: Virtualmin-Config 5 | Files: * 6 | Copyright: Joe Cooper 7 | License: GPL-3+ 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; version 3 dated June, 2007, or (at your 11 | option) any later version. 12 | . 13 | On Debian systems, the complete text of version 3 of the GNU General 14 | Public License can be found in `/usr/share/common-licenses/GPL-3'. 15 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Test2.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Test2; 2 | use strict; 3 | use warnings; 4 | use 5.010; 5 | use parent qw(Virtualmin::Config::Plugin); 6 | 7 | sub new { 8 | my ($class, %args) = @_; 9 | 10 | # inherit from Plugin 11 | my $self = $class->SUPER::new(name => 'Test2', depends => ['Test'], %args); 12 | 13 | return $self; 14 | } 15 | 16 | # actions method performs whatever configuration is needed for this 17 | # plugin. XXX Needs to make a backup so changes can be reverted. 18 | sub actions { 19 | my $self = shift; 20 | 21 | $self->spin(); 22 | eval { # try 23 | sleep 1; 24 | } or do { # catch 25 | $self->done(0); # Something failed 26 | }; 27 | $self->done(1); # OK! 28 | } 29 | 30 | 1; 31 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = Virtualmin-Config 2 | version = 8.0.4 3 | author = Joe Cooper 4 | license = GPL_3 5 | copyright_holder = Joe Cooper 6 | 7 | [@Starter] 8 | revision = 2 9 | -remove = License 10 | -remove = GatherDir 11 | 12 | [GatherDir] 13 | exclude_filename = Makefile.PL 14 | 15 | [PerlTidy] 16 | 17 | [Test::Perl::Critic] 18 | 19 | [GithubMeta] 20 | issues = 1 21 | user = SwellJoe 22 | 23 | [CopyFilesFromBuild] 24 | copy = Makefile.PL 25 | 26 | [ReadmeAnyFromPod / Pod_Readme] 27 | type = pod 28 | location = root ; do not include pod readmes in the build! 29 | 30 | [Prereqs] 31 | perl = 5.016 32 | Term::ANSIColor = 0 33 | Log::Log4perl = 0 34 | 35 | [RPM] 36 | spec_file = build/dist.spec 37 | sign = 1 38 | ignore_build_deps = 0 39 | push_packages = 0 40 | -------------------------------------------------------------------------------- /t/01-plugins.t: -------------------------------------------------------------------------------- 1 | # Test whether plugin pruning and dependency resolution Works 2 | use Test::More; 3 | use 5.010; 4 | 5 | require_ok('Virtualmin::Config'); 6 | 7 | my $bundle = Virtualmin::Config->new(bundle => 'Dummy'); 8 | 9 | my @plugins = $bundle->_gather_plugins(); 10 | ok( 11 | map { 12 | grep {/Test/} 13 | $_ 14 | } @plugins 15 | ); 16 | ok( 17 | map { 18 | grep {/Test2/} 19 | $_ 20 | } @plugins 21 | ); 22 | 23 | my $include = Virtualmin::Config->new(include => ['Test']); 24 | my @plugins2 = $include->_gather_plugins(); 25 | ok( 26 | map { 27 | grep {/^Test$/} 28 | $_ 29 | } @plugins2 30 | ); 31 | ok(scalar @plugins2 == 1); 32 | 33 | my $depends = Virtualmin::Config->new(include => ['Test2']); 34 | my @plugins3 = $depends->_gather_plugins(); 35 | my @resolved = $depends->_order_plugins(@plugins3); 36 | ok(grep {/^Test2$/} @resolved); 37 | ok(grep {/^Test$/} @resolved); # did the dependency get pulled in? 38 | 39 | done_testing(); 40 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: virtualmin-config 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Joe Cooper 5 | Standards-Version: 3.9.4 6 | Homepage: http://search.cpan.org/dist/Virtualmin-Config/ 7 | 8 | Package: virtualmin-config 9 | Architecture: all 10 | Depends: ${misc:Depends}, ${perl:Depends}, 11 | liblog-log4perl-perl, 12 | libterm-spinner-color-perl, 13 | perl, 14 | perl-modules 15 | Description: Configure a system for use by Virtualmin 16 | This is a mini-framework for configuring elements of a Virtualmin system. It 17 | uses Webmin as a library to abstract common configuration tasks, provides a 18 | friendly status indicator, and makes it easy to pick and choose the kind of 19 | configuration you want (should you choose to go that route). The Virtualmin 20 | install script chooses either the LAMP (with Apache) or LEMP (with nginx) 21 | bundle, and performs the configuration for the whole stack. 22 | . 23 | It includes plugins for all of the common tasks in a Virtualmin system, such 24 | as Apache, MySQL/MariaDB, Postfix, SpamAssassin, etc. 25 | -------------------------------------------------------------------------------- /t/data/etc/webmin/miniserv.conf.orig: -------------------------------------------------------------------------------- 1 | port=10000 2 | root=/usr/libexec/webmin 3 | mimetypes=/usr/libexec/webmin/mime.types 4 | addtype_cgi=internal/cgi 5 | realm=Webmin Server 6 | logfile=/var/webmin/miniserv.log 7 | errorlog=/var/webmin/miniserv.error 8 | pidfile=/var/webmin/miniserv.pid 9 | logtime=168 10 | ppath= 11 | ssl=1 12 | no_ssl2=1 13 | no_ssl3=1 14 | no_tls1=1 15 | no_tls1_1=1 16 | ssl_honorcipherorder=1 17 | no_sslcompression=1 18 | env_WEBMIN_CONFIG=t/data/etc/webmin 19 | env_WEBMIN_VAR=t/data/var/webmin 20 | atboot=1 21 | logout=t/data/etc/webmin/logout-flag 22 | listen=10000 23 | denyfile=\.pl$ 24 | log=1 25 | blockhost_failures=5 26 | blockhost_time=60 27 | syslog=1 28 | ipv6=1 29 | session=1 30 | premodules=WebminCore 31 | server=MiniServ/1.831 32 | userfile=t/data/etc/webmin/miniserv.users 33 | keyfile=t/data/etc/webmin/miniserv.pem 34 | passwd_file=t/data/etc/shadow 35 | passwd_uindex=0 36 | passwd_pindex=1 37 | passwd_cindex=2 38 | passwd_mindex=4 39 | passwd_mode=0 40 | preroot=authentic-theme 41 | passdelay=1 42 | failed_script=/etc/webmin/failed.pl 43 | login_script=/etc/webmin/login.pl 44 | cipher_list_def=1 45 | logout_script=/etc/webmin/logout.pl 46 | webprefix= 47 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Etckeeper.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Etckeeper; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our (%gconfig, %miniserv); 8 | our $trust_unknown_referers = 1; 9 | 10 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 11 | 12 | sub new { 13 | my ($class, %args) = @_; 14 | 15 | # inherit from Plugin 16 | my $self = $class->SUPER::new(name => 'Etckeeper', %args); 17 | 18 | return $self; 19 | } 20 | 21 | sub actions { 22 | my $self = shift; 23 | 24 | use Cwd; 25 | my $cwd = getcwd(); 26 | my $root = $self->root(); 27 | chdir($root); 28 | $0 = "$root/virtual-server/config-system.pl"; 29 | push(@INC, $root); 30 | push(@INC, "$root/vendor_perl"); 31 | eval 'use WebminCore'; ## no critic 32 | init_config(); 33 | 34 | $self->spin(); 35 | 36 | # Configure etckeeper on RHEL 37 | if (&has_command('etckeeper')) { 38 | if ($gconfig{'os_type'} eq 'redhat-linux' || 39 | $gconfig{'os_type'} eq "suse-linux") { 40 | $self->logsystem("etckeeper init"); 41 | $self->logsystem("systemctl enable etckeeper.timer"); 42 | $self->logsystem("systemctl start etckeeper.timer"); 43 | } 44 | $self->done(1); # OK! 45 | } 46 | else { 47 | $self->done(2); # Not installed but should have been 48 | } 49 | } 50 | 51 | 1; 52 | 53 | -------------------------------------------------------------------------------- /t/02-actions.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use 5.010; 3 | 4 | # Test actions in the Dummy bundle, on a test data set 5 | require_ok('Virtualmin::Config'); 6 | 7 | use Cwd; 8 | my $cwd = getcwd(); 9 | 10 | SKIP: { 11 | skip "Webmin isn't installed.", 3 12 | if (!-e "/usr/libexec/webmin/web-lib-funcs.pl"); 13 | 14 | $ENV{'WEBMIN_CONFIG'} = $cwd . "/t/data/etc/webmin"; 15 | $ENV{'WEBMIN_VAR'} = $cwd . "/t/data/var/webmin"; 16 | $ENV{'MINISERV_CONFIG'} = $ENV{'WEBMIN_CONFIG'} . "/miniserv.conf"; 17 | 18 | # Copy a test config file 19 | use File::Copy; 20 | copy( 21 | "$cwd/t/data/etc/webmin/miniserv.conf.orig", 22 | "$cwd/t/data/etc/webmin/miniserv.conf" 23 | ); 24 | 25 | my $bundle = Virtualmin::Config->new(bundle => 'Dummy', log => '/dev/null'); 26 | ok($bundle->{bundle} eq 'Dummy', "Bundle is Dummy"); 27 | 28 | ok($bundle->run(), "Config->run()"); 29 | 30 | ok(file_changed(), "Config file changed"); 31 | 32 | clean_up(); 33 | 34 | # Check to be sure the change within the actions in Test.pm plugin were applied 35 | sub file_changed { 36 | open(my $miniserv, "<", "$cwd/t/data/etc/webmin/miniserv.conf") 37 | || die "Cannot open miniserv.conf"; 38 | while (<$miniserv>) { 39 | return $_ if /dummy-theme/; 40 | } 41 | } 42 | 43 | sub clean_up { 44 | if (-e "$cwd/t/data/etc/webmin/miniserv.conf") { 45 | unlink "$cwd/t/data/etc/webmin/miniserv.conf"; 46 | } 47 | } 48 | 49 | } 50 | done_testing(); 51 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.014. 2 | use strict; 3 | use warnings; 4 | 5 | use 5.016; 6 | 7 | use ExtUtils::MakeMaker; 8 | 9 | my %WriteMakefileArgs = ( 10 | "ABSTRACT" => "Configure a system for use by Virtualmin", 11 | "AUTHOR" => "Joe Cooper ", 12 | "CONFIGURE_REQUIRES" => {"ExtUtils::MakeMaker" => 0}, 13 | "DISTNAME" => "Virtualmin-Config", 14 | "EXE_FILES" => ["bin/virtualmin-config-system"], 15 | "LICENSE" => "gpl", 16 | "MIN_PERL_VERSION" => "5.016", 17 | "NAME" => "Virtualmin::Config", 18 | "PREREQ_PM" => {"Log::Log4perl" => 0, "Term::ANSIColor" => 0}, 19 | "TEST_REQUIRES" => 20 | {"ExtUtils::MakeMaker" => 0, "File::Spec" => 0, "Test::More" => 0}, 21 | "VERSION" => "8.0.4", 22 | "test" => {"TESTS" => "t/*.t"} 23 | ); 24 | 25 | 26 | my %FallbackPrereqs = ( 27 | "ExtUtils::MakeMaker" => 0, 28 | "File::Spec" => 0, 29 | "Log::Log4perl" => 0, 30 | "Term::ANSIColor" => 0, 31 | "Test::More" => 0 32 | ); 33 | 34 | 35 | unless (eval { ExtUtils::MakeMaker->VERSION(6.63_03) }) { 36 | delete $WriteMakefileArgs{TEST_REQUIRES}; 37 | delete $WriteMakefileArgs{BUILD_REQUIRES}; 38 | $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; 39 | } 40 | 41 | delete $WriteMakefileArgs{CONFIGURE_REQUIRES} 42 | unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; 43 | 44 | WriteMakefile(%WriteMakefileArgs); 45 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/SELinux.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::SELinux; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | use Time::HiRes qw( sleep ); # XXX Figure out how to not need this. 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our $trust_unknown_referers = 1; 11 | 12 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 13 | 14 | sub new { 15 | my ($class, %args) = @_; 16 | 17 | # inherit from Plugin 18 | my $self = $class->SUPER::new(name => 'SELinux', %args); 19 | 20 | return $self; 21 | } 22 | 23 | # actions method performs whatever configuration is needed for this 24 | # plugin. XXX Needs to make a backup so changes can be reverted. 25 | sub actions { 26 | my $self = shift; 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | if (!-x "/usr/sbin/setsebool") { 38 | log->info("SELinux doesn't seem to be installed. Skipping."); 39 | return 1; 40 | } 41 | 42 | $self->spin(); 43 | sleep 0.3; # XXX Useless sleep, prevent spin from ending before it starts 44 | eval { 45 | 46 | $self->done(1); # OK! 47 | }; 48 | if ($@) { 49 | $log->error("Error configuring SELinux: $@"); 50 | $self->done(0); 51 | } 52 | } 53 | 54 | 1; 55 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Shells.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Shells; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | use Time::HiRes qw( sleep ); # XXX Figure out how to not need this. 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our $trust_unknown_referers = 1; 11 | 12 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 13 | 14 | sub new { 15 | my ($class, %args) = @_; 16 | 17 | # inherit from Plugin 18 | my $self = $class->SUPER::new(name => 'Shells', %args); 19 | 20 | return $self; 21 | } 22 | 23 | # actions method performs whatever configuration is needed for this 24 | # plugin. XXX Needs to make a backup so changes can be reverted. 25 | sub actions { 26 | my $self = shift; 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | sleep 0.3; # XXX Useless sleep, prevent spin from ending before it starts 39 | eval { 40 | my $lref = read_file_lines("/etc/shells"); 41 | my $idx = indexof("/bin/false", @$lref); 42 | if ($idx < 0) { 43 | 44 | # XXX Do we need jk_chrootsh here, or is it added by the package? 45 | push(@$lref, "/bin/false"); 46 | flush_file_lines("/etc/shells"); 47 | } 48 | $self->done(1); # OK! 49 | }; 50 | if ($@) { 51 | $log->error("Error configuring Shells: $@"); 52 | $self->done(0); 53 | } 54 | } 55 | 56 | 1; 57 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/MySQL.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::MySQL; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'MySQL', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. TODO Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | foreign_require("init"); 40 | if (init::action_status("mariadb")) { 41 | init::enable_at_boot("mariadb"); 42 | } 43 | elsif ($gconfig{'os_type'} eq "freebsd" || init::action_status("mysql")) { 44 | init::enable_at_boot("mysql"); 45 | } 46 | else { 47 | init::enable_at_boot("mysqld"); 48 | } 49 | foreign_require("mysql"); 50 | if (!mysql::is_mysql_running()) { 51 | mysql::start_mysql(); 52 | } 53 | $self->done(1); 54 | }; 55 | if ($@) { 56 | $log->error("Error configuring MySQL/MariaDB: $@"); 57 | $self->done(0); 58 | } 59 | } 60 | 61 | 1; 62 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/SpamAssassin.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::SpamAssassin; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | use Time::HiRes qw( sleep ); # XXX Figure out how to not need this. 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our $trust_unknown_referers = 1; 11 | 12 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 13 | 14 | sub new { 15 | my ($class, %args) = @_; 16 | 17 | # inherit from Plugin 18 | my $self = $class->SUPER::new(name => 'SpamAssassin', %args); 19 | 20 | return $self; 21 | } 22 | 23 | # actions method performs whatever configuration is needed for this 24 | # plugin. XXX Needs to make a backup so changes can be reverted. 25 | sub actions { 26 | my $self = shift; 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | sleep 0.3; # XXX Useless sleep, prevent spin from ending before it starts 39 | eval { 40 | foreign_require("init", "init-lib.pl"); 41 | 42 | # Stop it, so a default install is small. Can be enabled during wizard. 43 | init::disable_at_boot("spamassassin"); 44 | init::stop_action("spamassassin"); 45 | init::disable_at_boot("spamd"); 46 | init::stop_action("spamd"); 47 | $self->done(1); # OK! 48 | }; 49 | if ($@) { 50 | $log->error("Error configuring SpamAssassin: $@"); 51 | $self->done(0); 52 | } 53 | } 54 | 55 | 1; 56 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/PostgreSQL.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::PostgreSQL; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'PostgreSQL', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. TODO Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | if (foreign_check("postgresql")) { 38 | $self->spin(); 39 | eval { 40 | my $err; # We should handle errors better here. 41 | foreign_require("postgresql", "postgresql-lib.pl"); 42 | if (!-r $postgresql::config{'hba_conf'}) { 43 | 44 | # Needs to be initialized 45 | $err = postgresql::setup_postgresql(); 46 | } 47 | if (postgresql::is_postgresql_running() == 0) { 48 | $err = postgresql::start_postgresql(); 49 | } 50 | 51 | #if ($err) { # Log an error } # Something went wrong 52 | $self->done(0); 53 | }; 54 | if ($@) { 55 | $log->error("Error configuring PostgreSQL: $@"); 56 | $self->done(1); 57 | } 58 | } 59 | } 60 | 61 | 1; 62 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Mailman.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Mailman; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Mailman', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. TODO Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | if (foreign_check("mailman")) { 38 | $self->spin(); 39 | eval { 40 | my $err; # We should handle errors better here. 41 | foreign_require("virtualmin-mailman", "virtualmin-mailman-lib.pl"); 42 | my @lists = virtualmin_mailman::list_lists(); 43 | my ($mlist) = grep { $_->{'list'} eq 'mailman' } @lists; 44 | if (!$mlist) { 45 | 46 | # Need to create 47 | virtualmin_mailman::create_list( 48 | "mailman", undef, "Default mailing list", 49 | undef, 50 | "root\@" . get_system_hostname(), 51 | time() . $$ 52 | ); 53 | } 54 | 55 | $self->done(0); 56 | }; 57 | if ($@) { 58 | $log->error("Error configuring Mailman: $@"); 59 | $self->done(1); 60 | } 61 | } 62 | } 63 | 64 | 1; 65 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Webalizer.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Webalizer; 2 | use strict; 3 | use warnings; 4 | use 5.010; 5 | no warnings qw(once); 6 | use parent 'Virtualmin::Config::Plugin'; 7 | use Cwd; 8 | 9 | our $config_directory; 10 | our (%gconfig, %miniserv); 11 | our $trust_unknown_referers = 1; 12 | 13 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 14 | 15 | sub new { 16 | my ($class, %args) = @_; 17 | 18 | # inherit from Plugin 19 | my $self = $class->SUPER::new(name => 'Webalizer', %args); 20 | 21 | return $self; 22 | } 23 | 24 | # actions method performs whatever configuration is needed for this 25 | # plugin. XXX Needs to make a backup so changes can be reverted. 26 | sub actions { 27 | my $self = shift; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | 39 | if (has_command("webalizer")) { 40 | eval { 41 | foreign_require("webalizer", "webalizer-lib.pl"); 42 | my $conf = webalizer::get_config(); 43 | webalizer::save_directive($conf, "IncrementalName", "webalizer.current"); 44 | webalizer::save_directive($conf, "HistoryName", "webalizer.hist"); 45 | webalizer::save_directive($conf, "DNSCache", "dns_cache.db"); 46 | flush_file_lines($webalizer::config{'webalizer_conf'}); 47 | $self->done(1); # OK! 48 | }; 49 | if ($@) { 50 | $log->error("Error configuring Webalizer: $@"); 51 | $self->done(0); 52 | } 53 | } 54 | else { 55 | $log->error("Webalizer is not installed on this system, skipping configuration."); 56 | $self->done(2); 57 | } 58 | } 59 | 60 | 1; 61 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Test.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Test; 2 | use strict; 3 | use warnings; 4 | use 5.010; 5 | use Term::ANSIColor qw(:constants); 6 | use parent 'Virtualmin::Config::Plugin'; 7 | use Log::Log4perl; 8 | 9 | sub new { 10 | my ($class, %args) = @_; 11 | 12 | # inherit from Plugin 13 | my $self = $class->SUPER::new(name => 'Test', %args); 14 | return $self; 15 | } 16 | 17 | # actions method performs whatever configuration is needed for this 18 | # plugin. TODO Needs to make a backup so changes can be reverted. 19 | sub actions { 20 | my $self = shift; 21 | use Cwd; 22 | my $cwd = getcwd(); 23 | my $root = $self->root(); 24 | my $log = Log::Log4perl->get_logger(); 25 | $log->info("This is logging from the test plugin."); 26 | chdir($root); 27 | $0 = "$root/virtual-server/config-system.pl"; 28 | push(@INC, $root); 29 | push(@INC, "$root/vendor_perl"); 30 | 31 | #use lib $root; 32 | eval 'use WebminCore'; ## no critic 33 | $ENV{'WEBMIN_CONFIG'} = $cwd . "/t/data/etc/webmin"; 34 | $ENV{'WEBMIN_VAR'} ||= $cwd . "/t/data/var/webmin"; 35 | $ENV{'MINISERV_CONFIG'} = $ENV{'WEBMIN_CONFIG'} . "/miniserv.conf"; 36 | 37 | # TODO Somehow get init_config() into $self->config, or something. 38 | init_config(); 39 | 40 | $self->spin(); 41 | eval { 42 | foreign_require("webmin", "webmin-lib.pl"); 43 | my %gconfig; 44 | get_miniserv_config(\%gconfig); 45 | $gconfig{'theme'} = "dummy-theme"; 46 | put_miniserv_config(\%gconfig); 47 | $self->done(1); 48 | }; 49 | if ($@) { 50 | $log->error("Error configuring Test plugin: $@"); 51 | $self->done(0); 52 | } 53 | } 54 | 55 | sub tests { 56 | my $self = shift; 57 | 58 | # Pretend to test something 59 | $self->spin(); 60 | eval { 61 | sleep 2; 62 | $self->done(1); 63 | }; 64 | if ($@) { 65 | $log->error("Error running tests for Test plugin: $@"); 66 | $self->done(0); 67 | } 68 | } 69 | 70 | 1; 71 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/NTP.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::NTP; 2 | use strict; 3 | use warnings; 4 | use 5.010; 5 | use parent qw(Virtualmin::Config::Plugin); 6 | use Time::HiRes qw( sleep ); 7 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 8 | 9 | sub new { 10 | my ($class, %args) = @_; 11 | 12 | # inherit from Plugin 13 | my $self = $class->SUPER::new(name => 'NTP', %args); 14 | 15 | return $self; 16 | } 17 | 18 | # actions method performs whatever configuration is needed for this 19 | # plugin. XXX Needs to make a backup so changes can be reverted. 20 | sub actions { 21 | my $self = shift; 22 | my $clocksource; 23 | 24 | $self->spin(); 25 | sleep 0.5; 26 | eval { # try 27 | my $clockfile 28 | = "/sys/devices/system/clocksource/clocksource0/current_clocksource"; 29 | if (-e $clockfile) { 30 | open(my $CLOCK, "<", $clockfile) or die "Couldn't open $clockfile: $!"; 31 | $clocksource = do { local $/ = <$CLOCK> }; 32 | close $CLOCK; 33 | chomp($clocksource); 34 | } 35 | else { 36 | $clocksource = ""; 37 | } 38 | if ($clocksource eq "kvm-clock") { 39 | $log->info("System clock source is kvm-clock, skipping NTP"); 40 | } 41 | elsif ($clocksource eq "" 42 | || $clocksource eq "jiffies" 43 | || !defined($clocksource)) 44 | { 45 | $log->info("Could not determine system clock source, skipping NTP"); 46 | } 47 | elsif (-x "/usr/sbin/ntpdate-debian") { 48 | $self->logsystem("ntpdate-debian"); 49 | if (init::action_status("ntpd")) { 50 | init::enable_at_boot("ntpd"); 51 | } 52 | } 53 | elsif (-x "/usr/sbin/ntpdate") { 54 | $self->logsystem("ntpdate"); 55 | if (init::action_status("ntpd")) { 56 | init::enable_at_boot("ntpd"); 57 | } 58 | } 59 | 60 | $self->done(1); # OK! 61 | } or do { # catch 62 | $self->done(0); # Something failed 63 | $log->info("Something went wrong with NTP configuration"); 64 | return; 65 | }; 66 | } 67 | 68 | 1; 69 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Stack.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Stack; 2 | use strict; 3 | use warnings; 4 | use 5.010_001; 5 | 6 | # A stack for configuring inside plugins 7 | 8 | sub new { 9 | my ($class, %args) = @_; 10 | my $self = {}; 11 | return bless $self, $class; 12 | } 13 | 14 | # Common modules for all stacks (excluding DNS, mail and extra) 15 | sub common_modules { 16 | return ( 17 | "Webmin", "MySQL", "Firewall", 18 | "Quotas", "Shells", "Virtualmin", 19 | "Etckeeper", "Apache", "AWStats", 20 | "Fail2ban" 21 | ); 22 | } 23 | 24 | # Modules related to DNS 25 | sub dns_modules { 26 | return ( 27 | "Bind" 28 | ); 29 | } 30 | 31 | # Modules related to mail 32 | sub mail_modules { 33 | return ( 34 | "Postfix", "Dovecot", "SASL", 35 | "Procmail", "SpamAssassin", "ClamAV", 36 | ); 37 | } 38 | 39 | # Extra (resourceful) modules 40 | sub full_modules { 41 | return ( 42 | "ProFTPd", "Usermin", 43 | ); 44 | } 45 | 46 | # Replacement logic for modules 47 | sub replacements { 48 | my ($type) = @_; 49 | 50 | # Modern system with Firewalld? 51 | my $firewalld = 52 | grep { -x "$_/firewall-cmd" } split(/:/, "/usr/bin:/bin:$ENV{PATH}"); 53 | 54 | # Define replacements 55 | return { 56 | "Firewall" => $firewalld ? "Firewalld" : "Firewall", 57 | "Fail2ban" => $firewalld ? "Fail2banFirewalld" : "Fail2ban", 58 | "Apache" => $type eq 'lemp' ? "Nginx" : "Apache", 59 | }; 60 | } 61 | 62 | sub list { 63 | my ($self, $type, $subtype) = @_; 64 | 65 | # Get common and optional full modules 66 | my @modules = common_modules(); 67 | if ($subtype eq 'full') { 68 | push(@modules, dns_modules()); 69 | push(@modules, mail_modules()); 70 | push(@modules, full_modules()); 71 | } 72 | 73 | # Apply replacements 74 | my %replacements = %{ replacements($type) }; 75 | @modules = map { $replacements{$_} // $_ } @modules; 76 | 77 | return \@modules; 78 | } 79 | 80 | 1; 81 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Firewalld.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Firewalld; 2 | 3 | # Enables firewalld and installs a reasonable set of rules. 4 | use strict; 5 | use warnings; 6 | no warnings qw(once); 7 | use parent 'Virtualmin::Config::Plugin'; 8 | 9 | our $config_directory; 10 | our (%gconfig, %miniserv); 11 | our $trust_unknown_referers = 1; 12 | 13 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 14 | 15 | sub new { 16 | my ($class, %args) = @_; 17 | 18 | # inherit from Plugin 19 | my $self = $class->SUPER::new(name => 'Firewalld', %args); 20 | 21 | return $self; 22 | } 23 | 24 | # actions method performs whatever configuration is needed for this 25 | # plugin. XXX Needs to make a backup so changes can be reverted. 26 | sub actions { 27 | my $self = shift; 28 | 29 | use Cwd; 30 | my $cwd = getcwd(); 31 | my $root = $self->root(); 32 | chdir($root); 33 | $0 = "$root/virtual-server/config-system.pl"; 34 | push(@INC, $root); 35 | push(@INC, "$root/vendor_perl"); 36 | eval 'use WebminCore'; ## no critic 37 | init_config(); 38 | 39 | $self->spin(); 40 | my @services 41 | = qw(ssh smtp smtps smtp-submission ftp pop3 pop3s imap imaps http https dns mdns dns-over-tls); 42 | my @ports = qw(20/tcp 2222/tcp 10000-10100/tcp 20000/tcp 49152-65535/tcp); 43 | eval { 44 | foreign_require('init', 'init-lib.pl'); 45 | init::enable_at_boot('firewalld'); 46 | if (init::action_status('iptables')) { 47 | init::stop_action('iptables'); 48 | init::disable_at_boot('iptables'); 49 | } 50 | init::start_action('firewalld'); 51 | 52 | my $firewall_cmd = has_command('firewall-cmd'); 53 | if ($firewall_cmd) { 54 | $self->logsystem("$firewall_cmd --set-default-zone public"); 55 | foreach my $s (@services) { 56 | $self->logsystem("$firewall_cmd --zone=public --add-service=${s}"); 57 | $self->logsystem( 58 | "$firewall_cmd --zone=public --permanent --add-service=${s}"); 59 | } 60 | foreach my $s (@ports) { 61 | $self->logsystem("$firewall_cmd --zone=public --add-port=${s}"); 62 | $self->logsystem( 63 | "$firewall_cmd --zone=public --permanent --add-port=${s}"); 64 | } 65 | $self->logsystem("$firewall_cmd --complete-reload"); 66 | } 67 | $self->done(1); # OK! 68 | }; 69 | if ($@) { 70 | $log->error("Error configuring Firewalld: $@"); 71 | $self->done(0); 72 | } 73 | } 74 | 75 | 1; 76 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/AWStats.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::AWStats; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | use Time::HiRes qw( sleep ); 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our $trust_unknown_referers = 1; 11 | 12 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 13 | 14 | sub new { 15 | my ($class, %args) = @_; 16 | 17 | # inherit from Plugin 18 | my $self = $class->SUPER::new(name => 'AWStats', %args); 19 | 20 | return $self; 21 | } 22 | 23 | # actions method performs whatever configuration is needed for this 24 | # plugin. XXX Needs to make a backup so changes can be reverted. 25 | sub actions { 26 | my $self = shift; 27 | 28 | use Cwd; 29 | my $cwd = getcwd(); 30 | my $root = $self->root(); 31 | chdir($root); 32 | $0 = "$root/virtual-server/config-system.pl"; 33 | push(@INC, $root); 34 | push(@INC, "$root/vendor_perl"); 35 | eval 'use WebminCore'; ## no critic 36 | init_config(); 37 | 38 | $self->spin(); 39 | sleep 0.2; # XXX Pause to allow spin to start. 40 | eval { 41 | # Remove cronjobs for awstats on Debian/Ubuntu 42 | foreign_require("cron"); 43 | my @jobs = cron::list_cron_jobs(); 44 | my @dis = grep { 45 | $_->{'command'} 46 | =~ /\/usr\/share\/awstats\/tools\/(update|buildstatic)\.sh/ 47 | && $_->{'active'} 48 | } @jobs; 49 | if (@dis) { 50 | foreach my $job (@dis) { 51 | $job->{'active'} = 0; 52 | cron::change_cron_job($job); 53 | } 54 | } 55 | 56 | # Comment out cron job for awstats on CentOS/RHEL 57 | my $file = '/etc/cron.hourly/awstats'; 58 | if (-r $file) { 59 | my $lref = read_file_lines($file); 60 | foreach my $l (@$lref) { 61 | if ($l !~ /^\s*#/) { 62 | $l = "#" . $l; 63 | } 64 | } 65 | flush_file_lines($file); 66 | } 67 | }; 68 | if ($@) { 69 | $self->done(0); 70 | } 71 | else { 72 | # If system doesn't have AWStats support, disable it 73 | if (foreign_check("virtualmin-awstats")) { 74 | my %awstats_config = foreign_config("virtualmin-awstats"); 75 | if ($awstats_config{'awstats'} && !-r $awstats_config{'awstats'}) { 76 | $self->done(2); # Not installed! 77 | } 78 | else { 79 | $self->done(1); # OK 80 | } 81 | } 82 | else { 83 | $self->done(2); # Not installed! 84 | } 85 | } 86 | } 87 | 88 | 1; 89 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Procmail.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Procmail; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Procmail', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | foreign_require("procmail", "procmail-lib.pl"); 40 | my @recipes = procmail::get_procmailrc(); 41 | my ($defrec, $orgrec); 42 | foreach my $r (@recipes) { 43 | if ($r->{'name'}) { 44 | if ($r->{'name'} eq "DEFAULT") { 45 | $defrec = $r; 46 | } 47 | elsif ($r->{'name'} eq "ORGMAIL") { 48 | $orgrec = $r; 49 | } 50 | } 51 | } 52 | if ($defrec) { 53 | 54 | # Fix up this DEFAULT entry 55 | $defrec->{'value'} = '$HOME/Maildir/'; 56 | procmail::modify_recipe($defrec); 57 | } 58 | else { 59 | # Prepend a DEFAULT entry 60 | $defrec = {'name' => 'DEFAULT', 'value' => '$HOME/Maildir/'}; 61 | if (@recipes) { 62 | procmail::create_recipe_before($defrec, $recipes[0]); 63 | } 64 | else { 65 | procmail::create_recipe($defrec); 66 | } 67 | } 68 | if ($orgrec) { 69 | 70 | # Fix up this ORGMAIL entry 71 | $orgrec->{'value'} = '$HOME/Maildir/'; 72 | procmail::modify_recipe($orgrec); 73 | } 74 | else { 75 | # Prepend a ORGMAIL entry 76 | $orgrec = {'name' => 'ORGMAIL', 'value' => '$HOME/Maildir/'}; 77 | if (@recipes) { 78 | procmail::create_recipe_before($orgrec, $recipes[0]); 79 | } 80 | else { 81 | procmail::create_recipe($orgrec); 82 | } 83 | } 84 | $self->done(1); # OK! 85 | }; 86 | if ($@) { 87 | $log->error("Error configuring Procmail: $@"); 88 | $self->done(0); 89 | } 90 | } 91 | 92 | 1; 93 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Nginx.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Nginx; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | use Time::HiRes qw( sleep ); # XXX Figure out how to not need this. 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our $trust_unknown_referers = 1; 11 | 12 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 13 | 14 | sub new { 15 | my ($class, %args) = @_; 16 | 17 | # inherit from Plugin 18 | my $self = $class->SUPER::new(name => 'Nginx', %args); 19 | 20 | return $self; 21 | } 22 | 23 | # actions method performs whatever configuration is needed for this 24 | # plugin. XXX Needs to make a backup so changes can be reverted. 25 | sub actions { 26 | my $self = shift; 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | sleep 0.3; 39 | eval { 40 | 41 | $self->logsystem("systemctl enable nginx.service"); 42 | $self->logsystem("systemctl restart nginx.service"); 43 | 44 | my %vconfig = foreign_config("virtual-server"); 45 | $vconfig{'web'} = 0; 46 | $vconfig{'ssl'} = 0; 47 | $vconfig{'avail_virtualmin-dav'} = ''; 48 | $vconfig{'backup_feature_ssl'} = 0; 49 | 50 | if ($self->bundle() && $self->bundle() eq "MiniLEMP") { 51 | $vconfig{'plugins'} = 'virtualmin-nginx virtualmin-nginx-ssl'; 52 | } 53 | else { 54 | $vconfig{'plugins'} 55 | = 'virtualmin-awstats virtualmin-nginx virtualmin-nginx-ssl'; 56 | } 57 | save_module_config(\%vconfig, "virtual-server"); 58 | 59 | # Fix Nginx to start correctly after reboot 60 | my $systemd_path = "/etc/systemd/system"; 61 | if (-d $systemd_path) { 62 | write_file_contents($systemd_path . "/nginx.timer", 63 | "[Unit]\n" . 64 | "Description=Start Nginx after boot\n" . 65 | "PartOf=nginx.service\n\n" . 66 | "[Timer]\n" . 67 | "OnActiveSec=15\n" . 68 | "Unit=nginx.service\n\n" . 69 | "[Install]\n" . 70 | "WantedBy=multi-user.target\n"); 71 | $self->logsystem("systemctl daemon-reload"); 72 | $self->logsystem("systemctl enable nginx.timer"); 73 | $self->logsystem("systemctl restart nginx.timer"); 74 | } 75 | 76 | $self->done(1); # OK! 77 | }; 78 | if ($@) { 79 | $log->error("Error configuring Nginx: $@"); 80 | $self->done(0); 81 | } 82 | } 83 | 84 | 1; 85 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Limits.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Limits; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Limits', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | # Do we have /etc/sysctl.d? 40 | if (-d '/etc/sysctl.d') { 41 | open(my $fh, '>', '/etc/sysctl.d/10-virtualmin.conf') 42 | or die 'Unable to open /etc/sysctl.d/10-virtualmin.conf for writing'; 43 | print $fh '# Increase inotify limit, for noticron'; 44 | print $fh 45 | '# If, for some reason, you have more than a few thousand files'; 46 | print $fh '# in /etc, increase this value (will use more memory).'; 47 | print $fh 'fs.inotify.max_user_watches = 32768'; 48 | close $fh; 49 | } 50 | elsif (-e '/etc/sysctl.conf') { # sysctl.d, stick it into sysctl.conf 51 | open(my $fh, '+<', '/etc/sysctl.conf') 52 | or die 'Unable to open /etc/sysctl.conf for editing'; 53 | my $skip; 54 | while (my $line = <$fh>) { 55 | if ($line =~ /fs.inotify.max_user_watches/) { 56 | $log->warning('Found existing configuration: $line'); 57 | $log->warning('Cowardly refusing to overwrite it.'); 58 | $skip = 1; 59 | } 60 | } 61 | unless ($skip) { 62 | print $fh 'fs.inotify.max_user_watches = 32768'; 63 | } 64 | close $fh; 65 | } 66 | }; 67 | if ($@) { 68 | $log->error("Failed to write limits configuration: $@"); 69 | $self->done(0); 70 | } 71 | } 72 | 73 | 1; 74 | 75 | =pod 76 | 77 | =head1 Virtualmin::Config::Plugin::Limits 78 | 79 | Modify limits in sysctl.conf or sysctl.d. 80 | 81 | =head1 SYNOPSIS 82 | 83 | virtualmin config-system --include Limits 84 | 85 | =head1 LICENSE AND COPYRIGHT 86 | 87 | Licensed under the GPLv3. Copyright 2017, Joe Cooper 88 | 89 | =cut 90 | 91 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Firewall.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Firewall; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Firewall', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | my @tcpports 40 | = qw(ssh smtp submission smtps domain ftp ftp-data pop3 pop3s imap imaps http https 2222 10000:10100 20000 49152:65535); 41 | my @udpports = qw(domain); 42 | 43 | foreign_require("firewall", "firewall-lib.pl"); 44 | my @tables = firewall::get_iptables_save(); 45 | my @allrules = map { @{$_->{'rules'}} } @tables; 46 | if (@allrules) { 47 | my ($filter) = grep { $_->{'name'} eq 'filter' } @tables; 48 | if (!$filter) { 49 | $filter = { 50 | 'name' => 'filter', 51 | 'rules' => [], 52 | 'defaults' => 53 | {'INPUT' => 'ACCEPT', 'OUTPUT' => 'ACCEPT', 'FORWARD' => 'ACCEPT'} 54 | }; 55 | } 56 | foreach (@tcpports) { 57 | 58 | $log->info("Allowing traffic on TCP port: $_\n"); 59 | my $newrule = { 60 | 'chain' => 'INPUT', 61 | 'm' => [['', 'tcp']], 62 | 'p' => [['', 'tcp']], 63 | 'dport' => [['', $_]], 64 | 'j' => [['', 'ACCEPT']], 65 | }; 66 | splice(@{$filter->{'rules'}}, 0, 0, $newrule); 67 | } 68 | foreach (@udpports) { 69 | 70 | $log->info("Allowing traffic on UDP port: $_\n"); 71 | my $newrule = { 72 | 'chain' => 'INPUT', 73 | 'm' => [['', 'udp']], 74 | 'p' => [['', 'udp']], 75 | 'dport' => [['', $_]], 76 | 'j' => [['', 'ACCEPT']], 77 | }; 78 | splice(@{$filter->{'rules'}}, 0, 0, $newrule); 79 | } 80 | firewall::save_table($filter); 81 | firewall::apply_configuration(); 82 | } 83 | $self->done(1); # OK! 84 | }; 85 | if ($@) { 86 | $log->error("Error configuring Firewall: $@"); 87 | $self->done(0); 88 | } 89 | } 90 | 91 | 1; 92 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Webmin.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Webmin; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Webmin', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # Actions method performs whatever configuration is needed for this 23 | # plugin. TODO Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | # Configure status module 40 | foreign_require("status"); 41 | $status::config{'sched_mode'} = 1; 42 | $status::config{'sched_int'} ||= 5; 43 | $status::config{'sched_offset'} ||= 0; 44 | lock_file("$config_directory/status/config"); 45 | save_module_config(\%status::config, 'status'); 46 | unlock_file("$config_directory/status/config"); 47 | status::setup_cron_job(); 48 | # Disable Webmin upgrades from UI 49 | save_module_acl( { disallow => 'upgrade' }, 'root', 'webmin' ); 50 | # Update Webmin configuration 51 | foreign_require("webmin"); 52 | $gconfig{'nowebminup'} = 1; 53 | $gconfig{'theme'} = "authentic-theme"; 54 | $gconfig{'mobile_theme'} = "authentic-theme"; 55 | $gconfig{'logfiles'} = 1; 56 | lock_file("$config_directory/config"); 57 | write_file("$config_directory/config", \%gconfig); 58 | unlock_file("$config_directory/config"); 59 | # Configure miniserv 60 | get_miniserv_config(\%miniserv); 61 | $miniserv{'preroot'} = "authentic-theme"; 62 | $miniserv{'ssl'} = 1; 63 | $miniserv{'ssl_cipher_list'} = $webmin::strong_ssl_ciphers; 64 | $miniserv{'twofactor_provider'} = 'totp'; 65 | put_miniserv_config(\%miniserv); 66 | webmin::build_installed_modules(1); 67 | $self->logsystem("/etc/webmin/restart-by-force-kill > /dev/null 2>&1"); 68 | # Mailboxes configuration 69 | my $mini_stack = 70 | (defined $self->bundle() && $self->bundle() =~ /mini/i) ? 71 | ($self->bundle() =~ /LEMP/i ? 'LEMP' : 'LAMP') : 0; 72 | # Configure the Read User Mail module to look for sub-folders 73 | # under ~/Maildir 74 | if (!$mini_stack) { 75 | my %mconfig = foreign_config("mailboxes"); 76 | $mconfig{'mail_usermin'} = "Maildir"; 77 | $mconfig{'from_virtualmin'} = 1; 78 | $mconfig{'spam_buttons'} = 'list,mail'; 79 | save_module_config(\%mconfig, "mailboxes"); 80 | } 81 | $self->done(1); # OK! 82 | }; 83 | if ($@) { 84 | $log->error("Error configuring Webmin: $@"); 85 | $self->done(0); # NOK! 86 | } 87 | } 88 | 89 | 1; 90 | -------------------------------------------------------------------------------- /README.pod: -------------------------------------------------------------------------------- 1 | 2 | =pod 3 | 4 | =encoding utf8 5 | 6 | =head1 NAME 7 | 8 | Virtualmin::Config - A collection of plugins to initialize the configuration 9 | of services that Virtualmin manages, and a command line tool called 10 | config-system to run them. It can be thought of as a very specialized 11 | configuration management system (e.g. puppet, chef, whatever) for doing 12 | just one thing (setting up a system for Virtualmin). It has basic dependency 13 | resolution (via topological sort), logging, and ties into the Webmin API to 14 | make some common tasks (like starting/stopping services, setting them to run 15 | on boot) simpler to code. 16 | 17 | =head1 SYNOPSIS 18 | 19 | my $bundle = Virtualmin::Config->new(bundle => 'LAMP'); 20 | $bundle->run(); 21 | 22 | You can also call it with specific plugins, rather than a whole bundle of 23 | plugins. 24 | 25 | my $plugin = Virtualmin::Config->new(include => 'Apache'); 26 | $plugin->run(); 27 | 28 | Adding new features to the installer, or modifying installer features, should 29 | be done by creating new plugins or by adding to existing ones. 30 | 31 | =head1 DESCRIPTION 32 | 33 | This is a mini-framework for configuring elements of a Virtualmin system. It 34 | uses Webmin as a library to abstract common configuration tasks, provides a 35 | friendly status indicator, and makes it easy to pick and choose the kind of 36 | configuration you want (should you choose to go that route). The Virtualmin 37 | install script chooses either the LAMP (with Apache) or LEMP (with nginx) 38 | bundle, and performs the configuration for the whole stack. 39 | 40 | It includes plugins for all of the common tasks in a Virtualmin system, such 41 | as Apache, MySQL/MariaDB, Postfix, SpamAssassin, etc. 42 | 43 | =head1 INSTALLATION 44 | 45 | The recommended installation method is to use native packages for your 46 | distribution. We provide packages for Debian, Ubuntu, CentOS/RHEL, and Fedora 47 | in our repositories. 48 | 49 | You can use the standard Perl process to install from the source tarball or 50 | git clone: 51 | 52 | perl Makefile.PL 53 | make 54 | make test 55 | make install 56 | 57 | Or, use your system native package manager. The followin assumes you have all 58 | of the packages needed to build native packages installed. 59 | 60 | To build a dpkg for Debian/Ubuntu: 61 | 62 | dpkg-buildpackage -b -rfakeroot -us -uc 63 | 64 | And, for CentOS/Fedora/RHEL/etc. RPM distributions: 65 | 66 | dzil build # Creates a tarball 67 | cp Virtualmin-Config-*.tar.gz ~/rpmbuild/SOURCES 68 | rpmbuild -bb virtualmin-config.spec 69 | 70 | =head1 ATTRIBUTES 71 | 72 | =over 73 | 74 | =item bundle 75 | 76 | Selects the plugin bundle to be installed. A bundle is a list of plugins 77 | configured in a C class. 78 | 79 | =item include 80 | 81 | One or more additional plugins to include in the C. This can be 82 | used alongside C or by itself. Dependencies will also be run, and 83 | there is no way to disable dependencies (because they're depended on!). 84 | 85 | =item exclude 86 | 87 | One or more plugins to remove from the selected C. Plugins that are 88 | needed to resolve dependencies will be re-added automatically. 89 | 90 | =back 91 | 92 | =head1 METHODS 93 | 94 | =over 95 | 96 | =item run 97 | 98 | This method figures out which plugins to run (based on the C, 99 | C, and C attributes. 100 | 101 | =back 102 | 103 | =head1 LICENSE AND COPYRIGHT 104 | 105 | Licensed under the GPLv3. Copyright 2017 Virtualmin, Inc. 106 | 107 | =cut 108 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Dovecot.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Dovecot; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Dovecot', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | foreign_require("init", "init-lib.pl"); 40 | foreign_require("dovecot", "dovecot-lib.pl"); 41 | 42 | # Work out dirs for control and index files 43 | foreign_require("mount", "mount-lib.pl"); 44 | my $indexes = ""; 45 | my ($homedir) = mount::filesystem_for_dir("/home"); 46 | my ($vardir) = mount::filesystem_for_dir("/var"); 47 | if ($homedir ne $vardir) { 48 | if (!-d "/var/lib") { 49 | make_dir("/var/lib", oct(755)); 50 | } 51 | if (!-d "/var/lib/dovecot-virtualmin") { 52 | make_dir("/var/lib/dovecot-virtualmin", oct(755)); 53 | } 54 | make_dir("/var/lib/dovecot-virtualmin/index", oct(777)); 55 | make_dir("/var/lib/dovecot-virtualmin/control", oct(777)); 56 | $indexes = ":INDEX=/var/lib/dovecot-virtualmin/index/%u" 57 | . ":CONTROL=/var/lib/dovecot-virtualmin/control/%u"; 58 | } 59 | 60 | my $conf = dovecot::get_config(); 61 | dovecot::save_directive($conf, "protocols", "imap pop3"); 62 | # 2.4 63 | if (dovecot::find("mail_path", $conf, 2)) { 64 | dovecot::save_directive($conf, "mail_path", '~/Maildir'); 65 | dovecot::save_directive($conf, "mail_driver", 'maildir'); 66 | dovecot::save_directive($conf, "mail_home", undef); 67 | dovecot::save_directive($conf, "mail_inbox_path", undef); 68 | } 69 | # 2.3 70 | elsif (dovecot::find("mail_location", $conf, 2)) { 71 | dovecot::save_directive($conf, "mail_location", 72 | "maildir:~/Maildir" . $indexes); 73 | } 74 | elsif (dovecot::find("default_mail_env", $conf, 2)) { 75 | dovecot::save_directive($conf, "default_mail_env", 76 | "maildir:~/Maildir" . $indexes); 77 | } 78 | if (my $uidl_format = dovecot::find("pop3_uidl_format", $conf, 2)) { 79 | dovecot::save_directive($conf, "pop3_uidl_format", $uidl_format->{value}); 80 | } 81 | dovecot::save_directive($conf, 82 | dovecot::version_atleast("2.4") 83 | ? ("auth_allow_cleartext", "yes") 84 | : ("disable_plaintext_auth", "no")); 85 | my $am = dovecot::find_value("auth_mechanisms", $conf, 2); 86 | if ($am && $am !~ /login/) { 87 | $am .= " login"; 88 | dovecot::save_directive($conf, "auth_mechanisms", $am); 89 | } 90 | flush_file_lines(); 91 | 92 | #print "Enabling Dovecot POP3 and IMAP servers\n"; 93 | init::enable_at_boot("dovecot"); 94 | if (init::status_action('dovecot') != 1) { 95 | init::start_action('dovecot'); 96 | } 97 | $self->done(1); # OK! 98 | }; 99 | if ($@) { 100 | $log->error("Failed to configure Dovecot: $@"); 101 | $self->done(0); 102 | } 103 | } 104 | 105 | 1; 106 | -------------------------------------------------------------------------------- /bin/virtualmin-config-system: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | use 5.010_001; 5 | use Getopt::Long; 6 | BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Color'; } 7 | use Pod::Usage qw(pod2usage); 8 | use Term::ANSIColor qw(:constants); 9 | use Virtualmin::Config; 10 | 11 | sub main { 12 | my ($argv) = @_; 13 | my %opt; 14 | my (@include, @exclude); 15 | GetOptions( 16 | \%opt, 'help|h', 'test|t', 'bundle|b=s', 'log|l=s', 'list-bundles|s', 17 | 'list-plugins|p', 18 | 'include|i=s{1,}' => \@include, 19 | 'exclude|x=s{1,}' => \@exclude, 20 | ); 21 | pod2usage(0) if $opt{help}; 22 | if ($opt{'list-bundles'}) { 23 | my $config = Virtualmin::Config->new(); 24 | my @bundles = $config->list_bundles(); 25 | say YELLOW . "Available Configuration Bundles:\n"; 26 | my $numcols = 4; 27 | my $cur = 0; 28 | printf " "; 29 | for my $b (@bundles) { 30 | printf(CYAN . "%-18s ", $b); 31 | $cur++; 32 | if ($cur == $numcols) { $cur = 0; printf "\n " . RESET; } 33 | } 34 | say ''; 35 | printf "%s\n", RESET; 36 | exit 0; 37 | } 38 | if ($opt{'list-plugins'}) { 39 | my $config = Virtualmin::Config->new(); 40 | my @plugins = $config->list_plugins(); 41 | say YELLOW . "Available Plugins:\n"; 42 | my $numcols = 4; 43 | my $cur = 0; 44 | printf " "; 45 | for my $p (@plugins) { 46 | printf(CYAN . "%-18s ", $p); 47 | $cur++; 48 | if ($cur == $numcols) { $cur = 0; printf "\n " . RESET; } 49 | } 50 | say ''; 51 | printf "%s\n", RESET; 52 | exit 0; 53 | } 54 | 55 | unless ($opt{bundle} || @include) { 56 | pod2usage(-perldocopt => 'Color'); 57 | } 58 | 59 | my $bundle = Virtualmin::Config->new( 60 | bundle => $opt{bundle}, 61 | log => $opt{log}, 62 | include => \@include, 63 | exclude => \@exclude, 64 | test => $opt{test}, 65 | ); 66 | 67 | $bundle->run(); 68 | 69 | return 0; 70 | } 71 | 72 | exit main(\@ARGV); 73 | 74 | =pod 75 | 76 | =head1 config-system 77 | 78 | Perform initial configuration of system services 79 | 80 | =head1 SYNOPSIS 81 | 82 | virtualmin config-system [options] 83 | 84 | Options: 85 | --help|-h Print this summary of options and exit 86 | --list-bundles|-s List available installation bundles 87 | --list-plugins|-p List available plugins 88 | --bundle|-b A bundle of plugins to execute 89 | --log|-l Path to a file for logging actions 90 | --include|-i One or more extra plugins to include 91 | --exclude|-x One or more plugins to exclude 92 | --test|-t Test services after configured (when available) 93 | 94 | =head1 OPTIONS 95 | 96 | =over 97 | 98 | =item --bundle 99 | 100 | A set of configuration options, to initialize the system for use as a Virtualmin 101 | system. Default plugin bundle is "LAMP", which configures Apache, as well as 102 | a variety of other components. "LEMP" replaces Apache with nginx. 103 | 104 | =item --include 105 | 106 | Include one or more additional plugins. Works with or without a bundle 107 | specified. Multiple plugins can be provided with this option, separated by 108 | spaces. If no bundle is specified, only the included plugins, and dependencies 109 | will be installed. 110 | 111 | =item --exclude 112 | 113 | Exclude one or more plugins from either the default bundle, if no bundle is 114 | specified, or from the bundle specified. 115 | 116 | =item --test 117 | 118 | Run tests for plugins, when available. Testing a service during configuration 119 | confirms whether it is in the expected state. For large plugin bundles this can 120 | make the process take a long time. Disabled by default. 121 | 122 | =back 123 | 124 | =head1 EXIT CODES 125 | 126 | Returns 0 on success, 1 on failure. 127 | 128 | =head1 LICENSE AND COPYRIGHT 129 | 130 | Licensed under the GPLv3. Copyright 2017, Joe Cooper 131 | 132 | =cut 133 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Net.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Net; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Net', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | if (foreign_check("net")) { 40 | foreign_require("net", "net-lib.pl"); 41 | my $dns = net::get_dns_config(); 42 | if (indexof("127.0.0.1", @{$dns->{'nameserver'}}) < 0) { 43 | unshift(@{$dns->{'nameserver'}}, "127.0.0.1"); 44 | net::save_dns_config($dns); 45 | } 46 | 47 | # Force 127.0.0.1 into name servers in resolv.conf 48 | # XXX This shouldn't be necessary. There's some kind of bug in net:: 49 | my $resolvconf = '/etc/resolv.conf'; 50 | my $rlref = read_file_lines($resolvconf); 51 | if (indexof('nameserver 127.0.0.1'), @{$rlref} < 0) { 52 | $log->info("Adding name server 127.0.0.1 to resolv.conf."); 53 | unshift(@{$rlref}, 'nameserver 127.0.0.1'); 54 | unshift(@{$rlref}, '# Added by Virtualmin.'); 55 | } 56 | flush_file_lines($resolvconf); 57 | 58 | # On Debian/Ubuntu, if there are extra interfaces files, we need 59 | # to update them, too. 60 | # Check for additional included config files. 61 | my @interfaces_d = glob "/etc/network/interfaces.d/*"; 62 | if (@interfaces_d) { 63 | 64 | # Find all of the dns-nameservers entries and update'em 65 | foreach my $includefile (@interfaces_d) { 66 | open my $fh, "<", $includefile 67 | or 68 | $log->warning("Failed to open network config file: $includefile."); 69 | close $fh; 70 | } 71 | } 72 | 73 | # Check to see if we're configured with dhcp. 74 | my @dhcp = grep { $_->{'dhcp'} } net::boot_interfaces(); 75 | foreach my $includefile (@interfaces_d) { 76 | open my $fh, '<', $includefile or die; 77 | while (my $line = <$fh>) { 78 | 79 | # This is not smart. 80 | if ($line =~ /inet .* dhcp/) { 81 | push @dhcp, "1"; # Just stick something truthy in there. 82 | } 83 | } 84 | } 85 | if (@dhcp) { 86 | $log->warn( 87 | "Detected DHCP-configured network. This probably isn't ideal."); 88 | my $lref; 89 | my $file = '/etc/dhcp/dhclient.conf'; 90 | if (-e $file) { 91 | $lref = read_file_lines($file); 92 | if (indexof("prepend domain-name-servers 127.0.0.1;", @{$lref}) < 0) { 93 | $log->info( 94 | "Attempting to add name server 127.0.0.1 to dhcp configuration."); 95 | push(@{$lref}, 'prepend domain-name-servers 127.0.0.1;'); 96 | push(@{$lref}, '# Added by Virtualmin.'); 97 | } 98 | flush_file_lines($file); 99 | } 100 | } 101 | 102 | # Restart Postfix so that it picks up the new resolv.conf 103 | foreign_require("virtual-server"); 104 | virtual_server::stop_service_mail(); 105 | virtual_server::start_service_mail(); 106 | } 107 | $self->done(1); # OK! 108 | }; 109 | if ($@) { 110 | $log->error("Failed to configure Net plugin: $@"); 111 | $self->done(0); 112 | } 113 | } 114 | 115 | 1; 116 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/ClamAV.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::ClamAV; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | use Time::HiRes qw( sleep ); 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our $trust_unknown_referers = 1; 11 | 12 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 13 | 14 | sub new { 15 | my ($class, %args) = @_; 16 | 17 | # inherit from Plugin 18 | my $self = $class->SUPER::new(name => 'ClamAV', %args); 19 | 20 | return $self; 21 | } 22 | 23 | # actions method performs whatever configuration is needed for this 24 | # plugin. XXX Needs to make a backup so changes can be reverted. 25 | sub actions { 26 | my $self = shift; 27 | 28 | use Cwd; 29 | my $cwd = getcwd(); 30 | my $root = $self->root(); 31 | chdir($root); 32 | $0 = "$root/virtual-server/config-system.pl"; 33 | push(@INC, $root); 34 | push(@INC, "$root/vendor_perl"); 35 | eval 'use WebminCore'; ## no critic 36 | init_config(); 37 | 38 | $self->spin(); 39 | sleep 0.2; # XXX Pause to allow spin to start. 40 | eval { 41 | # Make sure freshclam is not disabled 42 | my $fcconf = "/etc/sysconfig/freshclam"; 43 | if (-r $fcconf) { 44 | my $lref = read_file_lines($fcconf); 45 | foreach my $l (@$lref) { 46 | if ($l =~ /^FRESHCLAM_DELAY=disabled/) { 47 | $l = "#$l"; 48 | } 49 | } 50 | flush_file_lines($fcconf); 51 | } 52 | 53 | # Remove idiotic Example line from clamd scan.conf 54 | my $scanconf = "/etc/clamd.d/scan.conf"; 55 | if (-r $scanconf) { 56 | my $lref = read_file_lines($scanconf); 57 | foreach my $l (@$lref) { 58 | if ($l =~ /^Example/) { 59 | $l = "#$l"; 60 | } 61 | $l =~ s/#+\s*(LocalSocket\s.*)$/$1/; 62 | } 63 | flush_file_lines($scanconf); 64 | } 65 | 66 | # Do not run freshclam if there is a daemon 67 | foreign_require('init'); 68 | if (!init::action_status('clamav-freshclam')) { 69 | if (has_command('freshclam')) { 70 | $self->logsystem("freshclam"); 71 | } 72 | } 73 | else { 74 | # Restart daemon to refresh the database in background, 75 | # it will have higher chances of avoiding post-install 76 | # false positive errors on Debian systems 77 | if (init::action_status('clamav-freshclam') == 2) { 78 | 79 | # Restart it only if already running 80 | init::restart_action('clamav-freshclam'); 81 | } 82 | elsif (init::action_status('clamav-freshclam') == 1) { 83 | 84 | # We have a daemon but it's not running, then run 85 | # freshclam to avoid issues on RHEL system (dumb!) 86 | if (has_command('freshclam')) { 87 | $self->logsystem("freshclam"); 88 | } 89 | } 90 | } 91 | 92 | $self->done(1); # OK! 93 | }; 94 | if ($@) { 95 | $log->error("Failed to configure ClamAV: $@"); 96 | $self->done(0); 97 | } 98 | } 99 | 100 | sub tests { 101 | my $self = shift; 102 | 103 | use Cwd; 104 | my $cwd = getcwd(); 105 | my $root = $self->root(); 106 | chdir($root); 107 | $0 = "$root/virtual-server/config-system.pl"; 108 | push(@INC, $root); 109 | push(@INC, "$root/vendor_perl"); 110 | eval 'use WebminCore'; ## no critic 111 | init_config(); 112 | 113 | # RHEL/CentOS/Fedora 114 | # Start clamd@scan and run clamdscan just to prime the damned thing. 115 | foreign_require("init", "init-lib.pl"); 116 | $self->done(1); 117 | eval { 118 | if ($gconfig{'os_type'} eq 'redhat-linux') { 119 | if (init::action_status('clamd@scan')) { 120 | init::enable_at_boot('clamd@scan'); 121 | init::start_action('clamd@scan'); 122 | } 123 | elsif (init::action_status('clamd')) { 124 | init::enable_at_boot('clamd'); 125 | init::start_action('clamd'); 126 | } 127 | sleep 60; # XXX This is ridiculous. But, clam is ridiculous. 128 | # If RHEL/CentOS/Fedora, the clamav packages don't work, by default. 129 | if (!-e '/etc/clamd.conf') { 130 | eval { symlink('/etc/clamd.d/scan.conf', '/etc/clamd.conf'); }; 131 | } 132 | my $res = `clamdscan --quiet - < /etc/webmin/miniserv.conf`; 133 | if ($res) { die 1; } 134 | if (init::action_status('clamd@scan')) { 135 | init::stop_action('clamd@scan'); 136 | } 137 | elsif (init::action_status('clamd')) { 138 | init::stop_action('clamd'); 139 | } 140 | } 141 | elsif ($gconfig{'os_type'} eq 'debian-linux') { 142 | init::enable_at_boot('clamav-daemon'); 143 | init::start_action('clamav-daemon'); 144 | sleep 60; 145 | $self->logsystem("clamdscan --quiet - < /etc/webmin/miniserv.conf"); 146 | init::stop_action('clamav-daemon'); 147 | } 148 | $self->done(0); 149 | }; 150 | if ($@) { 151 | $log->error("Failed to test ClamAV: $@"); 152 | $self->done(0); 153 | } 154 | } 155 | 156 | 1; 157 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Bind.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Bind; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Bind', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | foreign_require("init", "init-lib.pl"); 40 | if (init::action_status("bind9")) { 41 | init::enable_at_boot("bind9"); 42 | } 43 | elsif (init::action_status("named")) { 44 | init::enable_at_boot("named"); 45 | } 46 | foreign_require("bind8", "bind8-lib.pl"); 47 | my $conffile = bind8::make_chroot($bind8::config{'named_conf'}); 48 | if (!-r $conffile) { 49 | $bind8::config{'named_conf'} =~ /^(\S+)\/([^\/]+)$/; 50 | my $conf_directory = $1; 51 | my $pid_file = $bind8::config{'pid_file'} || "/var/run/named.pid"; 52 | my $pid_dir; 53 | 54 | # Make sure all directories used by BIND exist 55 | my $chroot = bind8::get_chroot(); 56 | if ($chroot && !-d $chroot) { 57 | mkdir($chroot, oct(755)); 58 | } 59 | if (!-d bind8::make_chroot($conf_directory)) { 60 | mkdir(bind8::make_chroot($conf_directory), oct(755)); 61 | } 62 | if ($bind8::config{'master_dir'} 63 | && !-d bind8::make_chroot($bind8::config{'master_dir'})) 64 | { 65 | mkdir(bind8::make_chroot($bind8::config{'master_dir'}), oct(755)); 66 | } 67 | if ($bind8::config{'slave_dir'} 68 | && !-d bind8::make_chroot($bind8::config{'slave_dir'})) 69 | { 70 | mkdir(bind8::make_chroot($bind8::config{'slave_dir'}), oct(777)); 71 | } 72 | if ($pid_file =~ /^(.*)\//) { 73 | $pid_dir = $1; 74 | if (!-d bind8::make_chroot($pid_dir)) { 75 | mkdir(bind8::make_chroot($pid_dir), oct(777)); 76 | } 77 | } 78 | 79 | # Need to setup named.conf file, with root zone 80 | open(my $BOOT, ">", "$conffile"); 81 | print $BOOT "options {\n"; 82 | print $BOOT " directory \"$conf_directory\";\n"; 83 | print $BOOT " pid-file \"$pid_file\";\n"; 84 | print $BOOT " allow-recursion { localnets; 127.0.0.1; };\n"; 85 | print $BOOT " };\n"; 86 | print $BOOT "\n"; 87 | print $BOOT "zone \".\" {\n"; 88 | print $BOOT " type hint;\n"; 89 | print $BOOT " file \"$conf_directory/db.cache\";\n"; 90 | print $BOOT " };\n"; 91 | print $BOOT "\n"; 92 | close($BOOT); 93 | $self->logsystem("cp $root/bind8/db.cache " 94 | . bind8::make_chroot("$conf_directory/db.cache")); 95 | bind8::set_ownership(bind8::make_chroot("$conf_directory/db.cache")); 96 | bind8::set_ownership($conffile); 97 | } 98 | 99 | # Remove any options that would make BIND listen on localhost only 100 | undef(@bind8::get_config_cache); 101 | my $conf = bind8::get_config(); 102 | my $options = bind8::find("options", $conf); 103 | if ($options) { 104 | bind8::save_directive($options, "allow-query", [], 0); 105 | foreach my $dir ("listen-on", "listen-on-v6") { 106 | my @listen = bind8::find($dir, $options->{'members'}); 107 | next if (!@listen); 108 | 109 | # XXX This is ridiculous. 110 | next 111 | if (!defined($listen[0]->{'values'}) 112 | || !defined($listen[0]->{'values'}->[0]) 113 | || !defined($listen[0]->{'values'}->[1]) 114 | || !defined($listen[0]->{'type'}) 115 | || !defined($listen[0]->{'members'}) 116 | || !defined($listen[0]->{'members'}->[0]->{'name'})); 117 | if ( 118 | $listen[0]->{'values'}->[0] eq 'port' 119 | && $listen[0]->{'values'}->[1] eq '53' 120 | && $listen[0]->{'type'} 121 | && ( $listen[0]->{'members'}->[0]->{'name'} eq '127.0.0.1' 122 | || $listen[0]->{'members'}->[0]->{'name'} eq '::1') 123 | ) 124 | { 125 | $listen[0]->{'members'}->[0]->{'name'} = 'any'; 126 | } 127 | bind8::save_directive($options, $dir, \@listen, 1); 128 | } 129 | bind8::flush_file_lines(); 130 | } 131 | 132 | if (!bind8::is_bind_running()) { 133 | bind8::start_bind(); 134 | } 135 | else { 136 | bind8::restart_bind(); 137 | } 138 | 139 | $self->done(1); # OK! 140 | }; 141 | if ($@) { 142 | $log->error("Failed to configure BIND: $@"); 143 | $self->done(0); 144 | } 145 | } 146 | 147 | 1; 148 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Apache.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Apache; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | my $delay = 2; 14 | 15 | sub new { 16 | my ($class, %args) = @_; 17 | 18 | # inherit from Plugin 19 | my $self = $class->SUPER::new(name => 'Apache', %args); 20 | 21 | return $self; 22 | } 23 | 24 | # actions method performs whatever configuration is needed for this 25 | # plugin. XXX Needs to make a backup so changes can be reverted. 26 | sub actions { 27 | my $self = shift; 28 | 29 | use Cwd; 30 | my $cwd = getcwd(); 31 | my $root = $self->root(); 32 | chdir($root); 33 | $0 = "$root/virtual-server/config-system.pl"; 34 | push(@INC, $root); 35 | push(@INC, "$root/vendor_perl"); 36 | eval 'use WebminCore'; ## no critic 37 | init_config(); 38 | 39 | $self->spin(); 40 | eval { 41 | 42 | # Start Apache on boot if disabled 43 | foreign_require("init"); 44 | my @apache_cmds = ('apache2', 'httpd', 'httpd24'); 45 | foreach my $service (@apache_cmds) { 46 | if (init::action_status($service) == 1) { 47 | init::enable_at_boot($service); 48 | last; 49 | } 50 | } 51 | 52 | # On Debian and Ubuntu, enable some modules which are disabled by default 53 | my @apache_mods = qw( 54 | suexec ssl slotmem_shm rewrite proxy_http proxy_fcgi 55 | proxy_connect proxy_balancer proxy lbmethod_byrequests 56 | include http2 cgid fcgid auth_digest actions 57 | ); 58 | # Configure Debian/Ubuntu and SUSE systems 59 | if ($gconfig{'os_type'} =~ /^(debian|ubuntu|suse)-linux$/) { 60 | # Enable required modules using proper command 61 | $self->logsystem("a2enmod --quiet @apache_mods ; sleep $delay"); 62 | # Disable default Apache sites 63 | $self->logsystem("a2dissite --quiet 000-default default-ssl ; sleep $delay"); 64 | # Fix suEXEC path and document root 65 | my $fn = "/etc/apache2/suexec/www-data"; 66 | if (-r $fn) { 67 | my $apache2suexec = read_file_lines($fn); 68 | $apache2suexec->[0] = "/home"; 69 | $apache2suexec->[1] = "public_html"; 70 | flush_file_lines($fn); 71 | } 72 | # Restart Apache to apply changes 73 | $self->logsystem("apache2ctl restart ; sleep $delay"); 74 | } 75 | # Configure RH systems 76 | elsif ($gconfig{'os_type'} eq 'redhat-linux') { 77 | # Comment out config files that conflict 78 | foreach my $file ( 79 | "/etc/httpd/conf.d/welcome.conf", 80 | "/etc/httpd/conf.d/php.conf", 81 | "/etc/httpd/conf.d/awstats.conf") { 82 | next if (!-r $file); 83 | my $lref = read_file_lines($file); 84 | foreach my $l (@$lref) { 85 | if ($l !~ /^\s*#/) { 86 | $l = "#" . $l; 87 | } 88 | } 89 | flush_file_lines($file); 90 | } 91 | 92 | # Remove default SSL VirtualHost on RH systems 93 | if (-r '/etc/httpd/conf.d/ssl.conf') { 94 | my $file = '/etc/httpd/conf.d/ssl.conf'; 95 | my $lref = read_file_lines($file); 96 | my $virtual_host_section = 0; 97 | foreach my $l (@$lref) { 98 | if ($l !~ /^\s*#/) { 99 | if ($l =~ /^\s*{'file'}); 121 | } 122 | 123 | # Force use of PCI-compliant SSL ciphers (Intermediate level) 124 | apache::save_directive("SSLProtocol", ["-all +TLSv1.2 +TLSv1.3"], 125 | $conf, $conf); 126 | apache::save_directive( 127 | "SSLOpenSSLConfCmd", [ "Curves X25519:prime256v1:secp384r1" ], 128 | $conf, $conf 129 | ); 130 | my $SSLCipherSuite = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-". 131 | "SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-". 132 | "GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-". 133 | "CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-". 134 | "AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305"; 135 | apache::save_directive("SSLCipherSuite", [$SSLCipherSuite], $conf, $conf); 136 | apache::save_directive("SSLHonorCipherOrder", ["Off"], $conf, $conf); 137 | apache::save_directive("SSLSessionTickets", ["Off"], $conf, $conf); 138 | 139 | # Turn off server signatures, which aren't PCI compliant 140 | apache::save_directive("ServerTokens", ["Prod"], $conf, $conf); 141 | apache::save_directive("ServerSignature", ["Off"], $conf, $conf); 142 | apache::save_directive("TraceEnable", ["Off"], $conf, $conf); 143 | flush_file_lines(); 144 | 145 | # Clear module cache to ensure changes take effect 146 | apache::clear_apache_modules_cache(); 147 | 148 | $self->done(1); # OK! 149 | }; 150 | if ($@) { 151 | $log->error("Failed to configure Apache : $@"); 152 | $self->done(0); # NOK! 153 | } 154 | } 155 | 156 | 1; 157 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Fail2ban.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Fail2ban; 2 | 3 | # Enables fail2ban and sets up a reasonable set of rules. 4 | use strict; 5 | use warnings; 6 | no warnings qw(once numeric); 7 | use parent 'Virtualmin::Config::Plugin'; 8 | 9 | our $config_directory; 10 | our (%gconfig, %miniserv); 11 | our $trust_unknown_referers = 1; 12 | 13 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 14 | 15 | sub new { 16 | my ($class, %args) = @_; 17 | 18 | # inherit from Plugin 19 | my $self 20 | = $class->SUPER::new(name => 'Fail2ban', depends => ['Firewall'], %args); 21 | 22 | return $self; 23 | } 24 | 25 | # actions method performs whatever configuration is needed for this 26 | # plugin. XXX Needs to make a backup so changes can be reverted. 27 | sub actions { 28 | my $self = shift; 29 | my $err; 30 | 31 | # XXX Webmin boilerplate. 32 | use Cwd; 33 | my $cwd = getcwd(); 34 | my $root = $self->root(); 35 | chdir($root); 36 | $0 = "$root/virtual-server/config-system.pl"; 37 | push(@INC, $root); 38 | push(@INC, "$root/vendor_perl"); 39 | eval 'use WebminCore'; ## no critic 40 | init_config(); 41 | 42 | # End of Webmin boilerplate. 43 | 44 | $self->spin(); 45 | eval { 46 | if (has_command('fail2ban-server')) { 47 | 48 | foreign_require('init', 'init-lib.pl'); 49 | init::enable_at_boot('fail2ban'); 50 | 51 | # Create a jail.local with some basic config 52 | create_fail2ban_jail($self); 53 | create_fail2ban_firewalld(); 54 | 55 | # Switch backend to use systemd to avoid failure on 56 | # fail2ban starting when actual log file is missing 57 | # e.g.: Failed during configuration: Have not found 58 | # any log file for [name] jail 59 | &foreign_require('fail2ban'); 60 | my $jfile = "$fail2ban::config{'config_dir'}/jail.conf"; 61 | my @jconf = &fail2ban::parse_config_file($jfile); 62 | my @lconf 63 | = &fail2ban::parse_config_file(&fail2ban::make_local_file($jfile)); 64 | &fail2ban::merge_local_files(\@jconf, \@lconf); 65 | my $jconf = &fail2ban::parse_config_file($jfile); 66 | my ($def) = grep { $_->{'name'} eq 'DEFAULT' } @jconf; 67 | &fail2ban::lock_all_config_files(); 68 | &fail2ban::save_directive("backend", 'systemd', $def); 69 | &fail2ban::unlock_all_config_files(); 70 | 71 | # Restart fail2ban 72 | init::restart_action('fail2ban'); 73 | $self->done(1); 74 | } 75 | else { 76 | $self->done(2); # Not available, as in Oracle 9 77 | } 78 | }; 79 | if ($@) { 80 | $log->error("Error configuring Fail2ban: $@"); 81 | $self->done(0); # NOK! 82 | } 83 | } 84 | 85 | sub create_fail2ban_jail { 86 | my $self = shift; 87 | # Postfix addendum 88 | my $postfix_jail_extra = ""; 89 | 90 | my $is_debian = $gconfig{'real_os_type'} =~ /debian/i; 91 | my $is_ubuntu = $gconfig{'real_os_type'} =~ /ubuntu/i; 92 | my $debian10_or_older = $is_debian && $gconfig{'real_os_version'} <= 10; 93 | my $ubuntu20_or_older = $is_ubuntu && int($gconfig{'real_os_version'}) <= 20; 94 | 95 | if ($debian10_or_older || $ubuntu20_or_older) { 96 | $postfix_jail_extra = "\nbackend = auto\nlogpath = /var/log/mail.log"; 97 | } 98 | elsif ($is_debian || $is_ubuntu) { 99 | $postfix_jail_extra 100 | = "\nbackend = systemd\njournalmatch = _SYSTEMD_UNIT=postfix\@-.service"; 101 | } 102 | 103 | # Proftpd addendum 104 | my $proftpd_jail_extra = ""; 105 | if ($is_debian || $is_ubuntu) { 106 | $proftpd_jail_extra 107 | = "\nbackend = auto\nlogpath = /var/log/proftpd/proftpd.log"; 108 | } 109 | elsif ($gconfig{'os_type'} eq 'redhat-linux') { 110 | $proftpd_jail_extra = "\nprefregex =\n"; 111 | $proftpd_jail_extra 112 | .= 'failregex = \(\S+\[\]\)[: -]+ USER \S+: no such user found from \S+ \[[0-9.]+\] to \S+:\S+\s*$' 113 | . "\n"; 114 | $proftpd_jail_extra 115 | .= ' \(\S+\[\]\)[: -]+ USER \S+ \(Login failed\):.*\s+$' 116 | . "\n"; 117 | $proftpd_jail_extra 118 | .= ' \(\S+\[\]\)[: -]+ Maximum login attempts \([0-9]+\) exceeded, connection refused.*\s+$' 119 | . "\n"; 120 | $proftpd_jail_extra 121 | .= ' \(\S+\[\]\)[: -]+ SECURITY VIOLATION: \S+ login attempted\.\s+$' 122 | . "\n"; 123 | $proftpd_jail_extra 124 | .= ' \(\S+\[\]\)[: -]+ Maximum login attempts \(\d+\) exceeded\s+$'; 125 | } 126 | my $mini_stack = 127 | (defined $self->bundle() && $self->bundle() =~ /mini/i) ? 128 | ($self->bundle() =~ /LEMP/i ? 'LEMP' : 'LAMP') : 0; 129 | my $proftpd_block = $mini_stack ? '' : 130 | "[proftpd]\n" . 131 | "enabled = true\nport = ftp,ftp-data,ftps,ftps-data,2222$proftpd_jail_extra\n\n"; 132 | 133 | open(my $JAIL_LOCAL, '>', '/etc/fail2ban/jail.local'); 134 | print $JAIL_LOCAL <', '/etc/fail2ban/jail.d/00-firewalld.conf'); 162 | print $FIREWALLD_CONF <get_logger("virtualmin-config-system"); 17 | 18 | sub new { 19 | my ($class, %args) = @_; 20 | 21 | # inherit from Plugin 22 | my $self = $class->SUPER::new( 23 | name => 'Fail2banFirewalld', 24 | depends => ['Firewalld'], 25 | %args 26 | ); 27 | 28 | return $self; 29 | } 30 | 31 | # actions method performs whatever configuration is needed for this 32 | # plugin. XXX Needs to make a backup so changes can be reverted. 33 | sub actions { 34 | my $self = shift; 35 | my $err; 36 | 37 | # XXX Webmin boilerplate. 38 | use Cwd; 39 | my $cwd = getcwd(); 40 | my $root = $self->root(); 41 | chdir($root); 42 | $0 = "$root/virtual-server/config-system.pl"; 43 | push(@INC, $root); 44 | push(@INC, "$root/vendor_perl"); 45 | eval 'use WebminCore'; ## no critic 46 | init_config(); 47 | 48 | # End of Webmin boilerplate. 49 | 50 | $self->spin(); 51 | eval { 52 | if (has_command('fail2ban-server')) { 53 | 54 | foreign_require('init', 'init-lib.pl'); 55 | init::enable_at_boot('fail2ban'); 56 | 57 | # Create a jail.local with some basic config 58 | create_fail2ban_jail($self); 59 | create_fail2ban_firewalld(); 60 | 61 | # Switch backend to use systemd to avoid failure on 62 | # fail2ban starting when actual log file is missing 63 | # e.g.: Failed during configuration: Have not found any log file for [name] jail 64 | &foreign_require('fail2ban'); 65 | my $jfile = "$fail2ban::config{'config_dir'}/jail.conf"; 66 | my @jconf = &fail2ban::parse_config_file($jfile); 67 | my @lconf 68 | = &fail2ban::parse_config_file(&fail2ban::make_local_file($jfile)); 69 | &fail2ban::merge_local_files(\@jconf, \@lconf); 70 | my $jconf = &fail2ban::parse_config_file($jfile); 71 | my ($def) = grep { $_->{'name'} eq 'DEFAULT' } @jconf; 72 | &fail2ban::lock_all_config_files(); 73 | &fail2ban::save_directive("backend", 'systemd', $def); 74 | &fail2ban::unlock_all_config_files(); 75 | 76 | # Restart fail2ban 77 | init::restart_action('fail2ban'); 78 | $self->done(1); 79 | } 80 | else { 81 | $self->done(2); # Not available, as in Oracle 9 82 | } 83 | }; 84 | if ($@) { 85 | $log->error("Error configuring Fail2ban: $@"); 86 | $self->done(0); # NOK! 87 | } 88 | } 89 | 90 | sub create_fail2ban_jail { 91 | my $self = shift; 92 | # Postfix addendum 93 | my $postfix_jail_extra = ""; 94 | 95 | my $is_debian = $gconfig{'real_os_type'} =~ /debian/i; 96 | my $is_ubuntu = $gconfig{'real_os_type'} =~ /ubuntu/i; 97 | my $debian10_or_older = $is_debian && $gconfig{'real_os_version'} <= 10; 98 | my $ubuntu20_or_older = $is_ubuntu && int($gconfig{'real_os_version'}) <= 20; 99 | 100 | if ($debian10_or_older || $ubuntu20_or_older) { 101 | $postfix_jail_extra = "\nbackend = auto\nlogpath = /var/log/mail.log"; 102 | } 103 | elsif ($is_debian || $is_ubuntu) { 104 | $postfix_jail_extra 105 | = "\nbackend = systemd\njournalmatch = _SYSTEMD_UNIT=postfix\@-.service"; 106 | } 107 | 108 | # Proftpd addendum 109 | my $proftpd_jail_extra = ""; 110 | if ($is_debian || $is_ubuntu) { 111 | $proftpd_jail_extra 112 | = "\nbackend = auto\nlogpath = /var/log/proftpd/proftpd.log"; 113 | } 114 | elsif ($gconfig{'os_type'} eq 'redhat-linux') { 115 | $proftpd_jail_extra = "\nprefregex =\n"; 116 | $proftpd_jail_extra 117 | .= 'failregex = \(\S+\[\]\)[: -]+ USER \S+: no such user found from \S+ \[[0-9.]+\] to \S+:\S+\s*$' 118 | . "\n"; 119 | $proftpd_jail_extra 120 | .= ' \(\S+\[\]\)[: -]+ USER \S+ \(Login failed\):.*\s+$' 121 | . "\n"; 122 | $proftpd_jail_extra 123 | .= ' \(\S+\[\]\)[: -]+ Maximum login attempts \([0-9]+\) exceeded, connection refused.*\s+$' 124 | . "\n"; 125 | $proftpd_jail_extra 126 | .= ' \(\S+\[\]\)[: -]+ SECURITY VIOLATION: \S+ login attempted\.\s+$' 127 | . "\n"; 128 | $proftpd_jail_extra 129 | .= ' \(\S+\[\]\)[: -]+ Maximum login attempts \(\d+\) exceeded\s+$'; 130 | } 131 | my $mini_stack = 132 | (defined $self->bundle() && $self->bundle() =~ /mini/i) ? 133 | ($self->bundle() =~ /LEMP/i ? 'LEMP' : 'LAMP') : 0; 134 | my $proftpd_block = $mini_stack ? '' : 135 | "[proftpd]\n" . 136 | "enabled = true\nport = ftp,ftp-data,ftps,ftps-data,2222$proftpd_jail_extra\n\n"; 137 | 138 | open(my $JAIL_LOCAL, '>', '/etc/fail2ban/jail.local'); 139 | print $JAIL_LOCAL <', '/etc/fail2ban/jail.d/virtualmin-firewalld.conf'); 166 | print $FIREWALLD_CONF <get_logger("virtualmin-config-system"); 17 | 18 | sub new { 19 | my ($class, %args) = @_; 20 | 21 | my $self = { 22 | name => $args{name}, 23 | depends => $args{depends}, 24 | total => $args{total}, 25 | bundle => $args{bundle} 26 | }; 27 | bless $self, $class; 28 | 29 | return $self; 30 | } 31 | 32 | # Plugin short name, used in config definitions 33 | sub name { 34 | my ($self, $name) = @_; 35 | if ($name) { $self->{name} = $name } 36 | return $self->{name}; 37 | } 38 | 39 | # Return a ref to an array of plugins that have to run before this one. 40 | # Dep resolution is very stupid. Don't do anything complicated. 41 | sub depends { 42 | my ($self, $name) = @_; 43 | if ($name) { $self->{depends} = shift } 44 | return $self->{depends}; 45 | } 46 | 47 | # Total number of plugins being run for running count 48 | sub total { 49 | my ($self, $total) = @_; 50 | if ($total) { $self->{total} = shift } 51 | return $self->{total}; 52 | } 53 | 54 | sub bundle { 55 | my ($self, $bundle) = @_; 56 | if ($bundle) { $self->{bundle} = shift } 57 | return $self->{bundle}; 58 | } 59 | 60 | sub spin { 61 | state $count = 1; 62 | my $self = shift; 63 | my $name = $self->name(); 64 | my $message = shift // "Configuring " . format_plugin_name($name); 65 | $log->info($message); 66 | spinner("new"); 67 | $message 68 | = "[" 69 | . YELLOW 70 | . $count 71 | . RESET . "/" 72 | . GREEN 73 | . $self->total() 74 | . RESET . "] " 75 | . $message; 76 | my $color_correction = length(YELLOW . RESET . GREEN . RESET); 77 | $count++; 78 | $message = $message 79 | . " " x (79 - length($message) - spinner("lastsize") + $color_correction); 80 | print $message; 81 | spinner("auto_start"); 82 | } 83 | 84 | sub done { 85 | my $self = shift; 86 | my $res = shift; 87 | spinner('auto_done'); 88 | if ($res == 1) { 89 | 90 | # Success! 91 | $log->info("Succeeded"); 92 | spinner("ok"); 93 | } 94 | elsif ($res == 2) { 95 | 96 | # Not quite OK 97 | $log->warn("Non-fatal error"); 98 | spinner("meh"); 99 | } 100 | else { 101 | # Failure! 102 | $log->warn("Failed"); 103 | spinner("nok"); 104 | } 105 | } 106 | 107 | sub root { 108 | my $self = shift; 109 | 110 | $ENV{'WEBMIN_CONFIG'} ||= "/etc/webmin"; 111 | $ENV{'WEBMIN_VAR'} ||= "/var/webmin"; 112 | $ENV{'MINISERV_CONFIG'} = $ENV{'WEBMIN_CONFIG'} . "/miniserv.conf"; 113 | open(my $CONF, "<", "$ENV{'WEBMIN_CONFIG'}/miniserv.conf") || die RED, 114 | "Failed to open miniserv.conf", RESET; 115 | my $root; 116 | while (<$CONF>) { 117 | if (/^root=(.*)/) { 118 | $root = $1; 119 | } 120 | } 121 | close($CONF); 122 | $root ||= "/usr/libexec/webmin"; 123 | 124 | return $root; 125 | } 126 | 127 | # format_plugin_name(plugin-name) 128 | # Alters plugin name depending on the system software 129 | sub format_plugin_name { 130 | my $name = shift; 131 | 132 | # Variations of database 133 | my $db = -x "/usr/bin/mariadb" ? "MariaDB" : "MySQL"; 134 | if ($name eq 'MySQL') { 135 | $name = $db; 136 | } 137 | return $name; 138 | } 139 | 140 | # logsystem(command) 141 | # Similar to system() or backticks but with logging. 142 | # Runs a single system command, and returns the result code. 143 | sub logsystem { 144 | my $self = shift; 145 | my $cmd = shift; 146 | 147 | my $res = `$cmd 2>&1` // "[]"; 148 | $log->info("Code: $? Result: $res"); 149 | return $?; 150 | } 151 | 152 | sub spinner { 153 | my ($cmd) = @_; 154 | state $slastsize = 3; 155 | state $pos = 1; 156 | state $schild; 157 | state $whitecolor; 158 | 159 | # Do we have shades of white? 160 | if (!$whitecolor) { 161 | my $colors = `tput colors 2>&1`; 162 | $whitecolor = 'white'; 163 | $whitecolor = 'bright_white' if ($colors && $colors > 8); 164 | } 165 | 166 | # Is new spinner 167 | $slastsize = 3, $pos = 1, $schild = undef, return if ($cmd eq 'new'); 168 | 169 | my $sseq = [ 170 | qw(▒▒▒ █▒▒ ██▒ ███ ▒██ ▒▒█ ▒▒▒)]; 171 | my $sbksp = chr(0x08); 172 | my $start = sub { 173 | print "\x1b[?25l"; 174 | $slastsize = 3; 175 | print colored("$sseq->[0]", 'cyan'); 176 | }; 177 | my $done = sub { print $sbksp x $slastsize; print "\x1b[?25h"; }; 178 | my $ok = sub { say colored(" ✔ ", "$whitecolor on_green"); }; 179 | my $meh = sub { say colored(" ⚠ ", "$whitecolor on_yellow"); }; 180 | my $nok = sub { say colored(" ✘ ", "$whitecolor on_red"); }; 181 | 182 | my $next = sub { 183 | print $sbksp x $slastsize; 184 | print colored("$sseq->[$pos]", 'cyan'); 185 | $pos = ++$pos % scalar @{$sseq}; 186 | $slastsize = length($sseq->[$pos]); 187 | }; 188 | 189 | # Fork and run spinner asynchronously, until signal received. 190 | my $auto_start = sub { 191 | my $ppid = $$; 192 | system('stty -echo 1>/dev/null 2>&1'); 193 | my $pid = fork(); 194 | die("Failed to fork progress indicator.\n") unless defined $pid; 195 | 196 | if ($pid) { # Parent 197 | $schild = $pid; 198 | return; 199 | } 200 | else { # Kid stuff 201 | &$start(); 202 | my $exists; 203 | while (1) { 204 | sleep 0.2; 205 | &$next(); 206 | 207 | # Check to be sure parent is still running, if not, die 208 | $exists = kill 0, $ppid; 209 | unless ($exists) { 210 | &$done(); 211 | exit 0; 212 | } 213 | $exists = ""; 214 | } 215 | exit 0; # Should never get here? 216 | } 217 | }; 218 | 219 | my $auto_done = sub { 220 | kill 'KILL', $schild; 221 | system('stty echo 1>/dev/null 2>&1'); 222 | my $pid = wait(); 223 | &$done(); 224 | }; 225 | 226 | # Returns 227 | &$auto_start() if ($cmd eq 'auto_start'); 228 | &$auto_done() if ($cmd eq 'auto_done'); 229 | &$ok() if ($cmd eq 'ok'); 230 | &$meh() if ($cmd eq 'meh'); 231 | &$nok() if ($cmd eq 'nok'); 232 | return $slastsize if ($cmd eq 'lastsize'); 233 | } 234 | 235 | 1; 236 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/SASL.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::SASL; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | use Time::HiRes qw( sleep ); # XXX Figure out how to not need this. 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our $trust_unknown_referers = 1; 11 | 12 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 13 | 14 | sub new { 15 | my ($class, %args) = @_; 16 | 17 | # inherit from Plugin 18 | my $self = $class->SUPER::new(name => 'SASL', %args); 19 | 20 | return $self; 21 | } 22 | 23 | # actions method performs whatever configuration is needed for this 24 | # plugin. XXX Needs to make a backup so changes can be reverted. 25 | sub actions { 26 | my $self = shift; 27 | 28 | use Cwd; 29 | my $cwd = getcwd(); 30 | my $root = $self->root(); 31 | chdir($root); 32 | $0 = "$root/virtual-server/config-system.pl"; 33 | push(@INC, $root); 34 | push(@INC, "$root/vendor_perl"); 35 | eval 'use WebminCore'; ## no critic 36 | init_config(); 37 | 38 | $self->spin(); 39 | sleep 0.3; 40 | eval { 41 | my $res = 1; 42 | foreign_require("init", "init-lib.pl"); 43 | init::enable_at_boot("saslauthd"); 44 | my ($saslinit, $cf, $libdir); 45 | if ( $gconfig{'os_type'} eq "debian-linux" 46 | or $gconfig{'os_type'} eq "ubuntu-linux") 47 | { 48 | # Update saslauthd default to start on boot 49 | my $fn = "/etc/default/saslauthd"; 50 | my $sasldefault = read_file_lines($fn) or die "Failed to open $fn!"; 51 | my $idx = indexof("# START=yes", @$sasldefault); 52 | my $idx2 = grep {/START=/} @$sasldefault; 53 | if ($idx < 0) { 54 | $idx = indexof("START=no", @$sasldefault); 55 | } 56 | if ($idx >= 0) { 57 | $sasldefault->[$idx] = "START=yes"; 58 | } 59 | if (!$idx2) { 60 | push(@{$sasldefault}, "START=yes"); 61 | } 62 | 63 | # Substitute options and params if already in file 64 | my $added_opts = 0; 65 | my $added_params = 0; 66 | foreach my $l (@$sasldefault) { 67 | if ($l =~ /^OPTIONS/) { 68 | $l = 'OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"'; 69 | $added_opts++; 70 | } 71 | if ($l =~ /^PARAMS/) { 72 | $l = 'PARAMS="-m /var/spool/postfix/var/run/saslauthd -r"'; 73 | $added_params++; 74 | } 75 | } 76 | 77 | # Add them, if not 78 | push(@$sasldefault, 79 | 'OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd -r"') 80 | if (!$added_opts && !grep {/^OPTIONS/} @$sasldefault); 81 | push(@$sasldefault, 82 | 'PARAMS="-m /var/spool/postfix/var/run/saslauthd -r"') 83 | if (!$added_params && !grep {/^PARAMS/} @$sasldefault); 84 | flush_file_lines($fn); 85 | 86 | $cf = "/etc/postfix/sasl/smtpd.conf"; 87 | $self->logsystem("mkdir -p -m 755 /var/spool/postfix/var/run/saslauthd"); 88 | $self->logsystem("adduser postfix sasl"); 89 | $saslinit = "/etc/init.d/saslauthd"; 90 | # Ubuntu 24.04 uses native saslauthd.service, so we need 91 | # to fix PIDFile location because chroot Postfix expects 92 | # it to be in /var/spool/postfix/var/run/saslauthd 93 | my $os_ver = $gconfig{'real_os_version'}; 94 | if (($gconfig{'real_os_type'} =~ /ubuntu/i && $os_ver && $os_ver =~ /^(2[468])/) || 95 | ($gconfig{'real_os_type'} =~ /debian/i && $os_ver && $os_ver >= 13)) { 96 | my $systemd_saslauthd_override_path = "/etc/systemd/system/saslauthd.service.d"; 97 | $self->logsystem("mkdir -p -m 755 $systemd_saslauthd_override_path") 98 | if (!-d $systemd_saslauthd_override_path); 99 | write_file_contents($systemd_saslauthd_override_path . "/override.conf", 100 | "[Service]\n". 101 | "PIDFile=/var/spool/postfix/var/run/saslauthd/saslauthd.pid\n"); 102 | $self->logsystem("systemctl daemon-reload"); 103 | $self->logsystem("systemctl restart saslauthd.service"); 104 | } 105 | } 106 | elsif ($gconfig{'os_type'} eq 'solaris') { 107 | 108 | # Use CSW saslauthd 109 | my $lref = read_file_lines("/opt/csw/etc/saslauthd.init"); 110 | foreach my $l (@$lref) { 111 | if ($l =~ /^\#+\s*MECHANISM/) { 112 | $l = "MECHANISM=pam"; 113 | } 114 | } 115 | flush_file_lines("/opt/csw/etc/saslauthd.init"); 116 | $cf = "/opt/csw/lib/sasl2/smtpd.conf"; 117 | $saslinit = "/etc/init.d/cswsaslauthd"; 118 | } 119 | else { 120 | # I'm not liking all of this jiggery pokery...need a better way to 121 | # detect which lib directory to work in.XXX 122 | if ($gconfig{'os_type'} eq 'freebsd') { $libdir = "/usr/local/lib"; } 123 | else { 124 | if (-e "/usr/lib64" && -e "/usr/lib64/perl") { $libdir = "/usr/lib64"; } 125 | else { $libdir = "/usr/lib"; } 126 | } 127 | if (-e "/etc/sasl2/smtpd.conf") { $cf = "/etc/sasl2/smtpd.conf"; } 128 | elsif (-e "$libdir/sasl2") { $cf = "$libdir/sasl2/smtpd.conf"; } 129 | elsif (-e "$libdir/sasl") { $cf = "$libdir/sasl/smtpd.conf"; } 130 | else { 131 | #print "No sasl library directory found. SASL authentication probably won't work"; 132 | $res = 0; 133 | } 134 | if ($gconfig{'os_type'} eq 'freebsd') { 135 | $saslinit = "/usr/local/etc/rc.d/saslauthd"; 136 | } 137 | else { $saslinit = "/etc/init.d/saslauthd"; } 138 | } 139 | if ($cf) { 140 | if (!-e $cf) { 141 | $self->logsystem("touch $cf"); 142 | } 143 | my $smtpdconf = read_file_lines($cf) or die "Failed to open $cf!"; 144 | push(@$smtpdconf, "pwcheck_method: saslauthd") 145 | if (!grep {/^pwcheck_method/} @$smtpdconf); 146 | push(@$smtpdconf, "mech_list: plain login") 147 | if (!grep {/^mech_list/} @$smtpdconf); 148 | flush_file_lines($cf); 149 | 150 | init::start_action('saslauthd'); 151 | } 152 | 153 | # Update flags to use realm as part of username 154 | my $saslconfig = "/etc/sysconfig/saslauthd"; 155 | if (-r $saslconfig) { 156 | my $lref = read_file_lines($saslconfig); 157 | foreach my $l (@$lref) { 158 | if ($l =~ /^\s*FLAGS=\s*$/) { 159 | $l = "FLAGS=\"-r\""; 160 | } 161 | elsif ($l =~ /^\s*FLAGS="(.*)"$/ && $l !~ /-r/) { 162 | $l = "FLAGS=\"$1 -r\""; 163 | } 164 | } 165 | flush_file_lines($saslconfig); 166 | } 167 | 168 | $self->done(1); # OK! 169 | }; 170 | if ($@) { 171 | $log->error("Error configuring SASL: $@"); 172 | $self->done(0); 173 | } 174 | } 175 | 176 | 1; 177 | -------------------------------------------------------------------------------- /virtualmin-config.spec: -------------------------------------------------------------------------------- 1 | Name: virtualmin-config 2 | Version: 7.0.41 3 | Release: 1 4 | Summary: Collection of plugins to initialize the configuration of services that Virtualmin manages, and a command line tool called config-system to run them 5 | License: GPL+ 6 | Group: Development/Libraries 7 | URL: https://github.com/virtualmin/Virtualmin-Config/ 8 | Source0: Virtualmin-Config-%{version}.tar.gz 9 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) 10 | BuildArch: noarch 11 | BuildRequires: perl >= 0:5.016 12 | BuildRequires: perl(ExtUtils::MakeMaker) 13 | BuildRequires: perl(File::Spec) 14 | BuildRequires: perl(Log::Log4perl) 15 | BuildRequires: perl(Test::More) 16 | BuildRequires: perl(Module::Load) 17 | Requires: webmin 18 | Requires: perl(Log::Log4perl) 19 | Requires: perl(Term::ANSIColor) 20 | Requires: perl(Module::Load) 21 | 22 | %description 23 | This is a mini-framework for configuring elements of a Virtualmin system. 24 | It uses Webmin as a library to abstract common configuration tasks, 25 | provides a friendly status indicator, and makes it easy to pick and choose 26 | the kind of configuration you want (should you choose to go that route). 27 | The Virtualmin install script chooses either the LAMP (with Apache) or LEMP 28 | (with nginx) bundle, and performs the configuration for the whole stack. 29 | 30 | %prep 31 | %setup -q -n Virtualmin-Config-%{version} 32 | 33 | %build 34 | %{__perl} Makefile.PL INSTALLDIRS=vendor 35 | make %{?_smp_mflags} 36 | 37 | %install 38 | rm -rf $RPM_BUILD_ROOT 39 | 40 | make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT 41 | mkdir -p $RPM_BUILD_ROOT/usr/libexec/webmin/virtual-server 42 | # link virtualmin-config-system into Virtualmin dir 43 | ln -s /usr/bin/virtualmin-config-system \ 44 | $RPM_BUILD_ROOT/usr/libexec/webmin/virtual-server/config-system.pl 45 | 46 | find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} \; 47 | find $RPM_BUILD_ROOT -depth -type d -exec rmdir {} 2>/dev/null \; 48 | 49 | %{_fixperms} $RPM_BUILD_ROOT/* 50 | 51 | #%check 52 | #make test 53 | 54 | %clean 55 | rm -rf $RPM_BUILD_ROOT 56 | 57 | %files 58 | %defattr(-,root,root,-) 59 | %doc dist.ini LICENSE META.json README 60 | %{perl_vendorlib}/* 61 | %{_mandir}/man1/* 62 | %{_mandir}/man3/* 63 | %{_bindir}/* 64 | /usr/libexec/webmin/virtual-server/config-system.pl 65 | 66 | %changelog 67 | * Mon Dec 15 2025 Ilia Ross 8.0.4 68 | - Fix to set up mail in each module respectively 69 | * Mon Sep 29 2025 Ilia Ross 8.0.3 70 | - Fix Apache-related issues 71 | * Tue Sep 16 2025 Ilia Ross 8.0.2 72 | - Add Debian 13 and EL 10 support 73 | * Sun Jun 15 2025 Ilia Ross 8.0.1 74 | - Fix Apache configuration and configuring Fail2Ban on mini stacks 75 | * Sat Jun 14 2025 Ilia Ross 8.0.0 76 | - Fix to adjust installation types and add support for Virtualmin 8 77 | * Mon Jan 27 2025 Ilia Ross 7.0.41 78 | - Fix to enable the use of Firewalld rich rules with Fail2ban 79 | * Wed Dec 25 2024 Ilia Ross 7.0.40 80 | - Fix to support new installation types 81 | * Sun Sep 29 2024 Ilia Ross 7.0.30 82 | - Fix to prevent breaking spinners on user input 83 | * Sun May 19 2024 Ilia Ross 7.0.22 84 | - Fix to directly create systemd override files 85 | * Tue May 14 2024 Ilia Ross 7.0.21 86 | - Fix to save Virtualmin configuration reliably 87 | * Tue May 14 2024 Ilia Ross 7.0.20 88 | - Fix bug when saving Virtualmin configuration 89 | * Sat Apr 27 2024 Ilia Ross 7.0.19 90 | - Add Ubuntu 24.04 support, fix Nginx start after reboot, enable DKIM at install time 91 | * Sun Dec 03 2023 Ilia Ross 7.0.18 92 | - Fix ProFTPd chroot jail, enable quotas in RHEL 9.3+ and mod_include in Apache 93 | * Wed Sep 20 2023 Ilia Ross 7.0.17 94 | - Fix temp directory environmental variable name and improve support for openSUSE 95 | * Sun Aug 27 2023 Ilia Ross 7.0.16 96 | - Fix to also disable a new `spamd` service in Debian 12 97 | * Sun Aug 20 2023 Ilia Ross 7.0.15 98 | - Add ability to create host default domain with Let's Encrypt SSL 99 | * Sun Aug 20 2023 Ilia Ross 7.0.14 100 | - Fix to support Webmin distributed Perl modules 101 | * Wed Aug 10 2023 Joe Cooper 7.0.13 102 | - Remove Usermin File Manager configuration 103 | * Tue Apr 18 2023 iliajie 7.0.12 104 | - Fix missing Fail2banFirewalld changes 105 | * Sun Apr 16 2023 iliajie 7.0.11 106 | - Fix Fail2ban issues 107 | * Thu Apr 13 2023 iliajie 7.0.10 108 | - Fix Usermin and ClamAV related issues 109 | * Mon Jan 09 2023 iliajie 7.0.9 110 | - Fix minor issues 111 | * Sat Dec 18 2022 Joe Cooper 7.0.8 112 | - Fix PHP-FPM, ProFTPd, self-signed SSL and spinners 113 | * Sat Jan 08 2022 Joe Cooper 7.0.0 114 | - New branch for Virtualmin 7 115 | * Sun Nov 04 2018 Joe Cooper 6.0.24-1 116 | - A bunch of changes for Ubuntu/Debian that don't impact RHEL 117 | * Fri Oct 13 2017 Joe Cooper 6.0.21-1 118 | - Update SASL flags 119 | * Thu Sep 28 2017 Joe Cooper 6.0.20-1 120 | - Minimal configs adds Dovecot, SASL, removes Fail2ban 121 | - Fix Apache default file handling on Ubuntu 16.04 122 | - ProFTPd shouldn't require TlS 123 | * Mon Sep 04 2017 Joe Cooper 6.0.16-1 124 | - Fix ProFTPd failure to write config changes 125 | * Fri Sep 01 2017 Joe Cooper 6.0.15-1 126 | - Remove MiniVirtualmin plugin, Virtualmin plugin handles it when bundle is Mini* 127 | - Handle some DHCP configured systems by adding localhost to name servers 128 | - Fix SSL default site on CentOS (disabling it). 129 | * Wed Aug 23 2017 Joe Cooper 6.0.14-1 130 | - Fix non-fatal warn on Quotas 131 | - Fix some SASL problems 132 | - Fix Quotas convertquota error by using checkquota instead 133 | - Make Virtualmin use nginx correctly 134 | * Tue Aug 22 2017 Joe Cooper 6.0.13-1 135 | - Fixes for Apache init/systemd detection 136 | - Non-fatal error on Quotas 137 | - New non-fatal error result type 138 | * Fri Jun 23 2017 Joe Cooper 6.0.5-1 139 | - Fail2ban and Firewalld modules added 140 | - Handle systemd or not 141 | * Mon May 08 2017 Joe Cooper 142 | - Rename to config-system 143 | * Mon May 08 2017 Joe Cooper 144 | - Tweak deps 145 | * Sat May 06 2017 Joe Cooper 6.0.0-1 146 | - Specfile autogenerated by cpanspec 1.78. 147 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/ProFTPd.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::ProFTPd; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'ProFTPd', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | foreign_require("init", "init-lib.pl"); 40 | init::enable_at_boot("proftpd"); 41 | foreign_require("proftpd", "proftpd-lib.pl"); 42 | my $conf = proftpd::get_config(); 43 | if ($gconfig{'os_type'} eq 'freebsd') { 44 | 45 | # This directory is missing on FreeBSD 46 | make_dir("/var/run/proftpd", oct(755)); 47 | 48 | # UseIPv6 doesn't work on FreeBSD 49 | proftpd::save_directive("UseIPv6", [], $conf, $conf); 50 | } 51 | 52 | # Create a virtualmin.conf file and Include it 53 | # Debian has /etc/proftpd/conf.d, CentOS we create it. 54 | my $config_directory = "/etc/proftpd/conf.d"; 55 | if (!-d $config_directory) { 56 | $log->info('/etc/proftpd/conf.d missing. Creating it.'); 57 | use File::Path 'make_path'; 58 | make_path($config_directory, {mode => oct(755)}); 59 | } 60 | 61 | # Where are certs and keys stored? 62 | my $get_openssl_version = sub { 63 | my $out = &backquote_command("openssl version 2>/dev/null"); 64 | if ($out =~ /OpenSSL\s+(\d\.\d)/) { 65 | return $1; 66 | } 67 | return 0; 68 | }; 69 | 70 | my ($keyfile, $certfile); 71 | if ($gconfig{'os_type'} =~ /debian-linux|ubuntu-linux/) { 72 | $certfile = '/etc/ssl/certs/proftpd.crt'; 73 | $keyfile = '/etc/ssl/private/proftpd.key'; 74 | 75 | # Add to end of file, if not already there Include /etc/proftpd/conf.d 76 | proftpd::save_directive('Include', 77 | ['/etc/proftpd/modules.conf', '/etc/proftpd/conf.d'], 78 | $conf, $conf); 79 | 80 | } 81 | elsif ($gconfig{'os_type'} eq 'redhat-linux') { 82 | $certfile = '/etc/pki/tls/certs/proftpd.pem'; 83 | $keyfile = '/etc/pki/tls/private/proftpd.pem'; 84 | proftpd::save_directive('Include', ['/etc/proftpd/conf.d'], $conf, $conf); 85 | } 86 | elsif ($gconfig{'os_type'} eq 'suse-linux') { 87 | $certfile = '/etc/proftpd/ssl/proftpd.cert.pem'; 88 | $keyfile = '/etc/proftpd/ssl/proftpd.key.pem'; 89 | proftpd::save_directive('Include', ['/etc/proftpd/conf.d'], $conf, $conf); 90 | } 91 | else { 92 | $log->warn("No configuration available for OS type $gconfig{'os_type'}."); 93 | die "Skipping additional ProFTPd configuration for this OS."; 94 | } 95 | 96 | # generate TLS cert/key pair 97 | my $hostname = get_system_hostname(); 98 | my $org = "Self-signed for $hostname"; 99 | my $addtextsup 100 | = &$get_openssl_version() >= 1.1 101 | ? "-addext subjectAltName=DNS:$hostname,DNS:localhost -addext extendedKeyUsage=serverAuth" 102 | : ""; 103 | 104 | $log->info('Generating a self-signed certificate for TLS.'); 105 | $self->logsystem( 106 | "openssl req -newkey rsa:2048 -x509 -nodes -out $certfile -keyout $keyfile -days 3650 -sha256 -subj '/CN=$hostname/C=US/L=Santa Clara/O=$org' $addtextsup" 107 | ); 108 | 109 | # Generate ssh key pairs 110 | if (!-f '/etc/proftpd/ssh_host_ecdsa_key') { 111 | $self->logsystem( 112 | "ssh-keygen -f /etc/proftpd/ssh_host_ecdsa_key -t ecdsa -N '' -m PEM"); 113 | } 114 | if (!-f '/etc/proftpd/ssh_host_rsa_key') { 115 | $self->logsystem( 116 | "ssh-keygen -f /etc/proftpd/ssh_host_rsa_key -t rsa -N '' -m PEM"); 117 | } 118 | 119 | my $vmconf = <<"EOF"; 120 | # Use standard passive ports 121 | 122 | PassivePorts 49152 65535 123 | 124 | 125 | # chroot users into their home by default 126 | DefaultRoot ~ 127 | 128 | # Enable TLS 129 | 130 | LoadModule mod_tls.c 131 | 132 | 133 | TLSEngine on 134 | TLSRequired off 135 | TLSRSACertificateFile $certfile 136 | TLSRSACertificateKeyFile $keyfile 137 | TLSOptions NoSessionReuseRequired 138 | TLSVerifyClient off 139 | TLSLog /var/log/proftpd/tls.log 140 | 141 | TLSSessionCache shm:/file=/var/run/proftpd/sesscache 142 | 143 | 144 | # VirtualHost for SFTP (FTP over SSH) port 145 | 146 | LoadModule mod_sftp.c 147 | 148 | 149 | 150 | SFTPEngine on 151 | SFTPLog /var/log/proftpd/sftp.log 152 | 153 | # Configure the server to listen on 2222 (openssh owns 22) 154 | Port 2222 155 | 156 | # chroot users into their home by default 157 | DefaultRoot ~ 158 | 159 | # Allow file overwrites 160 | AllowOverwrite on 161 | 162 | # Configure the RSA and ECDSA host keys, using the same host key 163 | # files that OpenSSH uses. 164 | SFTPHostKey /etc/proftpd/ssh_host_rsa_key 165 | SFTPHostKey /etc/proftpd/ssh_host_ecdsa_key 166 | 167 | # Configure the file used for comparing authorized public keys of users. 168 | SFTPAuthorizedUserKeys file:~/.sftp/authorized_keys 169 | 170 | # Enable compression 171 | SFTPCompression delayed 172 | 173 | # More then FTP max logins, as there are more ways to authenticate 174 | # using SSH2. 175 | MaxLoginAttempts 6 176 | 177 | EOF 178 | 179 | # Write out virtualmin.config 180 | open my $VMH, '>', '/etc/proftpd/conf.d/virtualmin.conf'; 181 | print $VMH $vmconf; 182 | close $VMH; 183 | 184 | # If SELinux is installed enable the right boolean 185 | # For SFTP? 186 | if (-x '/usr/sbin/setsebool') { 187 | $self->logsystem('setsebool -P ftpd_full_access 1'); 188 | } 189 | 190 | # Generate a basic config, subbing in the right variables. 191 | flush_file_lines(); 192 | 193 | # Create initial site file to satisfy config check 194 | my $site_conf = "$ENV{'WEBMIN_CONFIG'}/proftpd/site"; 195 | if (!-r $site_conf) { 196 | my $ver; 197 | my %site_conf; 198 | $ver = proftpd::get_proftpd_version(); 199 | $site_conf{'version'} = $ver; 200 | write_file($site_conf, \%site_conf); 201 | } 202 | 203 | # Restart ProFTPd 204 | init::restart_action("proftpd"); 205 | 206 | $self->done(1); # OK! 207 | }; 208 | if ($@) { 209 | $log->error("Failed to configure ProFTPd: $@"); 210 | $self->done(0); # NOK! 211 | } 212 | } 213 | 214 | 1; 215 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Quotas.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Quotas; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | $| = 1; 8 | 9 | our $config_directory; 10 | our (%gconfig, %miniserv); 11 | our $trust_unknown_referers = 1; 12 | 13 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 14 | 15 | sub new { 16 | my ($class, %args) = @_; 17 | 18 | # inherit from Plugin 19 | my $self = $class->SUPER::new(name => 'Quotas', %args); 20 | 21 | return $self; 22 | } 23 | 24 | # actions method performs whatever configuration is needed for this 25 | # plugin. XXX Needs to make a backup so changes can be reverted. 26 | sub actions { 27 | my $self = shift; 28 | 29 | use Cwd; 30 | my $cwd = getcwd(); 31 | my $root = $self->root(); 32 | chdir($root); 33 | $0 = "$root/virtual-server/config-system.pl"; 34 | push(@INC, $root); 35 | push(@INC, "$root/vendor_perl"); 36 | eval 'use WebminCore'; ## no critic 37 | init_config(); 38 | 39 | $self->spin(); 40 | eval { 41 | my $res = 1; 42 | foreign_require("mount", "mount-lib.pl"); 43 | mkdir("/home", 0755) if (!-d "/home"); 44 | my ($dir, $dev, $type, $opts) = mount::filesystem_for_dir("/home"); 45 | 46 | # Remove noquota, if it is present 47 | $opts = join(',', grep { !/noquota/ } (split(/,/, $opts))); 48 | 49 | mount::parse_options($type, $opts); 50 | if (running_in_zone() || running_in_vserver()) { 51 | 52 | #print STDERR "Skipping quotas for Vserver or Zones systems\n"; 53 | return; 54 | } 55 | elsif ($type eq 'btrfs') { 56 | # Check if Btrfs quotas are already enabled 57 | my $quota_status = `btrfs qgroup show $dir 2>&1`; 58 | if ($quota_status =~ /ERROR.*not enabled/) { 59 | # Enable Btrfs quotas 60 | $self->logsystem("btrfs quota enable $dir"); 61 | } 62 | $res = 1; # Indicate success 63 | } 64 | elsif ($gconfig{'os_type'} =~ /-linux$/) { 65 | $mount::options{'usrquota'} = ''; 66 | $mount::options{'grpquota'} = ''; 67 | $mount::options{'quota'} = ''; 68 | } 69 | elsif ($gconfig{'os_type'} =~ /freebsd|netbsd|openbsd|macos/) { 70 | 71 | # Skip if quotas are not enabled--requires a kernel rebuild 72 | my $quotav = `quota -v`; 73 | if ($quotav !~ /none$/) { 74 | $mount::options{'rw'} = ''; 75 | $mount::options{'userquota'} = ''; 76 | $mount::options{'groupquota'} = ''; 77 | } 78 | else { 79 | #print "Skipping quotas: Required kernel support is not enabled.\n"; 80 | return; 81 | } 82 | } 83 | elsif ($gconfig{'os_type'} =~ /solaris/) { 84 | $mount::options{'quota'} = ''; 85 | } 86 | else { 87 | #print STDERR "Don't know how to enable quotas on $gconfig{'real_os_type'} ($gconfig{'os_type'})\n"; 88 | } 89 | if ($type eq 'btrfs') { 90 | # No need to remount or activate quotas for Btrfs 91 | $res = 1; # Indicate success 92 | } 93 | else { 94 | $opts = mount::join_options($type); 95 | my @mounts = mount::list_mounts(); 96 | my $idx; 97 | for ($idx = 0; $idx < @mounts; $idx++) { 98 | last if ($mounts[$idx]->[0] eq $dir); 99 | } 100 | mount::change_mount( 101 | $idx, 102 | $mounts[$idx]->[0], 103 | $mounts[$idx]->[1], 104 | $mounts[$idx]->[2], 105 | $opts, 106 | $mounts[$idx]->[4], 107 | $mounts[$idx]->[5] 108 | ); 109 | my $err = mount::remount_dir($dir, $dev, $type, $opts); 110 | if ($type ne "ext4" || $err) { 111 | my $xfs = $type eq 'xfs'; 112 | my $smsg1 = "\b" x 7 . " " x 7; 113 | my $smsg2 = " " x ($xfs ? 26 : 34); 114 | my $msg1 115 | = "\nThe filesystem $dir could not be remounted with quotas enabled.\n"; 116 | my $msg2 117 | = $xfs 118 | ? "You will need to reboot your system to enable quotas." 119 | : "You may need to reboot your system, and/or enable quotas\nmanually in Webmin/System/Disk Quotas module."; 120 | $res = 2; 121 | my $prt_std_err = 1; 122 | if ($xfs) { 123 | 124 | my $grubby_cmd = &has_command('grubby'); 125 | my $grub_def_file = "/etc/default/grub"; 126 | my $grub_generate_config = sub { 127 | my $grub_conf_file = "/boot/grub2/grub.cfg"; 128 | my $grub_conf_cmd = "grub2-mkconfig"; 129 | if (!-r $grub_conf_file) { 130 | $grub_conf_file = "/boot/grub/grub.cfg"; 131 | $grub_conf_cmd = "grub-mkconfig"; 132 | } 133 | # Always regenerate the real GRUB config in /boot, 134 | # never the EFI stub. 135 | my $have_bls = -d "/boot/loader/entries"; 136 | my $have_bls_flag = 0; 137 | if ($grub_conf_cmd eq 'grub2-mkconfig' && $have_bls) { 138 | # Detect BLS support 139 | my $help = `$grub_conf_cmd --help 2>&1`; 140 | $have_bls_flag = ($help =~ /--update-bls-cmdline/); 141 | } 142 | if (-r $grub_conf_file) { 143 | ©_source_dest($grub_conf_file, "$grub_conf_file.orig"); 144 | my $cmd = "$grub_conf_cmd -o $grub_conf_file"; 145 | $cmd .= " --update-bls-cmdline" if ($have_bls_flag); 146 | $self->logsystem($cmd); 147 | } 148 | }; 149 | 150 | # Use grubby command to enable user and group quotas 151 | if (-x $grubby_cmd) { 152 | $self->logsystem( 153 | "$grubby_cmd --update-kernel=ALL --args=rootflags=uquota,gquota" 154 | ); 155 | # Generate a new actual config file 156 | &$grub_generate_config(); 157 | } 158 | # Update configuration manually 159 | elsif (-r $grub_def_file) { 160 | my %grub; 161 | &read_env_file($grub_def_file, \%grub) || ($res = 0); 162 | my $v = $grub{'GRUB_CMDLINE_LINUX'}; 163 | if (defined($v) && $v !~ /rootflags=.*?(?:uquota|gquota)/) { 164 | if ($v =~ /rootflags=(\S+)/) { 165 | $v =~ s/rootflags=(\S+)/rootflags=$1,uquota,gquota/; 166 | } 167 | else { 168 | $v .= " rootflags=uquota,gquota"; 169 | } 170 | $grub{'GRUB_CMDLINE_LINUX'} = $v; 171 | &write_env_file($grub_def_file, \%grub); 172 | 173 | # Generate a new actual config file 174 | &$grub_generate_config(); 175 | } 176 | else { 177 | $res = 1; 178 | $prt_std_err = 0; 179 | } 180 | } 181 | } 182 | if ($prt_std_err && $res) { 183 | print $smsg1; 184 | print $msg1; 185 | print $msg2; 186 | print $smsg2; 187 | } 188 | } 189 | else { 190 | # Activate quotas 191 | $self->logsystem("modprobe quota_v2"); 192 | $self->logsystem("quotacheck -vgum $dir"); 193 | $self->logsystem("quotaon -av"); 194 | $res = 1; 195 | } 196 | } 197 | $self->done($res); # Maybe OK! 198 | }; 199 | if ($@) { 200 | $log->error("Error configuring quotas: $@"); 201 | $ENV{'QUOTA_FAILED'} = '1'; 202 | $self->done(2); # 2 is a non-fatal error 203 | } 204 | } 205 | 206 | 1; 207 | -------------------------------------------------------------------------------- /.github/workflows/virtualmin.dev+virtualmin-config.yml: -------------------------------------------------------------------------------- 1 | name: "virtualmin.dev: virtualmin/virtualmin-config" 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | release: 8 | types: 9 | - prereleased 10 | - released 11 | workflow_dispatch: 12 | inputs: 13 | force-release: 14 | type: boolean 15 | default: true 16 | description: "Force a release build" 17 | version: 18 | type: string 19 | required: true 20 | description: "Version to build, e.g. 7.0.24" 21 | 22 | env: 23 | GH_REPO: ${{ github.repository }} 24 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | PKG_RELEASE: ${{ github.run_attempt }} 26 | PKG_NAME: "virtualmin-config" 27 | PKG_DESC: "This mini-framework simplifies configuring elements of a Virtualmin system. It leverages Webmin as a library to handle common configuration tasks, offers a user-friendly status indicator, and makes it easy to customize your setup if that’s the route you prefer. The Virtualmin install script automatically selects either the LAMP (Apache) or LEMP (with Nginx) stack and configures the entire system for you." 28 | PKG_HOMEPAGE: "https://github.com/virtualmin/Virtualmin-Config" 29 | PKG_SUMMARY: "Configure a system for use by Virtualmin" 30 | IS_RELEASE: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.force-release) }} 31 | IS_PRERELEASE: ${{ github.event.release.prerelease || false }} 32 | 33 | BUILD_DEPS: "git tar curl gzip gcc make dpkg-dev fakeroot rpm build-essential libc6-dev coreutils" 34 | BUILD_BOOTSTRAP: "https://raw.githubusercontent.com/webmin/webmin-ci-cd/main/build/bootstrap.bash" 35 | 36 | jobs: 37 | build-amd64: 38 | runs-on: ubuntu-latest 39 | if: ${{ github.event_name == 'release' || github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && !contains(github.event.head_commit.message, '[no-build]')) }} 40 | env: 41 | TZ: Europe/Nicosia 42 | steps: 43 | - uses: actions/checkout@v4 44 | 45 | - uses: awalsh128/cache-apt-pkgs-action@latest 46 | with: 47 | packages: ${{ env.BUILD_DEPS }} 48 | version: 1.0 49 | 50 | - name: Fetch dependencies 51 | run: curl -O ${{ env.BUILD_BOOTSTRAP }} 52 | 53 | - name: Set timezone 54 | run: sudo timedatectl set-timezone ${{ env.TZ }} 55 | 56 | - name: Build packages 57 | env: 58 | CLOUD__IP_ADDR: ${{ secrets.DEV_IP_ADDR }} 59 | CLOUD__IP_KNOWN_HOSTS: ${{ secrets.DEV_IP_KNOWN_HOSTS }} 60 | CLOUD__UPLOAD_SSH_USER: ${{ secrets.DEV_UPLOAD_SSH_USER }} 61 | CLOUD__UPLOAD_SSH_DIR: ${{ env.IS_RELEASE == 'true' && secrets.PRERELEASE_UPLOAD_SSH_DIR || secrets.DEV_UPLOAD_SSH_DIR }} 62 | CLOUD__SSH_PRV_KEY: ${{ secrets.DEV_SSH_PRV_KEY }} 63 | CLOUD__GPG_PH2: ${{ secrets.ALL_GPG_PH2 }} 64 | RELEASE_TAG: ${{ github.event.release.tag_name }} 65 | MANUAL_VERSION: ${{ inputs.version }} 66 | run: |- 67 | 68 | # Fail on error 69 | set -euo pipefail 70 | 71 | # Bootstrap build 72 | source bootstrap.bash \ 73 | $([[ "$IS_RELEASE" == "true" ]] && echo "--release" || echo "--testing") \ 74 | $([[ "$IS_PRERELEASE" == "true" ]] && echo "--prerelease") 75 | 76 | # Get version for the build, either from tag or latest commit 77 | if [ "$IS_RELEASE" = "true" ]; then 78 | if [ -n "${MANUAL_VERSION:-}" ]; then 79 | pkg_version="${MANUAL_VERSION#v}" 80 | else 81 | pkg_version="${RELEASE_TAG#v}" 82 | fi 83 | else 84 | pkg_version="$(get_remote_git_tag_version "$GH_REPO" "$GH_TOKEN" "$IS_RELEASE")" 85 | fi 86 | 87 | # Set package section 88 | pkg_section=$([ "$IS_RELEASE" = "true" ] && echo admin || echo devel) 89 | 90 | # Set build base directory 91 | pkg_name="${{ env.PKG_NAME }}" 92 | destdirbase="$HOME/$pkg_name" 93 | 94 | # Make for Debian 95 | perl Makefile.PL \ 96 | PERL_MM_OPT="$pkg_version" \ 97 | NO_PACKLIST=1 \ 98 | NO_PERLLOCAL=1 \ 99 | SITELIBEXP=/usr/share/perl5 \ 100 | INSTALLSITELIB=/usr/share/perl5 \ 101 | INSTALLSITEMAN1DIR=/usr/share/man/man1 \ 102 | INSTALLSITEMAN3DIR=/usr/share/man/man3 \ 103 | INSTALLBIN=/usr/bin \ 104 | SITEPREFIX=/usr \ 105 | PREFIX=/usr 106 | make 107 | make DESTDIR=$destdirbase-deb install 108 | 109 | # Make for RPM 110 | make clean 111 | perl Makefile.PL \ 112 | PERL_MM_OPT="$pkg_version" \ 113 | NO_PACKLIST=1 \ 114 | NO_PERLLOCAL=1 \ 115 | SITELIBEXP=/usr/share/perl5/vendor_perl \ 116 | INSTALLSITELIB=/usr/share/perl5/vendor_perl \ 117 | INSTALLSITEMAN1DIR=/usr/share/man/man1 \ 118 | INSTALLSITEMAN3DIR=/usr/share/man/man3 \ 119 | INSTALLBIN=/usr/bin \ 120 | SITEPREFIX=/usr \ 121 | PREFIX=/usr 122 | make 123 | make DESTDIR=$destdirbase-rpm install 124 | 125 | # Create symlinks 126 | declare -A paths=( 127 | ["deb"]="usr/share/webmin/virtual-server" 128 | ["rpm"]="usr/libexec/webmin/virtual-server" 129 | ) 130 | for suffix in "${!paths[@]}"; do 131 | target_path="${paths[$suffix]}" 132 | mkdir -p "$destdirbase-$suffix/$target_path" 133 | ln -s "../../../bin/$pkg_name-system" "$destdirbase-$suffix/$target_path/config-system.pl" 134 | done 135 | 136 | # Build Debian package 137 | build_native_package \ 138 | --architectures noarch \ 139 | --files $destdirbase-deb \ 140 | --target-dir "$ROOT_REPOS" \ 141 | --base-name "$pkg_name" \ 142 | --version "$pkg_version" \ 143 | --release "${{ env.PKG_RELEASE }}" \ 144 | --depends perl \ 145 | --depends perl-modules \ 146 | --depends liblog-log4perl-perl \ 147 | --depends webmin-virtual-server \ 148 | --section "$pkg_section" \ 149 | --skip rpm \ 150 | --description "${{ env.PKG_DESC }}" \ 151 | --summary "${{ env.PKG_SUMMARY }}" \ 152 | --homepage "${{ env.PKG_HOMEPAGE }}" 153 | 154 | # Build RPM package 155 | build_native_package \ 156 | --architectures noarch \ 157 | --files $destdirbase-rpm\ 158 | --target-dir "$ROOT_REPOS" \ 159 | --base-name "$pkg_name" \ 160 | --version "$pkg_version" \ 161 | --release "${{ env.PKG_RELEASE }}" \ 162 | --depends perl \ 163 | --depends "perl(File::Basename)" \ 164 | --depends "perl(File::Path)" \ 165 | --depends "perl(Getopt::Long)" \ 166 | --depends "perl(Log::Log4perl)" \ 167 | --depends "perl(POSIX)" \ 168 | --depends "perl(Time::HiRes)" \ 169 | --depends "perl(Term::ANSIColor)" \ 170 | --depends wbm-virtual-server \ 171 | --group "Development/Libraries" \ 172 | --spec-files-depth 3 \ 173 | --skip deb \ 174 | --description "${{ env.PKG_DESC }}" \ 175 | --summary "${{ env.PKG_SUMMARY }}" \ 176 | --homepage "${{ env.PKG_HOMEPAGE }}" 177 | 178 | # Upload and sign 179 | upload_list=("$ROOT_REPOS/"*) 180 | cloud_upload upload_list 181 | cloud_sign_and_build_repos_auto virtualmin.dev 182 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Usermin.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Usermin; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %uconfig, %uminiserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Usermin', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | # No Usermin installed, skip it 40 | if (!foreign_installed('usermin')) { 41 | $self->done(2); # Not installed 42 | return; 43 | } 44 | # Disable Usermin upgrades from UI 45 | save_module_acl( { upgrade => 0 }, 'root', 'usermin' ); 46 | # Update Usermin configuration 47 | foreign_require("init"); 48 | foreign_require("usermin"); 49 | usermin::get_usermin_config(\%uconfig); 50 | $uconfig{'theme'} = "authentic-theme"; 51 | $uconfig{'gotomodule'} = 'mailbox'; 52 | $uconfig{'ui_show'} = 'host time'; 53 | usermin::put_usermin_config(\%uconfig); 54 | usermin::get_usermin_miniserv_config(\%uminiserv); 55 | $uminiserv{'preroot'} = "authentic-theme"; 56 | $uminiserv{'ssl'} = "1"; 57 | $uminiserv{'ssl_cipher_list'} = $webmin::strong_ssl_ciphers; 58 | $uminiserv{'domainuser'} = 1; 59 | $uminiserv{'domainstrip'} = 1; 60 | 61 | # Enable 2FA 62 | $uminiserv{'twofactor_provider'} = 'totp'; 63 | $uminiserv{'twofactorfile'} 64 | ||= "$usermin::config{'usermin_dir'}/twofactor-users"; 65 | $uminiserv{'twofactor_wrapper'} 66 | = "$usermin::config{'usermin_dir'}/twofactor/twofactor.pl"; 67 | usermin::create_cron_wrapper($uminiserv{'twofactor_wrapper'}, 68 | "twofactor", "twofactor.pl"); 69 | my (%uacl, %umdirs); 70 | lock_file(usermin::usermin_acl_filename()); 71 | usermin::read_usermin_acl(\%uacl, \%umdirs); 72 | push(@{$umdirs{'user'}}, 'twofactor'); 73 | usermin::save_usermin_acl("user", $umdirs{'user'}); 74 | unlock_file(usermin::usermin_acl_filename()); 75 | 76 | usermin::put_usermin_miniserv_config(\%uminiserv); 77 | 78 | if (init::status_action("usermin")) { 79 | usermin::restart_usermin_miniserv(); 80 | } 81 | else { 82 | usermin::start_usermin(); 83 | } 84 | 85 | # Start Usermin at boot 86 | foreign_require("init", "init-lib.pl"); 87 | init::enable_at_boot("usermin"); 88 | 89 | # More configuration for non-mini stack 90 | my $mini_stack = 91 | (defined $self->bundle() && $self->bundle() =~ /mini/i) ? 92 | ($self->bundle() =~ /LEMP/i ? 'LEMP' : 'LAMP') : 0; 93 | 94 | if (!$mini_stack) { 95 | # Get the Postfix virtual map file, if Postfix is installed 96 | my $map; 97 | if (foreign_installed("postfix")) { 98 | foreign_require("postfix"); 99 | ($map) = postfix::get_maps_files( 100 | postfix::get_real_value($postfix::virtual_maps)); 101 | } 102 | # Setup the Usermin read mail module 103 | my $cfile = "$usermin::config{'usermin_dir'}/mailbox/config"; 104 | my %mailconfig; 105 | read_file($cfile, \%mailconfig); 106 | $mailconfig{'from_map'} = $map || "/etc/postfix/virtual"; 107 | $mailconfig{'from_format'} = 1; 108 | $mailconfig{'mail_system'} = 4; 109 | $mailconfig{'pop3_server'} = 'localhost'; 110 | $mailconfig{'mail_qmail'} = undef; 111 | $mailconfig{'mail_dir_qmail'} = 'Maildir'; 112 | $mailconfig{'server_attach'} = 0; 113 | $mailconfig{'send_mode'} = 'localhost'; 114 | $mailconfig{'nologout'} = 1; 115 | $mailconfig{'noindex_hostname'} = 1; 116 | $mailconfig{'edit_from'} = 0; 117 | write_file($cfile, \%mailconfig); 118 | 119 | # Set the mail folders subdir to Maildir 120 | my $ucfile = "$usermin::config{'usermin_dir'}/mailbox/uconfig"; 121 | my %umailconfig; 122 | read_file($ucfile, \%umailconfig); 123 | $umailconfig{'mailbox_dir'} = 'Maildir'; 124 | $umailconfig{'view_html'} = 2; 125 | $umailconfig{'view_images'} = 1; 126 | $umailconfig{'delete_mode'} = 1; 127 | 128 | # Configure the Usermin Mailbox module to display buttons on the top too 129 | $umailconfig{'top_buttons'} = 2; 130 | 131 | # Configure the Usermin Mailbox module not to display send buttons twice 132 | $umailconfig{'send_buttons'} = 0; 133 | 134 | # Configure the Usermin Mailbox module to always start with one attachment 135 | # for type 136 | $umailconfig{'def_attach'} = 1; 137 | 138 | # Default mailbox name for Sent mail 139 | $umailconfig{'sent_name'} = 'Sent'; 140 | write_file($ucfile, \%umailconfig); 141 | 142 | # Set the default Usermin ACL to only allow access to the needed modules 143 | usermin::save_usermin_acl( 144 | "user", 145 | [ 146 | "mailbox", "changepass", "spam", "filter", 147 | "language", "forward", "cron", "fetchmail", 148 | "updown", "schedule", "filemin", "gnupg" 149 | ] 150 | ); 151 | 152 | # Update user.acl 153 | my $afile = "$usermin::config{'usermin_dir'}/user.acl"; 154 | my %uacl; 155 | read_file($afile, \%uacl); 156 | $uacl{'root'} = ''; 157 | write_file($afile, \%uacl); 158 | 159 | # Configure the Usermin Change Password module to use Virtualmin's 160 | # change-password.pl script 161 | $cfile = "$usermin::config{'usermin_dir'}/changepass/config"; 162 | my %cpconfig; 163 | read_file($cfile, \%cpconfig); 164 | $cpconfig{'passwd_cmd'} 165 | = $config_directory eq "/etc/webmin" 166 | ? "$root/virtual-server/change-password.pl" 167 | : "virtualmin change-password"; 168 | $cpconfig{'cmd_mode'} = 1; 169 | write_file($cfile, \%cpconfig); 170 | 171 | # Also do the same thing for expired password changes 172 | $cfile = "$usermin::config{'usermin_dir'}/config"; 173 | my %umconfig; 174 | read_file($cfile, \%umconfig); 175 | $umconfig{'passwd_cmd'} = "$root/virtual-server/change-password.pl"; 176 | write_file($cfile, \%umconfig); 177 | 178 | # Configure the Usermin Filter module to use the right path for 179 | # Webmin config files. The defaults are incorrect on FreeBSD, where 180 | # we install under /usr/local/etc/webmin 181 | $cfile = "$usermin::config{'usermin_dir'}/filter/config"; 182 | my %ficonfig; 183 | read_file($cfile, \%ficonfig); 184 | $ficonfig{'virtualmin_config'} = "$config_directory/virtual-server"; 185 | $ficonfig{'virtualmin_spam'} 186 | = "$config_directory/virtual-server/lookup-domain.pl"; 187 | write_file($cfile, \%ficonfig); 188 | 189 | # Same for Usermin custom commands 190 | $cfile = "$usermin::config{'usermin_dir'}/commands/config"; 191 | my %ccconfig; 192 | read_file($cfile, \%ccconfig); 193 | $ccconfig{'webmin_config'} = "$config_directory/custom"; 194 | write_file($cfile, \%ccconfig); 195 | 196 | # Same for Usermin .htaccess files 197 | $cfile = "$usermin::config{'usermin_dir'}/htaccess/config"; 198 | my %htconfig; 199 | read_file($cfile, \%htconfig); 200 | $htconfig{'webmin_apache'} = "$config_directory/apache"; 201 | write_file($cfile, \%htconfig); 202 | } 203 | 204 | $self->done(1); # OK! 205 | }; 206 | if ($@) { 207 | $log->error("Error configuring Usermin: $@"); 208 | $self->done(0); # NOK! 209 | } 210 | } 211 | 212 | 1; 213 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config; 2 | 3 | # ABSTRACT: Configure a system for use by Virtualmin 4 | use strict; 5 | use warnings; 6 | no warnings qw(once); # We've got some globals that effect Webmin behavior 7 | use 5.010_001; # Version shipped with CentOS 6. Nothing older. 8 | use Module::Load; 9 | use Term::ANSIColor qw(:constants); 10 | use Log::Log4perl qw(:easy); 11 | 12 | # globals 13 | our (%gconfig, %uconfig, %miniserv, %uminiserv); 14 | our ($root_directory, $config_directory); 15 | our ($trust_unknown_referers, $no_acl_check, $error_must_die, $file_cache); 16 | 17 | sub new { 18 | my ($class, %args) = @_; 19 | my $self = {}; 20 | 21 | $self->{bundle} = $args{bundle}; 22 | $self->{include} = $args{include}; 23 | $self->{exclude} = $args{exclude}; 24 | $self->{test} = $args{test}; 25 | $self->{log} = $args{log} || "/root/virtualmin-install.log"; 26 | 27 | return bless $self, $class; 28 | } 29 | 30 | # Gathered plugins are processed 31 | sub run { 32 | my $self = shift; 33 | 34 | $| = 1; # No line buffering. 35 | 36 | # TODO This should really just be "use Webmin::Core" 37 | $no_acl_check = 1; 38 | $error_must_die = 1; 39 | 40 | # Initialize logger 41 | my $log_conf = qq( 42 | log4perl.logger = INFO, FileApp 43 | log4perl.appender.FileApp = Log::Log4perl::Appender::File 44 | log4perl.appender.FileApp.utf8 = 1 45 | log4perl.appender.FileApp.filename = $self->{log} 46 | log4perl.appender.FileApp.layout = PatternLayout 47 | log4perl.appender.FileApp.layout.ConversionPattern = [%d] [%p] - %m%n 48 | log4perl.appender.FileApp.mode = append 49 | ); 50 | Log::Log4perl->init(\$log_conf); 51 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 52 | $log->info("Starting init-system log..."); 53 | 54 | my @plugins = $self->_gather_plugins(); 55 | @plugins = $self->_order_plugins(@plugins); 56 | my $total = scalar @plugins; 57 | $log->info("Total plugins to be run: $total"); 58 | for (@plugins) { 59 | my $pkg = "Virtualmin::Config::Plugin::$_"; 60 | load $pkg || die "Loading Plugin failed: $_"; 61 | my $plugin = $pkg->new(total => $total, bundle => $self->{bundle}); 62 | $plugin->actions(); 63 | if ($self->{test} && $plugin->can('tests')) { 64 | $plugin->tests(); 65 | } 66 | } 67 | return 1; 68 | } 69 | 70 | # list_bundles 71 | # Returns a list of the available configuration bundles. 72 | sub list_bundles { 73 | my $self = shift; 74 | 75 | # Figure out our module home directory 76 | my $modpath = $INC{'Virtualmin/Config.pm'}; 77 | use File::Basename; 78 | my $bundlepath = dirname($modpath) . '/Config'; 79 | opendir(my $DIR, $bundlepath); 80 | my @bundles = grep(/\.pm$/, readdir($DIR)); 81 | closedir($DIR); 82 | @bundles = grep { $_ !~ /^(Dummy|Plugin|Stack)\.pm$/ } @bundles; 83 | for (@bundles) {s/\.pm$//} 84 | @bundles = sort(@bundles); 85 | return @bundles; 86 | } 87 | 88 | # list-plugins 89 | # Returns a sorted list of the available plugins. 90 | sub list_plugins { 91 | my $self = shift; 92 | 93 | # Figure out our module home directory 94 | require Virtualmin::Config::Plugin; 95 | my $modpath = $INC{'Virtualmin/Config/Plugin.pm'}; 96 | use File::Basename; 97 | my $pluginpath = dirname($modpath) . '/Plugin'; 98 | opendir(my $DIR, $pluginpath); 99 | my @plugins = grep(/\.pm$/, readdir($DIR)); 100 | closedir($DIR); 101 | @plugins = grep { $_ ne 'Test.pm' && $_ ne 'Test2.pm' } @plugins; 102 | for (@plugins) {s/\.pm$//} 103 | @plugins = sort(@plugins); 104 | return @plugins; 105 | } 106 | 107 | # Merges the selected bundle, with any extra includes, and removes excludes 108 | sub _gather_plugins { 109 | my $self = shift; 110 | my @plugins; 111 | 112 | # If bundle specified, load it up. 113 | if ($self->{bundle}) { 114 | # Get bundle 115 | my $pkg = "Virtualmin::Config::$self->{bundle}"; 116 | load $pkg; 117 | my $bundle = $pkg->new(); 118 | # Get stack 119 | my $pkg2 = "Virtualmin::Config::Stack"; 120 | load $pkg2; 121 | my $stack = $pkg2->new(); 122 | # Ask the bundle for a list of plugins 123 | @plugins = $bundle->plugins($stack); 124 | } 125 | 126 | # Check with the command arguments 127 | if (ref($self->{include}) eq 'ARRAY' || ref($self->{include}) eq 'STRING') { 128 | for my $include ($self->{'include'}) { 129 | push(@plugins, $include) 130 | unless (map { grep(/^$include$/, @{$_}) } @plugins); 131 | } 132 | } 133 | 134 | # Check for excluded plugins 135 | my @noplugins = _flat($self->{'exclude'}); 136 | if (@noplugins) { 137 | @plugins = _flat(@plugins); 138 | no warnings "uninitialized"; 139 | @plugins 140 | = grep { my $noplugin = $_; !grep(/^$noplugin$/, @noplugins) } @plugins; 141 | } 142 | return @plugins; 143 | } 144 | 145 | # Take the gathered list of plugins and sort them to resolve deps 146 | sub _order_plugins { 147 | my ($self, @plugins) = @_; 148 | my %plugin_details; # Will hold an array of hashes containing name/depends 149 | # Load up @plugin_details with name and dependency list 150 | if (ref($plugins[0]) eq 'ARRAY') { # XXX Why is this so stupid? 151 | @plugins = map {@$_} @plugins; # Flatten the array of refs into list. 152 | } 153 | for my $plugin_name (@plugins) { 154 | my $pkg = "Virtualmin::Config::Plugin::$plugin_name"; 155 | load $pkg; 156 | my $plugin = $pkg->new(); 157 | $plugin_details{$plugin->{'name'}} = $plugin->{'depends'} || []; 158 | } 159 | return _topo_sort(%plugin_details); 160 | } 161 | 162 | # Topological sort on dependencies 163 | sub _topo_sort { 164 | my (%deps) = @_; 165 | my (@order, %seen, $proc); 166 | $proc = sub { 167 | my ($node) = @_; 168 | return if ($seen{$node}++); 169 | foreach my $dep (@{$deps{$node} || []}) { 170 | $proc->($dep); 171 | } 172 | push(@order, $node); 173 | }; 174 | $proc->($_) for (sort keys(%deps)), (map(@$_, values %deps)); 175 | return _uniq(@order); 176 | } 177 | 178 | # uniq so we don't have to import List::MoreUtils 179 | sub _uniq { 180 | my %seen; 181 | return grep { !$seen{$_}++ } @_; 182 | } 183 | 184 | # Flatten into plain list 185 | sub _flat { 186 | return map { ref eq 'ARRAY' ? @$_ : $_ } @_; 187 | } 188 | 189 | 1; 190 | 191 | __END__ 192 | 193 | =pod 194 | 195 | =encoding utf8 196 | 197 | =for html 198 | 199 |   200 | 201 | Coverage Status 203 | 204 | 205 | =head1 NAME 206 | 207 | Virtualmin::Config - A collection of plugins to initialize the configuration 208 | of services that Virtualmin manages, and a command line tool called 209 | config-system to run them. It can be thought of as a very specialized 210 | configuration management system (e.g. puppet, chef, whatever) for doing 211 | just one thing (setting up a system for Virtualmin). It has basic dependency 212 | resolution (via topological sort), logging, and ties into the Webmin API to 213 | make some common tasks (like starting/stopping services, setting them to run 214 | on boot) simpler to code. 215 | 216 | =head1 SYNOPSIS 217 | 218 | my $bundle = Virtualmin::Config->new(bundle => 'LAMP'); 219 | $bundle->run(); 220 | 221 | You can also call it with specific plugins, rather than a whole bundle of 222 | plugins. 223 | 224 | my $plugin = Virtualmin::Config->new(include => 'Apache'); 225 | $plugin->run(); 226 | 227 | Adding new features to the installer, or modifying installer features, should 228 | be done by creating new plugins or by adding to existing ones. 229 | 230 | =head1 DESCRIPTION 231 | 232 | This is a mini-framework for configuring elements of a Virtualmin system. It 233 | uses Webmin as a library to abstract common configuration tasks, provides a 234 | friendly status indicator, and makes it easy to pick and choose the kind of 235 | configuration you want (should you choose to go that route). The Virtualmin 236 | install script chooses either the LAMP (with Apache) or LEMP (with nginx) 237 | bundle, and performs the configuration for the whole stack. 238 | 239 | It includes plugins for all of the common tasks in a Virtualmin system, such 240 | as Apache, MySQL/MariaDB, Postfix, SpamAssassin, etc. 241 | 242 | =head1 INSTALLATION 243 | 244 | The recommended installation method is to use native packages for your 245 | distribution. We provide packages for Debian, Ubuntu, CentOS/RHEL, and Fedora 246 | in our repositories. 247 | 248 | You can use the standard Perl process to install from the source tarball or 249 | git clone: 250 | 251 | perl Makefile.PL 252 | make 253 | make test 254 | make install 255 | 256 | Or, use your system native package manager. The followin assumes you have all 257 | of the packages needed to build native packages installed. 258 | 259 | To build a dpkg for Debian/Ubuntu: 260 | 261 | dpkg-buildpackage -b -rfakeroot -us -uc 262 | 263 | And, for CentOS/Fedora/RHEL/etc. RPM distributions: 264 | 265 | dzil build # Creates a tarball 266 | cp Virtualmin-Config-*.tar.gz ~/rpmbuild/SOURCES 267 | rpmbuild -bb virtualmin-config.spec 268 | 269 | =head1 ATTRIBUTES 270 | 271 | =over 272 | 273 | =item bundle 274 | 275 | Selects the plugin bundle to be installed. A bundle is a list of plugins 276 | configured in a C class. 277 | 278 | =item include 279 | 280 | One or more additional plugins to include in the C. This can be 281 | used alongside C or by itself. Dependencies will also be run, and 282 | there is no way to disable dependencies (because they're depended on!). 283 | 284 | =item exclude 285 | 286 | One or more plugins to remove from the selected C. Plugins that are 287 | needed to resolve dependencies will be re-added automatically. 288 | 289 | =back 290 | 291 | =head1 METHODS 292 | 293 | =over 294 | 295 | =item run 296 | 297 | This method figures out which plugins to run (based on the C, 298 | C, and C attributes. 299 | 300 | =back 301 | 302 | =head1 LICENSE AND COPYRIGHT 303 | 304 | Licensed under the GPLv3. Copyright 2017 Virtualmin, Inc. 305 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Postfix.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Postfix; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | use parent 'Virtualmin::Config::Plugin'; 6 | 7 | our $config_directory; 8 | our (%gconfig, %miniserv); 9 | our $trust_unknown_referers = 1; 10 | 11 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 12 | 13 | sub new { 14 | my ($class, %args) = @_; 15 | 16 | # inherit from Plugin 17 | my $self = $class->SUPER::new(name => 'Postfix', %args); 18 | 19 | return $self; 20 | } 21 | 22 | # actions method performs whatever configuration is needed for this 23 | # plugin. XXX Needs to make a backup so changes can be reverted. 24 | sub actions { 25 | my $self = shift; 26 | 27 | use Cwd; 28 | my $cwd = getcwd(); 29 | my $root = $self->root(); 30 | chdir($root); 31 | $0 = "$root/virtual-server/config-system.pl"; 32 | push(@INC, $root); 33 | push(@INC, "$root/vendor_perl"); 34 | eval 'use WebminCore'; ## no critic 35 | init_config(); 36 | 37 | $self->spin(); 38 | eval { 39 | foreign_require("init", "init-lib.pl"); 40 | foreign_require("postfix", "postfix-lib.pl"); 41 | 42 | # Alibaba Cloud images have a broken network config (no IPv6 lo) 43 | # that causes postconf to error out. 44 | { 45 | my $err 46 | = `sed -i "s/^inet_interfaces = localhost/inet_interfaces = all/" /etc/postfix/main.cf 2>&1`; 47 | if ($err) { 48 | $log->warning( 49 | "Something is wrong with the Postfix /etc/postfix/main.cf. Is it missing?" 50 | ); 51 | } 52 | } 53 | 54 | # Debian doesn't get a default main.cf unless apt-get is run 55 | # interactively. 56 | if (!-e "/etc/postfix/main.cf" && -e "/usr/share/postfix/main.cf.debian") { 57 | $self->logsystem("cp /usr/share/postfix/main.cf.debian /etc/postfix/main.cf"); 58 | } 59 | 60 | # FreeBSD doesn't get a default main.cf, or anything else 61 | if (!-e "/usr/local/etc/postfix/main.cf" 62 | && -e "/usr/local/etc/postfix/dist/main.cf") 63 | { 64 | $self->logsystem("cp /usr/local/etc/postfix/dist/* /usr/local/etc/postfix/"); 65 | } 66 | 67 | my $postetc = $postfix::config{'postfix_config_file'}; 68 | $postetc =~ s/\/[^\/]+$//; 69 | my @maptypes = `$postfix::config{'postfix_config_command'} -m`; 70 | chop(@maptypes); 71 | my $maptype = indexof("hash", @maptypes) >= 0 ? "hash" : 72 | indexof("lmdb", @maptypes) >= 0 ? "lmdb" : "dbm"; 73 | if (!postfix::get_real_value("virtual_alias_maps")) { 74 | postfix::set_current_value("virtual_alias_maps", 75 | "$maptype:$postetc/virtual", 1); 76 | } 77 | postfix::ensure_map("virtual_alias_maps"); 78 | postfix::regenerate_virtual_table(); 79 | 80 | # Setup aliases 81 | if (!postfix::get_real_value("alias_maps")) { 82 | postfix::set_current_value("alias_maps", 83 | "$maptype:/etc/aliases", 1); 84 | } 85 | postfix::ensure_map("alias_maps"); 86 | 87 | # Setup aliases database 88 | if (!postfix::get_real_value("alias_database")) { 89 | postfix::set_current_value("alias_database", 90 | "$maptype:/etc/aliases", 1); 91 | } 92 | postfix::ensure_map("alias_database"); 93 | 94 | # Setup BCC map 95 | if (!postfix::get_real_value("sender_bcc_maps")) { 96 | postfix::set_current_value("sender_bcc_maps", "$maptype:$postetc/bcc"); 97 | } 98 | postfix::ensure_map("sender_bcc_maps"); 99 | postfix::regenerate_bcc_table(); 100 | 101 | # Setup sender dependent map 102 | my ($major, $minor) = split(/\./, $postfix::postfix_version); 103 | if (($major >= 2 && $minor >= 7) || $major >= 3) { 104 | if (!postfix::get_real_value("sender_dependent_default_transport_maps")) { 105 | postfix::set_current_value("sender_dependent_default_transport_maps", 106 | "$maptype:$postetc/dependent"); 107 | } 108 | postfix::ensure_map("sender_dependent_default_transport_maps"); 109 | postfix::regenerate_any_table("sender_dependent_default_transport_maps"); 110 | } 111 | 112 | my $wrapper = "/usr/bin/procmail-wrapper"; 113 | postfix::set_current_value("mailbox_command", 114 | "$wrapper -o -a \$DOMAIN -d \$LOGNAME", 1); 115 | postfix::set_current_value("home_mailbox", "Maildir/", 1); 116 | postfix::set_current_value("inet_interfaces", "all", 1); 117 | 118 | # Add smtp auth stuff to main.cf 119 | postfix::set_current_value("smtpd_sasl_auth_enable", "yes", 1); 120 | postfix::set_current_value("smtpd_tls_security_level", "may", 1); 121 | postfix::set_current_value("smtpd_sasl_security_options", "noanonymous", 1); 122 | postfix::set_current_value("broken_sasl_auth_clients", "yes", 1); 123 | postfix::set_current_value("smtpd_recipient_restrictions", 124 | "permit_mynetworks permit_sasl_authenticated reject_unauth_destination", 125 | 1); 126 | 127 | # Opportunistic encryption for outgoing mail 128 | my $seclvl 129 | = compare_version_numbers($postfix::postfix_version, "2.11") >= 0 130 | ? "dane" 131 | : "may"; 132 | postfix::set_current_value("smtp_tls_security_level", $seclvl, 1); 133 | if ($seclvl eq "dane") { 134 | postfix::set_current_value("smtp_dns_support_level", "dnssec", 1); 135 | postfix::set_current_value("smtp_host_lookup", "dns", 1); 136 | } 137 | 138 | # Turn off limit on mailbox size 139 | postfix::set_current_value("mailbox_size_limit", "0"); 140 | 141 | # Turn off % hack, which breaks user%domain mailboxes 142 | postfix::set_current_value("allow_percent_hack", "no"); 143 | 144 | # Allow @ in usernames for receiving mail 145 | postfix::set_current_value("resolve_dequoted_address", "no", 1); 146 | 147 | # And master.cf 148 | my $master = postfix::get_master_config(); 149 | my ($smtp) = grep { $_->{'name'} eq 'smtp' && $_->{'enabled'} } @$master; 150 | $smtp || die "Failed to find SMTP postfix service!"; 151 | if ( $smtp->{'command'} !~ /smtpd_sasl_auth_enable/ 152 | || $smtp->{'command'} !~ /smtpd_tls_security_level/) 153 | { 154 | $smtp->{'command'} .= " -o smtpd_sasl_auth_enable=yes" 155 | if ($smtp->{'command'} !~ /smtpd_sasl_auth_enable/); 156 | $smtp->{'command'} .= " -o smtpd_tls_security_level=may" 157 | if ($smtp->{'command'} !~ /smtpd_tls_security_level/); 158 | postfix::modify_master($smtp); 159 | } 160 | 161 | # Add submission entry, if missing 162 | my ($submission) 163 | = grep { $_->{'name'} eq 'submission' && $_->{'enabled'} } @$master; 164 | if (!$submission) { 165 | $submission = {%$smtp}; 166 | $submission->{'name'} = 'submission'; 167 | postfix::create_master($submission); 168 | } 169 | 170 | # Add smtps entry, if missing 171 | my ($smtps) = grep { $_->{'name'} eq 'smtps' && $_->{'enabled'} } @$master; 172 | if (!$smtps) { 173 | $smtps = {%$smtp}; 174 | $smtps->{'name'} = 'smtps'; 175 | $smtps->{'command'} .= " -o smtpd_tls_wrappermode=yes"; 176 | postfix::create_master($smtps); 177 | } 178 | 179 | delete($main::file_cache{$postfix::config{'postfix_config_file'}}); 180 | postfix::reload_postfix(); 181 | 182 | # Make sure other code knows the Postfix version 183 | $postfix::postfix_version = backquote_command( 184 | "$postfix::config{'postfix_config_command'} -h mail_version"); 185 | $postfix::postfix_version =~ s/\r|\n//g; 186 | open_tempfile(my $VER, ">$postfix::module_config_directory/version"); 187 | print_tempfile($VER, $postfix::postfix_version, "\n"); 188 | close_tempfile($VER); 189 | 190 | # Force alias map rebuild if missing 191 | postfix::regenerate_aliases(); 192 | 193 | foreign_require("init", "init-lib.pl"); 194 | if (-e "/usr/sbin/alternatives" && 195 | -e "/usr/sbin/sendmail.postfix") { 196 | $self->logsystem("/usr/sbin/alternatives --set mta /usr/sbin/sendmail.postfix"); 197 | } 198 | if ($gconfig{'os_type'} eq 'freebsd') { 199 | 200 | # Fully disable sendmail in rc.conf 201 | print "Disabling Sendmail in rc.conf\n"; 202 | my $lref = read_file_lines("/etc/rc.conf"); 203 | foreach my $v ( 204 | "sendmail_enable", "sendmail_submit_enable", 205 | "sendmail_outbound_enable", "sendmail_msp_queue_enable" 206 | ) 207 | { 208 | my $found; 209 | foreach my $l (@$lref) { 210 | if ($l =~ /^\Q$v\E\s*=\s*"(\S+)"/i) { 211 | if ($1 ne "NO") { 212 | $l = $v . '="NO"'; 213 | } 214 | $found++; 215 | } 216 | } 217 | push(@$lref, $v . '="NO"') if (!$found); 218 | } 219 | flush_file_lines("/etc/rc.conf"); 220 | 221 | # Set default mailer to Postfix, in /etc/mail/mailer.conf 222 | print "Setting default mailer to Postfix\n"; 223 | $lref = read_file_lines("/etc/mail/mailer.conf"); 224 | foreach my $v ( 225 | ["sendmail", "/usr/local/sbin/sendmail"], 226 | ["send-mail", "/usr/local/sbin/sendmail"], 227 | ["mailq", "/usr/local/bin/mailq"], 228 | ["newaliases", "/usr/local/bin/newaliases"], 229 | ["hoststat", "/usr/local/sbin/sendmail"], 230 | ["purgestat", "/usr/local/sbin/sendmail"] 231 | ) 232 | { 233 | foreach my $l (@$lref) { 234 | if ($l =~ /^\Q$v->[0]\E\s+/) { 235 | $l = $v->[0] . "\t" . $v->[1]; 236 | } 237 | } 238 | } 239 | flush_file_lines("/etc/mail/mailer.conf"); 240 | } 241 | init::enable_at_boot("postfix"); 242 | init::disable_at_boot("sendmail"); 243 | init::disable_at_boot("exim4"); 244 | if (foreign_check("sendmail")) { 245 | foreign_require("sendmail", "sendmail-lib.pl"); 246 | if (sendmail::is_sendmail_running()) { 247 | sendmail::stop_sendmail(); 248 | } 249 | } 250 | $self->logsystem("killall -9 sendmail >/dev/null 2>&1"); 251 | $self->logsystem("newaliases"); 252 | if (!postfix::is_postfix_running()) { 253 | my $err = postfix::start_postfix(); 254 | print STDERR "Failed to start Postfix!\n" if ($err); 255 | } 256 | 257 | $self->done(1); # OK! 258 | }; 259 | if ($@) { 260 | $log->error("Failed to configure Postfix: $@"); 261 | $self->done(0); 262 | } 263 | } 264 | 265 | 1; 266 | -------------------------------------------------------------------------------- /lib/Virtualmin/Config/Plugin/Virtualmin.pm: -------------------------------------------------------------------------------- 1 | package Virtualmin::Config::Plugin::Virtualmin; 2 | use strict; 3 | use warnings; 4 | no warnings qw(once); 5 | no warnings 'uninitialized'; 6 | use parent 'Virtualmin::Config::Plugin'; 7 | 8 | our $config_directory; 9 | our (%gconfig, %miniserv); 10 | our (%config, $module_config_file); 11 | our $trust_unknown_referers = 1; 12 | 13 | my $log = Log::Log4perl->get_logger("virtualmin-config-system"); 14 | 15 | sub new { 16 | my ($class, %args) = @_; 17 | 18 | # inherit from Plugin 19 | my $self 20 | = $class->SUPER::new(name => 'Virtualmin', %args); 21 | 22 | return $self; 23 | } 24 | 25 | # actions method performs whatever configuration is needed for this 26 | # plugin. XXX Needs to make a backup so changes can be reverted. 27 | sub actions { 28 | my $self = shift; 29 | 30 | use Cwd; 31 | my $cwd = getcwd(); 32 | my $root = $self->root(); 33 | chdir($root); 34 | $0 = "$root/virtual-server/config-system.pl"; 35 | push(@INC, $root); 36 | push(@INC, "$root/vendor_perl"); 37 | eval 'use WebminCore'; ## no critic 38 | init_config(); 39 | 40 | $self->spin(); 41 | eval { 42 | my $mini_stack = 43 | (defined $self->bundle() && $self->bundle() =~ /mini/i) ? 44 | ($self->bundle() =~ /LEMP/i ? 'LEMP' : 'LAMP') : 0; 45 | foreign_require("virtual-server"); 46 | lock_file($module_config_file); 47 | $virtual_server::config{'mail_system'} = 0; 48 | $virtual_server::config{'nopostfix_extra_user'} = 1; 49 | $virtual_server::config{'aliascopy'} = 1; 50 | $virtual_server::config{'home_base'} = "/home"; 51 | $virtual_server::config{'webalizer'} = 0; 52 | $virtual_server::config{'postgresql'} = 0; 53 | $virtual_server::config{'ftp'} = 0; 54 | $virtual_server::config{'logrotate'} = 3; 55 | $virtual_server::config{'bind_spfall'} = 0; 56 | $virtual_server::config{'bind_spf'} = "yes"; 57 | $virtual_server::config{'bccs'} = 1; 58 | $virtual_server::config{'reseller_theme'} = "authentic-theme"; 59 | $virtual_server::config{'append_style'} = 6; 60 | 61 | # For mini stack 62 | if ($mini_stack) { 63 | $virtual_server::config{'mail_system'} = 99; 64 | $virtual_server::config{'spam'} = 0; 65 | $virtual_server::config{'virus'} = 0; 66 | $virtual_server::config{'dns'} = 0; 67 | $virtual_server::config{'mail'} = 0; 68 | } 69 | elsif (defined $self->bundle()) { 70 | $virtual_server::config{'spam'} = 1; 71 | $virtual_server::config{'virus'} = 1; 72 | $virtual_server::config{'default_procmail'} = 1, 73 | $virtual_server::config{'spam_delivery'} = "\$HOME/Maildir/.spam/" 74 | } 75 | 76 | if (defined $self->bundle() && $self->bundle() =~ /LEMP/i) { 77 | $virtual_server::config{'ssl'} = 0; 78 | $virtual_server::config{'web'} = 0; 79 | $virtual_server::config{'backup_feature_ssl'} = 0; 80 | } 81 | elsif (defined $self->bundle()) { 82 | $virtual_server::config{'ssl'} = 3; 83 | } 84 | 85 | # Enable extra default modules 86 | my @plugins = split /\s+/, ($virtual_server::config{'plugins'} || ''); 87 | push(@plugins, 'virtualmin-awstats', 'virtualmin-htpasswd'); 88 | if ($virtual_server::virtualmin_pro) { 89 | push(@plugins, 'virtualmin-wp-workbench'); 90 | } 91 | $virtual_server::config{'plugins'} = join(' ', unique(@plugins)); 92 | 93 | if ((!$mini_stack) && 94 | (-e "/etc/debian_version" || -e "/etc/lsb-release")) { 95 | $virtual_server::config{'proftpd_config'} 96 | = 'ServerName ${DOM} User ftp Group nogroup UserAlias anonymous ftp DenyAll RequireValidShell off '; 97 | } 98 | 99 | # Make the Virtualmin web directories a bit more secure 100 | # FreeBSD has a low secondary groups limit..skip this bit. 101 | # XXX ACLs can reportedly deal with this...needs research. 102 | unless ($gconfig{'os_type'} eq 'freebsd') { 103 | if (defined(getpwnam("www-data"))) { 104 | $virtual_server::config{'web_user'} = "www-data"; 105 | } 106 | else { 107 | $virtual_server::config{'web_user'} = "apache"; 108 | } 109 | $virtual_server::config{'html_perms'} = "0750"; 110 | } 111 | 112 | # Always force PHP-FPM mode 113 | $virtual_server::config{'php_suexec'} = 3; 114 | 115 | # If system doesn't have Jailkit support, disable it 116 | if (!has_command('jk_init')) { 117 | $virtual_server::config{'jailkit_disabled'} = 1; 118 | } 119 | 120 | # If system doesn't have AWStats support, disable it 121 | if (foreign_check("virtualmin-awstats")) { 122 | my %awstats_config = foreign_config("virtualmin-awstats"); 123 | if ($awstats_config{'awstats'} && !-r $awstats_config{'awstats'}) { 124 | my @plugins = split(/\s/, $virtual_server::config{'plugins'}); 125 | @plugins = grep { $_ ne 'virtualmin-awstats' } @plugins; 126 | $virtual_server::config{'plugins'} = join(' ', @plugins); 127 | } 128 | } 129 | 130 | # Enable DKIM at install time 131 | if (!$mini_stack && -r "/etc/opendkim.conf") { 132 | my $dkim = virtual_server::get_dkim_config(); 133 | if (ref($dkim) && !$dkim->{'enabled'}) { 134 | $dkim->{'selector'} = virtual_server::get_default_dkim_selector(); 135 | $dkim->{'sign'} = 1; 136 | $dkim->{'enabled'} = 1; 137 | $dkim->{'extra'} = [ get_system_hostname() ]; 138 | virtual_server::push_all_print(); 139 | virtual_server::set_all_null_print(); 140 | my $ok = virtual_server::enable_dkim($dkim, 1, 2048); 141 | virtual_server::pop_all_print(); 142 | if ($ok) { 143 | $virtual_server::config{'dkim_enabled'} = 1; 144 | } 145 | } 146 | } 147 | 148 | # Try to request SSL certificate for the hostname 149 | if (defined($ENV{'VIRTUALMIN_INSTALL_TEMPDIR'}) && 150 | !$virtual_server::config{'default_domain_ssl'} && 151 | !$virtual_server::config{'wizard_run'}) 152 | { 153 | my ($ok, $error) = virtual_server::setup_virtualmin_default_hostname_ssl(); 154 | write_file_contents("$ENV{'VIRTUALMIN_INSTALL_TEMPDIR'}/virtualmin_ssl_host_status", 155 | "SSL certificate request for the hostname : $ok : @{[html_strip($error)]}"); 156 | if ($ok) { 157 | mkdir("$ENV{'VIRTUALMIN_INSTALL_TEMPDIR'}/virtualmin_ssl_host_success"); 158 | } 159 | else { 160 | virtual_server::delete_virtualmin_default_hostname_ssl(); 161 | } 162 | } 163 | 164 | # Save Virtualmin configuration after all changes are made 165 | save_module_config(\%virtual_server::config); 166 | unlock_file($module_config_file); 167 | 168 | # Setup the Apache, BIND and DB modules to use tables for lists 169 | foreach my $t ( 170 | ['apache', 'show_list'], 171 | ['bind8', 'show_list'], 172 | ['mysql', 'style'], 173 | ['postgresql', 'style'] 174 | ) 175 | { 176 | next if (!foreign_installed($t->[0])); 177 | next if (!foreign_check($t->[0])); 178 | my %mconfig = foreign_config($t->[0]); 179 | $mconfig{$t->[1]} = 1; 180 | save_module_config(\%mconfig, $t->[0]); 181 | } 182 | 183 | # Make the default home directory permissions 750 184 | my %uconfig = foreign_config("useradmin"); 185 | if ($gconfig{'os_type'} eq 'freebsd') { 186 | $uconfig{'homedir_perms'} = "0751"; 187 | } 188 | else { $uconfig{'homedir_perms'} = "0750"; } 189 | save_module_config(\%uconfig, "useradmin"); 190 | 191 | # Turn on caching for downloads by Virtualmin 192 | if (!$gconfig{'cache_size'}) { 193 | $gconfig{'cache_size'} = 50 * 1024 * 1024; 194 | $gconfig{'cache_mods'} = "virtual-server"; 195 | write_file("$config_directory/config", \%gconfig); 196 | } 197 | 198 | # Fix to extend Jailkit [basicshell] paths 199 | if (has_command('jk_init') && foreign_check('jailkit')) { 200 | foreign_require('jailkit'); 201 | my $jk_init_conf = &jailkit::get_jk_init_ini(); 202 | my $jk_basicshell_paths = $jk_init_conf->val('basicshell', 'paths'); 203 | my @jk_basicshell_paths = split(/\s*,\s*/, $jk_basicshell_paths); 204 | my @jk_params = ( 205 | ['zsh', '/etc/zsh/zshrc', '/etc/zsh/zshenv'], 206 | ['rbash'], ['id', 'groups'], 207 | ); 208 | 209 | JKPARAMS: 210 | foreach my $jk_params (@jk_params) { 211 | foreach my $jk_param (@{$jk_params}) { 212 | if (grep(/^$jk_param$/, @jk_basicshell_paths)) { 213 | next JKPARAMS; 214 | } 215 | } 216 | $jk_basicshell_paths .= ", @{[join(', ', @{$jk_params})]}"; 217 | } 218 | 219 | $jk_init_conf->newval('basicshell', 'paths', $jk_basicshell_paths); 220 | &jailkit::write_jk_init_ini($jk_init_conf); 221 | } 222 | 223 | # Disable and stop certbot timer 224 | if (has_command('certbot')) { 225 | foreign_require('init', 'init-lib.pl'); 226 | 227 | # Unit name is differnet on different distros 228 | my @certbot_units = ('certbot-renew.timer', 'certbot.timer'); 229 | foreach my $certbot_unit (@certbot_units) { 230 | if (init::is_systemd_service($certbot_unit)) { 231 | init::disable_at_boot($certbot_unit); 232 | init::stop_action($certbot_unit); 233 | if (defined(&init::mask_action)) { 234 | init::mask_action($certbot_unit); 235 | } 236 | } 237 | } 238 | } 239 | 240 | # Terminal on the new installs 241 | # is allowed to have colors on 242 | if (&foreign_check('xterm')) { 243 | my %xterm_config = foreign_config("xterm"); 244 | $xterm_config{'rcfile'} = 1; 245 | save_module_config(\%xterm_config, "xterm"); 246 | } 247 | 248 | # Add PHP alias so users could execute 249 | # specific to virtual server PHP version 250 | my $profiled = "/etc/profile.d"; 251 | if (-d $profiled) { 252 | my $profiledphpalias = "$profiled/virtualmin-phpalias.sh"; 253 | my $phpalias 254 | = "php=\`which php 2>/dev/null\`\n" 255 | . "if \[ -x \"\$php\" \]; then\n" 256 | . " alias php='\$\(phpdom=\"bin/php\" ; \(while [ ! -f \"\$phpdom\" ] && [ \"\$PWD\" != \"/\" ]; do cd \"\$\(dirname \"\$PWD\"\)\" || \"\$php\" ; done ; if [ -f \"\$phpdom\" ] ; then echo \"\$PWD/\$phpdom\" ; else echo \"\$php\" ; fi\)\)'\n" 257 | . "fi\n"; 258 | write_file_contents($profiledphpalias, $phpalias); 259 | } 260 | 261 | # OpenSUSE PHP related fixes 262 | if ($gconfig{'os_type'} eq "suse-linux") { 263 | $self->logsystem("mv /etc/php8/fpm/php-fpm.conf.default /etc/php8/fpm/php-fpm.conf >/dev/null 2>&1"); 264 | } 265 | 266 | # Disable mod_php in package managers 267 | if ($gconfig{'os_type'} =~ /debian-linux|ubuntu-linux/) { 268 | # Disable libapache2-mod-php* in Ubuntu/Debian 269 | my $fpref = $gconfig{'real_os_type'} =~ /ubuntu/i ? 'ubuntu' : 'debian'; 270 | my $apt_pref_dir = "/etc/apt/preferences.d"; 271 | if (!-d $apt_pref_dir) { 272 | # Create the directory if it doesn't exist 273 | make_dir($apt_pref_dir, oct(755)); 274 | } 275 | # Create a file to restrict libapache2-mod-php* packages 276 | $self->logsystem( 277 | "echo \"Package: libapache2-mod-php*\nPin: release *\nPin-Priority: -1\" > ". 278 | "$apt_pref_dir/$fpref-virtualmin-restricted-packages"); 279 | } else { 280 | # Disable php and php*-php in RHEL and derivatives 281 | my $dnf_conf = "/etc/dnf/dnf.conf"; 282 | if (-f $dnf_conf) { 283 | lock_file($dnf_conf); 284 | my $lref = read_file_lines($dnf_conf); 285 | my $lnum; 286 | foreach my $i (0 .. $#$lref) { 287 | # If main section is found 288 | if ($lref->[$i] =~ /^\[main\]/) { 289 | $lnum = $i; 290 | } 291 | # If exclude= line is found, don't add another one 292 | if ($lref->[$i] =~ /^exclude=/) { 293 | $lnum = undef; 294 | } 295 | } 296 | # Add exclude= line if it's not 297 | # found right after [main] 298 | if (defined($lnum)) { 299 | $lref->[$lnum] .= "\nexclude=php php*-php"; 300 | } 301 | flush_file_lines($dnf_conf); 302 | unlock_file($dnf_conf); 303 | } 304 | } 305 | 306 | $self->done(1); # OK! 307 | }; 308 | if ($@) { 309 | $log->error("Error configuring Virtualmin: $@"); 310 | $self->done(0); 311 | } 312 | } 313 | 314 | 1; 315 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | virtualmin-config (8.0.4) stable; urgency=medium 2 | 3 | * Fix to set up mail in each module respectively 4 | 5 | -- Ilia Ross Mon, 15 Dec 2025 14:00:00 +0200 6 | 7 | virtualmin-config (8.0.3) stable; urgency=medium 8 | 9 | * Fix to clear Apache module cache to make sure it gets updated 10 | * Fix to still enable `cgid` module for CGI scripts to work under `suexec` 11 | 12 | -- Ilia Ross Mon, 29 Sep 2025 01:00:00 +0300 13 | 14 | virtualmin-config (8.0.2) stable; urgency=medium 15 | 16 | * Add Debian 13 support 17 | * Fix quota issues on EL 10 18 | * Fix to configure Fail2Ban to block port 2222 as well 19 | 20 | -- Ilia Ross Tue, 16 Sep 2025 19:00:00 +0300 21 | 22 | virtualmin-config (8.0.1) stable; urgency=medium 23 | 24 | * Fix Apache configuration 25 | * Fix configuring Fail2Ban on mini stacks 26 | 27 | -- Ilia Ross Sun, 15 Jun 2025 17:00:00 +0300 28 | 29 | virtualmin-config (8.0.0) stable; urgency=medium 30 | 31 | * Add support for Virtualmin 8 32 | * Fix to adjust installation types leaving only "full" and "mini" 33 | 34 | -- Ilia Ross Sat, 14 Jun 2025 15:00:00 +0300 35 | 36 | virtualmin-config (7.0.41) stable; urgency=medium 37 | 38 | * Fix to enable the use of Firewalld rich rules with Fail2ban 39 | * Fix to activate Nginx slightly later after reboot 40 | 41 | -- Ilia Ross Mon, 27 Jan 2025 21:15:00 +0200 42 | 43 | virtualmin-config (7.0.40) stable; urgency=medium 44 | 45 | * Add the ability to configure "Micro" and "Nano" installation types 46 | * Fix to display the "Report Spam" button when listing mail 47 | * Fix to retain the default `mydestination` option in Postfix 48 | 49 | -- Ilia Ross Wed, 25 Dec 2024 19:15:00 +0200 50 | 51 | virtualmin-config (7.0.30) stable; urgency=medium 52 | 53 | * Fix to prevent breaking spinners on user input 54 | * Fix to only show host and time in Usermin dashboard 55 | * Fix to enable plugins we consider default (on re-run) 56 | 57 | -- Ilia Ross Sun, 29 Sep 2024 03:45:00 +0300 58 | 59 | virtualmin-config (7.0.22) stable; urgency=medium 60 | 61 | * Fix to directly create `systemd` override files 62 | 63 | -- Ilia Ross Sun, 19 May 2024 20:40:00 +0300 64 | 65 | virtualmin-config (7.0.21) stable; urgency=high 66 | 67 | * Fix to save Virtualmin configuration reliably 68 | 69 | -- Ilia Ross Tue, 14 May 2024 22:30:00 +0300 70 | 71 | virtualmin-config (7.0.20) stable; urgency=high 72 | 73 | * Fix saving Virtualmin configuration 74 | 75 | -- Ilia Ross Tue, 14 May 2024 20:30:00 +0300 76 | 77 | virtualmin-config (7.0.19) stable; urgency=medium 78 | 79 | * Add Ubuntu 24.04 support 80 | * Fix Nginx start correctly after reboot 81 | * Enable DKIM at install time 82 | 83 | -- Ilia Ross Sat, 27 Apr 2024 22:43:41 +0300 84 | 85 | virtualmin-config (7.0.18) stable; urgency=medium 86 | 87 | * Fix ProFTPd chroot jail work when a user logs in via SFTP 88 | * Fix to enable quotas using `grubby` command 89 | * Fix to enable `mod_include` in Apache 90 | 91 | -- Ilia Ross Sun, 03 Dec 2023 23:33:36 +0200 92 | 93 | virtualmin-config (7.0.17) stable; urgency=medium 94 | 95 | * Fix temp directory environmental variable name and improve support for openSUSE 96 | 97 | -- Ilia Ross Wed, 20 Sep 2023 20:21:00 +0300 98 | 99 | virtualmin-config (7.0.16) stable; urgency=medium 100 | 101 | * Fix to also disable a new `spamd` service in Debian 12 102 | 103 | -- Ilia Ross Sun, 27 Aug 2023 18:45:00 +0300 104 | 105 | virtualmin-config (7.0.15) stable; urgency=medium 106 | 107 | * Add ability to create host default domain with Let's Encrypt SSL 108 | 109 | -- Ilia Ross Sun, 20 Aug 2023 20:41:00 +0300 110 | 111 | virtualmin-config (7.0.14) stable; urgency=medium 112 | 113 | * Fix to support Webmin distributed Perl modules 114 | 115 | -- Ilia Ross Sun, 20 Aug 2023 15:20:00 +0300 116 | 117 | virtualmin-config (7.0.13) stable; urgency=medium 118 | 119 | * Remove old Java File Manager code 120 | 121 | -- Joe Cooper Wed, 10 Aug 2023 00:16:15 -0600 122 | 123 | virtualmin-config (7.0.12) stable; urgency=medium 124 | 125 | * Fix missing Fail2banFirewalld changes 126 | 127 | -- Ilia Ross Sun, 16 Apr 13:02:40 2023 +0300 128 | 129 | virtualmin-config (7.0.11) stable; urgency=medium 130 | 131 | * Fix Fail2ban ProFTPd jail work 132 | * Fix Fail2ban Webmin jail use `journalmatch` for speed 133 | 134 | -- Ilia Ross Thu, 13 Apr 23:15:30 2023 +0300 135 | 136 | virtualmin-config (7.0.10) stable; urgency=medium 137 | 138 | * Fix to just enable and start Usermin 139 | * Fix to accomodate ClamAV logic depending on the distro 140 | * Fix default mailbox name for Sent mail 141 | 142 | -- Ilia Ross Mon, 09 Jan 00:20:47 2023 +0200 143 | 144 | virtualmin-config (7.0.9) stable; urgency=medium 145 | 146 | * Fix to test if `$myhostname` is set too in Postfix 147 | * Fix a warning `Use of uninitialized value $backend` for Fail2Ban 148 | * Fix Postfix banning failed logins 149 | * Fix to allow using `@` in usernames for receiving mail 150 | 151 | -- Ilia Ross Sun, 18 Dec 23:16:22 2022 +0200 152 | 153 | virtualmin-config (7.0.8) stable; urgency=medium 154 | 155 | * Add a smaller spinners 156 | * Fix to add `id` and `groups` command to jail env 157 | * Fix logger complain about wide character in Etckeeper 158 | 159 | -- Ilia Ross Thu, 08 Dec 15:33:19 2022 +0200 160 | 161 | virtualmin-config (7.0.7) stable; urgency=medium 162 | 163 | * Fix spinner color 164 | * Fix to drop dependency from `Term::Spinner::Color` package 165 | 166 | -- Ilia Ross Tue, 15 Nov 19:32:13 2022 +0200 167 | 168 | virtualmin-config (7.0.6) stable; urgency=medium 169 | 170 | * Fix to allow a new Terminal module to have colors for the new installs 171 | * Fix to avoid enabling Apache SSL if ran without bundle (using `-i` param) 172 | 173 | -- Ilia Ross Mon, 24 Oct 14:58:55 2022 +0300 174 | 175 | virtualmin-config (7.0.5) stable; urgency=medium 176 | 177 | * Add support for displaying conditional database name upon configuration 178 | * Fix to drop NoCertRequest deprecated and removed option for ProFTPd 179 | * Fix support for ProFTPd in passive mode 180 | * Fix to improve self-signed SSL certificate for ProFTPd 181 | * Fix to prevent loading modules if already loaded in ProFTPd 182 | * Fix to disable certbot.timer 183 | 184 | -- Ilia Ross Sun, 16 Oct 17:14:38 2022 +0300 185 | 186 | virtualmin-config (7.0.4) stable; urgency=medium 187 | 188 | * Fix to disable certbot auto-renewals 189 | 190 | -- Ilia Ross Mon, 10 Oct 22:01:20 2022 +0300 191 | 192 | virtualmin-config (7.0.3) stable; urgency=high 193 | 194 | * Fix to always force PHP-FPM 195 | 196 | -- Ilia Ross Tue, 06 Sep 21:05:56 2022 +0300 197 | 198 | virtualmin-config (7.0.2) stable; urgency=medium 199 | 200 | * Various bugfixes 201 | * Remove old hacks for Debian/Ubuntu no longer needed 202 | * Gracefully handle missing packages 203 | * Fix saslauthd start on newer Debian/Ubuntu 204 | 205 | -- Joe Cooper Sun, 21 Aug 2022 19:05:12 -0600 206 | 207 | virtualmin-config (7.0.0) stable; urgency=medium 208 | 209 | * Updates for Virtualmin 7 210 | * Remove Net, NTP, Webalizer, from default bundles 211 | * Freshclam runs during config 212 | 213 | -- Joe Cooper Sun, 29 May 2022 09:24:35 -0600 214 | 215 | virtualmin-config (6.0.27) stable; urgency=medium 216 | 217 | * Updates for CentOS 8 218 | * smtpd_tls_security_level option 219 | * Support sender maps in Postfix 3.x 220 | * Use /ec/webmin/restart instead of function call 221 | * PEM format for ProFTPd 222 | 223 | -- Joe Cooper Fri, 22 May 2020 09:24:35 -0600 224 | 225 | virtualmin-config (6.0.24) stable; urgency=medium 226 | 227 | * Remove SetHandler lines on newer Debian/Ubuntu versions 228 | * Catch DHCP config in cases where interfaces.d is in use 229 | * Be more insistent about updating resolv.conf on Debian/Ubuntu 230 | 231 | -- Joe Cooper Sun, 04 Nov 2018 19:14:55 -0600 232 | 233 | virtualmin-config (6.0.22) stable; urgency=low 234 | 235 | * Perl syntax updates 236 | * Open high ports in firewalld firewall for passive FTP 237 | 238 | -- Joe Cooper Thu, 24 May 2018 16:46:14 -0500 239 | 240 | virtualmin-config (6.0.21) stable; urgency=medium 241 | 242 | * Update saslauthd flags 243 | 244 | -- Joe Cooper Tue, 03 Oct 2017 21:00:53 -0500 245 | 246 | virtualmin-config (6.0.20-2) stable; urgency=medium 247 | 248 | * Actually fix Apache false failure on Ubuntu 16.04 249 | 250 | -- Joe Cooper Fri, 29 Sep 2017 05:57:40 -0500 251 | 252 | virtualmin-config (6.0.20) stable; urgency=medium 253 | 254 | * Minimal configs adds Dovecot, SASL, removes Fail2ban 255 | * Fix Apache default file handling on Ubuntu 16.04 256 | * ProFTPd shouldn't require TlS 257 | 258 | -- Joe Cooper Thu, 28 Sep 2017 00:41:40 -0500 259 | 260 | virtualmin-config (6.0.19) stable; urgency=medium 261 | 262 | * Fix ClamAV broken-by-default configuration (again) 263 | * Insure mod_tls.c is always loaded in ProFTPd no matter what 264 | 265 | -- Joe Cooper Thu, 14 Sep 2017 23:29:37 -0500 266 | 267 | virtualmin-config (6.0.18) stable; urgency=medium 268 | 269 | * Fix missing mail.warn file on Debian/Ubuntu, because it's stupid. 270 | 271 | -- Joe Cooper Tue, 12 Sep 2017 22:46:55 -0500 272 | 273 | virtualmin-config (6.0.17) stable; urgency=medium 274 | 275 | * ProFTPd fixes, especially for Debian/Ubuntu 276 | * Fail2ban w/ Firewalld fixes for broken Debian/Ubuntu systemd config 277 | 278 | -- Joe Cooper Tue, 12 Sep 2017 19:46:54 -0500 279 | 280 | virtualmin-config (6.0.16) stable; urgency=medium 281 | 282 | * Fix ProFTPd failure to write config changes 283 | 284 | -- Joe Cooper Mon, 04 Sep 2017 19:21:17 -0500 285 | 286 | virtualmin-config (6.0.15) stable; urgency=medium 287 | 288 | * Remove MiniVirtualmin plugin, Virtualmin plugin handles it when bundle is 289 | Mini* 290 | * Handle some DHCP configured systems by adding localhost to name servers 291 | * Fix SSL default site on CentOS (disabling it). 292 | 293 | -- Joe Cooper Fri, 01 Sep 2017 21:53:49 -0500 294 | 295 | virtualmin-config (6.0.13) stable; urgency=low 296 | 297 | * Quotas warn instead of error 298 | * Apache init is smarter about which init system is in use 299 | * Add non-fatal error type 300 | 301 | -- Joe Cooper Tue, 22 Aug 2017 04:34:12 -0500 302 | 303 | virtualmin-config (6.0.12) stable; urgency=low 304 | 305 | * Quotas fix 306 | 307 | -- Joe Cooper Fri, 11 Aug 2017 07:12:18 -0500 308 | 309 | virtualmin-config (6.0.11) stable; urgency=low 310 | 311 | * LEMP and minimal stack bugfixes 312 | 313 | -- Joe Cooper Thu, 10 Aug 2017 01:21:51 -0500 314 | 315 | virtualmin-config (6.0.10) stable; urgency=low 316 | 317 | * New minimal modes for LAMP and LEMP 318 | 319 | -- Joe Cooper Tue, 08 Aug 2017 06:07:16 -0500 320 | 321 | virtualmin-config (6.0.9-1) stable; urgency=low 322 | 323 | * Update to fix undefined var issue 324 | 325 | -- Joe Cooper Fri, 04 Aug 2017 03:24:32 -0500 326 | 327 | virtualmin-config (6.0.8-1) stable; urgency=low 328 | 329 | * Update to latest version 330 | 331 | -- Joe Cooper Sat, 22 Jul 2017 20:58:36 -0500 332 | 333 | virtualmin-config (6.0.6-1.1) stable; urgency=low 334 | 335 | * Fix detection of systemd on Debian, for real 336 | 337 | -- Joe Cooper Thu, 13 Jul 2017 21:52:45 -0500 338 | 339 | virtualmin-config (6.0.6-1) stable; urgency=low 340 | 341 | * Fix fail2ban systemd detection 342 | 343 | -- Joe Cooper Mon, 03 Jul 2017 00:51:48 -0500 344 | 345 | virtualmin-config (6.0.5-1) stable; urgency=low 346 | 347 | * Fail2ban and firewalld added 348 | * Bug fixes for Ubuntu 349 | 350 | -- Joe Cooper Fri, 23 Jun 2017 23:18:59 -0500 351 | 352 | virtualmin-config (6.0.2-1) stable; urgency=low 353 | 354 | * Add ubuntu support 355 | 356 | -- Joe Cooper Sun, 28 May 2017 14:37:31 -0500 357 | 358 | virtualmin-config (6.0.1-1) stable; urgency=low 359 | 360 | * Add debian support and many fixes 361 | 362 | -- Joe Cooper Sat, 27 May 2017 18:54:01 -0500 363 | 364 | virtualmin-config (6.0.0-1) unstable; urgency=low 365 | 366 | * Initial Release. 367 | 368 | -- Joe Cooper Mon, 22 May 2017 23:04:18 -0500 369 | --------------------------------------------------------------------------------