Skip to content

Commit 371676a

Browse files
authored
Deploy automation (#174)
* Add empty BASH script for publishing a release to the Nextcloud App Store. * Add usage documentation to the Nextcloud Publish script. * Add logic to check out a given tag in the Nextcloud Publish script. * Add logic to install project dependencies in the Nextcloud Publish script. * Add logic to create a release Tarball in the Nextcloud Publish script. * Add logic to upload a release Tarball to GitHub in the Nextcloud Publish script. * Add logic to create a signature file for the Tarball in the Nextcloud Publish script. * Add logic to post a release to the Nextcloud App Store in the Nextcloud Publish script. * Change location of variable declarations in the Nextcloud Publish script. * Add function to show usage information in the Nextcloud Publish script. * Add more user friendly error handling to the Nextcloud Publish script. * Add exit trap for cleanup in the Nextcloud Publish script. * Change logic to use signature instead of using a file in the Nextcloud Publish script. * Change function location so they can be exported in the Nextcloud Publish script. * Add function exports in the Nextcloud Publish script. * Add logic to validate AppInfo versions in the Nextcloud Publish script. * Change logic to use signature instead of using a file in the Nextcloud Publish script. * Change the Nextcloud Publish script to be cleaner - Update user documentation - Change single letter params to full params - Add missing quotes - Use same pattern for both sides of comparison * Change pattern matching to be case-insensitve in the Nextcloud Publish script. * Add GitHub Action (GHA) to publish a (pre)release to the Nextcloud App Store. * Change composer in publish action to use PHP specific version for install.. * Change composer install action to latest version and fix incorrect work-dir setting. * Fix typo in composer install action. * Change logic to use signature file instead of using a signature directly in the Nextcloud Publish script. * Add write permissions to GHA to upload asset to GitHub release. * Add debug information to Nextcloud publish GHA. * Remove unneeded `fail-fast` call in GHA. * Fix incorrect debug calls in GHA. * Fix bug caused by incorrect parameter parsing in publish script. * Remove debug statements.
1 parent 4534167 commit 371676a

File tree

2 files changed

+348
-0
lines changed

2 files changed

+348
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
name: Publish a (pre)release to the Nextcloud App Store
3+
4+
on:
5+
release:
6+
types:
7+
- prereleased
8+
- released
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
permissions:
15+
actions: write
16+
contents: write
17+
18+
jobs:
19+
publish:
20+
runs-on: ubuntu-24.04
21+
steps:
22+
- name: Validate Tag Format
23+
run: |
24+
sVersion="${{ github.event.release.tag_name }}"
25+
sPattern='^v?([0-9]+\.[0-9]+\.[0-9]+)(-RC\.[0-9]+)?$'
26+
27+
if ! echo "${sVersion}" | grep --ignore-case --perl-regexp --silent "${sPattern}"; then
28+
sTitle='Invalid Version format'
29+
sMessage="Provided tag '${sVersion}' does not match expected pattern '${sPattern}'"
30+
echo "::error file=tag,line=0,endLine=0,title=${sTitle}::${sMessage}"
31+
exit 1
32+
fi
33+
- name: Checkout
34+
uses: actions/checkout@v4
35+
- name: Check versions in `appinfo/info.xml`
36+
run: |
37+
source ./bin/publish-to-nextcloud-store.sh
38+
39+
sVersion="${{ github.event.release.tag_name }}"
40+
checkAppInfoVersion "${sVersion}" "${PWD}"
41+
- uses: shivammathur/setup-php@v2
42+
with:
43+
php-version: 8.2
44+
- uses: "ramsey/composer-install@v3"
45+
with:
46+
composer-options: --no-dev
47+
working-directory: "solid/"
48+
- name: Publish to Nextcloud App Store
49+
shell: bash
50+
run: |
51+
source ./bin/publish-to-nextcloud-store.sh
52+
53+
sNextcloudToken="${{ secrets.NEXTCLOUD_TOKEN }}"
54+
sVersion="${{ github.event.release.tag_name }}"
55+
sTarball="$(printf 'solid-%s.tar.gz' ${sVersion})"
56+
sKeyFile='nextcloud-app-store.key'
57+
58+
checkAppInfoVersion "${sVersion}" "${PWD}"
59+
createTarball "${PWD}" "${sTarball}"
60+
sUploadUrl="$(fetchGitHubUploadUrl "${sVersion}" "${{ secrets.GITHUB_TOKEN }}")"
61+
sTarballUrl="$(uploadAssetToGitHub "${sUploadUrl}" "${{ secrets.GITHUB_TOKEN }}" "${sTarball}")"
62+
echo "${{ secrets.NEXTCLOUD_PRIVATE_KEY }}" > "${sKeyFile}"
63+
sSignature="$(createSignature "${sTarball}" "${sKeyFile}")"
64+
rm -f "${sKeyFile}"
65+
publishToNextcloud "${sTarballUrl}" "${sSignature}" "${sNextcloudToken}"

bin/publish-to-nextcloud-store.sh

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
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

Comments
 (0)