10   unshift(@INC, $FindBin::Bin . '/../modules/override'); # Use our own versions of various modules (e.g. YAML).
 
  11   push   (@INC, $FindBin::Bin . '/..');                  # '.' will be removed from @INC soon.
 
  14 use CGI qw( -no_xhtml);
 
  19 use English qw(-no_match_vars);
 
  21 use List::MoreUtils qw(any);
 
  22 use List::Util qw(first);
 
  23 use POSIX qw(setlocale setuid setgid);
 
  26 use SL::DB::AuthClient;
 
  27 use SL::DB::BackgroundJob;
 
  28 use SL::System::Process;
 
  30 use SL::Helper::DateTime;
 
  31 use SL::InstanceConfiguration;
 
  36 use SL::System::Process;
 
  37 use SL::System::TaskServer;
 
  44   return if !$lx_office_conf{task_server}->{debug};
 
  45   $::lxdebug->message(LXDebug::DEBUG1(), join(' ', "task server:", @_));
 
  49   return SL::DB::Manager::AuthClient->get_all(where => [ '!task_server_user_id' => undef ]);
 
  52 sub initialize_kivitendo {
 
  59   $::lxdebug       = LXDebug->new;
 
  60   $::locale        = Locale->new($::lx_office_conf{system}->{language});
 
  62   $::auth          = SL::Auth->new;
 
  66   $::auth->set_client($client->id);
 
  68   $::form->{__ERROR_HANDLER} = sub { die @_ };
 
  70   $::instance_conf = SL::InstanceConfiguration->new;
 
  71   $::request       = SL::Request->new(
 
  73     layout         => SL::Layout::None->new,
 
  76   die 'cannot reach auth db'               unless $::auth->session_tables_present;
 
  78   $::auth->restore_session;
 
  79   $::auth->create_or_refresh_session;
 
  81   my $login = $client->task_server_user->login;
 
  83   die "cannot find user $login"            unless %::myconfig = $::auth->read_user(login => $login);
 
  84   die "cannot find locale for user $login" unless $::locale   = Locale->new($::myconfig{countrycode} || $::lx_office_conf{system}->{language});
 
  87 sub cleanup_kivitendo {
 
  88   eval { SL::DB->client->dbh->rollback; };
 
  90   $::auth->save_session;
 
  91   $::auth->expire_sessions;
 
 100 sub clean_before_sleeping {
 
 101   SL::DBConnect::Cache->disconnect_all_and_clear;
 
 102   SL::DB->db_cache->clear;
 
 104   File::Temp::cleanup();
 
 107 sub drop_privileges {
 
 108   my $user = $lx_office_conf{task_server}->{run_as};
 
 112   while (my @details = getpwent()) {
 
 113     next unless $details[0] eq $user;
 
 114     ($uid, $gid) = @details[2, 3];
 
 120     print "Error: Cannot drop privileges to ${user}: user does not exist\n";
 
 125     print "Error: Cannot drop group privileges to ${user} (group ID $gid): $!\n";
 
 130     print "Error: Cannot drop user privileges to ${user} (user ID $uid): $!\n";
 
 135 sub notify_on_failure {
 
 138   my $cfg = $lx_office_conf{'task_server/notify_on_failure'} || {};
 
 140   return if any { !$cfg->{$_} } qw(send_email_to email_from email_subject email_template);
 
 144   return debug("Template " . $cfg->{email_template} . " missing!") unless -f $cfg->{email_template};
 
 146   my $email_to = $cfg->{send_email_to};
 
 147   if ($email_to !~ m{\@}) {
 
 148     my %user = $::auth->read_user(login => $email_to);
 
 149     return debug("cannot find user for notification $email_to") unless %user;
 
 151     $email_to = $user{email};
 
 152     return debug("user for notification " . $user{login} . " doesn't have a valid email address") unless $email_to =~ m{\@};
 
 155   my $template  = Template->new({
 
 163   return debug("Could not create Template instance") unless $template;
 
 165   $params{client} = $::auth->client;
 
 169     $template->process($cfg->{email_template}, \%params, \$body);
 
 172       from         => $cfg->{email_from},
 
 174       subject      => $cfg->{email_subject},
 
 175       content_type => 'text/plain',
 
 182     debug("Sending a failure notification failed with an exception: $@");
 
 189   # Initialize character type locale to be UTF-8 instead of C:
 
 190   foreach my $locale (qw(de_DE.UTF-8 en_US.UTF-8)) {
 
 191     last if setlocale('LC_CTYPE', $locale);
 
 194   SL::LxOfficeConf->read($self->{configfile});
 
 196   die "Missing section [task_server] in config file" unless $lx_office_conf{task_server};
 
 198   if ($lx_office_conf{task_server}->{login} || $lx_office_conf{task_server}->{client}) {
 
 200 ERROR: The keys 'login' and/or 'client' are still present in the
 
 201 section [task_server] in the configuration file. These keys are
 
 202 deprecated. You have to configure the clients for which to run the
 
 203 task server in the web admin interface.
 
 205 The task server will refuse to start until the keys have been removed from
 
 206 the configuration file.
 
 211   initialize_kivitendo();
 
 213   my $dbupdater_auth = SL::DBUpgrade2->new(form => $::form, auth => 1)->parse_dbupdate_controls;
 
 214   if ($dbupdater_auth->unapplied_upgrade_scripts($::auth->dbconnect)) {
 
 216 The authentication database requires an upgrade. Please login to
 
 217 kivitendo's administration interface in order to apply it. The task
 
 218 server cannot start until the upgrade has been applied.
 
 228 sub run_single_job_for_all_clients {
 
 229   initialize_kivitendo();
 
 231   my $clients = enabled_clients();
 
 233   foreach my $client (@{ $clients }) {
 
 234     debug("Running single job ID $run_single_job for client ID " . $client->id . " (" . $client->name . ")");
 
 237       initialize_kivitendo($client);
 
 239       my $job = SL::DB::Manager::BackgroundJob->find_by(id => $run_single_job);
 
 242         debug(" Executing the following job: " . $job->package_name);
 
 244         debug(" No jobs to execute found");
 
 248       # Provide fresh global variables in case legacy code modifies
 
 250       initialize_kivitendo($client);
 
 252       my $history = $job->run;
 
 254       debug("   Executed job " . $job->package_name .
 
 255             "; result: " . (!$history ? "no return value" : $history->has_failed ? "failed" : "succeeded") .
 
 256             ($history && $history->has_failed ? "; error: " . $history->error_col : ""));
 
 258       notify_on_failure(history => $history) if $history && $history->has_failed;
 
 264       my $error = $EVAL_ERROR;
 
 265       $::lxdebug->message(LXDebug::WARN(), "Exception during execution: ${error}");
 
 266       notify_on_failure(exception => $error);
 
 273 sub run_once_for_all_clients {
 
 274   initialize_kivitendo();
 
 276   my $clients = enabled_clients();
 
 278   foreach my $client (@{ $clients }) {
 
 279     debug("Running for client ID " . $client->id . " (" . $client->name . ")");
 
 282       initialize_kivitendo($client);
 
 284       my $jobs = SL::DB::Manager::BackgroundJob->get_all_need_to_run;
 
 287         debug(" Executing the following jobs: " . join(' ', map { $_->package_name } @{ $jobs }));
 
 289         debug(" No jobs to execute found");
 
 292       foreach my $job (@{ $jobs }) {
 
 293         # Provide fresh global variables in case legacy code modifies
 
 295         initialize_kivitendo($client);
 
 297         my $history = $job->run;
 
 299         debug("   Executed job " . $job->package_name .
 
 300               "; result: " . (!$history ? "no return value" : $history->has_failed ? "failed" : "succeeded") .
 
 301               ($history && $history->has_failed ? "; error: " . $history->error_col : ""));
 
 303         notify_on_failure(history => $history) if $history && $history->has_failed;
 
 310       my $error = $EVAL_ERROR;
 
 311       $::lxdebug->message(LXDebug::WARN(), "Exception during execution: ${error}");
 
 312       notify_on_failure(exception => $error);
 
 320   if ($run_single_job) {
 
 321     run_single_job_for_all_clients();
 
 324   $::lxdebug->message(LXDebug::INFO(), "The task server for node " . SL::System::TaskServer::node_id() . " is up and running.");
 
 327     $SIG{'ALRM'} = 'IGNORE';
 
 329     run_once_for_all_clients();
 
 333     clean_before_sleeping();
 
 335     if (SL::System::Process::memory_usage_is_too_high()) {
 
 336       debug("Memory usage too high - exiting.");
 
 340     my $seconds = 60 - (localtime)[0];
 
 343         $SIG{'ALRM'} = 'IGNORE';
 
 344         debug("Got woken up by SIGALRM");
 
 347       sleep($seconds < 30 ? $seconds + 60 : $seconds);
 
 350       die $@ unless $@ eq "Alarm!\n";
 
 357     '--run-job=<id>' => 'Run the single job with the database ID <id> no matter if it is active or when its next execution is supposed to be; the daemon will exit afterwards',
 
 361 $exe_dir = SL::System::Process->exe_dir;
 
 362 chdir($exe_dir) || die "Cannot change directory to ${exe_dir}\n";
 
 364 mkdir SL::System::TaskServer::PID_BASE() if !-d SL::System::TaskServer::PID_BASE();
 
 366 my $file = first { -f } ("${exe_dir}/config/kivitendo.conf", "${exe_dir}/config/lx_office.conf", "${exe_dir}/config/kivitendo.conf.default");
 
 368 die "No configuration file found." unless $file;
 
 370 $file = File::Spec->abs2rel(Cwd::abs_path($file), Cwd::abs_path($exe_dir));
 
 372 newdaemon(configfile => $file,
 
 373           progname   => 'kivitendo-background-jobs',
 
 374           pidbase    => SL::System::TaskServer::PID_BASE() . '/',
 
 376             'run-job=i' => \$run_single_job,