Table of Contents

CentOS / RHEL7 / Fedora + Nginx + Backuppc

This combination gave me no documentation online, Recently Backuppc is available as SCGI Script version.
I did not had luck setting that up, as nginx lack documentation for scgi.

So let's get started, If you are here you might already know there is no packaged version of Backuppc 4.x available. I've decided to take shortcut and repackaged latest version available from fedora 27.

Once you install the package you should be good to go, configuration and all needed files are in place.

Installation

| Needed Packages
yum install -y BackupPC-4.1.5-1.el7.x86_64.rpm BackupPC-XS-0.57-1.el7.x86_64.rpm rsync-bpc-3.0.9.11-1.el7.x86_64.rpm nginx

<color red> Optional : CPAN Module only needed if you are planning to use scgi version. </color>

| Needed CPAN Module
cpan install SCGI

Configuration

In configuration i've only changed few of the options to make it work under browser.

/etc/BackupPC/config.pl
--- config.pl.sample    2018-02-14 13:48:27.000000000 -0500
+++ config.pl   2018-02-14 17:52:05.123281171 -0500
@@ -41,7 +41,7 @@
 #
 # Host name on which the BackupPC server is running.
 #
-$Conf{ServerHost} = 'localhost';
+$Conf{ServerHost} = 'backup.k2patel.in';
 
 #
 # TCP port number on which the BackupPC server listens for and accepts
@@ -257,7 +281,7 @@
 # Full path to the df command.  Security caution: normal users
 # should not allowed to write to this file or directory.
 #
-$Conf{DfPath} = '/bin/df';
+$Conf{DfPath} = '/usr/bin/df';
 
 #
 # Command to run df.  The following variables are substituted at run-time:
@@ -274,15 +298,15 @@
 #
 # Full path to various commands for archiving
 #
-$Conf{SplitPath} = '/bin/split';
-$Conf{ParPath}   = "/usr/bin/par2";
+$Conf{SplitPath} = '/usr/bin/split';
+$Conf{ParPath} = '/usr/bin/par2';
 
-$Conf{CatPath}   = '/bin/cat';
-$Conf{GzipPath}  = '/bin/gzip';
-$Conf{Bzip2Path} = '/bin/bzip2';
+$Conf{CatPath} = '/usr/bin/cat';
+$Conf{GzipPath} = '/usr/bin/gzip';
+$Conf{Bzip2Path} = '/usr/bin/bzip2';
 
 #
-# Maximum threshold for disk utilization on the /var/lib/BackupPC/ filesystem.
+# Maximum threshold for disk utilization on the __TOPDIR__ filesystem.
 # If the output from $Conf{DfPath} reports a percentage larger than
 # this number then no new regularly scheduled backups will be run.
 # However, user requested backups (which are usually incremental and
@@ -349,12 +373,12 @@
 # a symbolic link to the new location, or mount the new BackupPC
 # store at the existing $Conf{TopDir} setting.
 #
-$Conf{TopDir}      = '/var/lib/BackupPC/';
-$Conf{ConfDir}     = '/etc/BackupPC/';
-$Conf{LogDir}      = '/var/log/BackupPC';
-$Conf{RunDir}      = '/var/run/BackupPC';
-$Conf{InstallDir}  = '/usr/share/BackupPC';
-$Conf{CgiDir}      = '/usr/libexec/BackupPC';
+$Conf{TopDir} = '/var/lib/libvirt/images';
+$Conf{ConfDir} = '/etc/BackupPC/';
+$Conf{LogDir} = '/var/log/BackupPC';
+$Conf{RunDir} = '/var/run/BackupPC';
+$Conf{InstallDir} = '/usr/share/BackupPC';
+$Conf{CgiDir} = '/usr/libexec/BackupPC';
@@ -1984,8 +2001,8 @@
 #    $Conf{CgiAdminUsers}     = 'craig celia';
 #    --> administrative users are only craig and celia'.
 #
-$Conf{CgiAdminUserGroup} = '';
-$Conf{CgiAdminUsers}     = '';
+$Conf{CgiAdminUserGroup} = 'admin';
+$Conf{CgiAdminUsers} = 'admin';
 
 #
 # TCP port number of the SCGI server.  A negative value disables the
@@ -2016,7 +2033,7 @@
 # Full URL of the BackupPC_Admin CGI script, or the configured path
 # for SCGI.  Used for links in email messages.
 #
-$Conf{CgiURL} = "http://localhost/BackupPC";
+$Conf{CgiURL} = 'https://backup.k2patel.in/cgi-bin/BackupPC_Admin';
 
 #
 # Full path to the rrdtool command.  If available, graphs of pool usage
@@ -2151,7 +2171,7 @@
 # Example:
 #     $Conf{CgiImageDirURL} = '/BackupPC';
 #
-$Conf{CgiImageDirURL} = '/BackupPC/images';
+$Conf{CgiImageDirURL} = '/BackupPC';
 
 #
 # CSS stylesheet "skin" for the CGI interface.  It is stored

Nginx Configuration

| /etc/nginx/nginx.conf
worker_processes  1;
 
events {
    worker_connections  1024;
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on; 
 
    keepalive_timeout  65;
 
    gzip  on;
 
    server {
        listen       80;
        server_name  backup.k2patel.in;
 
        return 302 https://backup.k2patel.in$request_uri;
 
    }
 
    server {
        listen       443 ssl http2;
        server_name  backup.k2patel.in;
 
        ssl on;
        ssl_certificate      /etc/pki/tls/certs/k2patel.in.crt;
        ssl_certificate_key  /etc/pki/tls/private/k2patel.in.key;
        ssl_trusted_certificate /etc/pki/tls/certs/k2patel.in.int.ca;
 
        # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
        add_header Strict-Transport-Security max-age=63072000;
 
        # OCSP Stapling ---
        # fetch OCSP records from URL in ssl_certificate and cache them
        ssl_stapling on;
        ssl_stapling_verify off;
        # modern configuration. tweak to your needs.
        ssl_protocols TLSv1.2;
        ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
        ssl_prefer_server_ciphers on;
 
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_session_tickets off;
 
        add_header Strict-Transport-Security max-age=63072000;
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
 
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }
 
        access_log  /var/log/nginx/backuppc.access.log;
        error_log   /var/log/nginx/backuppc.error.log;
 
        location / {
          auth_basic "Backup";
          auth_basic_user_file /usr/local/etc/nginx/backuppc.users;
          root /usr/share/BackupPC/html;
          return 302 http://backup.k2patel.in/cgi-bin/BackupPC_Admin;
          index BackupPC.html;
        }
 
        location /BackupPC {
           alias /usr/share/BackupPC/html;
           index BackupPC.html;
        }
 
        #location ~\.cgi$ {
        location ~ ^/cgi-bin/BackupPC_Admin(/|$) {
          auth_basic "Backup";
          auth_basic_user_file /etc/nginx/backuppc.users;
          gzip off;
          include /etc/nginx/fastcgi_params;
          fastcgi_pass localhost:8999;
 
          fastcgi_param REMOTE_ADDR     $remote_addr;
          fastcgi_param REMOTE_USER     $remote_user;
          fastcgi_param SCRIPT_FILENAME /usr/share/BackupPC/sbin/BackupPC_Admin;
        }
 
        location ~ /\.ht {
            deny  all;  
        }
    }
}

Required Third party scripts

| /usr/local/sbin/fastcgi-wrapper | http://nginxlibrary.com/downloads/perl-fcgi/fastcgi-wrapper
#!/usr/bin/perl
 
use FCGI;
use Socket;
use POSIX qw(setsid);
 
require 'syscall.ph';
 
&daemonize;
 
#this keeps the program alive or something after exec'ing perl scripts
END() { } BEGIN() { }
*CORE::GLOBAL::exit = sub { die "fakeexit\nrc=".shift()."\n"; }; 
eval q{exit}; 
if ($@) { 
	exit unless $@ =~ /^fakeexit/; 
};
 
&main;
 
sub daemonize() {
    chdir '/'                 or die "Can't chdir to /: $!";
    defined(my $pid = fork)   or die "Can't fork: $!";
    exit if $pid;
    setsid                    or die "Can't start a new session: $!";
    umask 0;
}
 
sub main {
        $socket = FCGI::OpenSocket( "127.0.0.1:8999", 10 ); #use IP sockets
        $request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%req_params, $socket );
        if ($request) { request_loop()};
            FCGI::CloseSocket( $socket );
}
 
sub request_loop {
        while( $request->Accept() >= 0 ) {
 
           #processing any STDIN input from WebServer (for CGI-POST actions)
           $stdin_passthrough ='';
           $req_len = 0 + $req_params{'CONTENT_LENGTH'};
           if (($req_params{'REQUEST_METHOD'} eq 'POST') && ($req_len != 0) ){ 
                my $bytes_read = 0;
                while ($bytes_read < $req_len) {
                        my $data = '';
                        my $bytes = read(STDIN, $data, ($req_len - $bytes_read));
                        last if ($bytes == 0 || !defined($bytes));
                        $stdin_passthrough .= $data;
                        $bytes_read += $bytes;
                }
            }
 
            #running the cgi app
            if ( (-x $req_params{SCRIPT_FILENAME}) &&  #can I execute this?
                 (-s $req_params{SCRIPT_FILENAME}) &&  #Is this file empty?
                 (-r $req_params{SCRIPT_FILENAME})     #can I read this file?
            ){
		pipe(CHILD_RD, PARENT_WR);
		my $pid = open(KID_TO_READ, "-|");
		unless(defined($pid)) {
			print("Content-type: text/plain\r\n\r\n");
                        print "Error: CGI app returned no output - ";
                        print "Executing $req_params{SCRIPT_FILENAME} failed !\n";
			next;
		}
		if ($pid > 0) {
			close(CHILD_RD);
			print PARENT_WR $stdin_passthrough;
			close(PARENT_WR);
 
			while(my $s = <KID_TO_READ>) { print $s; }
			close KID_TO_READ;
			waitpid($pid, 0);
		} else {
	                foreach $key ( keys %req_params){
        	           $ENV{$key} = $req_params{$key};
                	}
        	        # cd to the script's local directory
	                if ($req_params{SCRIPT_FILENAME} =~ /^(.*)\/[^\/]+$/) {
                        	chdir $1;
                	}
 
			close(PARENT_WR);
			close(STDIN);
			#fcntl(CHILD_RD, F_DUPFD, 0);
			syscall(&SYS_dup2, fileno(CHILD_RD), 0);
			#open(STDIN, "<&CHILD_RD");
			exec($req_params{SCRIPT_FILENAME});
			die("exec failed");
		}
            } 
            else {
                print("Content-type: text/plain\r\n\r\n");
                print "Error: No such CGI app - $req_params{SCRIPT_FILENAME} may not ";
                print "exist or is not executable by this process.\n";
            }
 
        }
}

Systemd Script

| /etc/systemd/system/fcgiwrapper.service
[Unit]
Description=Fast CGI wrapper
After=network.target
 
[Service]
Type=forking
TimeoutStopSec=0
User=backuppc
Group=backuppc
ExecStart=/usr/local/sbin/fastcgi-wrapper
 
[Install]
WantedBy=multi-user.target

Final Note

Once everything is configured you should be able to access your backuppc using user admin.