diff --git a/snmp/nextcloud b/snmp/nextcloud new file mode 100644 index 000000000..e7d648737 --- /dev/null +++ b/snmp/nextcloud @@ -0,0 +1,337 @@ +#!/usr/bin/env perl + +=head1 NAME + +nextcloud - LibreNMS JSON SNMP extend for gathering backups for Nextcloud + +=head1 VERSION + +0.0.1 + +=head1 DESCRIPTION + +For more information, see L. + +=head1 SWITCHES + +=head2 -i + +Dir location for the Nextcloud install. + +The defaults are as below. + +FreeBSD: /usr/local/www/nextcloud +Linux: /var/www/nextcloud + +=head2 -m + +If set, does consider the user directories to not all be under the same mountpoint. + +=head2 -o + +Where to write the output to. + +Default: /var/cache/nextcloud_extend + +=head2 -q + +Don't print the JSON results when done. + +=head1 SETUP + +Create the required directory to write to. + + mkdir /var/cache/nextcloud_extend + chown -R $nextcloud_user /var/cache/nextcloud_extend + +snmpd.conf + + extend nextcloud /bin/cat /var/cache/nextcloud_extend/snmp + +cron, specify -o or -i if needed/desired + + */5 * * * * /etc/snmpd/nextcloud -q 2> /dev/null + +=head1 REQUIREMENTS + +Debian... + + apt-get install libjson-perl libfile-slurp-perl libmime-base64-perl cpanminus + cpanm Time::Piece + +FreeBSD... + + pkg install p5-JSON p5-File-Slurp p5-MIME-Base64 p5-Time-Piece + +Generic cpanm... + + cpanm JSON File::Slurp Mime::Base64 + +=cut + +#Copyright (c) 2024, Zane C. Bowers-Hadley +#All rights reserved. +# +#Redistribution and use in source and binary forms, with or without modification, +#are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +#IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +#INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +#DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +#LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +#OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +#THE POSSIBILITY OF SUCH DAMAGE. + +# Many thanks to Ben Rockwood, Jason J. Hellenthal, and Martin Matuska +# for zfs-stats and figuring out the math for all the stats +# +# Thanks to dlangille for pointing out the issues on 14 and Bobzikwick figuring out the fix in issues/501 + +use strict; +use warnings; +use JSON; +use Getopt::Long; +use File::Slurp; +use MIME::Base64; +use IO::Compress::Gzip qw(gzip $GzipError); +use Pod::Usage; +use String::ShellQuote; +use Time::Piece; + +sub main::VERSION_MESSAGE { + pod2usage( -exitval => 255, -verbose => 99, -sections => qw(VERSION), -output => \*STDOUT, ); +} + +sub main::HELP_MESSAGE { + pod2usage( -exitval => 255, -verbose => 2, -output => \*STDOUT, ); +} + +#this will be dumped to json at the end +my %tojson; +$tojson{total} = 0; +$tojson{user_count} = 0; +$tojson{free} = 0; +$tojson{used} = 0; +$tojson{enabled_apps} = 0; +$tojson{disabled_apps} = 0; +$tojson{encryption_enabled} = 0; +$tojson{calendars} = 0; +$tojson{multimount} = 0; +$tojson{users} = {}; + +# current user +my $current_user = $ENV{LOGNAME} || $ENV{USER} || getpwuid($<); + +#gets the options +my %opts; +my $be_quiet; +my $output_dir = '/var/cache/nextcloud_extend'; +my $install_dir; +my $version; +my $help; +my $multimount; +GetOptions( + q => \$be_quiet, + 'o=s' => \$output_dir, + 'i=s' => \$install_dir, + v => \$version, + version => \$version, + h => \$help, + help => \$help, + m => \$multimount, +); + +if ($version) { + pod2usage( -exitval => 255, -verbose => 99, -sections => qw(VERSION), -output => \*STDOUT, ); +} + +if ($help) { + pod2usage( -exitval => 255, -verbose => 2, -output => \*STDOUT, ); +} + +if ($multimount) { + $tojson{multimount} = 1; +} + +# get what to use for the install dir if not specified +if ( !defined($install_dir) ) { + if ( $^O eq 'freebsd' ) { + $install_dir = '/usr/local/www/nextcloud'; + } elsif ( $^O eq 'linux' ) { + $install_dir = '/var/www/nextcloud'; + } else { + die('-i not specified for the install dir for Nextcloud'); + } +} + +# ensure the install dir exists +if ( !-d $install_dir ) { + die( 'the Nextcloud install directory, "' . $install_dir . '", is not a directory or does not exist' ); +} + +# change to the install dir +chdir($install_dir) || die( 'failed to chdir to the Nextcloud install dir, "' . $install_dir . '",' ); + +# ensure the config exists +if ( !-f './config/config.php' ) { + die( '"./config/config.php" does not exist under the Nextcloud install dir ,"' . $install_dir . '",' ); +} + +# ensure ./occ happens +if ( !-f './occ' ) { + die( '"./occ" does not exist under the Nextcloud install dir ,"' . $install_dir . '",' ); +} + +# ensure the install dir exists and try to create it if it does not +if ( !-d $output_dir ) { + mkdir($output_dir) || die( '"' . $output_dir . '" does not exist and could not be created' ); +} + +### +### +### get user info +### +### +my $user_list_raw = `php occ user:list --output=json`; +if ( $? != 0 ) { + die( '"php occ user:list" existed non-zero with.... ' . "\n" . $user_list_raw . "\n..." ); +} +my @users; +eval { + my $decodes_users = decode_json($user_list_raw); + @users = keys( %{$decodes_users} ); +}; + +foreach my $user (@users) { + my $quoted_user = shell_quote($user); + my $user_info_raw = `php occ user:info --output=json $quoted_user`; + eval { + my $user_info = decode_json($user_info_raw); + if ( defined( $user_info->{user_id} ) + && defined( $user_info->{storage} ) + && ref( $user_info->{storage} ) eq 'HASH' + && defined( $user_info->{last_seen} ) ) + { + my $last_seen = $user_info->{last_seen}; + if ( $last_seen eq '1970-01-01T00:00:00+00:00' ) { + $last_seen = -1; + } else { + eval { + $last_seen =~ s/(\d+)\:(\d+)$/$1$2/; + my $t1 = gmtime; + my $t2 = Time::Piece->strptime( $last_seen, "%Y-%m-%dT%H:%M:%S%z" ); + $last_seen = $t1->epoch - $t2->epoch; + }; + if ($@) { + $last_seen = undef; + } + } ## end else [ if ( $last_seen eq '1970-01-01T00:00:00+00:00')] + $tojson{users}{$user} = { + 'free' => $user_info->{storage}{free}, + 'quota' => $user_info->{storage}{quota}, + 'relative' => $user_info->{storage}{relative}, + 'total' => $user_info->{storage}{total}, + 'used' => $user_info->{storage}{used}, + 'last_seen' => $last_seen, + 'calendars' => 0, + }; + $tojson{free} = $user_info->{storage}{free}; + $tojson{used} = $tojson{used} + $user_info->{storage}{used}; + if ( $user_info->{storage}{quota} > 0 ) { + $tojson{quota} = $tojson{quota} + $user_info->{storage}{quota}; + } + $tojson{user_count}++; + # does not currently support output options + my $calendar_info_raw = `php occ dav:list-calendars $quoted_user 2> /dev/null`; + if ( $? == 0 ) { + # if the table has more than 4 lines the other lines contain calender info + # so given it is zero index the number of calendars can be fournd via subtracting 3 + my @calendar_info_split = split( /\n/, $calendar_info_raw ); + if ( $#calendar_info_split > 3 ) { + $tojson{users}{$user}{'calendars'} = $#calendar_info_split - 3; + $tojson{calendars} = $tojson{'calendars'} + $tojson{users}{$user}{'calendars'}; + } + } + } ## end if ( defined( $user_info->{user_id} ) && defined...) + }; +} ## end foreach my $user (@users) + +### +### +### get app info +### +### +my $app_info_raw = `php occ app:list --output=json`; +if ( $? == 0 ) { + eval { + my $app_info = decode_json($app_info_raw); + if ( defined( $app_info->{disabled} ) + && ref( $app_info->{disabled} ) eq 'HASH' ) + { + my @disabled_apps = keys( %{ $app_info->{disabled} } ); + $tojson{disabled_apps} = $#disabled_apps + 1; + } + if ( defined( $app_info->{enabled} ) + && ref( $app_info->{enabled} ) eq 'HASH' ) + { + my @disabled_apps = keys( %{ $app_info->{enabled} } ); + $tojson{enabled_apps} = $#disabled_apps + 1; + } + }; +} ## end if ( $? == 0 ) + +### +### +### get encryption status +### +### +my $encrption_info_raw = `php occ encryption:status --output=json`; +if ( $? == 0 ) { + eval { + my $encrption_info = decode_json($encrption_info_raw); + if ( defined($encrption_info) + && ref( $encrption_info->{enabled} ) eq '' + && $encrption_info->{enabled} =~ /^(1|[Tt][Rr][Uu][Ee])$/ ) + { + $tojson{encryption_enabled} = 1; + } + }; +} ## end if ( $? == 0 ) + +my %head_hash; +$head_hash{data} = \%tojson; +$head_hash{version} = 1; +$head_hash{error} = 0; +$head_hash{errorString} = ''; + +my $json_output = encode_json( \%head_hash ); + +if ( !$be_quiet ) { + print $json_output. "\n"; +} + +eval { write_file( $output_dir . '/json', $json_output ); }; +if ($@) { + warn( 'failed to write out "' . $output_dir . '/json" ... ' . $@ ); +} + +my $toReturnCompressed; +gzip \$json_output => \$toReturnCompressed; +my $compressed = encode_base64($toReturnCompressed); +$compressed =~ s/\n//g; +$compressed = $compressed . "\n"; + +eval { write_file( $output_dir . '/snmp', $compressed ); }; +if ($@) { + warn( 'failed to write out "' . $output_dir . '/snmp" ... ' . $@ ); +}