Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions dist/rpm/MirrorCache-Exec-tmpfilesd.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Type Path Mode UID GID Age Argument
d /var/lib/mirrorcache-exec 0750 mirrorcache-exec mirrorcache - -
d /run/mirrorcache-exec 0750 mirrorcache-exec mirrorcache - -
3 changes: 3 additions & 0 deletions dist/rpm/MirrorCache-Exec-user.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Type Name ID GECOS [HOME]
u mirrorcache-exec - "MirrorCache" /var/lib/mirrorcache-exec
g mirrorcache
81 changes: 68 additions & 13 deletions dist/rpm/MirrorCache.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# spec file for package MirrorCache
#
# Copyright (c) 2021 SUSE LLC
# Copyright (c) 2021,2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
Expand All @@ -19,10 +19,11 @@
%define mirrorcache_services_restart mirrorcache.service mirrorcache-backstage.service mirrorcache-backstage-hashes.service mirrorcache-subtree.service
%define mirrorcache_services %{mirrorcache_services_restart} mirrorcache-hypnotoad.service
%define assetpack_requires perl(CSS::Minifier::XS) >= 0.01 perl(JavaScript::Minifier::XS) >= 0.11 perl(Mojolicious::Plugin::AssetPack) >= 1.36 perl(IO::Socket::SSL)
%define main_requires %{assetpack_requires} perl(Carp) perl(DBD::Pg) >= 3.7.4 perl(DBI) >= 1.632 perl(DBIx::Class) >= 0.082801 perl(DBIx::Class::DynamicDefault) perl(DateTime) perl(Encode) perl(Time::Piece) perl(Time::Seconds) perl(Time::ParseDate) perl(DateTime::Format::Pg) perl(Exporter) perl(File::Basename) perl(LWP::UserAgent) perl(Mojo::Base) perl(Mojo::ByteStream) perl(Mojo::IOLoop) perl(Mojo::JSON) perl(Mojo::Pg) perl(Mojo::URL) perl(Mojo::Util) perl(Mojolicious::Commands) perl(Mojolicious::Plugin) perl(Mojolicious::Plugin::RenderFile) perl(Mojolicious::Static) perl(Net::OpenID::Consumer) perl(POSIX) perl(Sort::Versions) perl(URI::Escape) perl(XML::Writer) perl(base) perl(constant) perl(diagnostics) perl(strict) perl(warnings) shadow rubygem(sass) perl(Net::DNS) perl(LWP::Protocol::https) perl(Digest::SHA) perl(Config::IniFiles)
%define common_requires perl(Carp) perl(DBD::Pg) >= 3.7.4 perl(DBI) >= 1.632 perl(DBIx::Class) >= 0.082801 perl(DBIx::Class::DynamicDefault) perl(DateTime) perl(Encode) perl(Time::Piece) perl(Time::Seconds) perl(Time::ParseDate) perl(DateTime::Format::Pg) perl(Exporter) perl(File::Basename) perl(LWP::UserAgent) perl(Mojo::Base) perl(Mojo::ByteStream) perl(Mojo::IOLoop) perl(Mojo::JSON) perl(Mojo::Pg) perl(Mojo::URL) perl(Mojo::Util) perl(Mojolicious::Commands) perl(Mojolicious::Plugin) perl(POSIX) perl(Sort::Versions) perl(URI::Escape) perl(XML::Writer) perl(base) perl(constant) perl(diagnostics) perl(strict) perl(warnings) shadow perl(Net::DNS) perl(LWP::Protocol::https) perl(Digest::SHA) perl(Config::IniFiles)
%define main_requires %{assetpack_requires} perl(Mojolicious::Plugin::RenderFile) perl(Mojolicious::Static) perl(Net::OpenID::Consumer) rubygem(sass)
%define build_requires %{assetpack_requires} rubygem(sass) tidy sysuser-shadow sysuser-tools
Name: MirrorCache
Version: 0.1
Version: 0
Release: 0
Summary: WebApp to redirect and manage mirrors
License: GPL-2.0-or-later
Expand All @@ -32,11 +33,13 @@ Source0: %{name}-%{version}.tar.xz
Source1: cache.tar.xz
Source2: %{name}-user.conf
Source3: %{name}-tmpfilesd.conf
Source4: %{name}-Exec-user.conf
Source5: %{name}-Exec-tmpfilesd.conf
# use update-cache (or tools/generate-packed-assets) to generate/update cache.tar.xz
Source101: update-cache.sh
BuildRequires: %{build_requires}
Requires: MirrorCache-common
Requires: %{main_requires}
Requires: perl(Minion) >= 10.0
BuildArch: noarch
%sysusers_requires

Expand All @@ -49,6 +52,7 @@ Mirror redirector web service, which automatically scans the main server and mir
%build
# make {?_smp_mflags}
%sysusers_generate_pre %{SOURCE2} %{name}
%sysusers_generate_pre %{SOURCE4} %{name}-Exec

%check

Expand All @@ -63,6 +67,8 @@ ln -s ../sbin/service %{buildroot}%{_sbindir}/rcmirrorcache-backstage-hashes
ln -s ../sbin/service %{buildroot}%{_sbindir}/rcmirrorcache-subtree
install -D -m 0644 %{SOURCE2} %{buildroot}%{_sysusersdir}/%{name}.conf
install -D -m 0644 %{SOURCE3} %{buildroot}%{_tmpfilesdir}/%{name}.conf
install -D -m 0644 %{SOURCE4} %{buildroot}%{_sysusersdir}/%{name}-Exec.conf
install -D -m 0644 %{SOURCE5} %{buildroot}%{_tmpfilesdir}/%{name}-Exec.conf

%pre -f %{name}.pre
%service_add_pre %{mirrorcache_services}
Expand All @@ -79,25 +85,74 @@ install -D -m 0644 %{SOURCE3} %{buildroot}%{_tmpfilesdir}/%{name}.conf
%service_del_postun_without_restart mirrorcache-hypnotoad.service

%files
%doc README.md
%license LICENSE
%{_sysusersdir}/%{name}.conf
%{_tmpfilesdir}/%{name}.conf
%config(noreplace) %attr(-,root,mirrorcache) %{_sysconfdir}/mirrorcache/
%ghost %dir %attr(0750,mirrorcache,-) %{_localstatedir}/lib/mirrorcache/
%{_sbindir}/rcmirrorcache
%{_sbindir}/rcmirrorcache-hypnotoad
%{_sbindir}/rcmirrorcache-backstage
%{_sbindir}/rcmirrorcache-backstage-hashes
%{_sbindir}/rcmirrorcache-subtree
%{_sysusersdir}/%{name}.conf
%{_tmpfilesdir}/%{name}.conf
%config(noreplace) %attr(-,root,mirrorcache) %{_sysconfdir}/mirrorcache/
%ghost %dir %attr(0750,mirrorcache,-) %{_localstatedir}/lib/mirrorcache/
# init
%dir %{_unitdir}
%{_unitdir}/mirrorcache.service
%{_unitdir}/mirrorcache-hypnotoad.service
%{_unitdir}/mirrorcache-backstage.service
%{_unitdir}/mirrorcache-backstage-hashes.service
%{_unitdir}/mirrorcache-subtree.service

%changelog

%package common

Summary: MirrorCache shared files
License: GPL-2.0-or-later
Group: Productivity/Networking/Web/Servers
URL: https://github.com/openSUSE/MirrorCache
Requires: %{common_requires}
Requires: perl(Minion) >= 10.0
BuildArch: noarch
%sysusers_requires

%description common
Shared files for MirrorCache packages

%files common
%doc README.md
%license LICENSE
%{_sbindir}/rcmirrorcache
%dir %{_unitdir}
# web libs
%{_datadir}/mirrorcache

%changelog
%package Exec

Summary: MirrorCache worker to execute scheduled tasks
License: GPL-2.0-or-later
Group: Productivity/Networking/Web/Servers
URL: https://github.com/openSUSE/MirrorCache
Requires: MirrorCache-common
BuildArch: noarch
%sysusers_requires

%description Exec
MirrorCache worker to execute scheduled shell scripts

%files Exec
%{_sysusersdir}/%{name}-Exec.conf
%{_tmpfilesdir}/%{name}-Exec.conf
%ghost %dir %attr(0750,mirrorcache,-) %{_localstatedir}/lib/mirrorcache-exec/
%{_unitdir}/mirrorcache-backstage-exec.service

%pre -f %{name}-Exec.pre Exec
%service_add_pre mirrorcache-backstage-exec.service

%post Exec
%tmpfiles_create %{_tmpfilesdir}/%{name}-Exec.conf
%service_add_post mirrorcache-backstage-exec.service

%preun Exec
%service_del_preun mirrorcache-backstage-exec.service

%postun Exec
%service_del_postun mirrorcache-backstage-exec.service

19 changes: 19 additions & 0 deletions dist/systemd/mirrorcache-backstage-exec.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[Unit]
Description=MirrorCache daemon for executing scheduled tasks

[Service]
User=mirrorcache-exec
Group=mirrorcache
ExecStart=/usr/share/mirrorcache/script/mirrorcache-backstage-exec
Nice=19
Restart=on-failure
RestartSec=10
EnvironmentFile=/etc/mirrorcache/conf.env
WorkingDirectory=/var/lib/mirrorcache-exec
Environment="MOJO_TMPDIR=/var/lib/mirrorcache-exec"
Environment="MOJO_LOG_LEVEL=error"
MemoryHigh=2G
MemoryMax=3G

[Install]
WantedBy=multi-user.target
141 changes: 141 additions & 0 deletions lib/MirrorCache/Task/Exec.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Copyright (C) 2025 SUSE LLC
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, see <http://www.gnu.org/licenses/>.

package MirrorCache::Task::Exec;
use diagnostics;
use IPC::Open3;
use IO::Select;
use Symbol 'gensym';
use POSIX ":sys_wait_h";

use Mojo::Base 'Mojolicious::Plugin';
use MirrorCache::Utils 'datetime_now';

# Exec command will execute bash commands provided by argv
#
# Examples to print string "1 2"
# perl:
# my $res = $minion->enqueue(exec => ("echo 1 2"));
# my $res = $minion->enqueue(exec => ("echo", 1, 2));
# my %args = (CMD => 'echo 1', DESC => 'Command that prints "1 2"', TIMEOUT => 1200, LOCK => 'mylock', LOCK_TIMEOUT => 60);
# my $res = $minion->enqueue(exec => (\%args, 1, 2));
#
# shell:
# /usr/share/mirrorcache/script/mirrorcache minion job -e exec -q myqueue -a '["echo 1 2"]'
# /usr/share/mirrorcache/script/mirrorcache minion job -e exec -q myqueue -a '["echo", 1, 2]'
# /usr/share/mirrorcache/script/mirrorcache minion job -e exec -q myqueue -a '[{"CMD":"echo","DESC":"Command that prints","LOCK":"mylock"}, 1, 2]'

sub register {
my ($self, $app) = @_;
$app->minion->add_task(exec => sub { _run($app, @_) });
}

sub _run {
my ($app, $job, $arg0, @argv) = @_;
my $minion = $app->minion;

return $job->finish('No command provided') unless $arg0;

my ($cmd, $cmdline, $desc, $timeout, $lockname, $locktimeout, @args);

if (ref $arg0 eq "HASH") {
$cmdline = $arg0->{CMD};
$desc = $arg0->{DESC};
$timeout = $arg0->{TIMEOUT};
$lockname = $arg0->{LOCK};
$locktimeout = $arg0->{LOCK_TIMEOUT};
} else {
$cmdline = $arg0;
}

my @cmdline = split(/\s/, $cmdline, 2);
if (scalar(@cmdline) > 1) {
$cmd = $cmdline[0];
} else {
$cmd = $cmdline;
}
$desc = $desc // "Command $cmd";
$timeout = $timeout // 1200;
$lockname = $lockname // "EXEC_LOCK_$cmd";
$locktimeout = $locktimeout // $timeout;

return $job->finish("Cannot lock $lockname")
unless my $guard = $minion->guard($lockname, $locktimeout);

my $pid;
my ($infh,$outfh,$errfh);
$errfh = gensym();

my $success = 0;
my $error;
eval {
$pid = open3($infh, $outfh, $errfh, $cmdline, @argv);
$success = 1;
};
return $job->fail("open3: $@") unless $success;
close($infh);

my $sel = new IO::Select;
$sel->add($outfh, $errfh);

my $start_time = time;
$job->note(pid => $pid, start_time => $start_time, cmdline => $cmdline);

while(1) {
$! = 0;
my @ready = $sel->can_read(5);
my $last = 0;
if ($!) { # error
$job->note(error_code => $!);
print STDERR "ERRR: $!\n";
waitpid(-1, WNOHANG);
$last = 1;
}
my $curr_time = time;
my (@lines, @elines);
foreach my $fh (@ready) {
my $line = <$fh>;
if(not defined $line){
$sel->remove($fh);
next;
}
chomp($line);
if($fh == $outfh) {
push @lines, $line;
} elsif($fh == $errfh) {# do the same for errfh
push @elines, $line;
}
}
$job->note("$curr_time O" => @lines) if @lines;
$job->note("$curr_time E" => @elines) if @elines;

my $x = waitpid($pid, WNOHANG);
if ($x < 0) {
$job->note(finished => $pid);
$last = 1;
}

last if $last;
if (($curr_time - $start_time) >= $timeout) {
waitpid(-1, WNOHANG);
return $job->fail("timeout expired!");
}
}

waitpid(-1, WNOHANG);
return $job->finish('finish');
}

1;
39 changes: 15 additions & 24 deletions lib/MirrorCache/WebAPI.pm
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,6 @@ sub new {
# setting pid_file in startup will not help, need to set it earlier
$self->config->{hypnotoad}{pid_file} = $ENV{MIRRORCACHE_HYPNOTOAD_PID} // '/run/mirrorcache/hypnotoad.pid';

# I wasn't able to find reliable way in Mojolicious to detect when WebUI is started
# (e.g. in daemon or hypnotoad in contrast to other commands like backend start / shoot)
# so the code below tries to detect if _setup_ui is needed to be called

my $started = 0;
for (my $i = 0; my @r = caller($i); $i++) {
next unless $r[3] =~ m/Hypnotoad/;
$self->_setup_webui;
$started = 1;
last;
}

$self->hook(before_command => sub {
my ($command, $arg) = @_;
$self->_setup_webui if ref($command) =~ m/daemon|prefork/;
}) unless $started;

$self;
}

Expand Down Expand Up @@ -104,15 +87,8 @@ sub startup {
$self->defaults(branding => $ENV{MIRRORCACHE_BRANDING});
$self->defaults(custom_footer_message => $ENV{MIRRORCACHE_CUSTOM_FOOTER_MESSAGE});

$self->plugin('RenderFile');

push @{$self->plugins->namespaces}, 'MirrorCache::WebAPI::Plugin';

$self->plugin('Backstage');
$self->plugin('AuditLog');
$self->plugin('RenderFileFromMirror');
$self->plugin('ReportMirror');
$self->plugin('HashedParams');

if ($geodb_file && $geodb_file =~ /\.mmdb$/i) {
require MaxMind::DB::Reader;
Expand Down Expand Up @@ -153,6 +129,21 @@ sub startup {
warn("Could not load plugin from {$plug}: $@\n")
}
}

$self->_setup_webui_plugins if ($ENV{MIRRORCACHE_INTERNAL_SETUP_WEBAPI});
}

sub _setup_webui_plugins {
my ($self) = shift;
my $root = $self->mcconfig->root;
$self->log->info("initializing WebUI");
$self->plugin('RenderFile');
$self->plugin('AuditLog');
$self->plugin('RenderFileFromMirror');
$self->plugin('ReportMirror');
$self->plugin('HashedParams');

$self->_setup_webui;
}

sub _setup_webui {
Expand Down
7 changes: 5 additions & 2 deletions lib/MirrorCache/WebAPI/Plugin/Backstage.pm
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ sub register_tasks {
push @permanent_jobs, 'mirror_provider_sync';
}

$app->plugin($_)
for (
unless ($ENV{MIRRORCACHE_INTERNAL_BACKSTAGE_EXEC}) {
$app->plugin($_) for (
qw(MirrorCache::Task::MirrorCheckFromStat),
qw(MirrorCache::Task::MirrorFileCheck),
qw(MirrorCache::Task::MirrorScanScheduleFromMisses),
Expand All @@ -73,6 +73,9 @@ sub register_tasks {
qw(MirrorCache::Task::StatAggSchedule),
qw(MirrorCache::Task::StatAggPkg),
);
} else {
$app->plugin("MirrorCache::Task::Exec");
}
}

sub register {
Expand Down
2 changes: 2 additions & 0 deletions script/mirrorcache-backstage-exec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh -e
MIRRORCACHE_INTERNAL_BACKSTAGE_EXEC=1 exec "$(dirname "$0")"/mirrorcache backstage run -j ${MIRRORCACHE_BACKSTAGE_WORKERS:-2} -q ${MIRRORCACHE_BACKSTAGE_EXEC_QUEUE:-exec}
2 changes: 1 addition & 1 deletion script/mirrorcache-daemon
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh -e
echo $MOJO_LISTEN
exec "$(dirname "$0")"/mirrorcache prefork -m production --proxy -w ${MIRRORCACHE_WORKERS:-8} "$@"
MIRRORCACHE_INTERNAL_SETUP_WEBAPI=1 exec "$(dirname "$0")"/mirrorcache prefork -m production --proxy -w ${MIRRORCACHE_WORKERS:-8} "$@"
Loading