user/dev discussion of public-inbox itself
 help / color / mirror / code / Atom feed
* [PATCH 0/5] lei/store: fix epoch roll, better errors
@ 2021-04-03 10:48 Eric Wong
  2021-04-03 10:48 ` [PATCH 1/5] lei_store: update alternates on new epoch Eric Wong
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Eric Wong @ 2021-04-03 10:48 UTC (permalink / raw)
  To: meta

Some things I noticed importing my personal IMAP mailboxes

Eric Wong (5):
  lei_store: update alternates on new epoch
  test_common: lei_ok: improve diagnostics
  lei tag: note message mismatches on failure
  lei: improve handling of Message-ID-less draft messages
  lei/store: (more) synchronous non-fatal error output

 MANIFEST                       |  1 +
 lib/PublicInbox/Import.pm      |  6 +--
 lib/PublicInbox/LeiSearch.pm   |  2 +-
 lib/PublicInbox/LeiStore.pm    | 74 ++++++++++++++++++++++++++++------
 lib/PublicInbox/LeiStoreErr.pm | 30 ++++++++++++++
 lib/PublicInbox/LeiTag.pm      |  3 +-
 lib/PublicInbox/OverIdx.pm     |  6 ++-
 lib/PublicInbox/Smsg.pm        |  2 +-
 lib/PublicInbox/TestCommon.pm  |  3 +-
 t/lei-import.t                 | 21 ++++++++++
 10 files changed, 127 insertions(+), 21 deletions(-)
 create mode 100644 lib/PublicInbox/LeiStoreErr.pm

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/5] lei_store: update alternates on new epoch
  2021-04-03 10:48 [PATCH 0/5] lei/store: fix epoch roll, better errors Eric Wong
@ 2021-04-03 10:48 ` Eric Wong
  2021-04-03 10:48 ` [PATCH 2/5] test_common: lei_ok: improve diagnostics Eric Wong
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2021-04-03 10:48 UTC (permalink / raw)
  To: meta

We'll just let the ExtSearchIdx code handle this uncommon case
by doing a full commit.
---
 lib/PublicInbox/LeiStore.pm | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lib/PublicInbox/LeiStore.pm b/lib/PublicInbox/LeiStore.pm
index d2dd4e7b..87082638 100644
--- a/lib/PublicInbox/LeiStore.pm
+++ b/lib/PublicInbox/LeiStore.pm
@@ -79,7 +79,10 @@ sub importer {
 		my $old = -e $latest;
 		PublicInbox::Import::init_bare($latest);
 		my $git = PublicInbox::Git->new($latest);
-		$git->qx(qw(config core.sharedRepository 0600)) if !$old;
+		if (!$old) {
+			$git->qx(qw(config core.sharedRepository 0600));
+			$self->done; # force eidx_init on next round
+		}
 		my $packed_bytes = $git->packed_bytes;
 		my $unpacked_bytes = $packed_bytes / $self->packing_factor;
 		if ($unpacked_bytes >= $self->rotate_bytes) {

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 2/5] test_common: lei_ok: improve diagnostics
  2021-04-03 10:48 [PATCH 0/5] lei/store: fix epoch roll, better errors Eric Wong
  2021-04-03 10:48 ` [PATCH 1/5] lei_store: update alternates on new epoch Eric Wong
@ 2021-04-03 10:48 ` Eric Wong
  2021-04-03 10:48 ` [PATCH 3/5] lei tag: note message mismatches on failure Eric Wong
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2021-04-03 10:48 UTC (permalink / raw)
  To: meta

$? is useful, as is labeling lei_err since I'm easily-confused :x
---
 lib/PublicInbox/TestCommon.pm | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/PublicInbox/TestCommon.pm b/lib/PublicInbox/TestCommon.pm
index d6e090dd..d506e4b5 100644
--- a/lib/PublicInbox/TestCommon.pm
+++ b/lib/PublicInbox/TestCommon.pm
@@ -494,7 +494,8 @@ sub lei_ok (@) {
 			s!\Q$PWD\E\b!\$PWD!g;
 		}
 	}
-	ok(lei(@_), "lei @msg". ($msg ? " ($$msg)" : '')) or diag $lei_err;
+	ok(lei(@_), "lei @msg". ($msg ? " ($$msg)" : '')) or
+		diag "\$?=$? err=$lei_err";
 }
 
 sub json_utf8 () {

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 3/5] lei tag: note message mismatches on failure
  2021-04-03 10:48 [PATCH 0/5] lei/store: fix epoch roll, better errors Eric Wong
  2021-04-03 10:48 ` [PATCH 1/5] lei_store: update alternates on new epoch Eric Wong
  2021-04-03 10:48 ` [PATCH 2/5] test_common: lei_ok: improve diagnostics Eric Wong
@ 2021-04-03 10:48 ` Eric Wong
  2021-04-03 10:48 ` [PATCH 4/5] lei: improve handling of Message-ID-less draft messages Eric Wong
  2021-04-03 10:48 ` [PATCH 5/5] lei/store: (more) synchronous non-fatal error output Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2021-04-03 10:48 UTC (permalink / raw)
  To: meta

Just exiting with a failure code and no error message is
confusing :x
---
 lib/PublicInbox/LeiTag.pm | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/PublicInbox/LeiTag.pm b/lib/PublicInbox/LeiTag.pm
index 06313139..c7a21c87 100644
--- a/lib/PublicInbox/LeiTag.pm
+++ b/lib/PublicInbox/LeiTag.pm
@@ -121,7 +121,8 @@ sub lei_tag { # the "lei tag" method
 
 sub note_missing {
 	my ($self) = @_;
-	$self->{lei}->child_error(1 << 8) if $self->{missing};
+	my $n = $self->{missing} or return;
+	$self->{lei}->child_error(1 << 8, "$n missed messages");
 }
 
 sub ipc_atfork_child {

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 4/5] lei: improve handling of Message-ID-less draft messages
  2021-04-03 10:48 [PATCH 0/5] lei/store: fix epoch roll, better errors Eric Wong
                   ` (2 preceding siblings ...)
  2021-04-03 10:48 ` [PATCH 3/5] lei tag: note message mismatches on failure Eric Wong
@ 2021-04-03 10:48 ` Eric Wong
  2021-04-03 10:48 ` [PATCH 5/5] lei/store: (more) synchronous non-fatal error output Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2021-04-03 10:48 UTC (permalink / raw)
  To: meta

We need a stable fallback time for digest2mid in the presence
of messages without Received/Date headers.  Furthermore, we
must avoid using uninitialized smsg->{mid} when parsing
References for draft replies.
---
 lib/PublicInbox/Import.pm    |  6 +++---
 lib/PublicInbox/LeiSearch.pm |  2 +-
 lib/PublicInbox/OverIdx.pm   |  6 ++++--
 lib/PublicInbox/Smsg.pm      |  2 +-
 t/lei-import.t               | 21 +++++++++++++++++++++
 5 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/lib/PublicInbox/Import.pm b/lib/PublicInbox/Import.pm
index 34738279..46f57e27 100644
--- a/lib/PublicInbox/Import.pm
+++ b/lib/PublicInbox/Import.pm
@@ -510,8 +510,8 @@ sub atfork_child {
 	}
 }
 
-sub digest2mid ($$) {
-	my ($dig, $hdr) = @_;
+sub digest2mid ($$;$) {
+	my ($dig, $hdr, $fallback_time) = @_;
 	my $b64 = $dig->clone->b64digest;
 	# Make our own URLs nicer:
 	# See "Base 64 Encoding with URL and Filename Safe Alphabet" in RFC4648
@@ -520,7 +520,7 @@ sub digest2mid ($$) {
 	# Add a date prefix to prevent a leading '-' in case that trips
 	# up some tools (e.g. if a Message-ID were a expected as a
 	# command-line arg)
-	my $dt = msg_datestamp($hdr);
+	my $dt = msg_datestamp($hdr, $fallback_time);
 	$dt = POSIX::strftime('%Y%m%d%H%M%S', gmtime($dt));
 	"$dt.$b64" . '@z';
 }
diff --git a/lib/PublicInbox/LeiSearch.pm b/lib/PublicInbox/LeiSearch.pm
index 69ba8303..148aa185 100644
--- a/lib/PublicInbox/LeiSearch.pm
+++ b/lib/PublicInbox/LeiSearch.pm
@@ -58,7 +58,7 @@ sub content_key ($) {
 			qw(Message-ID X-Alt-Message-ID Resent-Message-ID));
 	unless (@$mids) {
 		$eml->{-lei_fake_mid} = $mids->[0] =
-				PublicInbox::Import::digest2mid($dig, $eml);
+				PublicInbox::Import::digest2mid($dig, $eml, 0);
 	}
 	($chash, $mids);
 }
diff --git a/lib/PublicInbox/OverIdx.pm b/lib/PublicInbox/OverIdx.pm
index e1cd31b9..66dec099 100644
--- a/lib/PublicInbox/OverIdx.pm
+++ b/lib/PublicInbox/OverIdx.pm
@@ -264,8 +264,10 @@ sub add_overview {
 	$smsg->{lines} = $eml->body_raw =~ tr!\n!\n!;
 	my $mids = mids_for_index($eml);
 	my $refs = $smsg->parse_references($eml, $mids);
-	$mids->[0] //= $smsg->{mid} //= $eml->{-lei_fake_mid};
-	$smsg->{mid} //= '';
+	$mids->[0] //= do {
+		$smsg->{mid} //= '';
+		$eml->{-lei_fake_mid};
+	};
 	my $subj = $smsg->{subject};
 	my $xpath;
 	if ($subj ne '') {
diff --git a/lib/PublicInbox/Smsg.pm b/lib/PublicInbox/Smsg.pm
index b4cc2ecb..da8ce590 100644
--- a/lib/PublicInbox/Smsg.pm
+++ b/lib/PublicInbox/Smsg.pm
@@ -76,7 +76,7 @@ sub parse_references ($$$) {
 	return $refs if scalar(@$refs) == 0;
 
 	# prevent circular references here:
-	my %seen = ( $smsg->{mid} => 1 );
+	my %seen = ( ($smsg->{mid} // '') => 1 );
 	my @keep;
 	foreach my $ref (@$refs) {
 		if (length($ref) > PublicInbox::MID::MAX_MID_SIZE) {
diff --git a/t/lei-import.t b/t/lei-import.t
index 99289748..9bb4e1fa 100644
--- a/t/lei-import.t
+++ b/t/lei-import.t
@@ -79,6 +79,27 @@ is($res->[1], undef, 'only one result');
 is($res->[0]->{'m'}, 'k@y', 'got expected message');
 is_deeply($res->[0]->{kw}, ['seen'], "`seen' keywords set");
 
+# no From, Sender, or Message-ID
+$eml_str = <<'EOM';
+Subject: draft message with no sender
+References: <y@y>
+
+No use for a name
+EOM
+lei_ok([qw(import -F eml -)], undef, { %$lei_opt, 0 => \$eml_str });
+lei_ok(['q', 's:draft message with no sender']);
+my $draft_a = json_utf8->decode($lei_out);
+ok(!exists $draft_a->[0]->{'m'}, 'no fake mid stored or exposed');
+lei_ok([qw(tag -F eml - +kw:draft)], undef, { %$lei_opt, 0 => \$eml_str });
+lei_ok(['q', 's:draft message with no sender']);
+my $draft_b = json_utf8->decode($lei_out);
+my $kw = delete $draft_b->[0]->{kw};
+is_deeply($kw, ['draft'], 'draft kw set');
+is_deeply($draft_a, $draft_b, 'fake Message-ID lookup') or
+				diag explain($draft_a, $draft_b);
+lei_ok('blob', '--mail', $draft_b->[0]->{blob});
+is($lei_out, $eml_str, 'draft retrieved by blob');
+
 # see t/lei_to_mail.t for "import -F mbox*"
 });
 done_testing;

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 5/5] lei/store: (more) synchronous non-fatal error output
  2021-04-03 10:48 [PATCH 0/5] lei/store: fix epoch roll, better errors Eric Wong
                   ` (3 preceding siblings ...)
  2021-04-03 10:48 ` [PATCH 4/5] lei: improve handling of Message-ID-less draft messages Eric Wong
@ 2021-04-03 10:48 ` Eric Wong
  4 siblings, 0 replies; 6+ messages in thread
From: Eric Wong @ 2021-04-03 10:48 UTC (permalink / raw)
  To: meta

Since every command that writes to lei/store calls ->done
to commit its output, we can rely on that to return a
pathname for a readable file with errors in it.

Errors can still get crossed up if multiple lei commands
are writing to the store at once, but reduces the delay
in seeing them and ensures it won't get seen when somebody
is attempting to use shell completion.
---
 MANIFEST                       |  1 +
 lib/PublicInbox/LeiStore.pm    | 69 ++++++++++++++++++++++++++++------
 lib/PublicInbox/LeiStoreErr.pm | 30 +++++++++++++++
 3 files changed, 89 insertions(+), 11 deletions(-)
 create mode 100644 lib/PublicInbox/LeiStoreErr.pm

diff --git a/MANIFEST b/MANIFEST
index 64293bb6..b663c2a2 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -203,6 +203,7 @@ lib/PublicInbox/LeiQuery.pm
 lib/PublicInbox/LeiRemote.pm
 lib/PublicInbox/LeiSearch.pm
 lib/PublicInbox/LeiStore.pm
+lib/PublicInbox/LeiStoreErr.pm
 lib/PublicInbox/LeiSucks.pm
 lib/PublicInbox/LeiTag.pm
 lib/PublicInbox/LeiToMail.pm
diff --git a/lib/PublicInbox/LeiStore.pm b/lib/PublicInbox/LeiStore.pm
index 87082638..094e1555 100644
--- a/lib/PublicInbox/LeiStore.pm
+++ b/lib/PublicInbox/LeiStore.pm
@@ -3,9 +3,14 @@
 #
 # Local storage (cache/memo) for lei(1), suitable for personal/private
 # mail iff on encrypted device/FS.  Based on v2, but only deduplicates
-# based on git OID.
+# git storage based on git OID (index deduplication is done in ContentHash)
 #
 # for xref3, the following are constant: $eidx_key = '.', $xnum = -1
+#
+# We rely on the synchronous IPC API for this in lei-daemon and
+# multiple lei clients to write to it at once.  This allows the
+# lei/store IPC process to be decoupled from network latency in
+# lei WQ workers.
 package PublicInbox::LeiStore;
 use strict;
 use v5.10.1;
@@ -19,7 +24,10 @@ use PublicInbox::ContentHash qw(content_hash);
 use PublicInbox::MID qw(mids);
 use PublicInbox::LeiSearch;
 use PublicInbox::MDA;
+use PublicInbox::Spawn qw(spawn);
 use List::Util qw(max);
+use File::Temp ();
+use POSIX ();
 
 sub new {
 	my (undef, $dir, $opt) = @_;
@@ -102,18 +110,27 @@ sub search {
 	PublicInbox::LeiSearch->new($_[0]->{priv_eidx}->{topdir});
 }
 
+# follows the stderr file
+sub _tail_err {
+	my ($self) = @_;
+	print { $self->{-err_wr} } readline($self->{-tmp_err});
+}
+
 sub eidx_init {
 	my ($self) = @_;
 	my $eidx = $self->{priv_eidx};
+	my $tl = wantarray && $self->{-err_wr} ?
+			PublicInbox::OnDestroy->new($$, \&_tail_err, $self) :
+			undef;
 	$eidx->idx_init({-private => 1});
-	$eidx;
+	wantarray ? ($eidx, $tl) : $eidx;
 }
 
 sub _docids_for ($$) {
 	my ($self, $eml) = @_;
 	my %docids;
+	my $eidx = $self->{priv_eidx};
 	my ($chash, $mids) = PublicInbox::LeiSearch::content_key($eml);
-	my $eidx = eidx_init($self);
 	my $oidx = $eidx->{oidx};
 	my $im = $self->{im};
 	for my $mid (@$mids) {
@@ -137,7 +154,7 @@ sub _docids_for ($$) {
 
 sub set_eml_vmd {
 	my ($self, $eml, $vmd, $docids) = @_;
-	my $eidx = eidx_init($self);
+	my ($eidx, $tl) = eidx_init($self);
 	$docids //= [ _docids_for($self, $eml) ];
 	for my $docid (@$docids) {
 		$eidx->idx_shard($docid)->ipc_do('set_vmd', $docid, $vmd);
@@ -147,7 +164,7 @@ sub set_eml_vmd {
 
 sub add_eml_vmd {
 	my ($self, $eml, $vmd) = @_;
-	my $eidx = eidx_init($self);
+	my ($eidx, $tl) = eidx_init($self);
 	my @docids = _docids_for($self, $eml);
 	for my $docid (@docids) {
 		$eidx->idx_shard($docid)->ipc_do('add_vmd', $docid, $vmd);
@@ -157,7 +174,7 @@ sub add_eml_vmd {
 
 sub remove_eml_vmd {
 	my ($self, $eml, $vmd) = @_;
-	my $eidx = eidx_init($self);
+	my ($eidx, $tl) = eidx_init($self);
 	my @docids = _docids_for($self, $eml);
 	for my $docid (@docids) {
 		$eidx->idx_shard($docid)->ipc_do('remove_vmd', $docid, $vmd);
@@ -168,7 +185,7 @@ sub remove_eml_vmd {
 sub add_eml {
 	my ($self, $eml, $vmd, $xoids) = @_;
 	my $im = $self->importer; # may create new epoch
-	my $eidx = eidx_init($self); # writes ALL.git/objects/info/alternates
+	my ($eidx, $tl) = eidx_init($self); # updates/writes alternates file
 	my $oidx = $eidx->{oidx}; # PublicInbox::Import::add checks this
 	my $smsg = bless { -oidx => $oidx }, 'PublicInbox::Smsg';
 	$im->add($eml, undef, $smsg) or return; # duplicate returns undef
@@ -257,7 +274,7 @@ sub _external_only ($$$) {
 
 sub update_xvmd {
 	my ($self, $xoids, $eml, $vmd_mod) = @_;
-	my $eidx = eidx_init($self);
+	my ($eidx, $tl) = eidx_init($self);
 	my $oidx = $eidx->{oidx};
 	my %seen;
 	for my $oid (keys %$xoids) {
@@ -294,7 +311,7 @@ sub update_xvmd {
 sub set_xvmd {
 	my ($self, $xoids, $eml, $vmd) = @_;
 
-	my $eidx = eidx_init($self);
+	my ($eidx, $tl) = eidx_init($self);
 	my $oidx = $eidx->{oidx};
 	my %seen;
 
@@ -329,6 +346,21 @@ sub checkpoint {
 	$self->{priv_eidx}->checkpoint($wait);
 }
 
+sub xchg_stderr {
+	my ($self) = @_;
+	_tail_err($self) if $self->{-err_wr};
+	my $dir = $self->{priv_eidx}->{topdir};
+	return unless -e $dir;
+	my $old = delete $self->{-tmp_err};
+	my $pfx = POSIX::strftime('%Y%m%d%H%M%S', gmtime(time));
+	my $err = File::Temp->new(TEMPLATE => "$pfx.$$.lei_storeXXXX",
+				SUFFIX => '.err', DIR => $dir);
+	open STDERR, '>>', $err->filename or die "dup2: $!";
+	STDERR->autoflush(1); # shared with shard subprocesses
+	$self->{-tmp_err} = $err; # separate file description for RO access
+	undef;
+}
+
 sub done {
 	my ($self) = @_;
 	my $err = '';
@@ -339,7 +371,8 @@ sub done {
 			warn $err;
 		}
 	}
-	$self->{priv_eidx}->done;
+	$self->{priv_eidx}->done; # V2Writable::done
+	xchg_stderr($self);
 	die $err if $err;
 }
 
@@ -347,6 +380,11 @@ sub ipc_atfork_child {
 	my ($self) = @_;
 	my $lei = $self->{lei};
 	$lei->_lei_atfork_child(1) if $lei;
+	xchg_stderr($self);
+	if (my $err = delete($self->{err_pipe})) {
+		close $err->[0];
+		$self->{-err_wr} = $err->[1];
+	}
 	$SIG{__WARN__} = PublicInbox::Eml::warn_ignore_cb();
 	$self->SUPER::ipc_atfork_child;
 }
@@ -357,11 +395,20 @@ sub write_prepare {
 		my $d = $lei->store_path;
 		$self->ipc_lock_init("$d/ipc.lock");
 		substr($d, -length('/lei/store'), 10, '');
+		my $err_pipe;
+		unless ($lei->{oneshot}) {
+			pipe(my ($r, $w)) or die "pipe: $!";
+			$err_pipe = [ $r, $w ];
+		}
 		# Mail we import into lei are private, so headers filtered out
 		# by -mda for public mail are not appropriate
 		local @PublicInbox::MDA::BAD_HEADERS = ();
 		$self->ipc_worker_spawn("lei/store $d", $lei->oldset,
-					{ lei => $lei });
+					{ lei => $lei, err_pipe => $err_pipe });
+		if ($err_pipe) {
+			require PublicInbox::LeiStoreErr;
+			PublicInbox::LeiStoreErr->new($err_pipe->[0], $lei);
+		}
 	}
 	$lei->{sto} = $self;
 }
diff --git a/lib/PublicInbox/LeiStoreErr.pm b/lib/PublicInbox/LeiStoreErr.pm
new file mode 100644
index 00000000..68ce96d6
--- /dev/null
+++ b/lib/PublicInbox/LeiStoreErr.pm
@@ -0,0 +1,30 @@
+# Copyright (C) 2021 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# forwards stderr from lei/store process to any lei clients using
+# the same store
+package PublicInbox::LeiStoreErr;
+use strict;
+use v5.10.1;
+use parent qw(PublicInbox::DS);
+use PublicInbox::Syscall qw(EPOLLIN EPOLLONESHOT);
+
+sub new {
+	my ($cls, $rd, $lei) = @_;
+	my $self = bless { sock => $rd, store_path => $lei->store_path }, $cls;
+	$self->SUPER::new($rd, EPOLLIN | EPOLLONESHOT);
+}
+
+sub event_step {
+	my ($self) = @_;
+	$self->do_read(\(my $rbuf), 4096) or return;
+	my $cb;
+	for my $lei (values %PublicInbox::DS::DescriptorMap) {
+		$cb = $lei->can('store_path') // next;
+		next if $cb->($lei) ne $self->{store_path};
+		my $err = $lei->{2} // next;
+		print $err $rbuf;
+	}
+}
+
+1;

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-04-03 10:48 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-03 10:48 [PATCH 0/5] lei/store: fix epoch roll, better errors Eric Wong
2021-04-03 10:48 ` [PATCH 1/5] lei_store: update alternates on new epoch Eric Wong
2021-04-03 10:48 ` [PATCH 2/5] test_common: lei_ok: improve diagnostics Eric Wong
2021-04-03 10:48 ` [PATCH 3/5] lei tag: note message mismatches on failure Eric Wong
2021-04-03 10:48 ` [PATCH 4/5] lei: improve handling of Message-ID-less draft messages Eric Wong
2021-04-03 10:48 ` [PATCH 5/5] lei/store: (more) synchronous non-fatal error output Eric Wong

user/dev discussion of public-inbox itself

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://public-inbox.org/meta
	git clone --mirror http://czquwvybam4bgbro.onion/meta
	git clone --mirror http://hjrcffqmbrq6wope.onion/meta
	git clone --mirror http://ou63pmih66umazou.onion/meta

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V1 meta meta/ https://public-inbox.org/meta \
		meta@public-inbox.org
	public-inbox-index meta

Example config snippet for mirrors.
Newsgroups are available over NNTP:
	nntp://news.public-inbox.org/inbox.comp.mail.public-inbox.meta
	nntp://ou63pmih66umazou.onion/inbox.comp.mail.public-inbox.meta
	nntp://czquwvybam4bgbro.onion/inbox.comp.mail.public-inbox.meta
	nntp://hjrcffqmbrq6wope.onion/inbox.comp.mail.public-inbox.meta
	nntp://news.gmane.io/gmane.mail.public-inbox.general
 note: .onion URLs require Tor: https://www.torproject.org/

code repositories for project(s) associated with this inbox:

	https://80x24.org/public-inbox.git

AGPL code for this site: git clone https://public-inbox.org/public-inbox.git