autobuild/autobuild-setup

712 lines
20 KiB
Bash
Executable file

#!/usr/bin/env bash
# autobuild setup
# Copyright (C) 2024 rail5
# Free software (GNU Affero GPL v3)
if [[ "$(whoami)" != _autobuild ]]; then
echo "Setup must be run via autobuild -s"
exit 1
fi
local_storage_directory="/var/autobuild"
autobuild_directory="/usr/share/autobuild"
CONFIG_FILE="$local_storage_directory/config.toml"
window_title="Autobuild setup"
# Copy any updated files to $local_storage_directory/build-farm in case an upgrade took place
mkdir -p "$local_storage_directory/build-farm" 2>/dev/null
cp -ru --preserve=timestamps "${autobuild_directory:?}/build-farm/"* "${local_storage_directory:?}/build-farm/"
if [ ! -f "$CONFIG_FILE" ]; then
cp "$autobuild_directory/config.toml" "$CONFIG_FILE"
fi
BUILDFARM_SCRIPTS_FILE="$autobuild_directory/build-farm/scripts/scripts.sh"
# shellcheck source=./build-farm/scripts/scripts.sh
. "$BUILDFARM_SCRIPTS_FILE"
function setup_single_vm() {
if [[ $# != 1 ]]; then
echo "bag args to setup_single_vm" && exit
fi
local ARCH="$1" vm_is_installing="" vm_is_upgrading="" ARCH_DIRECTORY=""
ARCH_DIRECTORY="$local_storage_directory/build-farm/debian-stable-$ARCH"
cd "$ARCH_DIRECTORY" || (echo ""; echo "Local storage directory is not properly configured"; exit)
vm_is_installing=$(test -f "./installing" && echo "true" || echo "false")
vm_is_upgrading=$(test -f "./upgrading" && echo "true" || echo "false")
if [[ $vm_is_installing == true || $vm_is_upgrading == true ]]; then
dialog --title "$window_title" \
--infobox "The $ARCH VM is already being installed or upgraded" 15 55; sleep 3
return
fi
rm -f "./image.qcow"
rm -f "./preseed.cfg"
cp ../preseed.cfg ./preseed.cfg
echo "1" > "installing"
download_vm_image "$ARCH" "$ARCH_DIRECTORY" 2>/dev/null | dialog --title "$window_title" \
--progressbox "Downloading $ARCH image... (Please wait)" 15 55
preseed_vm_image "$ARCH" "$ARCH_DIRECTORY" 2>/dev/null | dialog --title "$window_title" \
--progressbox "Preparing $ARCH image... (Please wait)" 15 55
install_vm "$ARCH" "$ARCH_DIRECTORY" 2>/dev/null | dialog --title "$window_title" \
--progressbox "Installing $ARCH VM... (Please wait, this may take a while)" 15 55
rm -f "./"*.iso
rm -f "installing"
}
function setup_build_farm() {
amd64_vm_is_configured=$(test -f "$local_storage_directory/build-farm/debian-stable-amd64/image.qcow" && echo "true" || echo "false")
i386_vm_is_configured=$(test -f "$local_storage_directory/build-farm/debian-stable-i386/image.qcow" && echo "true" || echo "false")
arm64_vm_is_configured=$(test -f "$local_storage_directory/build-farm/debian-stable-arm64/image.qcow" && echo "true" || echo "false")
amd64_info_string="Not installed"
i386_info_string="Not installed"
arm64_info_string="Not installed"
if [[ $amd64_vm_is_configured == true ]]; then
amd64_info_string="Installed"
fi
if [[ $i386_vm_is_configured == true ]]; then
i386_info_string="Installed"
fi
if [[ $arm64_vm_is_configured == true ]]; then
arm64_info_string="Installed"
fi
# --checklist "prompt" width height list_height \
# "Option Tag" "Option comment" "Status"
# "Second option name" "Second option comment" "Status"
# The option is marked with '*' by default if status = "on"
{ user_choice="$(dialog --title "$window_title" \
--checklist "Which build farm VMs do you want to install?" 15 55 3 \
"amd64" "$amd64_info_string" off \
"i386" "$i386_info_string" off \
"arm64" "$arm64_info_string" off \
2>&1 1>&3 3>&- )"; } 3>&1 # Capture stderr output into user_choice variable
if [[ "$user_choice" != "" ]]; then
dialog --title "$window_title" \
--yesno "Are you sure you want to build VM(s) for $user_choice?\nThis will permanently erase & overwrite the currently-existing VMs for those architectures if they are already installed" 15 55
if [[ $? -eq 0 ]]; then
for choice in $user_choice; do
setup_single_vm "$choice"
done
else
dialog --title "$window_title" \
--infobox "Cancelled" 15 55; sleep 3
fi
fi
}
function get_list_of_debian_repos() {
find ${local_storage_directory:?}/repo/* -maxdepth 0 -type d 2>/dev/null
}
function delete_debian_repository() {
if [[ $# != 1 ]]; then
echo "bag args to create_new_debian_repository"; exit 1
fi
local repo_path="$1"
if [[ "$(dirname "$(realpath "$repo_path")")" != "${local_storage_directory:?}/repo" ]]; then
echo "Invalid path supplied to create_new_debian_repository"; exit 1
fi
dialog --title "$window_title" \
--yesno "Are you sure you would like to DELETE '$(basename "$repo_path")'?" 15 55
case $? in
0)
rm -rf "${repo_path:?}/"
return ;;
1)
return ;;
esac
}
function create_new_debian_repository() {
if [[ $# != 1 ]]; then
echo "bag args to create_new_debian_repository"; exit 1
fi
local repo_path="$1"
if [[ "$(dirname "$(realpath "$repo_path")")" != "${local_storage_directory:?}/repo" ]]; then
echo "Invalid path supplied to create_new_debian_repository"; exit 1
fi
local user_email="" user_name="" signing_key_fingerprint="" repo_url="" repo_name="" repo_friendlyname="" default_index_html_code="" repo_will_be_ghpages=false
# Get repository name
repo_name=$(basename "$repo_path")
# Get the repo "friendly name":
## Remove non-alphanumeric characters from the repo name
## And convert the remainder to lowercase
# shellcheck disable=SC2001
repo_friendlyname=$(sed 's/[^A-Za-z0-9\-]//g' <<<"$repo_name" | tr '[:upper:]' '[:lower:]')
# Ask the user for the name and email to use for the package signing key
{ user_email="$(dialog --title "$window_title" \
--inputbox "Input the EMAIL to be associated with the package GPG signing key" 15 55 \
2>&1 1>&3 3>&- )"; } 3>&1
if [[ $? -eq 1 ]]; then
# User pressed cancel
return
fi
# Check if we've already made a key with that email
if gpg --list-secret-keys "$user_email"; then
key_exists_already=true
user_name=$(gpg --with-colons -k "$user_email" | awk -F: '$1=="uid" {print $10}' | sed 's/<.\+>//')
else
key_exists_already=false
fi
if [[ $key_exists_already == false ]]; then
{ user_name="$(dialog --title "$window_title" \
--inputbox "Input the NAME to be associated with the package GPG signing key" 15 55 \
2>&1 1>&3 3>&- )"; } 3>&1
if [[ $? -eq 1 ]]; then
# User pressed cancel
return
fi
# Create the new key
autobuild -C -E "$user_email" -N "$user_name"
fi
# Get the signing key's fingerprint!
signing_key_fingerprint=$(gpg -K --with-colons "$user_email" | awk -F: '$1=="fpr" {print $10}' | head -n 1)
# Delete any old repository which may or may not exist
rm -rf "${repo_path:?}/"
# Prepare the new repository for reprepro
mkdir -p "${repo_path:?}/conf"
cat > "${repo_path:?}/conf/distributions" <<EOF
Origin: $repo_friendlyname
Label: $repo_friendlyname
Codename: unstable
Architectures: source amd64 i386 arm64
Components: main
Description: Apt repository for $repo_friendlyname
SignWith: $signing_key_fingerprint
Contents: .gz
EOF
# Ask the user for the repo URL
{ repo_url="$(dialog --title "$window_title" \
--inputbox "Input the FULL URL of the Debian Repository\nFor example: https://my.site.com/path/to/repo" 15 55 \
2>&1 1>&3 3>&- )"; } 3>&1
if [[ $? -eq 1 ]]; then
# User pressed cancel
return
fi
# Export the public key to a file
gpg --export "$signing_key_fingerprint" > "${repo_path:?}/${repo_friendlyname:?}-signing-key.gpg"
# Create the repo .list file
cat > "${repo_path:?}/${repo_friendlyname:?}.list" <<EOF
deb $repo_url unstable main
deb-src $repo_url unstable main
EOF
# Create the default index.html file for the repo
default_index_html_code=$(cat "${autobuild_directory:?}/repository/default-index.html")
default_index_html_code="${default_index_html_code//\%REPO_FRIENDLYNAME\%/"$repo_friendlyname"}"
default_index_html_code="${default_index_html_code//\%REPO_URL\%/"$repo_url"}"
cat > "${repo_path:?}/index.html" <<<"$default_index_html_code"
# Will this be some web server, or GitHub Pages?
# If it's GitHub Pages, we'll have to push changes etc rather than just change files
dialog --title "$window_title" \
--yesno "Will this repository be served via GitHub Pages?" 15 55
case $? in
0)
repo_will_be_ghpages=true
;;
1)
repo_will_be_ghpages=false
;;
esac
local repo_conf_file="${repo_path:?}/autobuild_repo.conf"
echo "[repo]" > "${repo_conf_file:?}"
if [[ $repo_will_be_ghpages == true ]]; then
# Parse autobuild config.toml to get GitHub Credentials
local config_json=""
config_json=$(toml2json "$CONFIG_FILE")
GITHUB_OWNER=$(jq -r ".github.repo_owner" <<<"$config_json")
GITHUB_EMAIL=$(jq -r ".github.email" <<<"$config_json")
GITHUB_ACCESS_TOKEN=$(jq -r ".github.access_token" <<<"$config_json")
if [[ "$GITHUB_OWNER" == "" || "$GITHUB_EMAIL" == "" || "$GITHUB_ACCESS_TOKEN" == "" ]]; then
echo "ghpages = false" >> "${repo_conf_file:?}"
dialog --title "$window_title" \
--infobox "Error: Your GitHub credentials are not properly configured in /var/autobuild/config.toml\n\nBefore trying again, please ensure that that file contains:\n - Your GitHub username\n - Your GitHub email\n - Your GitHub Access Token" 15 55; sleep 7
return
fi
echo "ghpages = true" >> "${repo_conf_file:?}"
# Ask the user for the Github URL
{ ghpages_url="$(dialog --title "$window_title" \
--inputbox "Input the FULL URL of the GitHub Repository that will be used\nFor example: https://github.com/user/repository.git" 15 55 \
2>&1 1>&3 3>&- )"; } 3>&1
if [[ $? -eq 1 ]]; then
# User pressed cancel
return
fi
echo "ghpages_url = \"$ghpages_url\"" >> "${repo_conf_file:?}"
# Initialize the git repo, set origin, and push
cd "${repo_path:?}" || (echo ""; echo "Confusing and fatal error in setting up GitHub Pages repository"; exit)
# Add our necessary credentials to the URL to push (https://user:pass@github.com/etc)
local push_url="${ghpages_url/:\/\//:\/\/$GITHUB_OWNER:$GITHUB_ACCESS_TOKEN@}"
local temporary_repo_changes_directory=""
temporary_repo_changes_directory=$(mktemp --tmpdir -d)
mv -f "./"* "${temporary_repo_changes_directory:?}/"
git init
git config user.email "$GITHUB_EMAIL"
git config user.name "$GITHUB_OWNER"
git remote add origin "$ghpages_url"
git pull "$push_url" -q
mv -f "${temporary_repo_changes_directory:?}/"* "./"
rm -rf "${temporary_repo_changes_directory:?}"
git add --all
git commit -m "Initialized Autobuild GitHub Pages Debian Repository"
git branch -M main
git push "$push_url" --all # Push changes
else
echo "ghpages = false" >> "${repo_conf_file:?}"
fi
dialog --title "$window_title" \
--infobox "Your Debian Repo is now configured\n\nThe files are served in:\n$repo_path\n\nIf your repo is managed via GitHub Pages, it may take a few moments to publish." 15 55; sleep 7
}
function setup_debian_repo() {
if [[ $# != 1 ]]; then
echo "bad args to setup_debian_repo"; exit 1
fi
local repo_path="$1" selected_repo_is_ok=false selected_repo_exists_already="" new_repo_name=""
# First, check: Are we either (1) making a new repository ('blank' input), or (2) modifying one which *actually* exists?
# In other words, refuse to continue if we've been asked to modify something which doesn't exist,
# Or which doesn't exist *inside* the proper directory.
if [[ "$repo_path" == "" ]]; then
# Creating a new repo, automatically ok
selected_repo_is_ok=true
else
for repository in $(get_list_of_debian_repos); do
if [[ "$repository" == "$repo_path" ]]; then
selected_repo_is_ok=true
break
fi
done
fi
if [[ "$selected_repo_is_ok" == false ]]; then
# Quit.
return
fi
# If we're making a new repository, let's get a name for it
if [[ "$repo_path" == "" ]]; then
{ new_repo_name="$(dialog --title "$window_title" \
--inputbox "Enter a name for this new repository:" 15 55 \
2>&1 1>&3 3>&- )"; } 3>&1
case "$new_repo_name" in
("")
# User decided to cancel
return ;;
(*[!a-zA-Z0-9\-\_]*)
# Invalid characters
# Allowed characters: a-z, A-Z, 0-9, -, and _
dialog --title "$window_title" \
--infobox "Invalid repository name!\nRepo names may contain letters, numbers, hyphens (-) and underscores (_)" 15 55;
sleep 7;
return ;;
(*)
# No problem. Move on
repo_path="$local_storage_directory/repo/$new_repo_name"
esac
fi
selected_repo_exists_already=$(test -f "${repo_path:?}/autobuild_repo.conf" && echo "true" || echo "false")
if [[ "$selected_repo_exists_already" == true ]]; then
dialog --title "$window_title" \
--yesno "Selected existing repository: $(basename "$repo_path").\nWould you like to DELETE this repository?" 15 55
case $? in
0)
delete_debian_repository "$repo_path"
return ;;
1)
return ;;
esac
else
create_new_debian_repository "$repo_path"
fi
}
function setup_debian_repos() {
local next_page="" number_of_debian_repos="" menu_command_args="" iterator=1 selected_repo_path=""
# How many (and which) Debian Repos do we have set up already?
number_of_debian_repos="$(wc -l <<<"$(get_list_of_debian_repos)")"
menu_command_args=("$((number_of_debian_repos + 1))")
for repository in $(get_list_of_debian_repos); do
menu_command_args+=("$iterator" "$(basename "$repository")")
iterator=$((iterator + 1))
done
menu_command_args+=("$iterator" "Create New Repository")
{ next_page="$(dialog --title "$window_title" --menu "Debian Repositories" 15 55 "${menu_command_args[@]}" \
2>&1 1>&3 3>&- )"; } 3>&1
if [[ "$next_page" == "" ]]; then
return # User pressed cancel
fi
selected_repo_path="$(sed "$next_page"'q;d' <<<"$(get_list_of_debian_repos)")"
setup_debian_repo "$selected_repo_path" # Otherwise, get going
}
function clear_builds_directory() {
rm -rf "${local_storage_directory:?}/builds/"*
dialog --title "$window_title" \
--infobox "Cleared $local_storage_directory/builds" 15 55; sleep 3
}
function get_field_from_config_file() {
if [[ $# != 1 ]]; then
echo "bag args to get_field_config_file" && exit 1
fi
local config_json="" field="$1" result=""
config_json="$(toml2json "$CONFIG_FILE")"
case $field in
"packages")
local package_urls=""
package_urls="$(jq -r ".packages.package_urls[]" <<<"$config_json")"
result="${package_urls//\"/}"
;;
"github-owner")
result="$(jq -r ".github.repo_owner" <<<"$config_json")"
;;
"github-email")
result="$(jq -r ".github.email" <<<"$config_json")"
;;
"github-access-token")
result="$(jq -r ".github.access_token" <<<"$config_json")"
;;
"forgejo-instance")
result="$(jq -r ".forgejo.instance_url" <<<"$config_json")"
;;
"forgejo-owner")
result="$(jq -r ".forgejo.repo_owner" <<<"$config_json")"
;;
"forgejo-access-token")
result="$(jq -r ".forgejo.access_token" <<<"$config_json")"
;;
esac
echo "$result"
}
function save_change_to_config_file() {
if [[ $# != 2 ]]; then
echo "bag args to save_change_to_config_file" && exit 1
fi
local package_urls="" github_owner="" github_email="" github_access_token="" forgejo_instance="" forgejo_owner="" forgejo_access_token="" final_config_file="" field_to_edit="$1" new_value="$2"
# First, parse the current contents of the config file
## Get the package URLs
package_urls="$(get_field_from_config_file "packages")"
## Get GitHub info
github_owner="$(get_field_from_config_file "github-owner")"
github_email="$(get_field_from_config_file "github-email")"
github_access_token="$(get_field_from_config_file "github-access-token")"
## Get Forgejo info
forgejo_instance="$(get_field_from_config_file "forgejo-instance")"
forgejo_owner="$(get_field_from_config_file "forgejo-owner")"
forgejo_access_token="$(get_field_from_config_file "forgejo-access-token")"
# Receive and process changes here
case $field_to_edit in
"packages")
package_urls="$new_value"
;;
"github-owner")
github_owner="$new_value"
;;
"github-email")
github_email="$new_value"
;;
"github-access-token")
github_access_token="$new_value"
;;
"forgejo-instance")
forgejo_instance="$new_value"
;;
"forgejo-owner")
forgejo_owner="$new_value"
;;
"forgejo-access-token")
forgejo_access_token="$new_value"
;;
esac
# Save resulting variables to the config file
final_config_file="# Autobuild configuration
[packages]
package_urls = ["
while IFS= read -r url; do
if [[ "$url" != "" ]]; then
final_config_file="$final_config_file
\"$url\","
fi
done <<<"$package_urls"
final_config_file="${final_config_file::-1}" # Remove the last appended comma from the package list
final_config_file="$final_config_file
]
[github]
# Owner username
repo_owner = \"$github_owner\"
# Github Email
email = \"$github_email\"
# Access token
access_token = \"$github_access_token\"
[forgejo]
# The distribution settings assume that the repository names are the same across github/forgejo/etc
# Location of your forgejo instance
instance_url = \"$forgejo_instance\"
# Owner username
repo_owner = \"$forgejo_owner\"
# Access token
access_token = \"$forgejo_access_token\"
"
# Now that we've constructed the file, save it
echo "$final_config_file" > "${CONFIG_FILE:?}"
}
function setup_config_packages() {
temporary_file="$(mktemp)"
## Get the list of package urls
local package_url_list=""
get_field_from_config_file "packages" > "$temporary_file"
{ package_url_list="$(dialog --title "$window_title" \
--backtitle "INFO: Enter your source package Git URLs (one URL per line)" \
--max-input 99999 \
--editbox "$temporary_file" 15 55 \
2>&1 1>&3 3>&- )"; } 3>&1
## If the input is blank, the user pressed cancel
if [[ "$package_url_list" != "" ]]; then
save_change_to_config_file "packages" "$package_url_list"
fi
rm -f "${temporary_file:?}"
}
function setup_config_field_entry() {
if [[ $# != 2 ]]; then
echo "bag args to setup_config_field_entry" && exit 1
fi
local field="$1" description="$2" value=""
value="$(get_field_from_config_file "$field")"
{ value="$(dialog --title "$window_title" \
--inputbox "Enter your $description:" 15 55 \
"$value" \
2>&1 1>&3 3>&- )"; } 3>&1
if [[ $? -eq 0 ]]; then
save_change_to_config_file "$field" "$value"
fi
}
function setup_config_github() {
local next_page=""
{ next_page="$(dialog --title "$window_title" \
--menu "GitHub settings" 15 55 \
4 \
1 "GitHub Username" \
2 "GitHub Email" \
3 "GitHub Access Token" \
4 "Back" \
2>&1 1>&3 3>&- )"; } 3>&1
case $next_page in
"" | 4) # User pressed "cancel" or "Exit"
return ;;
1)
setup_config_field_entry "github-owner" "GitHub Username"
setup_config_github
;;
2)
setup_config_field_entry "github-email" "GitHub Email"
setup_config_github
;;
3)
setup_config_field_entry "github-access-token" "GitHub Access Token"
setup_config_github
;;
esac
}
function setup_config_forgejo() {
local next_page=""
{ next_page="$(dialog --title "$window_title" \
--menu "Forgejo settings" 15 55 \
4 \
1 "Forgejo Instance URL" \
2 "Forgejo Username" \
3 "Forgejo Access Token" \
4 "Back" \
2>&1 1>&3 3>&- )"; } 3>&1
case $next_page in
"" | 4) # User pressed "cancel" or "Exit"
return ;;
1)
setup_config_field_entry "forgejo-instance" "Forgejo Instance URL"
setup_config_forgejo
;;
2)
setup_config_field_entry "forgejo-owner" "Forgejo Username"
setup_config_forgejo
;;
3)
setup_config_field_entry "forgejo-access-token" "Forgejo Access Token"
setup_config_forgejo
;;
esac
}
function setup_config_menu() {
local next_page=""
{ next_page="$(dialog --title "$window_title" \
--menu "Edit config" 15 55 \
4 \
1 "Packages" \
2 "GitHub settings" \
3 "Forgejo settings" \
4 "Exit" \
2>&1 1>&3 3>&- )"; } 3>&1
case $next_page in
"" | 4) # User pressed "cancel" or "Exit"
return ;;
1)
setup_config_packages
setup_config_menu
;;
2)
setup_config_github
setup_config_menu
;;
3)
setup_config_forgejo
setup_config_menu
;;
esac
}
function setup_main_menu() {
local next_page=""
{ next_page="$(dialog --title "$window_title" \
--menu "This menu can be reached anytime with 'autobuild -s'" 15 55 \
5 \
1 "Edit config" \
2 "Install build farm" \
3 "Configure Debian repositories" \
4 "Clear 'builds' directory" \
5 "Exit" \
2>&1 1>&3 3>&- )"; } 3>&1 # Capture stderr output into next_page variable
# 'dialog' writes user responses to stderr
case $next_page in
"" | 5) # User pressed "cancel" or "Exit"
exit ;;
1)
setup_config_menu
setup_main_menu
;;
2)
setup_build_farm
setup_main_menu
;;
3)
setup_debian_repos
setup_main_menu
;;
4)
clear_builds_directory
setup_main_menu
;;
esac
}
setup_main_menu