+ undef;
+ }
+}
+
+# abs_path resolves symlinks, so we want to avoid it if rel2abs
+# is sufficient and doesn't leave "/.." or "/../"
+sub rel2abs_collapsed {
+ require File::Spec;
+ my $p = File::Spec->rel2abs($_[-1]);
+ return $p if substr($p, -3, 3) ne '/..' && index($p, '/../') < 0;
+ require Cwd;
+ Cwd::abs_path($p);
+}
+
+sub _one_val {
+ my ($self, $pfx, $k) = @_;
+ my $v = $self->{"$pfx.$k"} // return;
+ return $v if !ref($v);
+ warn "W: $pfx.$k has multiple values, only using `$v->[-1]'\n";
+ $v->[-1];
+}
+
+sub repo_objs {
+ my ($self, $ibxish) = @_;
+ my $ibx_code_repos = $ibxish->{coderepo} or return;
+ $ibxish->{-repo_objs} //= do {
+ my $code_repos = $self->{-code_repos};
+ my @repo_objs;
+ for my $nick (@$ibx_code_repos) {
+ my @parts = split(m!/!, $nick);
+ for (@parts) {
+ @parts = () unless valid_foo_name($_);
+ }
+ unless (@parts) {
+ warn "invalid coderepo name: `$nick'\n";
+ next;
+ }
+ my $repo = $code_repos->{$nick} //=
+ _fill_code_repo($self, $nick);
+ push @repo_objs, $repo if $repo;
+ }
+ if (scalar @repo_objs) {
+ \@repo_objs;
+ } else {
+ delete $ibxish->{coderepo};
+ undef;
+ }
+ }
+}
+
+sub _fill_ibx {
+ my ($self, $name) = @_;
+ my $pfx = "publicinbox.$name";
+ my $ibx = {};
+ for my $k (qw(watch nntpserver)) {
+ my $v = $self->{"$pfx.$k"};
+ $ibx->{$k} = $v if defined $v;
+ }
+ for my $k (qw(filter inboxdir newsgroup replyto httpbackendmax feedmax
+ indexlevel indexsequentialshard)) {
+ my $v = _one_val($self, $pfx, $k) // next;
+ $ibx->{$k} = $v;
+ }
+
+ # "mainrepo" is backwards compatibility:
+ my $dir = $ibx->{inboxdir} //= $self->{"$pfx.mainrepo"} // return;
+ if (index($dir, "\n") >= 0) {
+ warn "E: `$dir' must not contain `\\n'\n";
+ return;
+ }
+ for my $k (qw(obfuscate)) {
+ my $v = $self->{"$pfx.$k"} // next;
+ if (defined(my $bval = git_bool($v))) {
+ $ibx->{$k} = $bval;
+ } else {
+ warn "Ignoring $pfx.$k=$v in config, not boolean\n";
+ }
+ }
+ # TODO: more arrays, we should support multi-value for
+ # more things to encourage decentralization
+ for my $k (qw(address altid nntpmirror coderepo hide listid url
+ infourl watchheader)) {
+ my $v = $self->{"$pfx.$k"} // next;
+ $ibx->{$k} = _array($v);
+ }
+
+ return unless valid_foo_name($name, 'publicinbox');
+ $ibx->{name} = $name;
+ $ibx->{-pi_cfg} = $self;
+ $ibx = PublicInbox::Inbox->new($ibx);
+ foreach (@{$ibx->{address}}) {
+ my $lc_addr = lc($_);
+ $self->{-by_addr}->{$lc_addr} = $ibx;
+ $self->{-no_obfuscate}->{$lc_addr} = 1;
+ }
+ if (my $listids = $ibx->{listid}) {
+ # RFC2919 section 6 stipulates "case insensitive equality"
+ foreach my $list_id (@$listids) {
+ $self->{-by_list_id}->{lc($list_id)} = $ibx;
+ }
+ }
+ if (defined(my $ngname = $ibx->{newsgroup})) {
+ if (ref($ngname)) {
+ delete $ibx->{newsgroup};
+ warn 'multiple newsgroups not supported: '.
+ join(', ', @$ngname). "\n";
+ # Newsgroup name needs to be compatible with RFC 3977
+ # wildmat-exact and RFC 3501 (IMAP) ATOM-CHAR.
+ # Leave out a few chars likely to cause problems or conflicts:
+ # '|', '<', '>', ';', '#', '$', '&',
+ } elsif ($ngname =~ m![^A-Za-z0-9/_\.\-\~\@\+\=:]! ||
+ $ngname eq '') {
+ delete $ibx->{newsgroup};
+ warn "newsgroup name invalid: `$ngname'\n";
+ } else {
+ # PublicInbox::NNTPD does stricter ->nntp_usable
+ # checks, keep this lean for startup speed
+ $self->{-by_newsgroup}->{$ngname} = $ibx;
+ }
+ }
+ unless (defined $ibx->{newsgroup}) { # for ->eidx_key
+ my $abs = rel2abs_collapsed($dir);
+ if ($abs ne $dir) {
+ warn "W: `$dir' canonicalized to `$abs'\n";
+ $ibx->{inboxdir} = $abs;
+ }
+ }
+ $self->{-by_name}->{$name} = $ibx;
+ if ($ibx->{obfuscate}) {
+ $ibx->{-no_obfuscate} = $self->{-no_obfuscate};
+ $ibx->{-no_obfuscate_re} = $self->{-no_obfuscate_re};
+ fill_all($self); # noop to populate -no_obfuscate
+ }
+ if (my $es = ALL($self)) {
+ require PublicInbox::Isearch;
+ $ibx->{isrch} = PublicInbox::Isearch->new($ibx, $es);
+ }
+ $self->{-by_eidx_key}->{$ibx->eidx_key} = $ibx;
+}
+
+sub _fill_ei ($$) {
+ my ($self, $name) = @_;
+ eval { require PublicInbox::ExtSearch } or return;
+ my $pfx = "extindex.$name";
+ my $d = $self->{"$pfx.topdir"} // return;
+ -d $d or return;
+ my $es = PublicInbox::ExtSearch->new($d);
+ for my $k (qw(indexlevel indexsequentialshard)) {
+ my $v = _one_val($self, $pfx, $k) // next;
+ $es->{$k} = $v;