summaryrefslogtreecommitdiff
path: root/frontends
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-07-07 13:41:50 +0300
committerPaul Buetow <paul@buetow.org>2025-07-07 13:41:50 +0300
commita42df04ee64563cfa65fa04465375e2ef848a583 (patch)
tree5cb536a6653616909907979370377df92a5af159 /frontends
parent95042c100f8a2d23b2ee39e2fa7b682abdef759f (diff)
add stats.foo.zone
Diffstat (limited to 'frontends')
-rw-r--r--frontends/Rexfile14
-rw-r--r--frontends/etc/httpd.conf.tpl2
-rw-r--r--frontends/etc/relayd.conf.tpl2
-rw-r--r--frontends/scripts/foostats.pl173
-rw-r--r--frontends/var/nsd/zones/master/foo.zone.zone.tpl13
5 files changed, 132 insertions, 72 deletions
diff --git a/frontends/Rexfile b/frontends/Rexfile
index fa407e5..995aebc 100644
--- a/frontends/Rexfile
+++ b/frontends/Rexfile
@@ -75,7 +75,7 @@ our @dns_zones = qw/buetow.org dtail.dev foo.zone irregular.ninja snonux.
our @dns_zones_remove = qw//;
our @f3s_hosts = qw/f3s.buetow.org/; # k3s cluster running on FreeBSD in my LAN
our @acme_hosts =
- qw/buetow.org git.buetow.org paul.buetow.org dory.buetow.org solarcat.buetow.org blog.buetow.org fotos.buetow.org znc.buetow.org dtail.dev foo.zone irregular.ninja alt.irregular.ninja snonux.foo/;
+ qw/buetow.org git.buetow.org paul.buetow.org dory.buetow.org solarcat.buetow.org blog.buetow.org fotos.buetow.org znc.buetow.org dtail.dev foo.zone stats.foo.zone irregular.ninja alt.irregular.ninja snonux.foo/;
push @acme_hosts, @f3s_hosts;
# UTILITY TASKS
@@ -557,6 +557,18 @@ task 'foostats',
group => 'wheel',
mode => '440';
+ file '/var/www/htdocs/gemtexter/stats.foo.zone',
+ ensure => 'directory',
+ owner => 'root',
+ group => 'wheel',
+ mode => '755';
+
+ file '/var/gemini/stats.foo.zone',
+ ensure => 'directory',
+ owner => 'root',
+ group => 'wheel',
+ mode => '755';
+
append_if_no_such_line '/etc/daily.local', 'perl /usr/local/bin/foostats.pl --parse-logs --replicate --report';
my @deps = qw(p5-Digest-SHA3 p5-PerlIO-gzip p5-JSON p5-String-Util p5-LWP-Protocol-https);
diff --git a/frontends/etc/httpd.conf.tpl b/frontends/etc/httpd.conf.tpl
index 89ca931..527f7c6 100644
--- a/frontends/etc/httpd.conf.tpl
+++ b/frontends/etc/httpd.conf.tpl
@@ -37,7 +37,7 @@ server "<%= "$hostname.$domain" %>" {
}
# Gemtexter hosts
-<% for my $host (qw/foo.zone/) { for my $prefix (@prefixes) { -%>
+<% for my $host (qw/foo.zone stats.foo.zone/) { for my $prefix (@prefixes) { -%>
server "<%= $prefix.$host %>" {
listen on * port 8080
log style forwarded
diff --git a/frontends/etc/relayd.conf.tpl b/frontends/etc/relayd.conf.tpl
index 4ad2d5d..b860309 100644
--- a/frontends/etc/relayd.conf.tpl
+++ b/frontends/etc/relayd.conf.tpl
@@ -42,9 +42,11 @@ relay "https6" {
tcp protocol "gemini" {
tls keypair foo.zone
+ tls keypair stats.foo.zone
tls keypair snonux.foo
tls keypair paul.buetow.org
tls keypair standby.foo.zone
+ tls keypair standby.stats.foo.zone
tls keypair standby.snonux.foo
tls keypair standby.paul.buetow.org
}
diff --git a/frontends/scripts/foostats.pl b/frontends/scripts/foostats.pl
index 5d3b5d4..2b7ca95 100644
--- a/frontends/scripts/foostats.pl
+++ b/frontends/scripts/foostats.pl
@@ -187,7 +187,8 @@ package Foostats::Logreader {
sub parse_gemini_logs ( $last_processed_date, $cb ) {
my sub parse_date ( $year, @line ) {
my $timestr = "$line[0] $line[1]";
- return Time::Piece->strptime( $timestr, '%b %d' )->strftime("$year%m%d");
+ return Time::Piece->strptime( $timestr, '%b %d' )
+ ->strftime("$year%m%d");
}
my sub parse_vger_line ( $year, @line ) {
@@ -222,8 +223,8 @@ package Foostats::Logreader {
};
}
- # Expect one vger and one relayd log line per event! So collect
- # both events (one from one log line each) and then merge the result hash!
+ # Expect one vger and one relayd log line per event! So collect
+ # both events (one from one log line each) and then merge the result hash!
my ( $vger, $relayd );
read_lines GEMINI_LOGS_GLOB, sub ( $year, @line ) {
if ( $line[4] eq 'vger:' ) {
@@ -249,7 +250,8 @@ package Foostats::Logreader {
};
}
- sub parse_logs ( $last_web_date, $last_gemini_date, $odds_file, $odds_log ) {
+ sub parse_logs ( $last_web_date, $last_gemini_date, $odds_file, $odds_log )
+ {
my $agg = Foostats::Aggregator->new( $odds_file, $odds_log );
say "Last web date: $last_web_date";
@@ -304,7 +306,8 @@ package Foostats::Filter {
next
unless contains( $uri_path, $_ );
- $self->log( 'WARN', $uri_path, "contains $_ and is odd and will therefore be blocked!" );
+ $self->log( 'WARN', $uri_path,
+ "contains $_ and is odd and will therefore be blocked!" );
return true;
}
@@ -341,7 +344,8 @@ package Foostats::Filter {
# IP requested site more than once within the same second!?
if ( 1 < ++( $count{$ip_hash} //= 0 ) ) {
- $self->log( 'WARN', $ip_hash, "blocked due to excessive requesting..." );
+ $self->log( 'WARN', $ip_hash,
+ "blocked due to excessive requesting..." );
return true;
}
@@ -455,8 +459,9 @@ package Foostats::FileOutputter {
}
sub last_processed_date ( $self, $proto ) {
- my $hostname = hostname();
- my @processed = glob $self->{stats_dir} . "/${proto}_????????.$hostname.json.gz";
+ my $hostname = hostname();
+ my @processed =
+ glob $self->{stats_dir} . "/${proto}_????????.$hostname.json.gz";
my ($date) =
@processed
? ( $processed[-1] =~ /_(\d{8})\.$hostname\.json.gz/ )
@@ -469,7 +474,8 @@ package Foostats::FileOutputter {
$self->for_dates(
sub ( $self, $date_key, $stats ) {
my $hostname = hostname();
- my $path = $self->{stats_dir} . "/${date_key}.$hostname.json.gz";
+ my $path =
+ $self->{stats_dir} . "/${date_key}.$hostname.json.gz";
FileHelper::write_json_gz
$path,
$stats;
@@ -537,7 +543,8 @@ package Foostats::Merger {
sub merge ($stats_dir) {
my %merge;
- $merge{$_} = merge_for_date( $stats_dir, $_ ) for DateHelper::last_month_dates;
+ $merge{$_} = merge_for_date( $stats_dir, $_ )
+ for DateHelper::last_month_dates;
return %merge;
}
@@ -583,7 +590,7 @@ package Foostats::Merger {
}
else {
die
- "Not merging tkey '%s' (ref:%s): '%s' (ref:%s) with '%s' (ref:%s)\n",
+"Not merging tkey '%s' (ref:%s): '%s' (ref:%s) with '%s' (ref:%s)\n",
$key,
ref($key), $a->{$key},
ref( $a->{$key} ),
@@ -653,7 +660,8 @@ package Foostats::Merger {
) for @stats;
# Keep only uniq IP count
- $merge{$key}->{$_} = scalar keys $merge{$key}->{$_}->%* for keys $merge{$key}->%*;
+ $merge{$key}->{$_} = scalar keys $merge{$key}->{$_}->%*
+ for keys $merge{$key}->%*;
}
return \%merge;
@@ -682,44 +690,44 @@ package Foostats::Reporter {
use Time::Piece;
sub truncate_url {
- my ($url, $max_length) = @_;
- $max_length //= 100; # Default to 100 characters
-
+ my ( $url, $max_length ) = @_;
+ $max_length //= 100; # Default to 100 characters
+
return $url if length($url) <= $max_length;
-
+
# Calculate how many characters we need to remove
- my $ellipsis = '...';
- my $ellipsis_length = length($ellipsis);
+ my $ellipsis = '...';
+ my $ellipsis_length = length($ellipsis);
my $available_length = $max_length - $ellipsis_length;
-
+
# Split available length between start and end, favoring the end
- my $keep_start = int($available_length * 0.4); # 40% for start
- my $keep_end = $available_length - $keep_start; # 60% for end
-
- my $start = substr($url, 0, $keep_start);
- my $end = substr($url, -$keep_end);
-
+ my $keep_start = int( $available_length * 0.4 ); # 40% for start
+ my $keep_end = $available_length - $keep_start; # 60% for end
+
+ my $start = substr( $url, 0, $keep_start );
+ my $end = substr( $url, -$keep_end );
+
return $start . $ellipsis . $end;
}
sub truncate_urls_for_table {
- my ($url_rows, $count_column_header) = @_;
-
+ my ( $url_rows, $count_column_header ) = @_;
+
# Calculate the maximum width needed for the count column
my $max_count_width = length($count_column_header);
for my $row (@$url_rows) {
- my $count_width = length($row->[1]);
+ my $count_width = length( $row->[1] );
$max_count_width = $count_width if $count_width > $max_count_width;
}
-
+
# Row format: "| URL... | count |" with padding
# Calculate: "| " (2) + URL + " | " (3) + count_with_padding + " |" (2)
my $max_url_length = 100 - 7 - $max_count_width;
- $max_url_length = 70 if $max_url_length > 70; # Cap at reasonable length
-
+ $max_url_length = 70 if $max_url_length > 70; # Cap at reasonable length
+
# Truncate URLs in place
for my $row (@$url_rows) {
- $row->[0] = truncate_url($row->[0], $max_url_length);
+ $row->[0] = truncate_url( $row->[0], $max_url_length );
}
}
@@ -745,7 +753,7 @@ package Foostats::Reporter {
}
my @table_lines;
- push @table_lines, $separator_line; # Add top terminator
+ push @table_lines, $separator_line; # Add top terminator
push @table_lines, $header_line;
push @table_lines, $separator_line;
@@ -756,8 +764,8 @@ package Foostats::Reporter {
}
push @table_lines, $row_line;
}
-
- push @table_lines, $separator_line; # Add bottom terminator
+
+ push @table_lines, $separator_line; # Add bottom terminator
return join( "
", @table_lines );
@@ -774,25 +782,38 @@ package Foostats::Reporter {
# Check if .gmi file exists and its age based on date in filename
my $gemtext_dir = "$stats_dir/gemtext";
my $report_path = "$gemtext_dir/$date.gmi";
-
+
# Calculate age of the data based on date in filename
- my $today = Time::Piece->new();
- my $file_date = Time::Piece->strptime($date, '%Y%m%d');
- my $age_days = ($today - $file_date) / (24 * 60 * 60);
-
- if (-e $report_path) {
+ my $today = Time::Piece->new();
+ my $file_date = Time::Piece->strptime( $date, '%Y%m%d' );
+ my $age_days = ( $today - $file_date ) / ( 24 * 60 * 60 );
+
+ if ( -e $report_path ) {
+
# File exists
- if ($age_days <= 3) {
+ if ( $age_days <= 3 ) {
+
# Data is recent (within 3 days), regenerate it
- say "Regenerating daily report for $year-$month-$day (data age: " . sprintf("%.1f", $age_days) . " days)";
- } else {
+ say
+"Regenerating daily report for $year-$month-$day (data age: "
+ . sprintf( "%.1f", $age_days )
+ . " days)";
+ }
+ else {
# Data is old (older than 3 days), skip if file exists
- say "Skipping daily report for $year-$month-$day (file exists, data age: " . sprintf("%.1f", $age_days) . " days)";
+ say
+"Skipping daily report for $year-$month-$day (file exists, data age: "
+ . sprintf( "%.1f", $age_days )
+ . " days)";
next;
}
- } else {
+ }
+ else {
# File doesn't exist, generate it
- say "Generating new daily report for $year-$month-$day (file doesn't exist, data age: " . sprintf("%.1f", $age_days) . " days)";
+ say
+"Generating new daily report for $year-$month-$day (file doesn't exist, data age: "
+ . sprintf( "%.1f", $age_days )
+ . " days)";
}
my $report_content = "";
@@ -831,14 +852,19 @@ package Foostats::Reporter {
";
my @feed_rows;
- push @feed_rows, [ 'Total', $stats->{feed_ips}{'Total'} // 0 ];
- push @feed_rows, [ 'Gemini Gemfeed', $stats->{feed_ips}{'Gemini Gemfeed'} // 0 ];
- push @feed_rows, [ 'Gemini Atom', $stats->{feed_ips}{'Gemini Atom'} // 0 ];
- push @feed_rows, [ 'Web Gemfeed', $stats->{feed_ips}{'Web Gemfeed'} // 0 ];
- push @feed_rows, [ 'Web Atom', $stats->{feed_ips}{'Web Atom'} // 0 ];
+ push @feed_rows, [ 'Total', $stats->{feed_ips}{'Total'} // 0 ];
+ push @feed_rows,
+ [ 'Gemini Gemfeed', $stats->{feed_ips}{'Gemini Gemfeed'} // 0 ];
+ push @feed_rows,
+ [ 'Gemini Atom', $stats->{feed_ips}{'Gemini Atom'} // 0 ];
+ push @feed_rows,
+ [ 'Web Gemfeed', $stats->{feed_ips}{'Web Gemfeed'} // 0 ];
+ push @feed_rows,
+ [ 'Web Atom', $stats->{feed_ips}{'Web Atom'} // 0 ];
$report_content .= "```
";
- $report_content .= format_table( [ 'Feed Type', 'Count' ], \@feed_rows );
+ $report_content .=
+ format_table( [ 'Feed Type', 'Count' ], \@feed_rows );
$report_content .= "
```
@@ -862,7 +888,8 @@ package Foostats::Reporter {
}
$report_content .= "```
";
- $report_content .= format_table( [ 'Host', 'Unique Visitors' ], \@host_rows );
+ $report_content .=
+ format_table( [ 'Host', 'Unique Visitors' ], \@host_rows );
$report_content .= "
```
";
@@ -889,12 +916,13 @@ package Foostats::Reporter {
for my $url (@sorted_urls) {
push @url_rows, [ $url, $urls->{$url} // 0 ];
}
-
+
# Truncate URLs to fit within 100-character rows
- truncate_urls_for_table(\@url_rows, 'Unique Visitors');
+ truncate_urls_for_table( \@url_rows, 'Unique Visitors' );
$report_content .= "```
";
- $report_content .= format_table( [ 'URL', 'Unique Visitors' ], \@url_rows );
+ $report_content .=
+ format_table( [ 'URL', 'Unique Visitors' ], \@url_rows );
$report_content .= "
```
";
@@ -910,7 +938,8 @@ package Foostats::Reporter {
$report_content .= "## Related Reports\n\n";
my $today = localtime;
my $current_month = $today->strftime('%Y%m%d');
- $report_content .= "=> ./30day_summary_$current_month.gmi 30-Day Summary Report\n\n";
+ $report_content .=
+ "=> ./30day_summary_$current_month.gmi 30-Day Summary Report\n\n";
# Ensure gemtext directory exists
mkdir $gemtext_dir unless -d $gemtext_dir;
@@ -940,7 +969,8 @@ package Foostats::Reporter {
$report_content .= build_feed_statistics_section( \@dates, \%merged );
# Aggregate and add top lists
- my ( $all_hosts, $all_urls ) = aggregate_hosts_and_urls( \@dates, \%merged );
+ my ( $all_hosts, $all_urls ) =
+ aggregate_hosts_and_urls( \@dates, \%merged );
$report_content .= build_top_hosts_section($all_hosts);
$report_content .= build_top_urls_section($all_urls);
@@ -978,7 +1008,9 @@ package Foostats::Reporter {
push @summary_rows, build_daily_summary_row( $date, $stats );
}
- $content .= format_table( [ 'Date', 'Total', 'Filtered', 'Gemini', 'Web', 'IPv4', 'IPv6' ], \@summary_rows );
+ $content .= format_table(
+ [ 'Date', 'Total', 'Filtered', 'Gemini', 'Web', 'IPv4', 'IPv6' ],
+ \@summary_rows );
$content .= "\n```\n\n";
return $content;
@@ -998,7 +1030,11 @@ package Foostats::Reporter {
my $ipv4 = $stats->{count}{IPv4} // 0;
my $ipv6 = $stats->{count}{IPv6} // 0;
- return [ $formatted_date, $total_requests, $filtered, $gemini, $web, $ipv4, $ipv6 ];
+ return [
+ $formatted_date, $total_requests, $filtered,
+ $gemini, $web, $ipv4,
+ $ipv6
+ ];
}
sub build_feed_statistics_section {
@@ -1014,7 +1050,10 @@ package Foostats::Reporter {
push @feed_rows, build_feed_statistics_row( $date, $stats );
}
- $content .= format_table( [ 'Date', 'Total', 'Gem Feed', 'Gem Atom', 'Web Feed', 'Web Atom' ], \@feed_rows );
+ $content .= format_table(
+ [ 'Date', 'Total', 'Gem Feed', 'Gem Atom', 'Web Feed', 'Web Atom' ],
+ \@feed_rows
+ );
$content .= "\n```\n\n";
return $content;
@@ -1047,7 +1086,8 @@ package Foostats::Reporter {
next unless $stats->{page_ips};
# Aggregate hosts
- while ( my ( $host, $count ) = each %{ $stats->{page_ips}{hosts} } ) {
+ while ( my ( $host, $count ) = each %{ $stats->{page_ips}{hosts} } )
+ {
$all_hosts{$host} //= 0;
$all_hosts{$host} += $count;
}
@@ -1095,9 +1135,9 @@ package Foostats::Reporter {
for my $url (@sorted_urls) {
push @url_rows, [ $url, $all_urls->{$url} ];
}
-
+
# Truncate URLs to fit within 100-character rows
- truncate_urls_for_table(\@url_rows, 'Visitors');
+ truncate_urls_for_table( \@url_rows, 'Visitors' );
$content .= format_table( [ 'URL', 'Visitors' ], \@url_rows );
$content .= "\n```\n\n";
@@ -1193,7 +1233,8 @@ package main {
if $replicate
or $all;
- Foostats::Reporter::report( $stats_dir, Foostats::Merger::merge($stats_dir) )
+ Foostats::Reporter::report( $stats_dir,
+ Foostats::Merger::merge($stats_dir) )
if $report
or $all;
}
diff --git a/frontends/var/nsd/zones/master/foo.zone.zone.tpl b/frontends/var/nsd/zones/master/foo.zone.zone.tpl
index a0ce3a8..4f2cc0f 100644
--- a/frontends/var/nsd/zones/master/foo.zone.zone.tpl
+++ b/frontends/var/nsd/zones/master/foo.zone.zone.tpl
@@ -19,9 +19,14 @@ www 300 IN AAAA <%= $ips->{current_master}{ipv6} %> ; Enable failover
standby 300 IN A <%= $ips->{current_standby}{ipv4} %> ; Enable failover
standby 300 IN AAAA <%= $ips->{current_standby}{ipv6} %> ; Enable failover
-f3s 300 IN A <%= $ips->{current_master}{ipv4} %> ; Enable failover
-f3s 300 IN AAAA <%= $ips->{current_master}{ipv6} %> ; Enable failover
-www.f3s 300 IN A <%= $ips->{current_master}{ipv4} %> ; Enable failover
-www.f3s 300 IN AAAA <%= $ips->{current_master}{ipv6} %> ; Enable failover
+f3s 300 IN A <%= $ips->{current_master}{ipv4} %> ; Enable failover
+f3s 300 IN AAAA <%= $ips->{current_master}{ipv6} %> ; Enable failover
+www.f3s 300 IN A <%= $ips->{current_master}{ipv4} %> ; Enable failover
+www.f3s 300 IN AAAA <%= $ips->{current_master}{ipv6} %> ; Enable failover
standby.f3s 300 IN A <%= $ips->{current_standby}{ipv4} %> ; Enable failover
standby.f3s 300 IN AAAA <%= $ips->{current_standby}{ipv6} %> ; Enable failover
+
+stats 300 IN A <%= $ips->{current_master}{ipv4} %> ; Enable failover
+stats 300 IN AAAA <%= $ips->{current_master}{ipv6} %> ; Enable failover
+standby.stats 300 IN A <%= $ips->{current_master}{ipv4} %> ; Enable failover
+standby.stats 300 IN AAAA <%= $ips->{current_master}{ipv6} %> ; Enable failover