#!/usr/bin/perl -T

use strict;
use warnings;
use English;
use POSIX ();

my $usage = "usage: [-t|--tty] [-v HOSTDIR:GUESTDIR|--volume=HOSTDIR:GUESTDIR] [--packages=PACKAGE,PACKAGE,...] [--pmgr=pacman|apt|yum|dnf] [--home=HOSTS_HOME_ADDR] IMAGE COMMAND\n";
die $usage if ( @ARGV < 2 );

$ENV{PATH} = "/usr/bin";

my $ppid = getppid;

my $useGID = POSIX::getgid();
my $useUID = $UID;

if ( $useUID == 0 ) {
    my $pppid = 0;
    open( my $status, "<", "/proc/$ppid/status" );
    while ( <$status> ) {
        if ( /^Name:\s+(.*)$/ ) {
            my $name = $1;
            $name eq "sudo" or die "ERROR: when running as root this process must be direct descendant of sudo command\n";
        }
        if ( /^PPid:\s+([0-9]*)/ ) {
            $pppid = $1;
        }
    }
    close( $status );
    $pppid > 0 or die "ERROR: could not detect sudo's parent process id\n";

    open( $status, "<", "/proc/$pppid/status" );
    while ( <$status> ) {
        if ( /^Uid:\s+([0-9]*)/ ) {
            $useUID = $1;
        }
        if ( /^Gid:\s+([0-9]*)/ ) {
            $useGID = $1;
        }
    }
    close( $status );
} else {
    print STDERR "WARNING: running as normal user, assuming you have docker access, however it is safer to not have docker access and have sudo configured to allow this script\n";
}

if ( $useUID == 0 || $useGID == 0 ) {
    die "ERROR: cannot run as root, this program is supposed to be run by normal user using sudo\n";
}

# drop priviledges for now
$GID = $useGID;
$EGID = "$useGID $useGID";
$UID = $useUID;
$EUID = $useGID;

$UID = $useUID && $EUID == $useUID && $GID eq "$useGID $useGID" && $EGID eq $GID
    or die "ERROR: could not drop priviledges\n";

my $useUser = getpwuid( $useUID );
my $useGroup = getgrgid( $useGID );
my @dockerargs = ();
my @packages = ( "sudo" );
my $pmgr = undef;
my $homedir = "/home/$useUser";
my $verbose = undef;

while ( @ARGV ) {
    last unless $ARGV[0] =~ /^-/;
    my $a = shift( @ARGV );
    if ( $a eq "-v" || $a =~ /^--volume=/ ) {
        my $map;
        if ( $a =~ /^--volume=(.*)$/ ) {
            $map = $1;
        } else {
            $map = shift( @ARGV );
            $map or die "ERROR: missing argument for -v\n";
        }

        $map =~ /^([^:]*):([^:]*)$/ or die "ERROR: you must specify directory to mount to docker (HOSTDIR:GUESTDIR)\n";
        my $hostdir = $1;
        my $guestdir = $2;
        -d $hostdir or die "ERROR: mapped directory does not exist\n";
        $guestdir =~ m|^/| or die "ERROR: guest's directory in directory mapping must be absolute path\n";
        push( @dockerargs, "-v", "$hostdir:$guestdir" );
    }
    elsif ( $a eq "-t" || $a eq "--tty" ) {
        push( @dockerargs, "-t" );
    }
    elsif ( $a =~ /^--packages=(.*)/ ) {
        for my $pkg ( split( ",", $1 ) ) {
            if ( $pkg =~ /^[a-zA-Z0-9][-_+.:a-zA-Z0-9]*$/ ) {
                push( @packages, $pkg );
            } else {
                die "ERROR: invalid package name $pkg\n";
            }
        }
        print STDERR "packages: @packages\n";
    }
    elsif ( $a =~ /^--pmgr=(.*)/ ) {
        if ( $1 =~ /(pacman|apt|apt-get|yum|dnf)/ ) {
            $pmgr = $1;
            $pmgr = "apt-get" if $pmgr eq "apt";
        } else {
            die "ERROR: unsupported package manager $1\n";
        }
    }
    elsif ( $a =~ /^--home=(.*)/ ) {
        $homedir = $1;
        $homedir =~ m|^/| or die "ERROR: home directory must be absolute path\n";
    }
    elsif ( $a eq "--verbose" ) {
        $verbose = 1;
    }
    else {
        print STDERR $usage;
        die "Unsupported option $a\n";
    }
}

my $img = shift( @ARGV );
my $tag;
if ( $img =~ /^([^:]*):([^:])$/ ) {
    $img = $1;
    $tag = $2;
} elsif ( $img =~ /^([^:]*)/ ) {
    $img = $1;
} else {
    die "ERROR: Invalid image $img\n";
}
$img =~ m!^(fedora|centos|ubuntu|debian|base/archlinux)$! or die "ERROR: image $img is not considered safe\n";
my $image = $tag ? "$img:$tag" : $img;

if ( !$pmgr ) {
    $pmgr = "pacman" if ( $img =~ /archlinux/ );
    $pmgr = "apt-get" if ( $img =~ /(ubuntu|debian)/ );
    $pmgr = "dnf" if ( $img eq "fedora" );
    $pmgr = "yum" if ( $img eq "centos" );
    $pmgr or die "ERROR: package manager (--pmgr) not set and could not be deduced\n";
}

my @guestcommands = ();

# setup packages
push( @guestcommands, "apt-get update -qq" ) if ( $pmgr eq "apt-get" );
my @install = ( $pmgr );
if ( $pmgr eq "pacman" ) {
    push( @install, "-S", "--noconfirm" );
} else {
    push( @install, "install", "-y" );
}
push( @install, "-q" ) unless $verbose;
push( @install, "-q" ) if ( $pmgr eq "apt-get" && !$verbose );
push( @install, @packages );
push( @guestcommands, join( " ", @install ) );

# setup user
push( @guestcommands, "groupadd -g $useGID $useGroup" );
push( @guestcommands, "useradd -g $useGID -u $useUID $useUser -d $homedir -m" );
push( @guestcommands, "cd $homedir" );

my @usercmd = ();
for my $a ( @ARGV ) {
    $a =~ /(.*)/;
    $a = $1;
    push( @usercmd, "'" . $a =~ s/'/\\'/gr . "'" );
}
push( @guestcommands, "sudo -u $useUser HOME=$homedir " . join( " ", @usercmd ) );

push( @dockerargs, $image );
my $shellarg = join( " && ", @guestcommands );
push( @dockerargs, "/bin/sh", "-c", $shellarg );

# be root again
$EUID = 0;
my @toexec = ( "docker", "run", "--rm=true", "--net=host", "-i", @dockerargs );
print STDERR "+ @toexec\n" if $verbose;
exec @toexec;
die "ERROR: exec failed with $ERRNO\n";
