Skip to content

Commit

Permalink
add a extend for nextcloud (#554)
Browse files Browse the repository at this point in the history
* very early initial work

* checkpoint

* add last_seen handling
  • Loading branch information
VVelox authored Nov 18, 2024
1 parent ca187e1 commit 9be4b6c
Showing 1 changed file with 337 additions and 0 deletions.
337 changes: 337 additions & 0 deletions snmp/nextcloud
Original file line number Diff line number Diff line change
@@ -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<https://docs.librenms.org/Extensions/Applications/#nextcloud>.
=head1 SWITCHES
=head2 -i <dir>
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 <output dir>
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 <dir> 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" ... ' . $@ );
}

0 comments on commit 9be4b6c

Please sign in to comment.