#!/bin/bash

RASPI_HOST=raspberrypi.local
RASPI_SSH=pi@$RASPI_HOST

####################################################################
# Configuration (from ~/.myconf)
####################################################################

eval "$(luax env ~/.myconf)"

####################################################################
# Download image and activate SSH at first boot
####################################################################

# 64-bit image: https://downloads.raspberrypi.org/raspios_arm64/images/

RASPIOS_DIR=~/raspios
RASPIOS=https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz

IMG=$RASPIOS_DIR/$(basename $RASPIOS .xz)

prepare_image()
{
    mkdir -p $RASPIOS_DIR
    if ! [ -f "$IMG" ]
    then
        wget -c $RASPIOS -O "$RASPIOS_DIR/$(basename $RASPIOS)"
        ( cd $RASPIOS_DIR && unxz "$(basename $RASPIOS)" )
    fi
    local BOOT=$RASPIOS_DIR/boot
    mkdir -p $BOOT
    echo "Mounting $IMG to activate Wifi and SSH on first boot"
    sudo mount -t vfat -o loop,offset=$((8192*512)) "$IMG" $BOOT
    sudo touch $BOOT/ssh
    echo -n "Wifi SSID    : "; read -r WIFI_SSID
    if [ -n "$WIFI_SSID" ]
    then
        echo -n "Wifi password: "; read -r WIFI_PASSWORD
        echo -n "Wifi country : "; read -r WIFI_COUNTRY
        sudo tee $BOOT/wpa_supplicant.conf >/dev/null <<EOF
country=$WIFI_COUNTRY
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="$WIFI_SSID"
    psk="$WIFI_PASSWORD"
}
EOF
    fi
    sudo umount $BOOT
    sync
}

####################################################################
# Flash image
####################################################################

flash_image()
{
    echo "Unplug and replug the SD card or USB disk for autodetection..."
    awk '$4~/^sd.$/ {print $4}' /proc/partitions > /tmp/parts
    local PART=""
    while [ -z "$PART" ]
    do
        awk '$4~/^sd.$/ {print $4}' /proc/partitions > /tmp/parts2
        PART=$(comm -1 -3 /tmp/parts /tmp/parts2)
        mv /tmp/parts2 /tmp/parts
    done
    rm /tmp/parts
    echo -n "Ready to write $(basename "$IMG") to /dev/$PART? "
    read -r
    case "$REPLY" in
        y|Y|o|O)    sudo dd if="$IMG" of="/dev/$PART" bs=8M status=progress ;;
        *)          echo "aborted" ;;
    esac
    sync
}

####################################################################
# raspi-config
####################################################################

raspi_config()
{
    ssh -t "$RASPI_SSH" "sudo raspi-config"
}

####################################################################
# ssh
####################################################################

open_ssh()
{
    ssh -t "$RASPI_SSH"
}


####################################################################
# push ssh keys
####################################################################

push_keys()
{
    [ -e ~/.ssh/id_rsa.pub ] || ssh-keygen
    ssh-copy-id "$RASPI_SSH"
    echo -n "Press any key"; read -r
}

####################################################################
# mc & sshfs
####################################################################

sshfs_mount()
{
    MNT=$RASPIOS_DIR/mnt-$RASPI_HOST
    MNT_ROOT=$RASPIOS_DIR/mnt-$RASPI_HOST-root
    mkdir -p "$MNT" "$MNT_ROOT"
    sshfs "$RASPI_SSH":/home/pi "$MNT"
    sshfs "$RASPI_SSH":/ "$MNT_ROOT"
}

sshfs_umount()
{
    echo "Wait for 5 seconds before unmounting the remote file system"
    sync
    sleep 5
    umount "$MNT"
    umount "$MNT_ROOT"
}

mc_sshfs()
{
    sshfs_mount
    mc . "$MNT"
    sshfs_umount
}

####################################################################
# Basic configuration
####################################################################

install_base()
{
    local PACKETS="git git-lfs exa grc fzf rlwrap htop ripgrep fd-find tmux mc vifm neovim vim-nox ufw fail2ban jq pandoc neofetch lm-sensors dialog timeshift libreadline-dev sshfs make gcc musl-tools curl ninja-build ddclient syncthing cockpit tuned"
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo apt update; sudo apt install $PACKETS; sudo apt upgrade"

    ssh "$RASPI_SSH" "mkdir -p ~/src; cd ~/src; [ -d luax ] || git clone https://codeberg.org/cdsoft/luax; cd luax && git fetch && git rebase && ./bootstrap.sh ssl && ninja install"

    sshfs_mount

    sed -i '/luax env/d' "$MNT"/.bashrc
    echo "eval \"\$(\\luax env)\"" >> "$MNT"/.bashrc

    sed -i '/luax env/d' "$MNT"/.profile
    echo "eval \"\$(\\luax env)\"" >> "$MNT"/.profile

    ssh "$RASPI_SSH" "sudo sed -i '/^tmpfs/d' /etc/fstab"
    ssh "$RASPI_SSH" "sudo sh -c \"echo 'tmpfs /tmp tmpfs defaults,noatime,nosuid 0 0' >> /etc/fstab\""
    ssh "$RASPI_SSH" "sudo sh -c \"echo 'tmpfs /var/log tmpfs defaults,noatime,nosuid,size=64m 0 0' >> /etc/fstab\""

    cat <<\EOF > "$MNT"/.bash_aliases
export PATH=~/.local/bin:~/bin:$PATH

export EDITOR=nvim
export VISUAL=nvim

# Aliases

alias ls="exa --classify"
alias ll='ls -lh'
alias la='ll -a'
#alias lt='ll -rt'
alias lt='ll --sort newest'

alias grep='grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}'

alias more=less
alias df='grc df -h'
alias du='grc du -h'

alias vi=nvim

function gd()
{
    local filepath="$(fd "$1" | fzf)"
    [ -f "$filepath" ] && filepath="$(dirname "$filepath")"
    cd "$filepath"
}
EOF

    cat <<EOF > "$MNT"/.tmux.conf
# https://hamvocke.com/blog/a-guide-to-customizing-your-tmux-conf/

#################################################
# Less awkward prefix keys
#################################################

# remap prefix from 'C-b' to 'C-a'
#unbind C-b
#set-option -g prefix C-a
#bind-key C-a send-prefix

set -g base-index 1

#################################################
# Intuitive Split Commands
#################################################

# split panes using | and -
bind h split-window -h -c "#{pane_current_path}"
bind v split-window -v -c "#{pane_current_path}"
unbind '"'
unbind %

#################################################
# Easy Config Reloads
#################################################

# reload config file (change file location to your the tmux.conf you want to use)
bind r source-file ~/.tmux.conf

#################################################
# Fast Pane-Switching
#################################################

# switch panes using Alt-arrow without prefix
bind -n M-Left select-pane -L
bind -n M-Right select-pane -R
bind -n M-Up select-pane -U
bind -n M-Down select-pane -D

# disable switching panes with Escape
set -s escape-time 0

# switch windows using Alt-Shift-arrow without prefix
bind -n M-S-Left select-window -t:-
bind -n M-S-Right select-window -t:+

# open new panes on the same directory
bind c new-window -c "#{pane_current_path}"

set-option -g history-limit 10000

#################################################
# Mouse mode
#################################################

# Enable mouse control (clickable windows, panes, resizable panes)
set -g mouse on

#################################################
# Stop Renaming Windows Automatically
#################################################

# don't rename windows automatically
#set-option -g allow-rename off

#################################################
# Changing the Look of tmux
#################################################

# DESIGN TWEAKS

# don't do anything when a 'bell' rings
#set -g visual-activity off
#set -g visual-bell off
#set -g visual-silence off
#setw -g monitor-activity off
#set -g bell-action none

# clock mode
setw -g clock-mode-colour yellow

# copy mode
setw -g mode-style 'fg=black bg=yellow dim'

# panes
set -g pane-border-style 'fg=grey'
set -g pane-active-border-style 'fg=green'

# statusbar
set -g status-position bottom
set -g status-justify left
set -g status-style 'fg=white bg=blue'

set -g status-left-style 'fg=black bg=yellow'
set -g status-left ' #S '
set -g status-left-length 16

set -g status-right-style 'fg=black bg=yellow'
set -g status-right ' %d/%m/%Y %H:%M '
set -g status-right-length 60

setw -g window-status-current-style 'fg=black bg=green'
setw -g window-status-current-format ' #I #W '

setw -g window-status-style 'fg=black bg=blue'
setw -g window-status-format ' #I #W '

setw -g window-status-bell-style 'fg=yellow bg=red bold'

# messages
set -g message-style 'fg=yellow bg=red bold'

# Status bar hidden by default, toggle with Ctrl-Space
set -g status off
bind -n C-Space set status

#################################################
# Some parameters from https://github.com/tmux-plugins/tmux-sensible
#################################################

# Increase tmux messages display duration from 750ms to 4s
set -g display-time 4000

# Emacs key bindings in tmux command prompt (prefix + :) are better than
# vi keys, even for vim users
set -g status-keys emacs

# Focus events enabled for terminals that support them
set -g focus-events on

#################################################
# Copy mode
#################################################

# Copy mode
# - https://superuser.com/questions/395158/tmux-copy-mode-select-text-block
# - https://unix.stackexchange.com/questions/450184/in-tmux-how-to-set-shortcut-for-copy-mode
bind-key -n 'C-M-c' copy-mode       # Ctrl-Alt-C => copy mode
setw -g mode-keys vi
unbind-key -T copy-mode-vi v
bind-key -T copy-mode-vi 'v' send -X begin-selection     # Begin selection in copy mode.
bind-key -T copy-mode-vi 'C-v' send -X rectangle-toggle  # Begin selection in copy mode.
bind-key -T copy-mode-vi 'y' send -X copy-selection      # Yank selection in copy mode.

# Navigate between prompts
bind-key -n 'C-M-Up' copy-mode \;   send -X start-of-line \; send -X cursor-up   \; send -X search-backward ".*> .*"
bind-key -T copy-mode-vi 'C-M-Up'   send -X start-of-line \; send -X cursor-up   \; send -X search-backward ".*> .*"
bind-key -T copy-mode-vi 'C-M-Down' send -X start-of-line \; send -X cursor-down \; send -X search-forward  ".*> .*"

set-option -s set-clipboard off
EOF

    ssh "$RASPI_SSH" "
        sudo ufw reset
        sudo ufw allow from 10.0.0.0/8
        sudo ufw allow from 172.16.0.0/12
        sudo ufw allow from 192.168.0.0/16
        sudo ufw allow \"SSH\"
        pgrep apache && sudo ufw allow \"WWW Full\"
        sudo ufw enable
        sudo ufw status verbose numbered
    "

    #DYN_IP=$(curl -s http://ipinfo.io/ip)

    cat <<EOF > "$MNT"/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12
findtime = 15m
bantime = 12w
maxretry = 3

[sshd]
backend = systemd
enabled = true
mode = agressive
bantime = 12w

# Ban IP and report to AbuseIPDB for SSH Brute-Forcing
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_SSH", abuseipdb_category="18,22"]

EOF
    ssh "$RASPI_SSH" "sudo mv -f jail.local /etc/fail2ban/jail.d/jail.local; sudo chown root:root /etc/fail2ban/jail.d/jail.local"
    ssh "$RASPI_SSH" "sudo sed -i 's/^#allowipv6 = auto/allowipv6 = auto/' /etc/fail2ban/fail2ban.conf"
    ssh "$RASPI_SSH" "sudo service fail2ban restart"

    # Dynamic DNS with Gandi
    if [ -n "$GANDI_API_KEY" ]
    then
        mkdir -p "$MNT"/bin
        cat <<\EOF > "$MNT"/bin/gdns
#!/bin/bash

API_KEY=GANDI_API_KEY
TTL=1800
ENDPOINT=https://dns.api.gandi.net/api/v5
DOMAIN=GANDI_DOMAIN
SUBDOMAIN=GANDI_SUBDOMAIN
CERTIFICATE_RENEW=CERTBOT_RENEW
case GANDI_IPVERSION in
    4) TYPE=A ;;
    6) TYPE=AAAA ;;
esac

date

# Get dynamic IP
case GANDI_IPVERSION in
    4) DYN_IP=$(curl -s http://ipinfo.io/ip) ;;
    6) DYN_IP=$(hostname -I | egrep -o '[0-9a-z:]+:[0-9a-z:]+' | head -n 1) ;;
esac
echo "Dynamic IP: $DYN_IP"
[ -z "$DYN_IP" ] && exit 1

# Get the current DNS IP from the cache
CACHE=/tmp/gdns.cache
if [ -f $CACHE ]
then
    DNS_IP=$(cat $CACHE)
    echo "DNS IP    : $DNS_IP (from cache)"
fi

# Get the DNS IP from the DNS record if the cache is empty
if [ -z "$DNS_IP" ]
then

    # Get the zone UUID for the domain
    UUID=$(curl -s -X GET \
                -H "Content-Type: application/json" \
                -H "X-Api-Key: $API_KEY" \
                $ENDPOINT/domains/$DOMAIN \
           | jq '.zone_uuid' | tr -d '"')
    #echo "UUID      : $UUID"
    [ -z "$UUID" ] && exit 1

    # Get the current DNS record configuration
    DNS_IP=$(curl -s -X GET \
                -H "Content-Type: application/json" \
                -H "X-Api-Key: $API_KEY" \
                $ENDPOINT/zones/$UUID/records/$SUBDOMAIN/$TYPE \
             | jq '.rrset_values[0]' | tr -d '"')
    echo "DNS IP    : $DNS_IP (from DNS record)"

    [ -n "$DNS_IP" ] && echo $DNS_IP > $CACHE

fi
[ -z "$DNS_IP" ] && exit 1

# Update the DNS record if the IP has changed
if [ "$DYN_IP" == "$DNS_IP" ]
then
    echo "The DNS record is uptodate"
else
    echo "The DNS record needs to be updated"
    if [ -z "$UUID" ]
    then
        # Get the zone UUID for the domain
        UUID=$(curl -s -X GET \
                    -H "Content-Type: application/json" \
                    -H "X-Api-Key: $API_KEY" \
                    $ENDPOINT/domains/$DOMAIN \
            | jq '.zone_uuid' | tr -d '"')
        #echo "UUID      : $UUID"
    fi
    curl -X PUT \
        -H "Content-Type: application/json" \
        -H "X-Api-Key: $API_KEY" \
        -d "{\"rrset_ttl\": $TTL, \"rrset_values\": [\"$DYN_IP\"]}" \
        $ENDPOINT/zones/$UUID/records/$SUBDOMAIN/$TYPE
    rm $CACHE
fi

if $CERTIFICATE_RENEW && sudo test -e /etc/letsencrypt/live/$SUBDOMAIN.$DOMAIN/cert.pem
then
    NOW=$(date +%s)
    CERT=$(sudo stat -L -c %Y /etc/letsencrypt/live/$SUBDOMAIN.$DOMAIN/cert.pem)
    AGE=$(( (NOW - CERT) / (24*60*60) ))
    echo "SSL certificate: $AGE days"
    if (( AGE > 60 ))
    then
        echo "The SSL certificate is too old and will be renewed"
        sudo certbot renew
    fi
fi

/home/pi/bin/unban.py
EOF
        sed -i "s/GANDI_API_KEY/$GANDI_API_KEY/" "$MNT"/bin/gdns
        sed -i "s/GANDI_DOMAIN/$GANDI_DOMAIN/" "$MNT"/bin/gdns
        sed -i "s/GANDI_SUBDOMAIN/$GANDI_SUBDOMAIN/" "$MNT"/bin/gdns
        sed -i "s/GANDI_IPVERSION/$GANDI_IPVERSION/" "$MNT"/bin/gdns
        sed -i "s/CERTBOT_RENEW/$CERTBOT_RENEW/" "$MNT"/bin/gdns
        chmod +x "$MNT"/bin/gdns

        mkdir -p "$MNT"/bin
        cat <<\EOF > "$MNT"/bin/unban.py
#!/usr/bin/env python

import os

banned = eval(os.popen("sudo fail2ban-client banned").read())

if not isinstance(banned, list):
    os.exit(1)

server_ip = open("/tmp/gdns.cache", "rt").read().strip()
print("server IP:", server_ip)

reload = False

for d in banned:
    for jail, ips in d.items():
        for ip in ips:
            if ip == server_ip:
                print("unban", ip, "from", jail)
                os.system("sudo fail2ban-client set "+jail+" addignoreip "+ip)
                reload = True
if reload:
    os.system("sudo fail2ban-client unban "+server_ip)
    os.system("sudo fail2ban-client reload")
EOF
        chmod +x "$MNT"/bin/unban.py

        ssh "$RASPI_SSH" "echo '*/5 * * * * pi /home/pi/bin/gdns >/dev/null 2>&1' | sudo tee /etc/cron.d/gdns"
    fi

    # Dynamic DNS with OVH
    if [ -n "$OVH_LOGIN" ]
    then
        cat <<EOF > "$MNT_ROOT"/tmp/ddclient.conf
# Configuration file for ddclient generated by rrpi
#
# /etc/ddclient.conf

daemon=2
protocol=dyndns2
server=www.ovh.com
use=web
login=$OVH_LOGIN
password='$OVH_PASSWORD'
$OVH_SUBDOMAIN.$OVH_DOMAIN
EOF
        ssh "$RASPI_SSH" "sudo mv /tmp/ddclient.conf /etc/ddclient.conf"
        ssh "$RASPI_SSH" "sudo chown root:root /etc/ddclient.conf"
        ssh "$RASPI_SSH" "sudo service ddclient restart"
    fi

    mkdir -p "$MNT"/bin
    cat <<\EOF > "$MNT"/bin/unban-all
#!/bin/bash

sudo fail2ban-client unban --all
EOF
    chmod +x "$MNT"/bin/unban-all

    mkdir -p "$MNT"/bin
    cat <<\EOF_status > "$MNT"/bin/status
#!/bin/bash
cat <<EOF
---
title: "Pi status"
date: "$(date)"
---

## Server

~~~
$(neofetch --stdout)

$(sensors | awk '$1~/^(temp|fan)/ {print}')

$(LANG=C df -h /)
~~~

## DNS

~~~
$(/home/pi/bin/gdns || echo "DNS status N/A")
~~~

EOF

EOF_status
    chmod +x "$MNT"/bin/status

    cat <<\EOF_netmon > "$MNT"/bin/netmon
#!/bin/bash

CSV=/var/www/html/netmon.csv
HTML=/var/www/html/netmon.html
PID=/tmp/netmon.pid

[ -f $PID ] && exit

echo $$ > $PID
trap "rm -f $PID" EXIT

NEW=false

check()
{
    [ -z "$1" ] && return
    ( timeout 2 ping -4 -c1 $1 || timeout 2 ping -4 -c1 $1 || timeout 2 ping -4 -c1 $1 ) >/dev/null 2>&1 \
        && echo ok || echo FAIL
}

init_csv()
{
    if ! [ -f $CSV ]
    then
        NEW=true
        cat <<EOF | sudo tee $CSV >/dev/null
date|time|timestamp|router|box|web1|web2|web3
----|----|---------|------|---|----|----|----
EOF
    fi
}

check_connections()
{
    local ROUTER=" "
    local BOX=" "
    local WEB1=" "
    local WEB2=" "
    local WEB3=" "

    local T=$(date +"%Y/%m/%d|%H:%M:%S|%s")

    ROUTER=$(check ROUTER_IP)
    [ "$ROUTER" = "ok" ] && BOX=$(check BOX_IP)
    [ "$BOX"    = "ok" ] && WEB1=$(check WEB1_URL)
    [ "$BOX"    = "ok" ] && [ "$WEB1" = "FAIL" ] && WEB2=$(check WEB2_URL)
    [ "$BOX"    = "ok" ] && [ "$WEB1-$WEB2" = "FAIL-FAIL" ] && WEB3=$(check WEB3_URL)

    local res="$ROUTER|$BOX|$WEB1|$WEB2|$WEB3"

    if $NEW || [[ "$res" =~ "FAIL" ]]
    then
        echo "$T|$res" | sudo tee -a $CSV >/dev/null
    fi

    NEW=false
}

publish()
{
    if ! [ -f $HTML ] || [ $CSV -nt $HTML ]
    then
        cat <<EOF | pandoc --self-contained --standalone --to html5 | sudo tee $HTML >/dev/null
---
title: "Network Monitor"
date: "$(date)"
---

$( (head -2 $CSV; sed '1,2d' $CSV | tac) | cut -d"|" -f1,2,4,5,6,7,8 )

EOF
    fi
}

rebooting()
{
    ## The internet box reboots at 4:00 -- ignore errors between 2:45 and 4:15 (valid for winter/summer time)
    #local T=$(( $(date '+%H*60+%M' | sed 's/+0/+/g') ))
    #[ $T -ge $((2*60+45)) ] && [ $T -le $((4*60+15)) ]
    false # currently the box is not rebooted every night, TODO: monitor the internet connection and restart the box when the connection is lost
}

while true
do
    init_csv
    if ! rebooting
    then
        check_connections
        publish
    fi
    sleep 15s
done
EOF_netmon
    sed -i "s/ROUTER_IP/$NETMON_ROUTER/" "$MNT"/bin/netmon
    sed -i "s/BOX_IP/$NETMON_BOX/" "$MNT"/bin/netmon
    sed -i "s/WEB1_URL/$NETMON_WEB_1/" "$MNT"/bin/netmon
    sed -i "s/WEB2_URL/$NETMON_WEB_2/" "$MNT"/bin/netmon
    sed -i "s/WEB3_URL/$NETMON_WEB_3/" "$MNT"/bin/netmon
    chmod +x "$MNT"/bin/netmon
    if $NETMON_ENABLE
    then
        ssh "$RASPI_SSH" "echo '*/5 * * * * pi nohup bash /home/pi/bin/netmon &' | sudo tee /etc/cron.d/netmon"
    fi

    # periodic backup with timeshift
    cat <<EOF_timeshift > "$MNT"/bin/timeshift-check
#!/bin/bash

sudo -u www-data php /var/www/html/nextcloud/occ maintenance:mode --on
sleep 5
trap "sudo -u www-data php /var/www/html/nextcloud/occ maintenance:mode --off" EXIT

sudo timeshift --check --scripted
sudo rm -vf /etc/cron.d/timeshift-hourly
EOF_timeshift
    chmod +x "$MNT"/bin/timeshift-check
    ssh "$RASPI_SSH" "echo '0 2 * * * pi /home/pi/bin/timeshift-check' | sudo tee /etc/cron.d/timeshift-check"

    ssh "$RASPI_SSH" "sudo sed -i \
        -e 's#\"schedule_monthly\" : \"false\"#\"schedule_monthly\" : \"true\"#' \
        -e 's#\"schedule_weekly\" : \"false\"#\"schedule_weekly\" : \"true\"#' \
        -e 's#\"schedule_daily\" : \"false\"#\"schedule_daily\" : \"true\"#' \
        /etc/timeshift/timeshift.json \
    "

    # Forgejo (https://forgejo.org/docs/latest/admin/installation-binary/)
    FORGEJO_LATEST=$(curl -Ls -o /dev/null -w "%{url_effective}" https://codeberg.org/forgejo/forgejo/releases/latest)
    FORGEJO_TAG=$(basename "$FORGEJO_LATEST")
    FORGEJO_VERSION=${FORGEJO_TAG#v}
    FORGEJO_URL=https://codeberg.org/forgejo/forgejo/releases/download/$FORGEJO_TAG/forgejo-$FORGEJO_VERSION-linux-arm64.xz
    curl "$FORGEJO_URL" -o "$MNT_ROOT"/tmp/"$(basename "$FORGEJO_URL")"
    xz -d -c "$MNT_ROOT"/tmp/"$(basename "$FORGEJO_URL")" > "$MNT_ROOT"/tmp/forgejo
    ssh "$RASPI_SSH" "sudo cp /tmp/forgejo /usr/local/bin/forgejo"
    ssh "$RASPI_SSH" "sudo chmod 755 /usr/local/bin/forgejo"
    ssh "$RASPI_SSH" "sudo apt install git git-lfs"
    ssh "$RASPI_SSH" "sudo adduser --system --shell /bin/bash --gecos 'Git Version Control' --group --disabled-password --home /home/git git"
    ssh "$RASPI_SSH" "sudo mkdir -p /var/lib/forgejo"
    ssh "$RASPI_SSH" "sudo chown git:git /var/lib/forgejo && sudo chmod 750 /var/lib/forgejo"
    ssh "$RASPI_SSH" "sudo mkdir -p /etc/forgejo"
    ssh "$RASPI_SSH" "sudo chown root:git /etc/forgejo && sudo chmod 770 /etc/forgejo"
    curl https://codeberg.org/forgejo/forgejo/raw/branch/forgejo/contrib/systemd/forgejo.service -o "$MNT_ROOT"/tmp/forgejo.service
    ssh "$RASPI_SSH" "sudo cp /tmp/forgejo.service /etc/systemd/system/forgejo.service"
    ssh "$RASPI_SSH" "sudo systemctl enable forgejo.service"
    ssh "$RASPI_SSH" "sudo systemctl start forgejo.service"

    # Syncthing
    # https://kleinprojects.com/using-the-raspberry-pi-as-a-syncthing-server/
    ssh "$RASPI_SSH" "sudo systemctl enable syncthing@pi.service"
    ssh "$RASPI_SSH" "sudo systemctl start syncthing@pi.service"
    ssh "$RASPI_SSH" "sed -i 's/127.0.0.1:8384/0.0.0.0:8384/' ~/.config/syncthing/config.xml"

    sshfs_umount
}

####################################################################
# Apache
####################################################################

install_apache()
{
    ssh "$RASPI_SSH" "sudo apt install \
        apache2 mariadb-server libapache2-mod-php \
        php-gd php-json php-mysql php-curl php-mbstring php-intl php-imagick php-xml php-zip \
        certbot python3-certbot-apache \
        sqlite3 jq \
    "

    sshfs_mount

    # .htaccess

    # https://raspberry-pi.fr/activer-fichiers-htaccess-raspbian/
    ssh "$RASPI_SSH" "sudo sed -i 's/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf"

    # Unlimited uploads with Nextcloud
    cat <<\EOF > "$MNT"/999-nextcloud_large_files.conf
LimitRequestBody 17179869184
EOF
    ssh "$RASPI_SSH" "sudo mv 999-nextcloud_large_files.conf /etc/apache2/sites-enabled"

    ssh "$RASPI_SSH" "[ -f /var/www/html/index.html ] && sudo mv /var/www/html/index.html /var/www/html/noindex.html"
    cat <<\EOF > "$MNT"/.htaccess
# Custom 404 errors
ErrorDocument 404 /404.html

# Redirect index to somewhere else...
RedirectMatch 301 ^/index\. /404.html

# Prevent directory listings
Options All -Indexes

# Prevent viewing of .htaccess file
<Files .htaccess>
order allow,deny
deny from all
</Files>
EOF
    cat <<\EOF > "$MNT"/404.html
<html>
<head>
<title>BSOD</title>
</head>
<body bgcolor="#000088" scroll="no">
<font face="monospace" size="4" color="#FFFFFF">
<p>A problem has been detected and windows has been shutdown to prevent damage to your computer.</p>
<p>DRIVER_IRQL_NOT_LES_OR_EQUAL</p>
<p>If this is the first time you've seen this stop error screen, restart your computer, If this screen appears again, follow these steps:</p>
<p>Check to make sure any new hardware or software is properly installed. If this is a new installation, ask your hardware or software manufacturer for any windows updates you might need.</p>
<p>If problems continue, disable or remove any newly installed hardware or software. Disable BIOS memory options such as caching or shadowing. If you need to use Safe Mode to remove or disable components, restart your computer, press F8 to select Advanced Startup Options, and then select Safe Mode.</p>
<p>Technical information:</p>
<p>*** STOP: 0x000000D1 (0x0000000C,0x00000002,0x00000000,0xF86B5A89)</p>
<p>***       gv3.sys - Address F86B5A89 base at F86B5000, DateStamp 3dd9919eb</p>
<p>Beginning dump of physical memory</p>
<p>Physical memory dump complete.</p>
<p>Contact your system administrator or technical support group for further assistance.</p>
</font>
</body>
</html>
EOF
    ssh "$RASPI_SSH" "sudo chown root:root .htaccess; sudo mv .htaccess /var/www/html/.htaccess"
    ssh "$RASPI_SSH" "sudo chown root:root 404.html;  sudo mv 404.html /var/www/html/404.html"

    ssh "$RASPI_SSH" "sudo systemctl restart apache2"

    ssh "$RASPI_SSH" "
        sudo ufw allow \"WWW Full\"
    "

    cat <<EOF > "$MNT"/apache.local
[apache-auth]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="18"]

[apache-badbots]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="18"]

[apache-botsearch]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="18"]

[apache-fakegooglebot]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="19"]

[apache-modsecurity]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="15,21"]

[apache-nohome]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="15,21"]

[apache-noscript]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="15,21"]

[apache-overflows]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="15,21"]

[apache-pass]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="15,21"]

[apache-shellshock]
enabled  = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="15,21"]

[php-url-fopen]
enabled = true
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_apikey="$ABUSEIPDB_API_KEY_WEB", abuseipdb_category="15,21"]

EOF
    ssh "$RASPI_SSH" "sudo mv -f apache.local /etc/fail2ban/jail.d/apache.local; sudo chown root:root /etc/fail2ban/jail.d/apache.local"
    ssh "$RASPI_SSH" "sudo service fail2ban restart"

    cat <<\EOF | sed "s/SERVER_NAME/$GANDI_SUBDOMAIN.$GANDI_DOMAIN/" > "$MNT"/status.php
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
</head>
<body>

<h1>Pi status</h1>
<p><?php date_default_timezone_set("Europe/Paris"); echo date("r"); ?></p>

<h2>Server</h2>

<pre>
<?php echo shell_exec("neofetch --stdout --disable Packages Shell"); ?>

<?php echo shell_exec("sensors | awk '$1~/^(temp|fan)/ {print}'"); ?>

<?php echo shell_exec("LANG=C df -h /"); ?>
</pre>

<h2>DNS</h2>

<pre>
Dynamic IP: <?php echo shell_exec("curl -s http://ipinfo.io/ip"); ?>

DNS IP    : <?php echo gethostbyname("SERVER_NAME"); ?>
</pre>

</body>
</html>
EOF
    ssh "$RASPI_SSH" "sudo mv -f status.php /var/www/html/status.php; sudo chown root:root /var/www/html/status.php"

    sshfs_umount
}

####################################################################
# Radicale
####################################################################

install_radicale()
{
    ssh "$RASPI_SSH" "sudo apt install python3-pip"
    ssh "$RASPI_SSH" "python3 -m pip install --upgrade radicale"

    sshfs_mount

    mkdir -p "$MNT"/.config/radicale
    cat <<\EOF > "$MNT"/.config/radicale/config
[server]
# Bind all addresses
hosts = 0.0.0.0:5232, [::]:5232

[auth]
type = htpasswd
htpasswd_filename = ~/.config/radicale/users
htpasswd_encryption = md5

[storage]
filesystem_folder = ~/.local/var/lib/radicale/collections
EOF
    if ! [ -f "$MNT"/.config/radicale/users ]
    then
        echo -n "Radicale user    : "; read -r RADICALE_USER
        echo -n "Radicale password: "; read -r RADICALE_PASSWORD
        htpasswd -mbc "$MNT"/.config/radicale/users "$RADICALE_USER" "$RADICALE_PASSWORD"
    fi
    mkdir -p "$MNT"/.config/systemd/user/
    cat <<\EOF > "$MNT"/.config/systemd/user/radicale.service
[Unit]
Description=A simple CalDAV (calendar) and CardDAV (contact) server

[Service]
ExecStart=/usr/bin/env python3 -m radicale
Restart=on-failure

[Install]
WantedBy=default.target
EOF

    sshfs_umount

    ssh "$RASPI_SSH" "systemctl --user enable radicale"
    ssh "$RASPI_SSH" "systemctl --user stop radicale"
    ssh "$RASPI_SSH" "systemctl --user start radicale"
}

####################################################################
# qBittorrent
####################################################################

install_qbittorrent()
{
    ssh "$RASPI_SSH" "sudo apt install qbittorrent-nox tmux"

    sshfs_mount

    mkdir -p "$MNT"/.config/qBittorrent "$MNT"/Downloads
    cat <<\EOF > "$MNT"/.config/qBittorrent/qBittorrent.conf
[AutoRun]
OnTorrentAdded\Enabled=false
OnTorrentAdded\Program=
enabled=false
program=

[BitTorrent]
Session\AddExtensionToIncompleteFiles=true
Session\AddTorrentPaused=false
Session\AddTrackersEnabled=false
Session\AlternativeGlobalDLSpeedLimit=20000
Session\AlternativeGlobalUPSpeedLimit=300
Session\BandwidthSchedulerEnabled=true
Session\DisableAutoTMMByDefault=true
Session\DisableAutoTMMTriggers\CategoryChanged=false
Session\DisableAutoTMMTriggers\CategorySavePathChanged=true
Session\DisableAutoTMMTriggers\DefaultSavePathChanged=true
Session\ExcludedFileNames=
Session\GlobalDLSpeedLimit=0
Session\GlobalUPSpeedLimit=3000
Session\IgnoreSlowTorrentsForQueueing=true
Session\MaxRatioAction=0
Session\PeXEnabled=true
Session\Port=49230
Session\Preallocation=false
Session\QueueingSystemEnabled=true
Session\SlowTorrentsDownloadRate=8
Session\SlowTorrentsUploadRate=8
Session\TempPath=/home/pi/Downloads/.temp/
Session\TempPathEnabled=true
Session\TorrentContentLayout=Original
Session\TorrentExportDirectory=/home/pi/Downloads/.torrents/
Session\UseAlternativeGlobalSpeedLimit=true
Session\UseRandomPort=false

[Core]
AutoDeleteAddedTorrentFile=Never

[LegalNotice]
Accepted=true

[Meta]
MigrationVersion=4

[Network]
Proxy\OnlyForTorrents=false

[Preferences]
Advanced\RecheckOnCompletion=false
Advanced\trackerPort=9000
Advanced\trackerPortForwarding=false
Bittorrent\AddTrackers=false
Bittorrent\MaxRatioAction=0
Bittorrent\PeX=true
Connection\GlobalDLLimitAlt=3000
Connection\GlobalUPLimitAlt=300
Connection\ResolvePeerCountries=true
Connection\alt_speeds_on=false
Downloads\PreAllocation=false
Downloads\StartInPause=false
Downloads\TempPath=/home/pi/Downloads/.temp/
Downloads\TempPathEnabled=true
Downloads\TorrentExportDir=/home/pi/Downloads/.torrents/
Downloads\UseIncompleteExtension=true
DynDNS\DomainName=changeme.dyndns.org
DynDNS\Enabled=false
DynDNS\Password=
DynDNS\Service=DynDNS
DynDNS\Username=
General\Locale=fr
General\UseRandomPort=false
MailNotification\email=
MailNotification\enabled=false
MailNotification\password=
MailNotification\req_auth=true
MailNotification\req_ssl=false
MailNotification\sender=qBittorrent_notification@example.com
MailNotification\smtp_server=smtp.changeme.com
MailNotification\username=
Queueing\IgnoreSlowTorrents=true
Scheduler\Enabled=true
Scheduler\days=EveryDay
Scheduler\end_time=@Variant(\0\0\0\xf\x4J\xa2\0)
Scheduler\start_time=@Variant(\0\0\0\xf\x1\xb7t\0)
WebUI\Address=*
WebUI\AlternativeUIEnabled=false
WebUI\AuthSubnetWhitelist=192.168.0.0/16
WebUI\AuthSubnetWhitelistEnabled=true
WebUI\BanDuration=3600
WebUI\CSRFProtection=true
WebUI\ClickjackingProtection=true
WebUI\CustomHTTPHeaders=
WebUI\CustomHTTPHeadersEnabled=false
WebUI\HTTPS\CertificatePath=
WebUI\HTTPS\Enabled=false
WebUI\HTTPS\KeyPath=
WebUI\HostHeaderValidation=true
WebUI\LocalHostAuth=false
WebUI\MaxAuthenticationFailCount=5
WebUI\Password_PBKDF2="@ByteArray(Z5uwO2vnTYjMmNA92LSAFA==:axTof+GuI9CcSJG+NV2qjn1e38PETSYxjVMnarjUfnE7MAfOw4gw3IJz/7mLXhXjPfs9AAw8aXetqRvHqSinQg==)"
WebUI\Port=8888
WebUI\ReverseProxySupportEnabled=false
WebUI\RootFolder=
WebUI\SecureCookie=true
WebUI\ServerDomains=*
WebUI\SessionTimeout=3600
WebUI\TrustedReverseProxiesList=
WebUI\UseUPnP=true
WebUI\Username=admin

[RSS]
AutoDownloader\DownloadRepacks=true
AutoDownloader\SmartEpisodeFilter=s(\\d+)e(\\d+), (\\d+)x(\\d+), "(\\d{4}[.\\-]\\d{1,2}[.\\-]\\d{1,2})", "(\\d{1,2}[.\\-]\\d{1,2}[.\\-]\\d{4})"
EOF

    sshfs_umount
}

####################################################################
# Nextcloud
####################################################################

# https://raspberrytips.com/install-nextcloud-raspberry-pi/

install_nextcloud()
{
    local NEXTCLOUD_URL=https://download.nextcloud.com/server/releases/latest.tar.bz2

    # NextCloud prerequisites
    ssh "$RASPI_SSH" "sudo apt install apache2 mariadb-server libapache2-mod-php"
    ssh "$RASPI_SSH" "sudo apt install php-gd php-json php-mysql php-curl php-mbstring php-intl php-imagick php-xml php-zip"
    ssh "$RASPI_SSH" "sudo apt install certbot python3-certbot-apache"
    ssh "$RASPI_SSH" "sudo apt install php-apcu php-bz2 php-gmp php-bcmath ffmpeg"

    sshfs_mount

    cat <<EOF > "$MNT"/bin/nextcloud-off
#!/bin/bash
sudo -u www-data php /var/www/html/nextcloud/occ maintenance:mode --on
EOF

    cat <<EOF > "$MNT"/bin/nextcloud-on
#!/bin/bash
sudo -u www-data php /var/www/html/nextcloud/occ maintenance:mode --off
EOF

    chmod +x "$MNT"/bin/nextcloud-{on,off}

    if ! [ -d "$MNT_ROOT"/var/www/html/nextcloud ]
    then

        # Download NextCloud
        wget -c $NEXTCLOUD_URL -O "$RASPIOS_DIR/$(basename $NEXTCLOUD_URL)"

        #echo "Copying $(basename $NEXTCLOUD_URL)"
        mkdir -p "$MNT"/Downloads
        cp -uv "$RASPIOS_DIR/$(basename $NEXTCLOUD_URL)" "$MNT"/Downloads
        echo "Extracting $(basename $NEXTCLOUD_URL)"
        # shellcheck disable=SC2029
        ssh "$RASPI_SSH" "sudo tar xjf ~/Downloads/$(basename $NEXTCLOUD_URL) -C /var/www/html"
        ssh "$RASPI_SSH" "cd /var/www/html && sudo chmod 750 nextcloud -R && sudo chown www-data:www-data nextcloud -R"

        # MySQL configuration
        echo -n "MySQL nextcloud password: "; read -r MYSQL_PASSWORD
        cat <<EOF > "$MNT_ROOT"/tmp/mysqlconf
CREATE USER 'nextcloud' IDENTIFIED BY '$MYSQL_PASSWORD';
CREATE DATABASE nextcloud;
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@localhost IDENTIFIED BY '$MYSQL_PASSWORD';
FLUSH PRIVILEGES;
quit
EOF
        ssh "$RASPI_SSH" "sudo mysql < /tmp/mysqlconf"

    fi

    # https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/background_jobs_configuration.html?highlight=cron#cron
    ssh "$RASPI_SSH" "echo '*/5 * * * * www-data php /var/www/html/nextcloud/cron.php' | sudo tee /etc/cron.d/nextcloud-cron"

    # Fix some Nextcloud warnings
    ssh "$RASPI_SSH" "sudo sed -i 's/^memory_limit.*/memory_limit = 512M/' \$(php -i | grep php.ini | head -1 | sed 's#[^/]*##' | sed 's#/cli#/apache2#')/php.ini"

    cat <<EOF
Please add the following parameters to /var/www/html/nextcloud/config/config.php

  array (
    0 => 'pi',
    1 => 'public host name',
    2 => '192.168.0.0/16',
  ),
  'default_phone_region' => 'FR'
  'memcache.local' => '\OC\Memcache\APCu'
  'enable_previews' => true,
  'enabledPreviewProviders' =>
    array (
      'OC\Preview\Movie',
      'OC\Preview\PNG',
      'OC\Preview\JPEG',
      'OC\Preview\GIF',
      'OC\Preview\BMP',
      'OC\Preview\XBitmap',
      'OC\Preview\MP3',
      'OC\Preview\MP4',
      'OC\Preview\TXT',
      'OC\Preview\MarkDown',
      'OC\Preview\PDF'
    ),
EOF
    read -r

    sshfs_umount
}

install_upnp()
{
    ssh "$RASPI_SSH" "sudo apt install minidlna"

    sshfs_mount

    mkdir -p "$MNT"/dlna

#    cat <<EOF > "$MNT_ROOT"/etc/minidlna.conf
#media_dir=A,/home/pi/Music
#media_dir=P,/home/pi/Pictures
#media_dir=V,/home/pi/Videos
#friendly_name=Raspberry Pi
#inotify=yes
#EOF
    ssh "$RASPI_SSH" "sudo sed -i 's#^media_dir=.*#media_dir=/home/dlna#' /etc/minidlna.conf"
    ssh "$RASPI_SSH" "sudo service minidlna stop"
    ssh "$RASPI_SSH" "sudo service minidlna start"
    ssh "$RASPI_SSH" "sudo update-rc.d minidlna defaults"

    sshfs_umount
}

####################################################################
# Main menu
####################################################################

menu()
{
    local choice
    choice=$(dialog --stdout --title "Remote Raspberry Pi Configuration" \
        --menu "Configuring $RASPI_SSH" 0 0 0 \
        Prepare "a RaspiOS image" \
        Flash "image" \
        Configure "with raspi-config on $RASPI_HOST" \
        Keys "from host to $RASPI_HOST" \
        Ssh "on $RASPI_HOST" \
        Browse "$RASPI_HOST with mc and sshfs" \
        Install "basic packets (zsh, git, tmux, ufw, fail2ban, dynamic DNS, SSL certificate, ...)" \
        Radicale "CalDav/CardDav server" \
        qBittorrent "small torrent downloader" \
        Apache "Web server" \
        Nextcloud "to replace evil Google services" \
        UPnP "UPnP media server" \

    )
    case "$choice" in
        Prepare)        prepare_image ;;
        Flash)          flash_image ;;
        Configure)      raspi_config ;;
        Keys)           push_keys ;;
        Ssh)            open_ssh ;;
        Browse)         mc_sshfs ;;
        Install)        install_base ;;
        Radicale)       install_radicale ;;
        qBittorrent)    install_qbittorrent ;;
        Apache)         install_apache ;;
        Nextcloud)      install_nextcloud ;;
        UPnP)           install_upnp ;;
        "")             exit ;;
    esac

    echo "Done. Press enter to return to the menu"
    read -r
}

####################################################################
# Command line arguments
####################################################################

if [ -n "$1" ]
then
    RASPI_HOST="$1"
    RASPI_SSH="pi@$1"
fi

while true
do
    menu
done
