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;
29 use SL::BackgroundJob::ALL;
31 use SL::Helper::DateTime;
32 use SL::InstanceConfiguration;
37 use SL::System::Process;
38 use SL::System::TaskServer;
45 return if !$lx_office_conf{task_server}->{debug};
46 $::lxdebug->message(LXDebug::DEBUG1(), join(' ', "task server:", @_));
50 return SL::DB::Manager::AuthClient->get_all(where => [ '!task_server_user_id' => undef ]);
53 sub initialize_kivitendo {
60 $::lxdebug = LXDebug->new;
61 $::locale = Locale->new($::lx_office_conf{system}->{language});
63 $::auth = SL::Auth->new;
67 $::auth->set_client($client->id);
69 $::form->{__ERROR_HANDLER} = sub { die @_ };
71 $::instance_conf = SL::InstanceConfiguration->new;
72 $::request = SL::Request->new(
74 layout => SL::Layout::None->new,
77 die 'cannot reach auth db' unless $::auth->session_tables_present;
79 $::auth->restore_session;
80 $::auth->create_or_refresh_session;
82 my $login = $client->task_server_user->login;
84 die "cannot find user $login" unless %::myconfig = $::auth->read_user(login => $login);
85 die "cannot find locale for user $login" unless $::locale = Locale->new($::myconfig{countrycode} || $::lx_office_conf{system}->{language});
88 sub cleanup_kivitendo {
89 eval { SL::DB->client->dbh->rollback; };
91 $::auth->save_session;
92 $::auth->expire_sessions;
101 sub clean_before_sleeping {
102 SL::DBConnect::Cache->disconnect_all_and_clear;
103 SL::DB->db_cache->clear;
105 File::Temp::cleanup();
108 sub drop_privileges {
109 my $user = $lx_office_conf{task_server}->{run_as};
113 while (my @details = getpwent()) {
114 next unless $details[0] eq $user;
115 ($uid, $gid) = @details[2, 3];
121 print "Error: Cannot drop privileges to ${user}: user does not exist\n";
126 print "Error: Cannot drop group privileges to ${user} (group ID $gid): $!\n";
131 print "Error: Cannot drop user privileges to ${user} (user ID $uid): $!\n";
136 sub notify_on_failure {
139 my $cfg = $lx_office_conf{'task_server/notify_on_failure'} || {};
141 return if any { !$cfg->{$_} } qw(send_email_to email_from email_subject email_template);
145 return debug("Template " . $cfg->{email_template} . " missing!") unless -f $cfg->{email_template};
147 my $email_to = $cfg->{send_email_to};
148 if ($email_to !~ m{\@}) {
149 my %user = $::auth->read_user(login => $email_to);
150 return debug("cannot find user for notification $email_to") unless %user;
152 $email_to = $user{email};
153 return debug("user for notification " . $user{login} . " doesn't have a valid email address") unless $email_to =~ m{\@};
156 my $template = Template->new({
164 return debug("Could not create Template instance") unless $template;
166 $params{client} = $::auth->client;
170 $template->process($cfg->{email_template}, \%params, \$body);
173 from => $cfg->{email_from},
175 subject => $cfg->{email_subject},
176 content_type => 'text/plain',
183 debug("Sending a failure notification failed with an exception: $@");
190 # Initialize character type locale to be UTF-8 instead of C:
191 foreach my $locale (qw(de_DE.UTF-8 en_US.UTF-8)) {
192 last if setlocale('LC_CTYPE', $locale);
195 SL::LxOfficeConf->read($self->{configfile});
197 die "Missing section [task_server] in config file" unless $lx_office_conf{task_server};
199 if ($lx_office_conf{task_server}->{login} || $lx_office_conf{task_server}->{client}) {
201 ERROR: The keys 'login' and/or 'client' are still present in the
202 section [task_server] in the configuration file. These keys are
203 deprecated. You have to configure the clients for which to run the
204 task server in the web admin interface.
206 The task server will refuse to start until the keys have been removed from
207 the configuration file.
212 initialize_kivitendo();
214 my $dbupdater_auth = SL::DBUpgrade2->new(form => $::form, auth => 1)->parse_dbupdate_controls;
215 if ($dbupdater_auth->unapplied_upgrade_scripts($::auth->dbconnect)) {
217 The authentication database requires an upgrade. Please login to
218 kivitendo's administration interface in order to apply it. The task
219 server cannot start until the upgrade has been applied.
229 sub run_single_job_for_all_clients {
230 initialize_kivitendo();
232 my $clients = enabled_clients();
234 foreach my $client (@{ $clients }) {
235 debug("Running single job ID $run_single_job for client ID " . $client->id . " (" . $client->name . ")");
238 initialize_kivitendo($client);
240 my $job = SL::DB::Manager::BackgroundJob->find_by(id => $run_single_job);
243 debug(" Executing the following job: " . $job->package_name);
245 debug(" No jobs to execute found");
249 # Provide fresh global variables in case legacy code modifies
251 initialize_kivitendo($client);
253 my $history = $job->run;
255 debug(" Executed job " . $job->package_name .
256 "; result: " . (!$history ? "no return value" : $history->has_failed ? "failed" : "succeeded") .
257 ($history && $history->has_failed ? "; error: " . $history->error_col : ""));
259 notify_on_failure(history => $history) if $history && $history->has_failed;
265 my $error = $EVAL_ERROR;
266 $::lxdebug->message(LXDebug::WARN(), "Exception during execution: ${error}");
267 notify_on_failure(exception => $error);
274 sub run_once_for_all_clients {
275 initialize_kivitendo();
277 my $clients = enabled_clients();
279 foreach my $client (@{ $clients }) {
280 debug("Running for client ID " . $client->id . " (" . $client->name . ")");
283 initialize_kivitendo($client);
285 my $jobs = SL::DB::Manager::BackgroundJob->get_all_need_to_run;
288 debug(" Executing the following jobs: " . join(' ', map { $_->package_name } @{ $jobs }));
290 debug(" No jobs to execute found");
293 foreach my $job (@{ $jobs }) {
294 # Provide fresh global variables in case legacy code modifies
296 initialize_kivitendo($client);
298 my $history = $job->run;
300 debug(" Executed job " . $job->package_name .
301 "; result: " . (!$history ? "no return value" : $history->has_failed ? "failed" : "succeeded") .
302 ($history && $history->has_failed ? "; error: " . $history->error_col : ""));
304 notify_on_failure(history => $history) if $history && $history->has_failed;
311 my $error = $EVAL_ERROR;
312 $::lxdebug->message(LXDebug::WARN(), "Exception during execution: ${error}");
313 notify_on_failure(exception => $error);
321 if ($run_single_job) {
322 run_single_job_for_all_clients();
325 $::lxdebug->message(LXDebug::INFO(), "The task server for node " . SL::System::TaskServer::node_id() . " is up and running.");
328 $SIG{'ALRM'} = 'IGNORE';
330 run_once_for_all_clients();
334 clean_before_sleeping();
336 if (SL::System::Process::memory_usage_is_too_high()) {
337 debug("Memory usage too high - exiting.");
341 my $seconds = 60 - (localtime)[0];
344 $SIG{'ALRM'} = 'IGNORE';
345 debug("Got woken up by SIGALRM");
348 sleep($seconds < 30 ? $seconds + 60 : $seconds);
351 die $@ unless $@ eq "Alarm!\n";
358 '--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',
362 $exe_dir = SL::System::Process->exe_dir;
363 chdir($exe_dir) || die "Cannot change directory to ${exe_dir}\n";
365 mkdir SL::System::TaskServer::PID_BASE() if !-d SL::System::TaskServer::PID_BASE();
367 my $file = first { -f } ("${exe_dir}/config/kivitendo.conf", "${exe_dir}/config/lx_office.conf", "${exe_dir}/config/kivitendo.conf.default");
369 die "No configuration file found." unless $file;
371 $file = File::Spec->abs2rel(Cwd::abs_path($file), Cwd::abs_path($exe_dir));
373 newdaemon(configfile => $file,
374 progname => 'kivitendo-background-jobs',
375 pidbase => SL::System::TaskServer::PID_BASE() . '/',
377 'run-job=i' => \$run_single_job,