#!/bin/bash

RASPI_HOST=raspberrypi.local
RASPI_USER=pi
RASPI_SSH=$RASPI_USER@$RASPI_HOST

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

eval "$(luax env ~/conf.toml)"

####################################################################
# 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-2025-12-04/2025-12-04-raspios-trixie-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
    local ROOTFS=$RASPIOS_DIR/rootfs
    mkdir -p $BOOT $ROOTFS
    echo "Mounting $IMG to activate Wifi and SSH on first boot"
    # See fdisk -l $IMG to get is boot partition offset
    # shellcheck disable=SC2155
    local BOOT_OFFSET=$(fdisk -l "$IMG" | awk '$1~/img1/{print $2}')
    # shellcheck disable=SC2155
    local ROOTFS_OFFSET=$(fdisk -l "$IMG" | awk '$1~/img2/{print $2}')
    sudo mount -t vfat -o loop,offset=$((BOOT_OFFSET*512)) "$IMG" $BOOT
    sudo mount -t vfat -o loop,offset=$((ROOTFS_OFFSET*512)) "$IMG" $ROOTFS
    sudo touch $BOOT/ssh
    echo "$RASPI_HOST" | sudo tee $ROOTFS/etc/hostname > /dev/null
    sudo sed -i "s/raspberrypi/$RASPI_HOST/" $ROOTFS/etc/hosts
    echo -n "$RASPI_USER password: "; read -r RASPI_PASSWORD
    echo "$RASPI_USER:$(openssl passwd -6 "$RASPI_PASSWORD")" | sudo tee $BOOT/userconf > /dev/null
    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
    sudo umount $ROOTFS
    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/$RASPI_USER" "$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()
{
    mc . "$MNT"
}

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

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

    ssh "$RASPI_SSH" "mkdir -p ~/.local"
    ssh "$RASPI_SSH" "curl -sSL cdelord.fr/install-all.sh | sh"
    # shellcheck disable=SC2088
    ssh "$RASPI_SSH" "~/.local/bin/luax postinstall"

    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\""

    cp files/bash_aliases "$MNT"/.bash_aliases
    cp files/tmux.conf "$MNT"/.tmux.conf
    cp files/vifmrc "$MNT"/.config/vifm/vifmrc
    cp files/christophe.vifm "$MNT"/.config/vifm/colors/christophe.vifm

    mkdir -p "$MNT"/bin
    cp files/status "$MNT"/bin/status
    chmod +x "$MNT"/bin/status

    # periodic backup with timeshift
    cp files/timeshift-check "$MNT"/bin/timeshift-check
    chmod +x "$MNT"/bin/timeshift-check
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "echo '0 3 * * * $RASPI_USER /home/$RASPI_USER/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 \
    "

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

    # Reboot once a month at 2h
    # -shellcheck disable=SC2029
    #ssh "$RASPI_SSH" "echo '0 2 1 * * root /sbin/reboot' | sudo tee /etc/cron.d/reboot"
}

####################################################################
# Forgejo
####################################################################

install_forgejo()
{
    # 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 systemctl stop forgejo.service"
    ssh "$RASPI_SSH" "sudo install /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 sqlite3"
    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"
}

####################################################################
# Caddy
####################################################################

install_caddy()
{
    ssh "$RASPI_SSH" "sudo apt install debian-keyring debian-archive-keyring apt-transport-https fail2ban rsyslog ufw"

    #######################################################
    # Caddy
    #######################################################

    ssh "$RASPI_SSH" "sudo systemctl stop caddy"

    ssh "$RASPI_SSH" "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg"
    ssh "$RASPI_SSH" "curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list"
    ssh "$RASPI_SSH" "sudo apt update"
    ssh "$RASPI_SSH" "sudo apt install caddy"

    ssh "$RASPI_SSH" "id www-data || sudo adduser --system --group --no-create-home www-data"

    ssh "$RASPI_SSH" "sudo usermod -aG www-data \$USER"

    ssh "$RASPI_SSH" "sudo mkdir -p /var/www /var/www/log"
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo chown -R www-data:www-data /var/www"
    ssh "$RASPI_SSH" "sudo chmod -R 775 /var/www"
    ssh "$RASPI_SSH" "sudo chmod g+s /var/www"

    cat <<EOF > "$MNT"/Caddyfile
# /etc/caddy/Caddyfile

# https://doc.simschab.cloud/docs/system/caddy-bot-protection.html
(bot-protection) {
    @_bots {
        path_regexp wp_paths ^/(wp-admin|wp-content|wp-includes|wp-.*\.php|xmlrpc\.php|\.env(\..*)?$|phpinfo.*|\.git.*|\.aws.*|\.htaccess|\.DS_Store|\.vscode|\.idea|\.editorconfig|composer\.(json|lock)|package(-lock)?\.json|yarn\.lock|docker-compose\.ya?ml|application\.properties|settings\.py|config\.env|.*\.(bak|sql|ini|log|conf|yml|xml|old))$
    }
    respond @_bots "Access denied" 403
}
EOF

    for i in $(seq 10); do
        local server=PICFG_CADDY_${i}_SERVER
        local root=PICFG_CADDY_${i}_ROOT
        local redir=PICFG_CADDY_${i}_REDIRECTIONS
        if [ -n "${!server}" ]; then
            caddyconf "${!server}" "${!root}" "${!redir}"
        fi
    done

    ssh "$RASPI_SSH" "sudo mv -f Caddyfile /etc/caddy/Caddyfile; sudo chown root:root /etc/caddy/Caddyfile"

    cp files/caddylog "$MNT/bin/caddylog"
    ssh "$RASPI_SSH" "sudo chmod +x bin/caddylog"

    ssh "$RASPI_SSH" "sudo systemctl restart caddy"

    # Firewall
    ssh "$RASPI_SSH" "
        sudo ufw --force 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\"
        sudo ufw allow \"WWW Full\"
        sudo ufw --force enable
        sudo ufw status verbose numbered
    "

    #######################################################
    # Various tools
    #######################################################

    install_ipd

}

caddyconf()
{
    local server="$1"
    local root="$2"
    local redir="${3/#\~/$HOME}"
    local redirections=$(cat "$redir" 2>/dev/null | base64)

    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo mkdir -p /var/www/$root"
    cat >> "$MNT"/Caddyfile <<EOF

$server {
    import bot-protection
    root * /var/www/$root
$(echo "$redirections" | base64 -d)
    file_server
    handle_errors 404 {
        rewrite * /404.html
        file_server
    }
    log {
        level INFO
        output file /var/www/log/access-$server.log {
            roll_size 10MiB
            roll_keep 5
        }
    }
}
EOF
}

####################################################################
# Dynamic IP
####################################################################

install_ipd()
{
    test -n "$IPD_INSTALLED" && return
    IPD_INSTALLED=true

    ssh "$RASPI_SSH" "sudo apt install libcurl4-openssl-dev"

    ssh "$RASPI_SSH" "sudo systemctl stop ipd"

    mkdir -p "$MNT"/bin
    cp files/ipd.c "$MNT"/ipd.c
    ssh "$RASPI_SSH" "gcc ipd.c -o bin/ipd -lpthread -lcurl && rm ipd.c"

    cat > "$MNT"/ipd.service <<EOF
[Unit]
Description=IP Daemon
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=$RASPI_USER
ExecStart=/home/$RASPI_USER/bin/ipd
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF
    ssh "$RASPI_SSH" "sudo mv -f ipd.service /etc/systemd/system/; sudo chown root:root /etc/systemd/system/ipd.service"

    ssh "$RASPI_SSH" "sudo systemctl daemon-reload"
    ssh "$RASPI_SSH" "sudo systemctl enable ipd"
    ssh "$RASPI_SSH" "sudo systemctl restart ipd"

}

####################################################################
# UPnP
####################################################################

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

    mkdir -p "$MNT"/dlna/{Music,Pictures,Videos}

    ssh "$RASPI_SSH" "sudo useradd -g minidlna --system --no-create-home --shell /usr/sbin/nologin minidlna"
    #ssh "$RASPI_SSH" "sudo groupadd --system minidlna"
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo usermod -a -G minidlna $RASPI_USER"
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo chmod a+rx /home/$RASPI_USER"
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo chmod -R a+rx /home/$RASPI_USER/dlna"

    cat <<EOF > "$MNT_ROOT"/tmp/minidlna.conf
media_dir=A,/home/$RASPI_USER/dlna/Music
media_dir=P,/home/$RASPI_USER/dlna/Pictures
media_dir=V,/home/$RASPI_USER/dlna/Videos
log_dir=/var/log/minidlna
port=8200
inotify=yes
notify_interval=895
EOF
    ssh "$RASPI_SSH" "sudo systemctl stop minidlna"
    ssh "$RASPI_SSH" "sudo mv -f /tmp/minidlna.conf /etc/minidlna.conf"
    ssh "$RASPI_SSH" "sudo systemctl start minidlna"
    ssh "$RASPI_SSH" "sudo systemctl enable minidlna"
}

####################################################################
# Forge Gigabit ethernet on Raspberry Pi 4
####################################################################

# TODO: doesn't work, eth0 is still set to 100Mb/s after a while

force_gigabit()
{
    local IFACE="eth0"

    local MODEL
    MODEL=$(ssh "$RASPI_SSH" "tr -d '\0' < /proc/device-tree/model")

    if [[ "$MODEL" != *"Raspberry Pi 4"* ]]
    then
        echo "Not a Rasperry Pi 4"
        return
    fi

    # Disable USB energy saving
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "echo on | sudo tee /sys/class/net/$IFACE/device/power/control >/dev/null"

    # Force Gigabit
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo ethtool -s $IFACE speed 1000 duplex full autoneg on"

    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo ethtool $IFACE | grep Speed"

    cat <<EOF > "$MNT_ROOT/tmp/force-gigabit.service"
[Unit]
Description=Force 1Gbps Ethernet on Raspberry Pi 4
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'echo on > /sys/class/net/$IFACE/device/power/control && /usr/sbin/ethtool -s $IFACE speed 1000 duplex full autoneg on'

[Install]
WantedBy=multi-user.target
EOF
    ssh "$RASPI_SSH" "sudo mv -f /tmp/force-gigabit.service /etc/systemd/system/force-gigabit.service"
    ssh "$RASPI_SSH" "sudo systemctl daemon-reload; sudo systemctl enable force-gigabit.service"
}

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

install_radicale()
{
    ssh "$RASPI_SSH" "sudo apt install python3-pip python3-venv"
    ssh "$RASPI_SSH" "sudo mkdir -p /opt/radicale"
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo chown -R $RASPI_USER:$RASPI_USER /opt/radicale"
    ssh "$RASPI_SSH" "python -m venv /opt/radicale"
    ssh "$RASPI_SSH" ". /opt/radicale/bin/activate; pip install radicale"

    ssh "$RASPI_SSH" "sudo systemctl stop radicale"

    # shellcheck disable=SC2029
    ssh "$RASPI_USER" "sudo mkdir -p /etc/radicale; sudo chown -R $RASPI_USER:$RASPI_USER /etc/radicale"
    cat <<\EOF > "$MNT"/config
[server]
hosts = 0.0.0.0:5232

[storage]
filesystem_folder = /var/lib/radicale/collections

[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = md5
EOF
    ssh "$RASPI_USER" "sudo mv -f config /etc/radicale/config"
    # shellcheck disable=SC2029
    ssh "$RASPI_USER" "sudo mkdir -p /var/lib/radicale/collections; sudo chown -R $RASPI_USER:$RASPI_USER /var/lib/radicale/collections"
    htpasswd -mcb "$MNT"/users "$RASPI_USER" "$RADICALE_PASSWORD"
    # shellcheck disable=SC2029
    ssh "$RASPI_SSH" "sudo mv -f users /etc/radicale; sudo chown $RASPI_USER:$RASPI_USER /etc/radicale/users"
    cat <<EOF > "$MNT"/radicale.service
[Unit]
Description=A simple CalDAV (calendar) and CardDAV (contact) server
After=network.target

[Service]
User=$RASPI_USER
Environment="PATH=/opt/radicale/bin"
ExecStart=/opt/radicale/bin/radicale --config /etc/radicale/config
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
    ssh "$RASPI_SSH" "sudo mv -f radicale.service /etc/systemd/system/; sudo chown root:root /etc/systemd/system/radicale.service"

    ssh "$RASPI_SSH" "sudo systemctl daemon-reload"
    ssh "$RASPI_SSH" "sudo systemctl enable --now radicale"
    ssh "$RASPI_SSH" "sudo systemctl start radicale"
}

####################################################################
# 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, SSL certificate, ...)" \
        Forgejo "a local Github alternative" \
        Caddy "simple web-server with automatic HTTPS" \
        Radicale "small but powerful CalDAV and CardDAV server" \
        UPnP "UPnP media server" \
        Gigabit "force Gigabit ethernet on Raspberry Pi 4" \

    )
    clear
    eval "$(luax env ~/.myconf)"
    case "$choice" in
        Prepare)    prepare_image ;;
        Flash)      flash_image ;;
        Configure)  raspi_config ;;
        Keys)       push_keys ;;
        Ssh)        open_ssh ;;
        Browse)     sshfs_mount; mc_sshfs;          sshfs_umount ;;
        Install)    sshfs_mount; install_base;      sshfs_umount ;;
        Forgejo)    sshfs_mount; install_forgejo;   sshfs_umount ;;
        Caddy)      sshfs_mount; install_caddy;     sshfs_umount ;;
        Radicale)   sshfs_mount; install_radicale;  sshfs_umount ;;
        UPnP)       sshfs_mount; install_upnp;      sshfs_umount ;;
        Gigabit)    sshfs_mount; force_gigabit;     sshfs_umount ;;
        "")         exit ;;
    esac

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

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

if [ -n "$1" ]
then
    case "$1" in
        *@*)
            RASPI_USER="${1%@*}"
            RASPI_HOST="${1#*@}"
            ;;
        *)
            RASPI_HOST="$1"
            ;;
    esac
    RASPI_SSH="$RASPI_USER@$RASPI_HOST"
fi

while true
do
    menu
done
