|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +set -o errexit -o errtrace -o nounset -o pipefail |
| 4 | + |
| 5 | +# ============================================================================== |
| 6 | +# PUBLISH TO NEXTCLOUD STORE |
| 7 | +# ------------------------------------------------------------------------------ |
| 8 | +#/ This script takes all steps needed to publish a release of solid-nextcloud to the Nextcloud App store. |
| 9 | +#/ |
| 10 | +#/ This consists of: |
| 11 | +#/ |
| 12 | +#/ 1. Building a package |
| 13 | +#/ - checking out the created tag |
| 14 | +#/ - installing the project dependencies |
| 15 | +#/ - creating a tarball from the project and its dependencies |
| 16 | +#/ 2. Deploying the package' |
| 17 | +#/ - getting the URL from GitHub to where the tarball should be uploaded |
| 18 | +#/ - uploading the tarball to GitHub |
| 19 | +#/ 3. Creating a release in the Nextcloud App store |
| 20 | +#/ - creating a signature file for the tarball |
| 21 | +#/ - publishing the tarball to the Nextcloud store |
| 22 | +#/ |
| 23 | +#/ There are various assumption made by this script: |
| 24 | +#/ |
| 25 | +#/ - A git tag has been created |
| 26 | +#/ - A GitHub Release has been created (so a Release Page exists where a package can be uploaded to) |
| 27 | +#/ - The `transfer/solid.key` and `transfer/solid.crt` files exist (both are needed to create a signature) |
| 28 | +#/ |
| 29 | +# |
| 30 | +# @CHECKME: The need for the `transfer/solid.crt` file is taken from the original deploy instructions, |
| 31 | +# But it is not _explicitly_ used in this script. Is the need implicit? Or incorrect? |
| 32 | +# ------------------------------------------------------------------------------ |
| 33 | +#/ Usage: |
| 34 | +#/ |
| 35 | +#/ $0 [options] <subject-path> <version> <github-token> <nextcloud-token> |
| 36 | +#/ |
| 37 | +#/ Where: |
| 38 | +#/ |
| 39 | +#/ <subject-path> The path to where the project repository is located |
| 40 | +#/ <version> The version for which a release should be published |
| 41 | +#/ <github-token> A token used to make GET and POST calls to the GitHub API |
| 42 | +#/ <nextcloud-token> The access token used to POST to the Nextcloud Apps Store API |
| 43 | +#/ |
| 44 | +#/ Options: |
| 45 | +#/ |
| 46 | +#/ -h, --help |
| 47 | +#/ Display this help message. |
| 48 | +#/ |
| 49 | +#/ Usage example: |
| 50 | +#/ |
| 51 | +#/ $0 "${PWD}" 'v0.9.0' "$(cat /path/to/github.token)" "${NEXTCLOUD_TOKEN}" |
| 52 | +#/ |
| 53 | +#/ Various executable used by this script (curl, docker, git, jq, openssl, tar) |
| 54 | +#/ can be overridden by setting their respective environmental variable before |
| 55 | +#/ calling this script. For instance, to override curl and git: |
| 56 | +#/ |
| 57 | +#/ CURL=/usr/local/curl GIT=/usr/local/git-plus $0 <subject-path> <version> <github-token> <nextcloud-token> |
| 58 | +#/ |
| 59 | +# ------------------------------------------------------------------------------ |
| 60 | +# Besides external tools (curl, docker, git, jq, openssl, tar) which can be overridden, |
| 61 | +# this script also uses various shell utilities (basename, cat, cut, grep, file, printf, stat, tr) which cannot. |
| 62 | +# ------------------------------------------------------------------------------ |
| 63 | +# DEVELOPER NOTE: The variable naming scheme used in this code is an adaption of |
| 64 | +# Systems Hungarian which is explained at http://blog.pother.ca/VariableNamingConvention/ |
| 65 | +# ============================================================================== |
| 66 | + |
| 67 | +# Allow overriding the executables used in this script |
| 68 | +: "${CURL:=curl}" |
| 69 | +: "${DOCKER:=docker}" |
| 70 | +: "${GIT:=git}" |
| 71 | +: "${JQ:=jq}" |
| 72 | +: "${OPENSSL:=openssl}" |
| 73 | +: "${TAR:=tar}" |
| 74 | + |
| 75 | +# @FIXME: Add functions to validate required tools are installed |
| 76 | + |
| 77 | +: readonly -i "${EXIT_NOT_ENOUGH_PARAMETERS:=65}" |
| 78 | + |
| 79 | +print_usage() { |
| 80 | + local sScript sUsage |
| 81 | + |
| 82 | + sScript="$(basename "$0")" |
| 83 | + readonly sScript |
| 84 | + |
| 85 | + sUsage="$(grep '^#/' <"$0" | cut --characters 4-)" |
| 86 | + readonly sUsage |
| 87 | + |
| 88 | + echo -e "${sUsage//\$0/${sScript}}" |
| 89 | +} |
| 90 | + |
| 91 | +checkAppInfoVersion() { |
| 92 | + local sAppInfoVersion sFile sPattern sSourceDirectory sVersion |
| 93 | + |
| 94 | + readonly sVersion="${1?Two parameters required: <version> <subject-path>}" |
| 95 | + readonly sSourceDirectory="${2?Two parameters required: <version> <subject-path>}" |
| 96 | + |
| 97 | + readonly sFile="${sSourceDirectory}/solid/appinfo/info.xml" |
| 98 | + readonly sPattern='([0-9]+\.[0-9]+\.[0-9]+)(-RC\.[0-9]+)?' |
| 99 | + |
| 100 | + sAppInfoVersion="$( |
| 101 | + grep --ignore-case --perl-regexp "<version>${sPattern}</version>" "${sFile}" \ |
| 102 | + | grep --ignore-case --only-matching --perl-regexp "${sPattern}" |
| 103 | + )" |
| 104 | + readonly sAppInfoVersion |
| 105 | + |
| 106 | + if [ "${sAppInfoVersion}" != "$(echo "${sVersion}" | grep --ignore-case --only-matching --perl-regexp "${sPattern}")" ]; then |
| 107 | + echo " ERROR: Provided version number does not match solid/appinfo/info.xml version" |
| 108 | + diff <(echo "v${sAppInfoVersion}") <(echo "${sVersion}") || true |
| 109 | + return 1 |
| 110 | + fi |
| 111 | +} |
| 112 | + |
| 113 | +createSignature() { |
| 114 | + local sKeyFile sTarball |
| 115 | + |
| 116 | + readonly sTarball="${1?Two parameter required: <tarball-name> <key-file>}" |
| 117 | + readonly sKeyFile="${2?Two parameter required: <tarball-name> <key-file>}" |
| 118 | + |
| 119 | + "${OPENSSL}" dgst -sha512 -sign "${sKeyFile}" "${sTarball}" \ |
| 120 | + | "${OPENSSL}" base64 \ |
| 121 | + | tr --delete "\n" |
| 122 | +} |
| 123 | + |
| 124 | +createTarball() { |
| 125 | + local sSourceDirectory sTarball |
| 126 | + |
| 127 | + readonly sSourceDirectory="${1?Two parameters required: <source-path> <tarball-name>}" |
| 128 | + readonly sTarball="${2?Two parameters required: <source-path> <tarball-name>}" |
| 129 | + |
| 130 | + "${TAR}" --directory="${sSourceDirectory}" --create --file "${sTarball}" --gzip "solid" |
| 131 | +} |
| 132 | + |
| 133 | +fetchGitHubUploadUrl() { |
| 134 | + local sGithubToken sVersion |
| 135 | + |
| 136 | + readonly sVersion="${1?Two parameters required: <version> <github-token>}" |
| 137 | + readonly sGithubToken="${2?Two parameters required: <version> <github-token>}" |
| 138 | + |
| 139 | + "${CURL}" \ |
| 140 | + --header "Accept: application/vnd.github+json" \ |
| 141 | + --header "Authorization: Bearer ${sGithubToken}" \ |
| 142 | + --silent \ |
| 143 | + "https://api.github.com/repos/pdsinterop/solid-nextcloud/releases/tags/${sVersion}" \ |
| 144 | + | "${JQ}" --raw-output '.upload_url' \ |
| 145 | + | cut --delimiter '{' --fields 1 |
| 146 | +} |
| 147 | + |
| 148 | +publishToNextcloud() { |
| 149 | + local sDownloadUrl sJson sNextcloudToken sSignature |
| 150 | + |
| 151 | + readonly sDownloadUrl="${1?Three parameters required: <download-url> <signature> <nextcloud-token>}" |
| 152 | + readonly sSignature="${2?Three parameters required: <download-url> <signature> <nextcloud-token>}" |
| 153 | + readonly sNextcloudToken="${3?Three parameters required: <download-url> <signature> <nextcloud-token>}" |
| 154 | + |
| 155 | + sJson="$( |
| 156 | + printf '{"download":"%s", "signature": "%s"}' \ |
| 157 | + "${sDownloadUrl}" \ |
| 158 | + "${sSignature}" |
| 159 | + )" |
| 160 | + readonly sJson |
| 161 | + |
| 162 | + "${CURL}" \ |
| 163 | + --data "${sJson}" \ |
| 164 | + --header "Authorization: Token ${sNextcloudToken}" \ |
| 165 | + --header "Content-Type: application/json" \ |
| 166 | + --request POST \ |
| 167 | + --silent \ |
| 168 | + 'https://apps.nextcloud.com/api/v1/apps/releases' |
| 169 | +} |
| 170 | + |
| 171 | +uploadAssetToGitHub() { |
| 172 | + local sGithubToken sTarball sUrl |
| 173 | + |
| 174 | + readonly sUrl="${1?Three parameters required: <upload-url> <github-token> <tarbal-name>}" |
| 175 | + readonly sGithubToken="${2?Three parameters required: <upload-url> <github-token> <tarbal-name>}" |
| 176 | + readonly sTarball="${3?Three parameters required: <upload-url> <github-token> <tarbal-name>}" |
| 177 | + |
| 178 | + "${CURL}" \ |
| 179 | + --header "Accept: application/vnd.github+json" \ |
| 180 | + --header "Authorization: Bearer ${sGithubToken}" \ |
| 181 | + --header "Content-Length: $(stat --printf="%s" "${sTarball}")" \ |
| 182 | + --header "Content-Type: $(file --brief --mime-type "${sTarball}")" \ |
| 183 | + --header "X-GitHub-Api-Version: 2022-11-28" \ |
| 184 | + --request POST \ |
| 185 | + --silent \ |
| 186 | + --upload-file "${sTarball}" \ |
| 187 | + "${sUrl}?name=${sTarball}" \ |
| 188 | + | "${JQ}" --raw-output '.browser_download_url' |
| 189 | +} |
| 190 | + |
| 191 | +publish_to_nextcloud_store() { |
| 192 | + checkoutTag() { |
| 193 | + local sVersion |
| 194 | + |
| 195 | + readonly sVersion="${1?One parameter required: <version>}" |
| 196 | + |
| 197 | + "${GIT}" checkout "${sVersion}" |
| 198 | + } |
| 199 | + |
| 200 | + installDependencies() { |
| 201 | + local sDockerFile sSourceDirectory |
| 202 | + |
| 203 | + readonly sDockerFile="${1?Two parameters required: <docker-file> <subject-path>}" |
| 204 | + readonly sSourceDirectory="${2?Two parameters required: <docker-file> <subject-path>}" |
| 205 | + |
| 206 | + "${DOCKER}" run \ |
| 207 | + -it \ |
| 208 | + --network=host \ |
| 209 | + --rm \ |
| 210 | + --volume "${sSourceDirectory}/solid:/app" \ |
| 211 | + --volume ~/.cache/composer/:/root/composer/ \ |
| 212 | + --workdir /app \ |
| 213 | + "${sDockerFile}" \ |
| 214 | + bash -c 'php --version && composer --version \ |
| 215 | + && COMPOSER_CACHE_DIR=/root/composer/ composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist \ |
| 216 | + ' |
| 217 | + } |
| 218 | + |
| 219 | + local sOption bShowUsage |
| 220 | + bShowUsage=false |
| 221 | + |
| 222 | + for sOption in "${@}"; do |
| 223 | + case "${sOption}" in |
| 224 | + -h | --help) |
| 225 | + bShowUsage=true |
| 226 | + ;; |
| 227 | + esac |
| 228 | + done |
| 229 | + |
| 230 | + if [ "${bShowUsage}" = true ];then |
| 231 | + print_usage |
| 232 | + elif [[ "$#" -lt 4 ]];then |
| 233 | + printf " [ERROR]: %s\n\n%s\n" 'This script expects four command-line arguments' 'Call --help for more details' >&2 |
| 234 | + exit "${EXIT_NOT_ENOUGH_PARAMETERS}" |
| 235 | + else |
| 236 | + local sDownloadUrl sGithubToken sKeyFile sNextcloudToken sSignature sSourceDirectory sTarball sUploadUrl sVersion |
| 237 | + |
| 238 | + readonly sSourceDirectory="${1?Four parameters required: <subject-path> <version> <github-token> <nextcloud-token>}" |
| 239 | + readonly sVersion="${2?Four parameters required: <subject-path> <version> <github-token> <nextcloud-token>}" |
| 240 | + readonly sGithubToken="${3?Four parameters required: <subject-path> <version> <github-token> <nextcloud-token>}" |
| 241 | + readonly sNextcloudToken="${4?Four parameters required: <subject-path> <version> <github-token> <nextcloud-token>}" |
| 242 | + |
| 243 | + # @FIXME: This just hard-codes the path, either the path or the contents of |
| 244 | + # the file should be passed in as parameter |
| 245 | + sKeyFile="$(dirname "$(dirname "$(realpath "$0")")")/transfer/solid.key" |
| 246 | + readonly sKeyFile |
| 247 | + readonly sTarball='solid.tar.gz' |
| 248 | + |
| 249 | + checkoutTag "${sVersion}" |
| 250 | + # @TODO: The PHP version should either be a param, parsed from composer.json or both! |
| 251 | + # (Allow to be set but used parsed value as default...) |
| 252 | + installDependencies 'composer:2.2.17' "${sSourceDirectory}" |
| 253 | + checkAppInfoVersion "${sVersion}" "${sSourceDirectory}" |
| 254 | + createTarball "${sSourceDirectory}" "${sTarball}" |
| 255 | + |
| 256 | + sUploadUrl="$(fetchGitHubUploadUrl "${sVersion}" "${sGithubToken}")" |
| 257 | + readonly sUploadUrl |
| 258 | + sDownloadUrl="$(uploadAssetToGitHub "${sUploadUrl}" "${sGithubToken}" "${sTarball}")" |
| 259 | + |
| 260 | + finish() { |
| 261 | + echo 'Done.' |
| 262 | + } |
| 263 | + trap finish EXIT |
| 264 | + |
| 265 | + sSignature="$(createSignature "${sTarball}" "${sKeyFile}")" |
| 266 | + readonly sSignature |
| 267 | + |
| 268 | + publishToNextcloud "${sDownloadUrl}" "${sSignature}" "${sNextcloudToken}" |
| 269 | + fi |
| 270 | +} |
| 271 | + |
| 272 | +if [ -n "${BASH_SOURCE:-}" ] && [ "${BASH_SOURCE[0]}" != "${0}" ]; then |
| 273 | + export publish_to_nextcloud_store |
| 274 | + |
| 275 | + export checkAppInfoVersion |
| 276 | + export createSignature |
| 277 | + export createTarball |
| 278 | + export fetchGitHubUploadUrl |
| 279 | + export publishToNextcloud |
| 280 | + export uploadAssetToGitHub |
| 281 | +else |
| 282 | + publish_to_nextcloud_store "${@}" |
| 283 | +fi |
0 commit comments