diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..63bc02ddbc474c6a43ed4622a47689aa2e58d3f4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +stages: + - deploy + +pages: + stage: deploy + image: buildpack-deps + variables: + LATEST_VERSION_URL: https://docs.funkwhale.audio/latest.txt + script: + - | + echo "Retrieving latest version from $LATEST_VERSION_URL" + funkwhale_version=$(curl -sfL $LATEST_VERSION_URL || true) + if [ -z "$funkwhale_version" ]; then + echo "Could not retrieve latest version!" + exit 1 + fi + - echo "Latest version is $funkwhale_version" + - sed -i "0,/funkwhale_version_placeholder/{s/funkwhale_version_placeholder/$funkwhale_version/}" install.sh + - mkdir -p public + - cp install.sh public/ + - cp install.sh public/index.html + - cp upgrade.sh public/upgrade.sh + + artifacts: + paths: + - public + only: + - master@funkwhale/ansible + tags: + - docker diff --git a/README.md b/README.md index 65cf620261d64874594ee6f7ecf78e96d4cb5d88..d8d18d6d48c92228465d641cf774c0f725483fdb 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Create a playbook requirements and inventory file: touch requirements.yml touch playbook.yml touch inventory.ini - ansible.cfg + touch ansible.cfg Add the following to `requirements.yml`: diff --git a/install.sh b/install.sh new file mode 100644 index 0000000000000000000000000000000000000000..e38ea748607af35a2b54a059361e77d876f3611b --- /dev/null +++ b/install.sh @@ -0,0 +1,300 @@ +#!/bin/sh +set -eu + +# This script is meant for quick & easy install via: +# $ sh -c "$(curl -sSL https://get.funkwhale.audio/)" +# +# If Ansible step fails with ascii decore error, ensure you have a locale properly set on +# your system e.g apt-get install -y locales locales-all +export LANG="en_US.UTF-8" + +funkwhale_version="${FUNKWHALE_VERSION-funkwhale_version_placeholder}" +funkwhale_hostname="${FUNKWHALE_DOMAIN-}" +funkwhale_admin_email="${FUNKWHALE_ADMIN_EMAIL-}" +funkwhale_admin_username="${FUNKWHALE_ADMIN_USERNAME-}" +ansible_flags="${ANSIBLE_FLAGS- --diff}" +ansible_version="${ANSIBLE_VERSION-2.8.2}" +customize_install="${CUSTOMIZE_INSTALL-}" +skip_confirm="${SKIP_CONFIRM-}" +is_dry_run=${DRY_RUN-false} +min_python_version_major="3" +min_python_version_minor="5" +base_path="/srv/funkwhale" +ansible_conf_path="$base_path/ansible" +ansible_bin_path="$HOME/.local/bin" +ansible_funkwhale_role_version="${ANSIBLE_FUNKWHALE_ROLE_VERSION-master}" +funkwhale_systemd_after="" +total_steps="4" + +echo +setup() { + + if [ "$funkwhale_version" = 'funkwhale_version_placeholder' ]; then + echo "No FUNKWHALE_VERSION found and the script didn't include a default one." + echo "Relaunch the script with FUNKWHALE_VERSION=yourdesiredversion" + exit 1 + fi + + + yesno_prompt() { + local default="${3-}" + local __resultvar=$1 + local result="" + local options="yes/no" + if [ "$default" = "yes" ]; then + local options="YES/no" + fi + if [ "$default" = "no" ]; then + local options="yes/NO" + fi + while true + do + read -p "$2 [$options]: " result + if [ ! -z "$default" ] && [ -z "$result" ]; then + result="$default" + fi + case $result in + [Yy]* ) result="true"; break;; + [Nn]* ) result="false"; break;; + "" ) result="true"; break;; + *) echo "Please answer [y]es or [n]o";; + esac + done + eval $__resultvar="'$result'" + } + + echo + if [ -z "$funkwhale_hostname" ]; then + read -p "Enter your desired Funkwhale domain (e.g funkwhale.example): " funkwhale_hostname + fi + if [ -z "$funkwhale_admin_username" ]; then + read -p "Enter the username for the admin account (leave empty to skip account creation) " funkwhale_admin_username + fi + if [ -z "$funkwhale_admin_email" ]; then + read -p "Enter the email used for the admin user and Let's Encrypt certificate: " funkwhale_admin_email + fi + if [ -z "$customize_install" ]; then + yesno_prompt customize_install "The complete installation will setup Nginx, PostgresQL and Redis. Do you want customize what is installed?" "no" + fi + + if [ "$customize_install" = "true" ]; then + yesno_prompt funkwhale_nginx_managed 'Install and manage Nginx?' 'yes' + yesno_prompt funkwhale_database_managed 'Install and manage PostgreSQL?' 'yes' + if [ "$funkwhale_database_managed" = "false" ]; then + read -p "Enter your database configuration, (e.g postgresql://user@localhost:5432/database_name): " funkwhale_database_url + funkwhale_systemd_after="funkwhale_systemd_after: " + fi + yesno_prompt funkwhale_redis_managed 'Install and manage Redis?' 'yes' + if [ "$funkwhale_redis_managed" = "false" ]; then + read -p "Enter your redis configuration, (e.g redis://127.0.0.1:6379/0): " funkwhale_redis_url + funkwhale_systemd_after="funkwhale_systemd_after: " + fi + else + funkwhale_nginx_managed="true" + funkwhale_database_managed="true" + funkwhale_redis_managed="true" + fi + + + echo + echo "Installation summary:" + echo + echo "- version: $funkwhale_version" + echo "- domain: $funkwhale_hostname" + echo "- Admin username: $funkwhale_admin_username" + echo "- Admin email: $funkwhale_admin_email" + echo "- Manage nginx: $funkwhale_nginx_managed" + echo "- Manage redis: $funkwhale_redis_managed" + if [ "$funkwhale_redis_managed" = "false" ]; then + echo " - Custom redis configuration: $funkwhale_redis_url" + fi + echo "- Manage PostgreSQL: $funkwhale_database_managed" + if [ "$funkwhale_database_managed" = "false" ]; then + echo " - Custom PostgreSQL configuration: $funkwhale_database_url" + fi + + if [ "$is_dry_run" = "true" ]; then + echo "Running with dry-run mode, your system will be not be modified (apart from Ansible installation)." + echo "Rerun with DRY_RUN=false for a real install." + fi; + echo + if [ -z "$skip_confirm" ]; then + yesno_prompt proceed 'Do you want to proceed with the installation?' 'yes' + case $proceed in + [Nn]* ) echo "Aborting script."; exit 1;; + *) echo "Please answer yes or no";; + esac + fi + +} + +semver_parse() { + major="${1%%.*}" + minor="${1#$major.}" + minor="${minor%%.*}" + patch="${1#$major.$minor.}" + patch="${patch%%[-.]*}" +} + +get_python_version () { + python_version="" + if [ -x "$(command -v python3)" ]; then + python_version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:3])))') + fi +} +has_sufficient_python_version() { + python_ok="" + semver_parse "$1" + if [ "$major" -ge "$min_python_version_major" ] && [ "$minor" -ge "$min_python_version_minor" ]; then + python_ok="true" + fi +} +install_packages() { + package_manager="apt-get" + if [ "$package_manager" = 'apt-get' ]; then + $package_manager update + $package_manager install -y "$@" + fi +} + +do_install() { + setup + echo '[Beginning installation]' + echo "[1/$total_steps] Checking python3 version" + get_python_version + should_install_python="1" + if [ ! -z "$python_version" ]; then + has_sufficient_python_version "$python_version" + if [ ! -z "$python_ok" ]; then + echo "[1/$total_steps] $python_version with sufficient version found, skipping" + should_install_python="0" + fi + fi + if [ -z "$python_version" ]; then + echo "[1/$total_steps] Python3 not found, installing" + install_packages python3 python3-pip + + elif [ "$should_install_python" -eq "1" ]; then + echo "[1/$total_steps] Python $python_version found, $min_python_version_major.$min_python_version_minor needed, upgrading" + install_packages python3 python3-pip + else + echo "[1/$total_steps] Found Python $python_version, skipping upgrade" + fi + init_ansible + if [ "$is_dry_run" = "false" ]; then + run_playbook + configure_server + fi + if [ "$is_dry_run" = "true" ]; then + echo "Rerun with DRY_RUN=false for a real install." + else + echo "Done!" + echo " - Everything was installed in the $base_path directory" + if [ ! -z "$funkwhale_admin_username" ]; then + echo " - Created a superuser account with username $funkwhale_admin_username and the password you supplied" + fi + if [ "$funkwhale_nginx_managed" = "true" ]; then + echo " - Your Funkwhale server is now up and running at https://$funkwhale_hostname" + else + echo " - To complete the installation, you need to setup an Nginx or Apache2 reverse proxy: https://docs.funkwhale.audio/installation/index.html#reverse-proxy" + fi + + echo " - You can run management commands by calling $base_path/manage, e.g $base_path/manage import_files" + fi + +} + +init_ansible() { + echo "[2/$total_steps] Installing ansible dependencies..." + install_packages curl git python3-pip python3-apt sudo locales locales-all + echo "[2/$total_steps] Installing Ansible..." + pip3 install --user ansible=="$ansible_version" psycopg2-binary + + echo "[2/$total_steps] Creating ansible configuration files in $ansible_conf_path..." + mkdir -p "$ansible_conf_path" + cd "$ansible_conf_path" + cat <<EOF >requirements.yml +- src: git+https://dev.funkwhale.audio/funkwhale/ansible + name: funkwhale + version: $ansible_funkwhale_role_version +EOF + cat <<EOF >ansible.cfg +[defaults] +# Needed to use become with unprevileged users, +# see https://docs.ansible.com/ansible/latest/user_guide/become.html#becoming-an-unprivileged-user +#allow_world_readable_tmpfiles=true +EOF + cat <<EOF >playbook.yml +- hosts: funkwhale_servers + roles: + - role: funkwhale + funkwhale_hostname: $funkwhale_hostname + funkwhale_version: $funkwhale_version + funkwhale_letsencrypt_email: $funkwhale_admin_email + funkwhale_nginx_managed: $funkwhale_nginx_managed + funkwhale_redis_managed: $funkwhale_redis_managed + funkwhale_database_managed: $funkwhale_database_managed +EOF + if [ "$funkwhale_database_managed" = "false" ]; then + cat <<EOF >>playbook.yml + funkwhale_database_url: $funkwhale_database_url +EOF + fi + if [ "$funkwhale_redis_managed" = "false" ]; then + cat <<EOF >>playbook.yml + funkwhale_redis_url: $funkwhale_redis_url +EOF + fi + if [ ! -z "$funkwhale_systemd_after" ]; then + cat <<EOF >>playbook.yml + $funkwhale_systemd_after +EOF + fi + cat <<EOF >inventory.ini +[funkwhale_servers] +127.0.0.1 ansible_connection=local ansible_python_interpreter=/usr/bin/python3 +EOF + echo "[2/$total_steps] Downloading Funkwhale playbook dependencies" + $ansible_bin_path/ansible-galaxy install -r requirements.yml -f + +} +run_playbook() { + cd "$ansible_conf_path" + echo "[3/$total_steps] Installing Funkwhale using ansible playbook in $ansible_conf_path..." + playbook_command="$ansible_bin_path/ansible-playbook -i $ansible_conf_path/inventory.ini $ansible_conf_path/playbook.yml -u root $ansible_flags" + if [ "$is_dry_run" = "true" ]; then + playbook_command="$playbook_command --check" + echo "[3/$total_steps] Skipping playbook because DRY_RUN=true" + return 0 + fi + + echo "[3/$total_steps] Applying playbook with:" + echo " $playbook_command" + $playbook_command +} + +configure_server() { + echo "[4/$total_steps] Running final server configuration…" + echo "[4/$total_steps] Creating simple management script at $base_path/manage" + cat <<EOF >$base_path/manage +#!/bin/sh +set -eu +sudo -u funkwhale -E $base_path/virtualenv/bin/python $base_path/api/manage.py \$@ +EOF + chmod +x $base_path/manage + if [ -z "$funkwhale_admin_username" ]; then + echo "[4/$total_steps] Skipping superuser account creation" + else + echo "[4/$total_steps] Creating superuser account…" + echo " Please input the password for the admin account password" + LOGLEVEL=error sudo -u funkwhale -E $base_path/virtualenv/bin/python \ + $base_path/api/manage.py createsuperuser \ + --email $funkwhale_admin_email \ + --username $funkwhale_admin_username \ + -v 0 + fi +} + +# wrapped up in a function so that we have some protection against only getting +# half the file during "curl | sh" +do_install diff --git a/tasks/packages.yml b/tasks/packages.yml index fc2d1b87c466c37f1ca00a6372dd333122492118..7fecfc0a178b5d386c2711f53ff434cdd91b29e6 100644 --- a/tasks/packages.yml +++ b/tasks/packages.yml @@ -6,7 +6,7 @@ - "python3" - "python3-dev" - "python3-pip" - - "python-virtualenv" # for ansible + - "python-virtualenv" # for ansible - "python3-virtualenv" - "libldap2-dev" - "libsasl2-dev" @@ -19,5 +19,8 @@ - "libmagic-dev" - "libpq-dev" - "postgresql-client" + # Needed to build cryptography wheel sometimes + - libssl-dev + - libffi-dev # not strictly needed but useful - "curl" diff --git a/upgrade.sh b/upgrade.sh new file mode 100755 index 0000000000000000000000000000000000000000..0ab0d868790e156a54f3c1e11be4b804410398c8 --- /dev/null +++ b/upgrade.sh @@ -0,0 +1,110 @@ +#!/bin/sh +set -eu + +# This script is meant for quick & easy upgrade of Funkwhale pods installed via: +# $ sh -c "$(curl -sSL https://get.funkwhale.audio/)" +# +# If Ansible step fails with ascii decore error, ensure you have a locale properly set on +# your system e.g apt-get install -y locales locales-all +export LANG="en_US.UTF-8" + +funkwhale_version="${1-}" +ansible_flags="${ANSIBLE_FLAGS- --diff}" +skip_confirm="${SKIP_CONFIRM-}" +is_dry_run=${DRY_RUN-false} +base_path="/srv/funkwhale" +ansible_conf_path="$base_path/ansible" +ansible_bin_path="$HOME/.local/bin" +ansible_funkwhale_role_version="${ANSIBLE_FUNKWHALE_ROLE_VERSION-master}" +funkwhale_systemd_after="" +total_steps="4" +latest_version_url="https://docs.funkwhale.audio/latest.txt" +echo +yesno_prompt() { + local default="${3-}" + local __resultvar=$1 + local result="" + local options="yes/no" + if [ "$default" = "yes" ]; then + local options="YES/no" + fi + if [ "$default" = "no" ]; then + local options="yes/NO" + fi + while true + do + read -p "$2 [$options]: " result + if [ ! -z "$default" ] && [ -z "$result" ]; then + result="$default" + fi + case $result in + [Yy]* ) result="true"; break;; + [Nn]* ) result="false"; break;; + "" ) result="true"; break;; + *) echo "Please answer [y]es or [n]o";; + esac + done + eval $__resultvar="'$result'" +} + +do_upgrade() { + echo '[Beginning upgrade]' + playbook_path="$ansible_conf_path/playbook.yml" + echo "[1/$total_steps] Retrieving currently installed version from $playbook_path" + current_version=$(grep 'funkwhale_version:' $playbook_path | xargs | sed 's/funkwhale_version: //' | xargs) + if [ -z $current_version ]; then + echo "Could not retrieve the current installed version from $playbook_path, the file should exist and contain a 'funkwhale_version:' line" + exit 1 + fi + if [ -z "$funkwhale_version" ]; then + echo "[1/$total_steps] No target version specified, retrieving latest version from $latest_version_url" + funkwhale_version=$(curl -sfL $latest_version_url || true) + if [ -z "$funkwhale_version" ]; then + echo "Could not retrieve latest version, ensure you have network connectivity" + exit 1 + fi + fi + echo + echo "Upgrade summary:" + echo + echo "- Current version: $current_version" + echo "- Target version: $funkwhale_version" + if [ "$is_dry_run" = "true" ]; then + echo "- Running with dry-run mode, your system will be not be modified" + echo " Rerun with DRY_RUN=false for a real upgrade." + else + echo "- Upgrading may cause temporary downtime on your pod" + fi; + echo + + if [ -z "$skip_confirm" ]; then + yesno_prompt proceed 'Do you want to proceed with the upgrade?' 'yes' + if [ "$proceed" = "false" ]; then + echo "Aborting upgrade"; + exit 1; + fi + fi + echo "[2/$total_steps] Replacing current version number in $playbook_path…" + if [ "$is_dry_run" = "false" ]; then + sed -i.bak -r "s/(funkwhale_version:)(.*)/\1 $funkwhale_version/" $playbook_path + fi + cd "$ansible_conf_path" + echo "[3/$total_steps] Upgrading ansible dependencies..." + playbook_command="$ansible_bin_path/ansible-playbook -i $ansible_conf_path/inventory.ini $ansible_conf_path/playbook.yml -u root $ansible_flags" + if [ "$is_dry_run" = "true" ]; then + echo "[3/$total_steps] Skipping playbook because DRY_RUN=true" + else + echo "[3/$total_steps] Upgrading Funkwhale using ansible playbook in $ansible_conf_path..." + $ansible_bin_path/ansible-galaxy install -r requirements.yml -f + echo "[3/$total_steps] Applying playbook with:" + echo " $playbook_command" + $playbook_command + fi + echo + echo "Upgrade to $funkwhale_version complete!" + exit +} + +# wrapped up in a function so that we have some protection against only getting +# half the file during "curl | sh" +do_upgrade