Web and Backend daemon: watch config, same uid/gid as file

This commit is contained in:
Oliver Gorwits
2014-03-11 14:02:17 +00:00
parent e94c049f29
commit f0b4cc3580
6 changed files with 211 additions and 59 deletions

View File

@@ -1,5 +1,11 @@
2.025000 -
[NEW FEATURES]
* Web and Backend daemons will restart when deployment.yml is updated
* Web and Backend daemons will drop privilege to same uid/gid as their
on-disk files (to allow run-control symlink as non-root)
[ENHANCEMENTS]
* Use daterange for IP Subnets (same as IP Inventory)

View File

@@ -3,12 +3,13 @@
use strict;
use warnings FATAL => 'all';
our $home;
our $home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
BEGIN {
# try to find a localenv if one isn't already in place.
$home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
use FindBin;
FindBin::again();
# try to find a localenv if one isn't already in place.
if (!exists $ENV{PERL_LOCAL_LIB_ROOT}) {
use File::Spec;
my $localenv = File::Spec->catfile($FindBin::RealBin, 'localenv');
@@ -19,12 +20,20 @@ BEGIN {
die "Sorry, can't find libs required for App::Netdisco.\n"
if !exists $ENV{PERLBREW_PERL};
}
use Path::Class;
# stuff useful locations into @INC and $PATH
unshift @INC,
dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
dir($FindBin::RealBin, 'lib')->stringify;
}
use FindBin;
FindBin::again();
use Path::Class;
use Daemon::Control;
use Filesys::Notify::Simple;
use App::Netdisco::Environment;
my $config = ($ENV{PLACK_ENV} || $ENV{DANCER_ENVIRONMENT}) .'.yml';
my $netdisco = file($FindBin::RealBin, 'netdisco-daemon-fg');
my @args = (scalar @ARGV > 1 ? @ARGV[1 .. $#ARGV] : ());
@@ -32,20 +41,73 @@ my @args = (scalar @ARGV > 1 ? @ARGV[1 .. $#ARGV] : ());
my $log_dir = dir($home, 'logs');
mkdir $log_dir if ! -d $log_dir;
my $uid = stat($netdisco)[4] || 0;
my $gid = stat($netdisco)[5] || 0;
my $uid = (stat($netdisco->stringify))[4] || 0;
my $gid = (stat($netdisco->stringify))[5] || 0;
Daemon::Control->new({
name => 'Netdisco Daemon',
program => $netdisco,
program => \&restarter,
program_args => [@args],
pid_file => file($home, 'netdisco-daemon.pid'),
stderr_file => file($log_dir, 'netdisco-daemon.log'),
stdout_file => file($log_dir, 'netdisco-daemon.log'),
uid => $uid,
gid => $gid,
uid => $uid, gid => $gid,
})->run;
# the guts of this are borrowed from Plack::Loader::Restarter - many thanks!!
sub restarter {
my ($daemon, @program_args) = @_;
my $child = fork_and_start(@program_args);
exit(1) unless $child;
my $watcher = Filesys::Notify::Simple->new([$ENV{DANCER_ENVDIR}]);
warn "config watcher: watching $ENV{DANCER_ENVDIR} for updates.\n";
local $SIG{TERM} = sub { signal_child('TERM', $child); exit(0); };
while (1) {
my @restart;
# this is blocking
$watcher->wait(sub {
my @events = @_;
@events = grep {file($_->{path})->basename eq $config} @events;
return unless @events;
@restart = @events;
});
next unless @restart;
warn "-- $_->{path} updated.\n" for @restart;
signal_child('TERM', $child);
$child = fork_and_start(@program_args);
exit(1) unless $child;
}
}
sub fork_and_start {
my @daemon_args = @_;
my $pid = fork;
die "Can't fork: $!" unless defined $pid;
if ($pid == 0) { # child
exec( $netdisco->stringify, @daemon_args );
}
else {
return $pid;
}
}
sub signal_child {
my ($signal, $pid) = @_;
return unless $signal and $pid;
warn "config watcher: sending $signal to the server (pid:$pid)...\n";
kill $signal => $pid;
waitpid($pid, 0);
}
=head1 NAME
netdisco-daemon - Job Control Daemon for Netdisco

View File

@@ -3,12 +3,13 @@
use strict;
use warnings FATAL => 'all';
our $home;
our $home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
BEGIN {
# try to find a localenv if one isn't already in place.
$home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
use FindBin;
FindBin::again();
# try to find a localenv if one isn't already in place.
if (!exists $ENV{PERL_LOCAL_LIB_ROOT}) {
use File::Spec;
my $localenv = File::Spec->catfile($FindBin::RealBin, 'localenv');
@@ -19,33 +20,97 @@ BEGIN {
die "Sorry, can't find libs required for App::Netdisco.\n"
if !exists $ENV{PERLBREW_PERL};
}
use Path::Class;
# stuff useful locations into @INC and $PATH
unshift @INC,
dir($FindBin::RealBin)->parent->subdir('lib')->stringify,
dir($FindBin::RealBin, 'lib')->stringify;
}
use FindBin;
FindBin::again();
use Path::Class;
use Daemon::Control;
use Filesys::Notify::Simple;
use App::Netdisco::Environment;
my $config = ($ENV{PLACK_ENV} || $ENV{DANCER_ENVIRONMENT}) .'.yml';
my $netdisco = file($FindBin::RealBin, 'netdisco-web-fg');
my @args = (scalar @ARGV > 1 ? @ARGV[1 .. $#ARGV] : ());
my $uid = (stat($netdisco->stringify))[4] || 0;
my $gid = (stat($netdisco->stringify))[5] || 0;
my $log_dir = dir($home, 'logs');
mkdir $log_dir if ! -d $log_dir;
my $uid = stat($netdisco)[4] || 0;
my $gid = stat($netdisco)[5] || 0;
Daemon::Control->new({
name => 'Netdisco Web',
program => 'starman',
program_args => ['--disable-keepalive', @args, $netdisco->stringify],
program => \&restarter,
program_args => [
'--disable-keepalive',
'--user', $uid, '--group', $gid,
@args, $netdisco->stringify
],
pid_file => file($home, 'netdisco-web.pid'),
stderr_file => file($log_dir, 'netdisco-web.log'),
stdout_file => file($log_dir, 'netdisco-web.log'),
uid => $uid,
gid => $gid,
})->run;
# the guts of this are borrowed from Plack::Loader::Restarter - many thanks!!
sub restarter {
my ($daemon, @program_args) = @_;
my $child = fork_and_start(@program_args);
exit(1) unless $child;
my $watcher = Filesys::Notify::Simple->new([$ENV{DANCER_ENVDIR}]);
warn "config watcher: watching $ENV{DANCER_ENVDIR} for updates.\n";
# TODO: starman also supports TTIN,TTOU,INT,QUIT
local $SIG{HUP} = sub { signal_child('HUP', $child); };
local $SIG{TERM} = sub { signal_child('TERM', $child); exit(0); };
while (1) {
my @restart;
# this is blocking
$watcher->wait(sub {
my @events = @_;
@events = grep {file($_->{path})->basename eq $config} @events;
return unless @events;
@restart = @events;
});
next unless @restart;
warn "-- $_->{path} updated.\n" for @restart;
signal_child('HUP', $child);
}
}
sub fork_and_start {
my @starman_args = @_;
my $pid = fork;
die "Can't fork: $!" unless defined $pid;
if ($pid == 0) { # child
exec( 'starman', @starman_args );
}
else {
return $pid;
}
}
sub signal_child {
my ($signal, $pid) = @_;
return unless $signal and $pid;
warn "config watcher: sending $signal to the server (pid:$pid)...\n";
kill $signal => $pid;
waitpid($pid, 0);
}
=head1 NAME
netdisco-web - Web Application Server for Netdisco

View File

@@ -4,45 +4,12 @@ use strict;
use warnings FATAL => 'all';
use 5.010_000;
use File::ShareDir 'dist_dir';
use Path::Class;
our $VERSION = '2.024004';
BEGIN {
if (not ($ENV{DANCER_APPDIR} || '')
or not -f file($ENV{DANCER_APPDIR}, 'config.yml')) {
my $auto = dir(dist_dir('App-Netdisco'))->absolute;
my $home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
$ENV{DANCER_APPDIR} ||= $auto->stringify;
$ENV{DANCER_CONFDIR} ||= $auto->stringify;
my $test_envdir = dir($home, 'environments')->stringify;
$ENV{DANCER_ENVDIR} ||= (-d $test_envdir
? $test_envdir : $auto->subdir('environments')->stringify);
$ENV{DANCER_ENVIRONMENT} ||= 'deployment';
$ENV{PLACK_ENV} ||= $ENV{DANCER_ENVIRONMENT};
$ENV{DANCER_PUBLIC} ||= $auto->subdir('public')->stringify;
$ENV{DANCER_VIEWS} ||= $auto->subdir('views')->stringify;
}
{
# Dancer 1 uses the broken YAML.pm module
# This is a global sledgehammer - could just apply to Dancer::Config
use YAML;
use YAML::XS;
no warnings 'redefine';
*YAML::LoadFile = sub { goto \&YAML::XS::LoadFile };
}
}
# set up database schema config from simple config vars
use App::Netdisco::Environment;
use Dancer ':script';
# set up database schema config from simple config vars
if (ref {} eq ref setting('database')) {
my $name = (setting('database')->{name} || 'netdisco');
my $host = setting('database')->{host};

View File

@@ -0,0 +1,40 @@
package App::Netdisco::Environment;
use strict;
use warnings FATAL => 'all';
use File::ShareDir 'dist_dir';
use Path::Class;
BEGIN {
if (not ($ENV{DANCER_APPDIR} || '')
or not -f file($ENV{DANCER_APPDIR}, 'config.yml')) {
my $auto = dir(dist_dir('App-Netdisco'))->absolute;
my $home = ($ENV{NETDISCO_HOME} || $ENV{HOME});
$ENV{DANCER_APPDIR} ||= $auto->stringify;
$ENV{DANCER_CONFDIR} ||= $auto->stringify;
my $test_envdir = dir($home, 'environments')->stringify;
$ENV{DANCER_ENVDIR} ||= (-d $test_envdir
? $test_envdir : $auto->subdir('environments')->stringify);
$ENV{DANCER_ENVIRONMENT} ||= 'deployment';
$ENV{PLACK_ENV} ||= $ENV{DANCER_ENVIRONMENT};
$ENV{DANCER_PUBLIC} ||= $auto->subdir('public')->stringify;
$ENV{DANCER_VIEWS} ||= $auto->subdir('views')->stringify;
}
{
# Dancer 1 uses the broken YAML.pm module
# This is a global sledgehammer - could just apply to Dancer::Config
use YAML;
use YAML::XS;
no warnings 'redefine';
*YAML::LoadFile = sub { goto \&YAML::XS::LoadFile };
}
}
1;

View File

@@ -36,6 +36,18 @@ but they are backwards compatible.
=back
=head1 2.025000
=head2 General Changes
The Web and Backend daemons (C<netdisco-web> and C<netdisco-daemon>
respectively) will now watch your C<deployment.yml> configuration file, and
restart themselves whenever it is changed.
The Web and Backend daemons will also now drop privilege to the same user and
group as their files on disk. This allows you to symlink the programs as
run-control scripts, yet maintain non-root privilege status.
=head1 2.023000
=head2 Incompatible Changes