#!/bin/sh

all=""
env=""
port=""
force=

log()
{
    logger -I $PPID -t container -p local1.notice -- "$*"
}

# Unpacks a given oci-archive.tar[.gz] in the current directory.  Sanity
# checks, at least one index.json in the top-level dir of the archive.
# If there are more index files, this function does not handle them.
unpack_archive()
{
    image=$1
    name=$2

    # Supported transports for load and create
    case "$image" in
	oci:*)			# Unpacked OCI image
	    file=${image#oci:}
	    ;;
	oci-archive:*)		# Packed OCI image, .tar or .tar.gz format
	    file=${image#oci-archive:}
	    ;;
	*)			# docker://*, docker-archive:*, or URL
	    echo "$image"
	    return 0
	    ;;
    esac

    if [ ! -e "$file" ]; then
	if [ -e "/var/lib/containers/oci/$file" ]; then
	    file="/var/lib/containers/oci/$file"
	elif [ -e "/lib/oci/$file" ]; then
	    file="/lib/oci/$file"
	else
	    log "Error: cannot find OCI archive $file in search path."
	    exit 1
	fi
    fi

    if [ -d "$file" ]; then
	index=$(find "$file" -name index.json)
	if [ -z "$index" ]; then
	    log "Error: cannot find index.json in OCI image $file"
	    exit 1
	fi
    else
	index=$(tar tf "$file" |grep index.json)
	if [ -z "$index" ]; then
	    log "Error: invalid OCI archive, cannot find index.json in $file"
	    exit 1
	fi

	[ -n "$quiet" ] || log "Extracting OCI archive $file ..."
        tar xf "$file" || (log "Error: failed unpacking $file in $(pwd)"; exit 1)
	remove=true
    fi

    dir=$(dirname "$index")
    [ -n "$quiet" ] || log "Loading OCI image $dir ..."
    podman load -qi "$dir"        >/dev/null

    # Rename image from podman default $dir:latest
    if [ -n "$name" ]; then
	podman tag "$dir" "$name" >/dev/null
	podman rmi "$dir"         >/dev/null
    else
	name=$dir
    fi

    if [ "$remove" = "true" ]; then
	rm -rf "$file"
    fi

    echo "$name"
}

running()
{
    run=$(podman inspect "$1" 2>/dev/null |jq .[].State.Running)
    [ "$run" = "true" ] && return 0
    return 1
}

# shellcheck disable=SC2086
create()
{
    name=$1
    image=$2
    shift 2

    if [ -z "$name" ] || [ -z "$image" ]; then
	echo "Usage:"
	echo "  container create NAME IMAGE"
	exit 1
    fi

    # Unpack and load docker-archive/oci/oci-archive, returning image
    # name, or return docker:// URL for download.
    image=$(unpack_archive "$image")

    if [ -z "$logging" ]; then
	logging="--log-driver k8s-file --log-opt path=/run/containers/$name.fifo"
    fi

    args="$args --replace --quiet --cgroup-parent=containers $caps"
    args="$args --restart=$restart --systemd=false --tz=local $privileged"
    args="$args $ro $vol $mount $hostname $entrypoint $env $port $logging"
    pidfn=/run/container:${name}.pid

    [ -n "$quiet" ] || log "---------------------------------------"
    [ -n "$quiet" ] || log "Got name: $name image: $image"
    [ -n "$quiet" ] || log "Got networks: $network"

    if [ -n "$network" ]; then
	for net in $network; do
	    args="$args --net=$net"
	done

	for srv in $dns; do
	    args="$args --dns=$srv"
	done

	for domain in $search; do
	    args="$args --dns-search=$domain"
	done
    else
	args="$args --network=none"
    fi

    # shellcheck disable=SC2048
    log "Calling podman create --name $name --conmon-pidfile=$pidfn $args $image $*"
    if podman create --name "$name" --conmon-pidfile="$pidfn" $args "$image" $*; then
	[ -n "$quiet" ] || log "Successfully created container $name from $image"
	rm -f "/run/containers/env/${name}.env"
	[ -n "$manual" ] || start "$name"
	exit 0
    fi

    log "Error: failed creating container $name, please check the configuration."
    exit 1
}

delete()
{
    name=$1
    image=$2

    if [ -z "$name" ]; then
	echo "Usage:"
	echo "  container delete NAME"
	exit 1
    fi

    podman rm -vif "$name" >/dev/null 2>&1
    [ -n "$quiet" ] || log "Container $name has been removed."
}

waitfor()
{
    timeout=$2
    while [ ! -f "$1" ]; do
	_=$((timeout -= 1))
	if [ $timeout -le 0 ]; then
	    log "Timeout waiting for $1, aborting!"
	    exit 1
	fi
	sleep 1;
    done
}

start()
{
    name=$1

    if running "$name"; then
	[ -n "$quiet" ] || echo "$name: already running."
	return
    fi

    initctl -bq cond set "container:$name"
    # Real work is done by wrap() courtesy of finit sysv emulation
}

stop()
{
    name=$1

    if ! running "$name"; then
	[ -n "$quiet" ] || echo "$name: not running."
	return
    fi

    initctl -bq cond clr "container:$name"
    # Real work is done by wrap() courtesy of finit sysv emulation
}

wrap()
{
    name=$1
    cmd=$2

    podman "$cmd" "$name"
}

# Removes network $1 from all containers
netwrm()
{
    net=$1

    for c in $(podman ps $all --format "{{.Names}}"); do
	for n in $(podman inspect "$c" |jq -r '.[].NetworkSettings.Networks | keys[]'); do
	    if [ "$n" = "$net" ]; then
		podman network disconnect $force "$n" "$c" >/dev/null
	    fi
	done
    done
}

# Schedule restart of (any) container using network $1 to activate network changes
netrestart()
{
    net=$1

    for c in $(podman ps $all --format "{{.Names}}"); do
	for n in $(podman inspect "$c" |jq -r '.[].NetworkSettings.Networks | keys[]'); do
	    if [ "$n" = "$net" ]; then
		initctl -nbq touch "container@$c"
	    fi
	done
    done
}

usage()
{
	cat <<EOF
usage:
  container [opt] cmd [arg]

options:
  -a, --all                Show all, of something
      --dns NAMESERVER     Set nameserver(s) when creating a container
      --dns-search LIST    Set host lookup search list when creating container
      --cap-add CAP        Add capability to unprivileged container
      --cap-drop CAP       Drop capability, for privileged containter
  -c, --creds USR[:PWD]    Credentials to pass to curl -u for remote ops
  -d, --detach             Detach a container started with 'run IMG [CMD]'
  -e, --env FILE           Environment variables when creating container
      --entrypoint         Disable container image's ENTRYPOINT, run cmd + arg
  -f, --force              Force operation, e.g. remove
  -h, --help               Show this help text
      --hostname NAME      Set hostname when creating container
      --net NETWORK        Network interface(s) when creating or finding container
  -l, --log-driver DRV     Log driver to use
      --log-opt OPT        Logging options to log driver
      --log-path PATH      Path for k8s-file log pipe
  -m, --mount HOST:DEST    Bind mount a read-only file inside a container
      --manual             Do not start container automatically after creation
  -n, --name NAME          Alternative way of supplying name to start/stop/restart
      --privileged         Give container extended privileges
  -p, --publish PORT       Publish ports when creating container
                           Syntax: [[ip:][hostPort]:]containerPort[/protocol]
  -q, --quiet              Quiet operation, called from confd
  -r, --restart POLICY     One of "no", "always", or "on-failure:NUM"
      --read-only          Do not create a writable layer
  -s, --simple             Show output in simplified format
  -v, --volume NAME:PATH   Create named volume mounted inside container on PATH

commands:
  create   NAME IMAGE NET  Create container NAME using IMAGE with networks NET
  delete  [network] NAME   Remove container NAME or network NAME from all containers
  exec     NAME CMD        Run a command inside a container
  find    [ifname PID]     Find PID of container where '--net IFNAME' currently lives
                           or, find the name of our IFNAME inside the container @PID
  help                     Show this help text
  list    [image | oci]    List names (only) of containers, images, or OCI archives
  load    [NAME | URL] NM  Load OCI tarball fileNAME or URL to image NM
  locate                   Find container that currently owns '--net IFNAME'
  remove   IMAGE           Remove an (unused) container image
  restart [network] NAME   Restart a (crashed) container or container(s) using network
  run      NAME [CMD]      Run a container interactively, with an optional command
  save     IMAGE FILE      Save a container image to an OCI tarball FILE[.tar.gz]
  shell                    Start a shell inside a container
  show    [image | volume] Show containers, images, or volumes
  stat                     Show continuous stats about containers (Ctrl-C aborts)
  start   [NAME]           Start a container, see -n
  stop    [NAME]           Stop a container, see -n
  volume  [prune]          Prune unused volumes
EOF
}

while [ "$1" != "" ]; do
    case $1 in
	-a | --all)
	    all="-a"
	    ;;
	--cap-add)
	    shift
	    caps="$caps --cap-add=$1"
	    ;;
	--cap-drop)
	    shift
	    caps="$caps --cap-drop=$1"
	    ;;
	-c | --creds)
	    shift
	    creds="-u $1"
	    ;;
	-d | --detach)
	    detach="-d"
	    ;;
	--dns)
	    shift
	    dns="$dns $1"
	    ;;
	--dns-search)
	    shift
	    search="$search $1"
	    ;;
	-e | --env)
	    shift
	    env="$env --env-file=$1"
	    ;;
	--entrypoint)
	    entrypoint="--entrypoint=\"\""
	    ;;
	-f | --force)
	    force="-f"
	    ;;
	-h | --help)
	    usage
	    exit 0
	    ;;
	--hostname)
	    shift
	    hostname="--hostname $1"
	    ;;
	-l | --log-driver)
	    shift
	    logging=" --log-driver=$1"
	    ;;
	--log-opt)
	    shift
	    logging="$logging --log-opt $1"
	    ;;
	--log-path)
	    shift
	    logging="$logging --log-opt path=$1"
	    log_path="$1"
	    ;;
	-m | --mount)
	    shift
	    mount="--mount=$1"
	    ;;
	--manual)
	    manual=true
	    ;;
	-n | --name)
	    shift
	    name="$1"
	    ;;
	--net)
	    shift
	    if [ -n "$network" ]; then
		network="$network $1"
	    else
		network=$1
	    fi
	    ;;
	--privileged)
	    privileged="--privileged=true"
	    ;;
	-p | --publish)
	    shift
	    port="$port -p $1"
	    ;;
	-q | --quiet)
	    quiet="-q"
	    ;;
	-r | --restart)
	    shift
	    restart=$1
	    ;;
	--read-only)
	    ro="--read-only=true"
	    ;;
	-s | --simple)
	    simple=true
	    ;;
	-v | --volume)
	    shift
	    vol="$vol -v $1"
	    ;;
	*)
	    break
	    ;;
    esac
    shift
done

cmd=$1
if [ -n "$cmd" ]; then
    shift
fi

case $cmd in
    # Does not work atm., cannot attach to TTY because
    # we monitor 'podman start -ai foo' with Finit.
    # attach)
    # 	podman attach "$1"
    # 	;;
    create)
	[ -n "$quiet" ] || log "Got create args: $*"
	create "$@"
	;;
    delete)
	cmd=$1
	name=$2
	if [ "$cmd" = "network" ] && [ -n "$name" ]; then
	    netwrm "$name"
	else
	    delete "$@"
	fi
	;;
    exec)
	podman exec -it "$@"
	;;
    find)
	cmd=$1
	pid=$2
	if [ "$cmd" = "ifname" ] && [ -n "$pid" ]; then
	    nsenter -t "$pid" -n ip -d -j link | \
		jq --arg ifname "$network" -r '.[] | select(.ifalias==$ifname) | .ifname'
	else
	    containers=$(podman ps $all --format "{{.Names}}")
	    for c in $containers; do
		json=$(podman inspect "$c")
		nets=$(echo "$json" |jq -r '.[].NetworkSettings.Networks | keys[]' 2>/dev/null)
		for n in $nets; do
		    if [ "$network" = "$n" ]; then
			pid=$(echo "$json" | jq .[].State.Pid)
			echo "$pid"
			exit 0
		    fi
		done
	    done
	fi
	;;
    help)
	usage
	;;
    load)
	url=$1
	name=$2
	# shellcheck disable=SC2086
	if echo "$url" | grep -q "://"; then
	    file=$(basename "$url")
            curl -k $creds -Lo "$file" "$url"
	else
            file="$url"
	fi

	# shellcheck disable=SC2086
	name=$(unpack_archive "$file" $name)

	# Show resulting image(s) matching $name
	if [ -n "$name" ]; then
	    podman images -n "$name"
	else
	    exit 1
	fi
	;;
    locate)			# Find where the host's ifname lives
	if [ -z "$network" ]; then
	    echo "Missing --net IFNAME option."
	    exit 1
	fi
	containers=$(podman ps $all --format "{{.Names}}")
	for c in $containers; do
	    json=$(podman inspect "$c")
	    nets=$(echo "$json" |jq -r '.[].NetworkSettings.Networks | keys[]' 2>/dev/null)
	    for n in $nets; do
		if [ "$network" = "$n" ]; then
		    echo "$c"
		    exit 0;
		fi
	    done
	done
	;;
    ls | list)
	cmd=$1
	[ -n "$cmd" ] && shift
	case $cmd in
	    image*)
		podman images $all --format "{{.Repository}}:{{.Tag}}"
		;;
	    oci)
		find /lib/oci /var/lib/containers/oci -type f 2>/dev/null
		;;
	    *)		
		podman ps $all --format "{{.Names}}"
		;;
	esac
	;;
    pull)
	podman pull "$@"
	;;
    remove)
	podman rmi $all $force -i "$1"
	;;
    run)
	img=$1
	cmd=$2
	[ -n "$port" ] || port="-P"
	if [ -n "$cmd" ]; then
	    shift 2
	    [ -n "$detach" ] || echo "Starting $img ENTRYPOINT $cmd :: use Ctrl-p Ctrl-q to detach"
	    podman run -it --rm $detach $port --entrypoint="$cmd" "$img" "$@"
	else
	    [ -n "$detach" ] || echo "Starting $img :: use Ctrl-p Ctrl-q to detach"
	    podman run -it --rm $detach $port "$img"
	fi
	;;
    save)
	name=$1
	file=$2
	if echo "$file" | grep -q ".gz"; then
	    file=${file%%.gz}
	    gzip=true
	fi
	if ! echo "$file" | grep -q ".tar"; then
	    file=${file}.tar
	    gzip=true
	fi
	podman save -o "$file" "$name"
	if [ -s "$file" ] && [ -n "$gzip" ]; then
	    gzip "$file"
	fi
	;;
    shell)
	podman exec -it "$1" sh -l
	;;
    show)
	cmd=$1
	[ -n "$cmd" ] && shift
	case $cmd in
	    image*)
		if [ -n "$simple" ]; then
		    podman images $all --format "{{.Names}} {{.Size}}" \
			| sed 's/\[\(.*\)\] /\1 /g' \
			| awk '{ printf "%-60s %s %s\n", $1, $2, $3}'
		else
		    podman images $all
		fi
		;;
	    volume*)
		printf "%-20s  CONTAINER\n" "VOLUME"
		for v in $(podman volume ls --format "{{.Name}}"); do
		    printf "%-20s" "$v"
		    podman ps -a --filter volume="$v"  --format '{{.Names}}' | sed 's/^/  /'
		done
		;;
	    *)
		if [ -n "$simple" ]; then
		    podman ps $all --format "{{.ID}}  {{.Names}}  {{.Image}}" \
			| awk '{ printf "%s  %-30s %s\n", $1, $2, $3}'
		else
		    podman ps $all
		fi
		;;
	esac
	;;
    start)
	if [ -n "$name" ]; then
	    wrap "$name" start
	elif [ -n "$1" ]; then
	    start "$1"
	else
	    usage
	    exit 1
	fi
	;;
    restart)
	if [ -n "$name" ]; then
	    wrap "$name" restart
	elif [ -n "$1" ]; then
	    cmd=$1
	    name=$2
	    if [ "$cmd" = "network" ] && [ -n "$name" ]; then
		netrestart "$name"
	    else
		name=$1
		stop "$name"
		timeout=20
		while running "$name"; do
		    _=$((timeout -= 1))
		    if [ $timeout -le 0 ]; then
			log "Timeout waiting for container $1 to stop before restarting it."
			exit 1
		    fi
		    sleep 1
		done
		start "$name"
	    fi
	else
	    usage
	    exit 1
	fi
	;;
    stop)
	if [ -n "$name" ]; then
	    wrap "$name" stop
	elif [ -n "$1" ]; then
	    stop "$1"
	else
	    usage
	    exit 1
	fi
	;;
    stat*)
	podman stats -i 2
	;;
    upgrade)
	# Start script used to initially create container
	script=/var/lib/containers/active/S01-${1}.sh

	# Find container image
	img=$(podman inspect "$1" | jq -r .[].ImageName)
	if [ -z "$img" ]; then
	    echo "No such container ($1), or invalid ImageName.  Cannot upgrade."
	    exit 1;
	fi

	# Likely an OCI archive, or local directory, assume user has updated image.
	if echo "$img" | grep -Eq '^localhost/'; then
	    file=$(awk '{s=$NF} END{print s}' "$script")
	    echo "Upgrading container ${1} with local archive: $file ..."
	else
	    printf ">> Stopping ... "
	    podman stop "$1"
	    printf ">> "
	    podman pull "$img" || (echo "Failed fetching $img, check your network (settings)."; exit 1)
	fi
	echo ">> Starting $1 ..."
	if ! "$script"; then
	    echo ">> Failed recreating container $1"
	    exit 1
	fi
	echo ">> Done."
	;;
    volume)
	cmd=$1
	[ -n "$cmd" ] && shift
	case $cmd in
	    prune)
		podman volume $force prune
		;;
	    *)
		false
		;;
	esac
	;;
    *)
	usage
	exit 1
	;;
esac
