#!/bin/bash

# Enable strict mode
set -eo pipefail

SCRIPT=$(basename "$0")
ARCHITECTURES=("i386" "amd64" "arm64" "armhf")

# Check if DEBUG is set to true or 1, enable debug print statements if so
if [[ "$DEBUG" == "true" || "$DEBUG" == "1" ]]; then
  set -x
fi

# Default artifacts directory
ARTIFACTS_DIR="$(pwd)/deb-repo-test"

bold() {
  echo -e "\033[1m$1\033[0m"
}

header() {
  echo
  echo "================================================================================"
  bold "$1"
  echo
}

# Function to prompt user to continue to the next step with explanations
prompt_continue() {
  echo
  read -p "Press Enter to continue..."
}

# Function to start up Docker container for environment
docker_setup() {
  # If the gpg-playground container already exists, prompt the user whether to tear it down
  if [ "$(docker ps -a -q -f name=gpg-playground)" ]; then
    read -p "Do you want to teardown the existing container and recreate it? (y/n): " choice
    if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
      docker_teardown
    else
      echo "Skip setup and using existing container."
      docker exec -it gpg-playground bash
      exit 0
    fi
  fi

  echo "Setting up a new container."
  docker run -it -d --name gpg-playground -v "$(pwd)/script/debian-devel:/root/debian-devel" -w "/root" ubuntu:22.04

  # Set up MOTD to display on shell entry
  docker exec gpg-playground bash -c "cat >> /root/.bashrc << EOF

# Display MOTD
cat << "MOTD"
================================================================================
  Self-bootstrapping Debian Repository Development Environment
================================================================================

Welcome to the gpg-playground container!

Available commands:
  $SCRIPT setup      - Create initial Debian repository and packages
  $SCRIPT newkey     - Generate new signing key
  $SCRIPT deprecate  - Deprecate old signing key
  $SCRIPT teardown   - Clean up repository and configuration

Run $SCRIPT without arguments for full help.

================================================================================
MOTD
EOF"

  docker exec -it gpg-playground bash
}

# Function to handle the setup process
setup() {
  header "Step 1: Installing prerequisites"

  if ! command -v sudo &> /dev/null; then
    # This is to make sure the script runs on both containers and VMs.
    apt-get update
    apt-get install -y sudo
  fi

  sudo apt-get update
  sudo apt-get install --no-upgrade -y busybox gpg reprepro tree

  if [ ! -d "$ARTIFACTS_DIR" ]; then
    echo "Directory does not exist. Creating $ARTIFACTS_DIR..."
    mkdir -p "$ARTIFACTS_DIR"
    echo "Directory $ARTIFACTS_DIR created."
  else
    echo "Directory $ARTIFACTS_DIR already exists."
  fi

  prompt_continue
  header "Step 2: Creating a PGP key/cert that expires in 1 minute"

  cd "$ARTIFACTS_DIR"
  echo "%echo Generating an example PGP key
Key-Type: RSA
Key-Length: 4096
Name-Real: example-key-1
Name-Email: example@example.com
Expire-Date: seconds=$((1 * 60))
%no-ask-passphrase
%no-protection
%commit" > example-pgp-key-1.batch

  mkdir -m 700 -p "$ARTIFACTS_DIR/temp-gpg-home"
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --no-tty --batch --gen-key example-pgp-key-1.batch
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --list-keys
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --armor --export-secret-keys > pgp-key.private # ASCII version
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --armor --export > pgp-key.public # ASCII version
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --export > pgp-key.gpg # Binary version

  KEY1_FINGERPRINT="$(GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --list-keys --list-options show-only-fpr-mbox | cut -f1 -d' ' | head -n 1)"
  echo -n "$KEY1_FINGERPRINT" > "$ARTIFACTS_DIR/key1-fingerprint"

  echo "Generated PGP key with fingerprint: $KEY1_FINGERPRINT"

  prompt_continue
  header "Step 3: Creating the archive-keyring deb file"

  mkdir -p "$ARTIFACTS_DIR/example-archive-keyring_0.0.1-1_all"
  cd "$ARTIFACTS_DIR/example-archive-keyring_0.0.1-1_all"
  mkdir -p usr/share/keyrings
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --export > usr/share/keyrings/example-archive-keyring.gpg
  mkdir -p etc/apt/sources.list.d
  echo -n "# Created by example-archive-keyring package
deb [arch=$(echo "${ARCHITECTURES[@]}" | sed 's/ /,/g') allow-insecure=yes signed-by=/usr/share/keyrings/example-archive-keyring.gpg] http://127.0.0.1:8000/apt-repo stable main
" > etc/apt/sources.list.d/example.list

  # Add the preferences file to enable automatic upgrades for the archive keyring package.
  # The `Pin-Priority: 100` ensures that the package is considered for upgrades.
  #
  # See:
  # - https://wiki.debian.org/DebianRepository/UseThirdParty#Standard_pinning
  # - https://wiki.debian.org/DebianRepository/UseThirdParty#Certificate_rollover_and_updates
  mkdir -p etc/apt/preferences.d
  echo -n "# Created by example-archive-keyring package
Package: example-archive-keyring
Pin: origin example.com
Pin-Priority: 100
" > etc/apt/preferences.d/example.pref

  mkdir -p DEBIAN
  # The "Section:" and "Priority:" values are to make the deb files similar to what
  # we build in our CI. Also, omitting them from here will result in reprepro failure.
  echo -n "Package: example-archive-keyring
Version: 0.0.1
Maintainer: example <example@example.com>
Architecture: all
Section:
Priority: optional
Homepage: http://example.com
Description: Example archive keyring
" > DEBIAN/control
  echo "Control file for example-archive-keyring created at $ARTIFACTS_DIR/example-archive-keyring_0.0.1-1_all/DEBIAN/control"

  cd "$ARTIFACTS_DIR"
  dpkg --build example-archive-keyring_0.0.1-1_all
  echo "Deb package created: example-archive-keyring_0.0.1-1_all.deb"

  # To inspect the .deb package:
  # dpkg-deb --info example-archive-keyring_0.0.1-1_all.deb
  # dpkg-deb --contents example-archive-keyring_0.0.1-1_all.deb

  prompt_continue
  header "Step 4: Creating the hello-world deb files for supported architectures"

  for arch in "${ARCHITECTURES[@]}"; do
    mkdir -p "$ARTIFACTS_DIR/hello-world_0.0.1-1_$arch"
    cd "$ARTIFACTS_DIR/hello-world_0.0.1-1_$arch"

    mkdir -p usr/bin
    cat << EOF > usr/bin/hello-world
#!/bin/bash
echo "hello world (packaged for $arch)"
EOF
    chmod +x usr/bin/hello-world

    # Create DEBIAN control file
    mkdir -p DEBIAN
    # The "Section:" and "Priority:" values are to make the deb files similar to what
    # we build in our CI. Also, omitting them from here will result in reprepro failure.
    echo "Package: hello-world
Version: 0.0.1
Maintainer: example <example@example.com>
Architecture: $arch
Section:
Priority: optional
Homepage: http://example.com
Description: A statically compiled Go program that prints hello" > DEBIAN/control

    # Build the deb package
    cd "$ARTIFACTS_DIR"
    dpkg --build hello-world_0.0.1-1_$arch
    echo "Deb package created: hello-world_0.0.1-1_$arch.deb"

    # Inspect the deb package
    # dpkg-deb --info hello-world_0.0.1-1_$arch.deb
    # dpkg-deb --contents hello-world_0.0.1-1_$arch.deb
  done

  prompt_continue
  header "Step 5: Creating apt repository using reprepro"

  mkdir -p "$ARTIFACTS_DIR/apt-repo"

  # make reprepro config template file
  cat <<EOF > "$ARTIFACTS_DIR/distributions.template"
Origin: Example Repository
Label: Example
Codename: stable
Architectures: ${ARCHITECTURES[@]}
Components: main
Description: An example software repository
SignWith: <FINGERPRINT-PLACEHOLDER>
EOF

  # Create a temp directory before calling reprepro. This is to mimic the stateless
  # implementation we have in our CI workflow. Further reprepro calls will have a
  # separate temp directory.
  mkdir -p "$ARTIFACTS_DIR/temp-reprepro-1/conf"
  cd "$ARTIFACTS_DIR/temp-reprepro-1/conf"
  cp "$ARTIFACTS_DIR/distributions.template" distributions
  sed -i "s/<FINGERPRINT-PLACEHOLDER>/$KEY1_FINGERPRINT/g" distributions

  cd "$ARTIFACTS_DIR/temp-reprepro-1"
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" reprepro includedeb stable "$ARTIFACTS_DIR/example-archive-keyring_0.0.1-1_all.deb"
  for arch in "${ARCHITECTURES[@]}"; do
    GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" reprepro includedeb stable "$ARTIFACTS_DIR/hello-world_0.0.1-1_$arch.deb"
  done
  cp -a dists/ pool/ "$ARTIFACTS_DIR/apt-repo/"

  echo "This is the content of the Packages file (for amd64):"
  cat dists/stable/main/binary-amd64/Packages
  echo "! Note that there should be two entries above; hello-world and the archive-keyring package."

  prompt_continue
  header "Step 6: Hosting repository with a simple HTTP server"

  cd "$ARTIFACTS_DIR"
  busybox httpd -p 8000 # runs in background by default
  echo "HTTP server started at http://127.0.0.1:8000"

  prompt_continue
  header "Step 7: Adding apt repo entry to /etc/apt/sources.list.d"

  # Install the trust anchor (public key) on the host system for later verification.
  #
  # Note: This trust anchor installation is the manual step that should be done when the
  # package is installed for the first time. That is we should still include this step
  # in our installation docs as we already have it. Future automatic upgrades of the
  # archive-keyring package will update the installed keyring, so the user would not
  # need to do this step ever again.

  sudo mkdir -p -m 755 /usr/share/keyrings
  sudo cp "$ARTIFACTS_DIR/pgp-key.gpg" /usr/share/keyrings/example-archive-keyring.gpg
  sudo chmod go+r /usr/share/keyrings/example-archive-keyring.gpg

  echo "deb [arch=$(echo "${ARCHITECTURES[@]}" | sed 's/ /,/g') allow-insecure=yes signed-by=/usr/share/keyrings/example-archive-keyring.gpg] http://127.0.0.1:8000/apt-repo stable main" | sudo tee /etc/apt/sources.list.d/example.list
  echo
  echo "! Note that with this config, apt allows plain HTTP comms but still verifies package signatures."
  echo "! If you wait here until the generated key expires, you'll see the next step fails due to the expired key."

  prompt_continue
  header "Step 8: Updating apt and installing the packages"

  sudo apt-get update # We don't need --allow-insecure-repositories
  sudo apt-get install -y example-archive-keyring hello-world

  echo
  bold "✅ Hello World and the archive keyring packages installed successfully."
  echo
  echo "Run 'newkey' target next to rotate the signing key."
  echo
  echo "! Note that there should be no warnings about unverified packages in the logs above."
}

# Function to handle the signing key rotation process
newkey() {
  # Verify that the current key is not yet expired
  header "Step 1: Verifying the current key is not yet expired"

  KEY1_FINGERPRINT="$(cat "$ARTIFACTS_DIR/key1-fingerprint")"

  expiry_date="$(GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --list-keys --with-colons --fingerprint $KEY1_FINGERPRINT | grep -E "^pub:" | cut -f7 -d:)"
  current_date="$(date +%s)"
  if [ "$expiry_date" -lt "$current_date" ]; then
    echo
    bold "⚠️ The key is already expired. Please 'teardown' and start over."
    exit 1
  else
    echo "Confirmed that the current signing key is still valid."
  fi

  # Generate a new key and add it to the keyring
  prompt_continue
  header "Step 2: Generating a new PGP key (expires in 15 mins) and add it to the keyring"

  # Get the list of existing keys
  existing_keys_list="$(GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --list-keys --with-colons)"

  cd "$ARTIFACTS_DIR"
  echo "%echo Generating an example PGP key
Key-Type: RSA
Key-Length: 4096
Name-Real: example-key-2
Name-Email: example@example.com
Expire-Date: seconds=$((15 * 60))
%no-ask-passphrase
%no-protection
%commit" > example-pgp-key-2.batch

  mkdir -m 700 -p "$ARTIFACTS_DIR/temp-gpg-home"
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --no-tty --batch --gen-key example-pgp-key-2.batch
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --list-keys
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --armor --export-secret-keys > pgp-key.private # ASCII version
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --armor --export > pgp-key.public # ASCII version
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --export > pgp-key.gpg # Binary version

  new_keys_list="$(GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --list-keys --with-colons)"
  added_key="$(comm -1 -3 <(echo "$existing_keys_list" | sort) <(echo "$new_keys_list" | sort))"
  KEY2_FINGERPRINT="$(echo "$added_key" | grep -E "^fpr:" | cut -f10 -d: | head -n1)"
  echo -n "$KEY2_FINGERPRINT" > "$ARTIFACTS_DIR/key2-fingerprint"

  echo "Generated PGP key with fingerprint: $KEY2_FINGERPRINT"

  prompt_continue
  header "Step 3: Generating the new archive-keyring package and updating the apt repository."

  # create a copy of the files of the previous version of the archive keyring package
  cp -r "$ARTIFACTS_DIR/example-archive-keyring_0.0.1-1_all" "$ARTIFACTS_DIR/example-archive-keyring_0.0.2-1_all"
  # update the keyring file with the new keyring (including both keys)
  cd "$ARTIFACTS_DIR/example-archive-keyring_0.0.2-1_all"
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --export > usr/share/keyrings/example-archive-keyring.gpg
  # bump the version in the debian control file
  sed -i 's/Version: 0\.0\.1/Version: 0.0.2/' DEBIAN/control

  cd "$ARTIFACTS_DIR"
  dpkg --build example-archive-keyring_0.0.2-1_all
  echo "Deb package created: example-archive-keyring_0.0.2-1_all.deb"

  # To inspect the .deb package:
  # dpkg-deb --info example-archive-keyring_0.0.2-1_all.deb
  # dpkg-deb --contents example-archive-keyring_0.0.2-1_all.deb

  # Create a new temp directory before calling reprepro.
  mkdir -p "$ARTIFACTS_DIR/temp-reprepro-2/conf"
  cd "$ARTIFACTS_DIR/temp-reprepro-2/conf"
  cp "$ARTIFACTS_DIR/distributions.template" distributions
  sed -i "s/<FINGERPRINT-PLACEHOLDER>/$KEY1_FINGERPRINT $KEY2_FINGERPRINT/g" distributions

  cd "$ARTIFACTS_DIR/temp-reprepro-2"
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" reprepro includedeb stable "$ARTIFACTS_DIR/example-archive-keyring_0.0.2-1_all.deb"
  for arch in "${ARCHITECTURES[@]}"; do
    GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" reprepro includedeb stable "$ARTIFACTS_DIR/hello-world_0.0.1-1_$arch.deb"
  done
  cp -a dists/ pool/ "$ARTIFACTS_DIR/apt-repo/"

  cd "$ARTIFACTS_DIR/apt-repo"
  echo "New packages (for amd64):"
  cat dists/stable/main/binary-amd64/Packages

  echo
  bold "✅ apt repository has been updated with the new archive keyring package."
  echo
  echo "! Note that there should be two entries above, with the archive keyring version bumped to 0.0.2."

  prompt_continue
  header "Step 4: Upgrading the archive keyring package on the host system."

  sudo apt-get update
  sudo apt-get install -y --only-upgrade example-archive-keyring

  echo
  bold "✅ archive-keyring package upgraded successfully."
  echo
  echo "Run 'deprecate' target next to deprecate the old key."
  echo
  echo "! Note that in the above logs the archive-keyring is now bumped to 0.0.2."
}

deprecate() {
  header "Step 1: Verifying the old/first key is expired"

  KEY1_FINGERPRINT="$(cat "$ARTIFACTS_DIR/key1-fingerprint")"
  KEY2_FINGERPRINT="$(cat "$ARTIFACTS_DIR/key2-fingerprint")"

  expiry_date="$(GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --list-keys --with-colons --fingerprint $KEY1_FINGERPRINT | grep -E "^pub:" | cut -f7 -d:)"
  current_date="$(date +%s)"

  if [ "$expiry_date" -gt "$current_date" ]; then
    SLEEP=$(( $expiry_date - $current_date + 2 ))
    bold "Sleeping $SLEEP seconds to allow the old/first key to expire."
    sleep $SLEEP
  fi

  prompt_continue
  header "Step 2: Generating the new archive-keyring package with the old key removed."

  # create a copy of the files of the previous version of the archive keyring package
  cp -r "$ARTIFACTS_DIR/example-archive-keyring_0.0.2-1_all" "$ARTIFACTS_DIR/example-archive-keyring_0.0.3-1_all"
  # update the keyring file with the new keyring (now with only the latest key)
  cd "$ARTIFACTS_DIR/example-archive-keyring_0.0.3-1_all"
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" gpg --export $KEY2_FINGERPRINT > usr/share/keyrings/example-archive-keyring.gpg
  # bump the version in the debian control file
  sed -i 's/Version: 0\.0\.2/Version: 0.0.3/' DEBIAN/control

  cd "$ARTIFACTS_DIR"
  dpkg --build example-archive-keyring_0.0.3-1_all
  echo "Deb package created: example-archive-keyring_0.0.3-1_all.deb"

  # To inspect the .deb package:
  # dpkg-deb --info example-archive-keyring_0.0.3-1_all.deb
  # dpkg-deb --contents example-archive-keyring_0.0.3-1_all.deb

  # Create a new temp directory before calling reprepro.
  mkdir -p "$ARTIFACTS_DIR/temp-reprepro-3/conf"
  cd "$ARTIFACTS_DIR/temp-reprepro-3/conf"
  cp "$ARTIFACTS_DIR/distributions.template" distributions
  sed -i "s/<FINGERPRINT-PLACEHOLDER>/$KEY2_FINGERPRINT/g" distributions

  cd "$ARTIFACTS_DIR/temp-reprepro-3"
  GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" reprepro includedeb stable "$ARTIFACTS_DIR/example-archive-keyring_0.0.3-1_all.deb"
  for arch in "${ARCHITECTURES[@]}"; do
    GNUPGHOME="$ARTIFACTS_DIR/temp-gpg-home" reprepro includedeb stable "$ARTIFACTS_DIR/hello-world_0.0.1-1_$arch.deb"
  done
  cp -a dists/ pool/ "$ARTIFACTS_DIR/apt-repo/"

  cd "$ARTIFACTS_DIR/apt-repo"
  echo "New packages (for amd64):"
  cat dists/stable/main/binary-amd64/Packages

  echo
  bold "✅ apt repository has been updated with the new archive keyring package."
  echo
  echo "! Note that there should be two entries above, with the archive keyring version bumped to 0.0.3."

  prompt_continue
  header "Step 3: Upgrading the archive keyring package on the host system."

  sudo apt-get update
  sudo apt-get install -y --only-upgrade example-archive-keyring

  echo
  bold "✅ archive-keyring package upgraded successfully."
  echo
  echo "! Note that in the above logs the archive-keyring is now bumped to 0.0.3."
}

# Function to handle the teardown process
teardown() {
  # Kill the HTTP server if it's running
  if pgrep -f "busybox httpd" > /dev/null; then
    header "Stopping the HTTP server..."
    pkill -f "busybox httpd"
    echo "Server stopped."
  fi

  # Check if the hello-world package is installed, and remove it safely
  if dpkg -l | grep -q "^ii  hello-world"; then
    header "Removing the hello-world package..."
    sudo apt-get remove --purge -y hello-world
    echo "Package hello-world removed."
  else
    echo "Package hello-world is not installed, skipping removal."
  fi

  # Remove the PGP keyrings
  if [ -f /etc/apt/keyrings/example-archive-keyring.gpg ]; then
    header "Removing the PGP keyring from /etc/apt/keyrings..."
    sudo rm /etc/apt/keyrings/example-archive-keyring.gpg
    echo "PGP keyring removed from /etc/apt/keyrings."
  fi

  if [ -f /usr/share/keyrings/example-archive-keyring.gpg ]; then
    header "Removing the PGP keyring from /usr/share/keyrings..."
    sudo rm /usr/share/keyrings/example-archive-keyring.gpg
    echo "PGP keyring removed from /usr/share/keyrings."
  fi

  # Kill gpg-agent
  if pgrep "^gpg-agent" > /dev/null; then
    header "Stopping the GPG agent..."
    pkill "^gpg-agent"
    echo "GPG agent stopped."
  fi

  # Remove the artifacts directory
  if [ -d "$ARTIFACTS_DIR" ]; then
    header "Removing the artifacts directory: $ARTIFACTS_DIR"
    rm -rf "$ARTIFACTS_DIR"
    echo "Artifacts directory removed."
  fi

  # Remove installed test packages
  if [ -f /etc/apt/sources.list.d/example.list ]; then
    sudo apt-get remove example-archive-keyring hello-world -y || echo "Test packages not installed, skipping removal."
  fi

  # Remove apt source remains (if not removed by the above command)
  if [ -f /etc/apt/sources.list.d/example.list ]; then
    header "Removing the repository from /etc/apt/sources.list.d..."
    sudo rm /etc/apt/sources.list.d/example.list
    echo "Repository removed."
  fi

  # Remove the preferences from preferences.d
  if [ -f /etc/apt/preferences.d/example.pref ]; then
    header "Removing the preferences from /etc/apt/preferences.d..."
    sudo rm /etc/apt/preferences.d/example.pref
    echo "Preferences removed."
  fi

  # Clean apt cache
  header "Cleaning apt cache..."
  sudo apt-get clean
  sudo apt-get update
  echo "Cleanup complete."
}

# Function to start up Docker container for environment
docker_teardown() {
  docker stop gpg-playground && docker rm gpg-playground
}

# Check for the setup or teardown argument
if [ "$1" == "docker_setup" ]; then
  docker_setup
elif [ "$1" == "setup" ]; then
  setup
elif [ "$1" == "newkey" ]; then
  newkey
elif [ "$1" == "deprecate" ]; then
  deprecate
elif [ "$1" == "teardown" ]; then
  teardown
elif [ "$1" == "docker_teardown" ]; then
  docker_teardown
else
  echo "
$(bold "Self-bootstrapping Debian repository development tool")

Usage: $SCRIPT TARGET

The following targets are typical order used to setup and exercise the GitHub CLI Debian packaging process:

- $(bold docker_setup):     create Docker container for testing environment
- $(bold setup):            create initial Debian repository, create and sign sample application and keyring archive packages
- $(bold newkey):           generate new signing key, update keyring archive package
- $(bold deprecate):        deprecate old signing key, update keyring archive package, sign new sample application package
- $(bold teardown):         clean up Debian repository and local configuration
- $(bold docker_teardown):  stop and remove Docker container
"
  exit 1
fi
