From c337452016b530763caedb3e5be225bf4f4d4d2a Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Fri, 23 Feb 2024 01:55:42 -0500 Subject: convert mbsyncloop to systemd --- local/.bin/mbsyncloop | 307 -------------------- mbsyncloop/.bin/mbsyncloop | 312 +++++++++++++++++++++ mbsyncloop/.config/systemd/user/mbsyncloop.service | 6 + mbsyncloop/.services/available/mbsyncloop/log/run | 4 - mbsyncloop/.services/available/mbsyncloop/run | 3 - mbsyncloop/Makefile | 9 +- mutt/.config/mutt/local | 2 +- 7 files changed, 326 insertions(+), 317 deletions(-) delete mode 100755 local/.bin/mbsyncloop create mode 100755 mbsyncloop/.bin/mbsyncloop create mode 100644 mbsyncloop/.config/systemd/user/mbsyncloop.service delete mode 100755 mbsyncloop/.services/available/mbsyncloop/log/run delete mode 100755 mbsyncloop/.services/available/mbsyncloop/run diff --git a/local/.bin/mbsyncloop b/local/.bin/mbsyncloop deleted file mode 100755 index a1cf181..0000000 --- a/local/.bin/mbsyncloop +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env perl -use 5.016; -use strict; -use warnings; - -use File::Path; -use File::Spec; -use File::Temp; -use JSON::PP; -use POSIX 'mkfifo'; - -my $RUN_DIR = make_run_dir(); -my ($PW_PID, $GOIMAPNOTIFY_PID); - -$SIG{INT} = $SIG{TERM} = sub { cleanup(); exit }; -END { cleanup() } - -my $config = "$ENV{HOME}/.config/mbsyncloop/config.json"; - -main($config); - -sub main { - my ($config) = @_; - - my $pw_pipe = make_pw_pipe(); - my $password_command_pipe = "head -n1 '$pw_pipe'"; - - my $config_data; - if (-f $config) { - $config_data = JSON::PP::decode_json(slurp($config)); - } - else { - $config_data = {}; - } - - my $mbsync_config_data = slurp( - ($config_data->{mbsync_config} // '~/.mbsyncrc') - =~ s{^~/}{$ENV{HOME}/}r - ); - my $password_command = extract_password_command($mbsync_config_data); - - spawn_pw_proc($pw_pipe, $password_command); - - my $goimapnotify_config_data = extract_goimapnotify_config( - $config_data, - $mbsync_config_data, - $password_command_pipe, - ); - - my $goimapnotify_r = spawn_goimapnotify_proc( - $goimapnotify_config_data, - ); - - my $generated_mbsync_config_file = write_mbsync_config( - $mbsync_config_data, - $goimapnotify_config_data->{boxes}, - $password_command_pipe, - ); - - loop( - $config_data, - $generated_mbsync_config_file, - $goimapnotify_r, - ); -} - -sub make_pw_pipe { - my $file = File::Spec->catfile($RUN_DIR, "mbsyncloop"); - unlink($file); - mkfifo($file, 0700) or die "couldn't create $file: $!"; - $file -} - -sub extract_goimapnotify_config { - my ($config_data, $mbsync_config_data, $password_command) = @_; - - (my $host) = $mbsync_config_data =~ /^Host (.*)$/m; - (my $port) = $mbsync_config_data =~ /^Port (.*)$/m; - (my $user) = $mbsync_config_data =~ /^User (.*)$/m; - - my $tls; - if ($mbsync_config_data =~ /SSLType\s+IMAPS/) { - $tls = JSON::PP::true; - $port //= 993; - } - else { - $tls = JSON::PP::false; - $port //= 143; - } - - my $goimapnotify_config = { - host => $host, - port => $port, - tls => $tls, - username => $user, - passwordCmd => $password_command, - }; - - my @mailboxes; - if ($config_data->{boxes}) { - @mailboxes = @{ $config_data->{boxes} }; - } - else { - @mailboxes = read_mailboxes($goimapnotify_config); - if ($config_data->{box_patterns}) { - @mailboxes = grep { - my $mailbox = $_; - grep { - $mailbox =~ /$_/ - } @{ $config_data->{box_patterns} } - } @mailboxes - } - } - - $goimapnotify_config->{onNewMail} = "echo new"; - $goimapnotify_config->{boxes} = \@mailboxes; - - $goimapnotify_config -} - -sub read_mailboxes { - my ($config) = @_; - - my $tmp = File::Temp->new(DIR => $RUN_DIR); - $tmp->print(JSON::PP::encode_json($config)); - $tmp->flush; - open my $fh, '-|', 'goimapnotify', '--conf', $tmp->filename, '--list' - or die "couldn't run goimapnotify: $!"; - <$fh>; - - map { chomp; s/^[^ ]* //r } <$fh> -} - -sub extract_password_command { - my ($mbsync_config) = @_; - (my $password_command) = $mbsync_config =~ /^PassCmd "(.*)"$/m; - $password_command -} - -sub spawn_pw_proc { - my ($pw_pipe, $password_command) = @_; - - my $pw = fetch_password($password_command); - - $PW_PID = fork; - die "fork failed: $!" unless defined $PW_PID; - if (!$PW_PID) { - $SIG{PIPE} = 'IGNORE'; - setpgrp(0, 0); - while (1) { - open my $fh, '>', $pw_pipe or die "couldn't open $pw_pipe"; - $fh->print("$pw\n"); - close $fh; - } - } -} - -sub fetch_password { - my ($password_command) = @_; - my $pw = `$password_command`; - die "failed to fetch password: command returned $?" if $?; - $pw -} - -sub spawn_goimapnotify_proc { - my ($config) = @_; - - pipe(my $goimapnotify_r, my $goimapnotify_w) - or die "failed to create unnamed pipe: $!"; - - $GOIMAPNOTIFY_PID = fork; - die "fork failed: $!" unless defined $GOIMAPNOTIFY_PID; - if (!$GOIMAPNOTIFY_PID) { - setpgrp(0, 0); - close $goimapnotify_r; - - my $tmp = File::Temp->new(DIR => $RUN_DIR); - $tmp->print(JSON::PP::encode_json($config)); - $tmp->flush; - - while (1) { - open my $fh, '-|', 'goimapnotify', '--conf', $tmp->filename - or die "couldn't run goimapnotify: $!"; - while (<$fh>) { - $goimapnotify_w->print("N\n"); - $goimapnotify_w->flush; - } - } - } - close $goimapnotify_w; - - $goimapnotify_r -} - -sub write_mbsync_config { - my ($mbsync_config, $mailboxes, $password_command) = @_; - - $mbsync_config =~ s/^PassCmd .*$/PassCmd "$password_command"/m; - my ($far) = $mbsync_config =~ /^IMAPStore (.*)$/m; - my ($near) = $mbsync_config =~ /^MaildirStore (.*)$/m; - - my $patterns = join "\n", map { "Pattern $_" } @$mailboxes; - my $mbsync_channels = <new(DIR => $RUN_DIR); - $tmp->print($mbsync_config); - $tmp->print("\n"); - $tmp->print($mbsync_channels); - $tmp->flush; - - $tmp -} - -sub loop { - my ($config_data, $mbsync_config, $goimapnotify_r) = @_; - - my $poll_interval = $config_data->{poll_interval} // 15 * 60; - my $last_all = 0; - $SIG{HUP} = sub { $last_all = 0 }; - while (1) { - my $now = time; - if (($now - $last_all) >= $poll_interval) { - sync( - $mbsync_config, - "mbsyncloop_all", - $config_data->{on_new_mail}, - ); - $last_all = $now; - } - if (idle($goimapnotify_r, $poll_interval)) { - sync( - $mbsync_config, - "mbsyncloop_priority", - $config_data->{on_new_mail}, - ); - } - } -} - -sub sync { - my ($config, $channel, $on_new_mail) = @_; - my $config_file = $config->filename; - while (1) { - my $status = system("mbsync -c '$config_file' $channel"); - if (!$status) { - system($on_new_mail) if defined $on_new_mail; - last; - } - sleep 5; - } -} - -sub idle { - my ($goimapnotify_r, $max_delay) = @_; - my $rin = ''; - vec($rin, fileno($goimapnotify_r), 1) = 1; - my $ready = select(my $rout = $rin, undef, undef, $max_delay); - return 0 if $ready == -1 && $! == POSIX::EINTR; - die "failed to read goimapnotify output: $!" if $ready == -1; - if ($ready) { - while (1) { - my $ready = select(my $rout = $rin, undef, undef, 0.01); - return 0 if $ready == -1 && $! == POSIX::EINTR; - die "failed to read goimapnotify output: $!" if $ready == -1; - last unless $ready; - sysread $goimapnotify_r, my $data, 4096; - } - return 1; - } - return 0; -} - -sub slurp { - my ($file) = @_; - local $/; - open my $fh, '<', $file or die "couldn't open $file: $!"; - <$fh> -} - -sub make_run_dir { - my $dir = "/run/user/$>"; - if (!-d $dir) { - $dir = File::Spec->tmpdir(); - } - unlink File::Spec->catfile($dir, "mbsyncloop"); - mkdir File::Spec->catfile($dir, "mbsyncloop"); - mkdir File::Spec->catfile($dir, "mbsyncloop", $$); - File::Spec->catfile($dir, "mbsyncloop", $$) -} - -sub cleanup { - kill KILL => -$PW_PID if $PW_PID; - kill KILL => -$GOIMAPNOTIFY_PID if $GOIMAPNOTIFY_PID; - File::Path::remove_tree($RUN_DIR) if $RUN_DIR; -} diff --git a/mbsyncloop/.bin/mbsyncloop b/mbsyncloop/.bin/mbsyncloop new file mode 100755 index 0000000..bb16df1 --- /dev/null +++ b/mbsyncloop/.bin/mbsyncloop @@ -0,0 +1,312 @@ +#!/usr/bin/env perl +use 5.016; +use strict; +use warnings; + +use File::Path; +use File::Spec; +use File::Temp; +use JSON::PP; +use POSIX 'mkfifo'; + +# running a local build until +# https://gitlab.com/shackra/goimapnotify/-/merge_requests/14 makes it into a +# release +my $GOIMAPNOTIFY = "$ENV{HOME}/.bin/local/goimapnotify"; + +my $RUN_DIR = make_run_dir(); +my ($PW_PID, $GOIMAPNOTIFY_PID); + +$SIG{INT} = $SIG{TERM} = sub { cleanup(); exit }; +END { cleanup() } + +my $config = "$ENV{HOME}/.config/mbsyncloop/config.json"; + +main($config); + +sub main { + my ($config) = @_; + + my $pw_pipe = make_pw_pipe(); + my $password_command_pipe = "head -n1 '$pw_pipe'"; + + my $config_data; + if (-f $config) { + $config_data = JSON::PP::decode_json(slurp($config)); + } + else { + $config_data = {}; + } + + my $mbsync_config_data = slurp( + ($config_data->{mbsync_config} // '~/.mbsyncrc') + =~ s{^~/}{$ENV{HOME}/}r + ); + my $password_command = extract_password_command($mbsync_config_data); + + spawn_pw_proc($pw_pipe, $password_command); + + my $goimapnotify_config_data = extract_goimapnotify_config( + $config_data, + $mbsync_config_data, + $password_command_pipe, + ); + + my $goimapnotify_r = spawn_goimapnotify_proc( + $goimapnotify_config_data, + ); + + my $generated_mbsync_config_file = write_mbsync_config( + $mbsync_config_data, + $goimapnotify_config_data->{boxes}, + $password_command_pipe, + ); + + loop( + $config_data, + $generated_mbsync_config_file, + $goimapnotify_r, + ); +} + +sub make_pw_pipe { + my $file = File::Spec->catfile($RUN_DIR, "mbsyncloop"); + unlink($file); + mkfifo($file, 0700) or die "couldn't create $file: $!"; + $file +} + +sub extract_goimapnotify_config { + my ($config_data, $mbsync_config_data, $password_command) = @_; + + (my $host) = $mbsync_config_data =~ /^Host (.*)$/m; + (my $port) = $mbsync_config_data =~ /^Port (.*)$/m; + (my $user) = $mbsync_config_data =~ /^User (.*)$/m; + + my $tls; + if ($mbsync_config_data =~ /SSLType\s+IMAPS/) { + $tls = JSON::PP::true; + $port //= 993; + } + else { + $tls = JSON::PP::false; + $port //= 143; + } + + my $goimapnotify_config = { + host => $host, + port => $port, + tls => $tls, + username => $user, + passwordCmd => $password_command, + }; + + my @mailboxes; + if ($config_data->{boxes}) { + @mailboxes = @{ $config_data->{boxes} }; + } + else { + @mailboxes = read_mailboxes($goimapnotify_config); + if ($config_data->{box_patterns}) { + @mailboxes = grep { + my $mailbox = $_; + grep { + $mailbox =~ /$_/ + } @{ $config_data->{box_patterns} } + } @mailboxes + } + } + + $goimapnotify_config->{onNewMail} = "echo new"; + $goimapnotify_config->{boxes} = \@mailboxes; + + $goimapnotify_config +} + +sub read_mailboxes { + my ($config) = @_; + + my $tmp = File::Temp->new(DIR => $RUN_DIR); + $tmp->print(JSON::PP::encode_json($config)); + $tmp->flush; + open my $fh, '-|', $GOIMAPNOTIFY, '--conf', $tmp->filename, '--list' + or die "couldn't run goimapnotify: $!"; + <$fh>; + + map { chomp; s/^[^ ]* //r } <$fh> +} + +sub extract_password_command { + my ($mbsync_config) = @_; + (my $password_command) = $mbsync_config =~ /^PassCmd "(.*)"$/m; + $password_command +} + +sub spawn_pw_proc { + my ($pw_pipe, $password_command) = @_; + + my $pw = fetch_password($password_command); + + $PW_PID = fork; + die "fork failed: $!" unless defined $PW_PID; + if (!$PW_PID) { + $SIG{PIPE} = 'IGNORE'; + setpgrp(0, 0); + while (1) { + open my $fh, '>', $pw_pipe or die "couldn't open $pw_pipe"; + $fh->print("$pw\n"); + close $fh; + } + } +} + +sub fetch_password { + my ($password_command) = @_; + my $pw = `$password_command`; + die "failed to fetch password: command returned $?" if $?; + $pw +} + +sub spawn_goimapnotify_proc { + my ($config) = @_; + + pipe(my $goimapnotify_r, my $goimapnotify_w) + or die "failed to create unnamed pipe: $!"; + + $GOIMAPNOTIFY_PID = fork; + die "fork failed: $!" unless defined $GOIMAPNOTIFY_PID; + if (!$GOIMAPNOTIFY_PID) { + setpgrp(0, 0); + close $goimapnotify_r; + + my $tmp = File::Temp->new(DIR => $RUN_DIR); + $tmp->print(JSON::PP::encode_json($config)); + $tmp->flush; + + while (1) { + open my $fh, '-|', $GOIMAPNOTIFY, '--conf', $tmp->filename + or die "couldn't run goimapnotify: $!"; + while (<$fh>) { + $goimapnotify_w->print("N\n"); + $goimapnotify_w->flush; + } + } + } + close $goimapnotify_w; + + $goimapnotify_r +} + +sub write_mbsync_config { + my ($mbsync_config, $mailboxes, $password_command) = @_; + + $mbsync_config =~ s/^PassCmd .*$/PassCmd "$password_command"/m; + my ($far) = $mbsync_config =~ /^IMAPStore (.*)$/m; + my ($near) = $mbsync_config =~ /^MaildirStore (.*)$/m; + + my $patterns = join "\n", map { "Pattern $_" } @$mailboxes; + my $mbsync_channels = <new(DIR => $RUN_DIR); + $tmp->print($mbsync_config); + $tmp->print("\n"); + $tmp->print($mbsync_channels); + $tmp->flush; + + $tmp +} + +sub loop { + my ($config_data, $mbsync_config, $goimapnotify_r) = @_; + + my $poll_interval = $config_data->{poll_interval} // 15 * 60; + my $last_all = 0; + $SIG{HUP} = sub { $last_all = 0 }; + while (1) { + my $now = time; + if (($now - $last_all) >= $poll_interval) { + sync( + $mbsync_config, + "mbsyncloop_all", + $config_data->{on_new_mail}, + ); + $last_all = $now; + } + if (idle($goimapnotify_r, $poll_interval)) { + sync( + $mbsync_config, + "mbsyncloop_priority", + $config_data->{on_new_mail}, + ); + } + } +} + +sub sync { + my ($config, $channel, $on_new_mail) = @_; + my $config_file = $config->filename; + while (1) { + my $status = system("mbsync -c '$config_file' $channel"); + if (!$status) { + system($on_new_mail) if defined $on_new_mail; + last; + } + sleep 5; + } +} + +sub idle { + my ($goimapnotify_r, $max_delay) = @_; + my $rin = ''; + vec($rin, fileno($goimapnotify_r), 1) = 1; + my $ready = select(my $rout = $rin, undef, undef, $max_delay); + return 0 if $ready == -1 && $! == POSIX::EINTR; + die "failed to read goimapnotify output: $!" if $ready == -1; + if ($ready) { + while (1) { + my $ready = select(my $rout = $rin, undef, undef, 0.01); + return 0 if $ready == -1 && $! == POSIX::EINTR; + die "failed to read goimapnotify output: $!" if $ready == -1; + last unless $ready; + sysread $goimapnotify_r, my $data, 4096; + } + return 1; + } + return 0; +} + +sub slurp { + my ($file) = @_; + local $/; + open my $fh, '<', $file or die "couldn't open $file: $!"; + <$fh> +} + +sub make_run_dir { + my $dir = "/run/user/$>"; + if (!-d $dir) { + $dir = File::Spec->tmpdir(); + } + unlink File::Spec->catfile($dir, "mbsyncloop"); + mkdir File::Spec->catfile($dir, "mbsyncloop"); + mkdir File::Spec->catfile($dir, "mbsyncloop", $$); + File::Spec->catfile($dir, "mbsyncloop", $$) +} + +sub cleanup { + kill KILL => -$PW_PID if $PW_PID; + kill KILL => -$GOIMAPNOTIFY_PID if $GOIMAPNOTIFY_PID; + File::Path::remove_tree($RUN_DIR) if $RUN_DIR; +} diff --git a/mbsyncloop/.config/systemd/user/mbsyncloop.service b/mbsyncloop/.config/systemd/user/mbsyncloop.service new file mode 100644 index 0000000..c53e989 --- /dev/null +++ b/mbsyncloop/.config/systemd/user/mbsyncloop.service @@ -0,0 +1,6 @@ +[Service] +ExecStart=%h/.bin/mbsyncloop +Restart=always + +[Install] +WantedBy=default.target diff --git a/mbsyncloop/.services/available/mbsyncloop/log/run b/mbsyncloop/.services/available/mbsyncloop/log/run deleted file mode 100755 index 5930eda..0000000 --- a/mbsyncloop/.services/available/mbsyncloop/log/run +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -set -eu - -exec multilog t s16777215 "$HOME/.log/mbsyncloop" diff --git a/mbsyncloop/.services/available/mbsyncloop/run b/mbsyncloop/.services/available/mbsyncloop/run deleted file mode 100755 index ac7e636..0000000 --- a/mbsyncloop/.services/available/mbsyncloop/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -exec mbsyncloop 2>&1 diff --git a/mbsyncloop/Makefile b/mbsyncloop/Makefile index 5997d23..b095cbd 100644 --- a/mbsyncloop/Makefile +++ b/mbsyncloop/Makefile @@ -1,4 +1,9 @@ include ../Makefile.include -install: $(HOME)/.services/enabled/mbsyncloop - @mkdir -p $(HOME)/.log/mbsyncloop +install: + @systemctl --user enable mbsyncloop + @systemctl --user start mbsyncloop + +uninstall: + @systemctl --user stop mbsyncloop + @systemctl --user disable mbsyncloop diff --git a/mutt/.config/mutt/local b/mutt/.config/mutt/local index a4fea33..636bd29 100644 --- a/mutt/.config/mutt/local +++ b/mutt/.config/mutt/local @@ -10,7 +10,7 @@ mailboxes `\ echo -n "\"=$basename\" ";\ fi\ done` -macro index R "unset wait_keysvc -h ~/.services/enabled/mbsyncloopset wait_key" "fetch mail with mbsync" +macro index R "unset wait_keykill -HUP $(systemctl --user show --property MainPID --value mbsyncloop)set wait_key" "fetch mail with mbsync" set timeout=3 # vim: ft=neomuttrc -- cgit v1.2.3-54-g00ecf