Skip to content

Commit dd0a415

Browse files
committed
Merge branch 'adapt-linux-repository-generation-to-encompass-mullvad-des-866'
2 parents ea9d4b1 + c13a42f commit dd0a415

15 files changed

+950
-227
lines changed

ci/buildserver-build.sh

+16-18
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ shopt -s nullglob
1616

1717
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
1818
BUILD_DIR="$SCRIPT_DIR/mullvadvpn-app"
19+
# All non-dev builds have their artifacts placed under this directory
20+
ARTIFACT_DIR="$SCRIPT_DIR/artifacts"
21+
# Keeps track of which git commit hashes has been built, to not build them again
1922
LAST_BUILT_DIR="$SCRIPT_DIR/last-built"
2023

2124
BRANCHES_TO_BUILD=("origin/main")
@@ -44,27 +47,19 @@ case "$(uname -s)" in
4447
;;
4548
esac
4649

50+
51+
# Automatically copy artifacts to the inbox of the repository builder service
52+
# for dev and staging (production is pushed manually)
4753
function publish_linux_repositories {
4854
local artifact_dir=$1
4955
local version=$2
5056

51-
local deb_repo_dir="$SCRIPT_DIR/deb/$version"
52-
echo "Preparing Apt repository in $deb_repo_dir"
53-
"$SCRIPT_DIR/prepare-apt-repository.sh" "$artifact_dir" "$version" "$deb_repo_dir"
54-
55-
local rpm_repo_dir="$SCRIPT_DIR/rpm/$version"
56-
echo "Preparing RPM repository in $rpm_repo_dir"
57-
"$SCRIPT_DIR/prepare-rpm-repository.sh" "$artifact_dir" "$version" "$rpm_repo_dir"
57+
"$SCRIPT_DIR/publish-app-to-repositories.sh" --dev "$artifact_dir" "$version"
5858

59-
"$SCRIPT_DIR/publish-linux-repositories.sh" --dev "$version" \
60-
--deb "$deb_repo_dir" \
61-
--rpm "$rpm_repo_dir"
6259
# If this is a release build, also push to staging.
6360
# Publishing to production is done manually.
6461
if [[ $version != *"-dev-"* ]]; then
65-
"$SCRIPT_DIR/publish-linux-repositories.sh" --staging "$version" \
66-
--deb "$deb_repo_dir" \
67-
--rpm "$rpm_repo_dir"
62+
"$SCRIPT_DIR/publish-app-to-repositories.sh" --staging "$artifact_dir" "$version"
6863
fi
6964
}
7065

@@ -86,9 +81,9 @@ function upload {
8681
sha256sum "${files[@]}" > "$checksums_path"
8782

8883
case "$(uname -s)" in
89-
# Linux is both the build and upload server. Just move directly to target dir
84+
# Linux is both the build and upload server. Just copy directly to target dir
9085
Linux*)
91-
mv "${files[@]}" "$checksums_path" "$UPLOAD_DIR/"
86+
cp "${files[@]}" "$checksums_path" "$UPLOAD_DIR/"
9287
;;
9388
# Other platforms need to transfer their artifacts to the Linux build machine.
9489
Darwin*|MINGW*|MSYS_NT*)
@@ -192,7 +187,7 @@ function build_ref {
192187
local version=""
193188
version="$(run_in_build_env cargo run -q --bin mullvad-version | tr -d "\r" || return 1)"
194189

195-
local artifact_dir="dist/$version"
190+
local artifact_dir="$ARTIFACT_DIR/$version"
196191
mkdir -p "$artifact_dir"
197192

198193
local build_args=(--optimize --sign)
@@ -240,8 +235,11 @@ function build_ref {
240235
publish_linux_repositories "$artifact_dir" "$version"
241236
fi
242237
(cd "$artifact_dir" && upload "$version") || return 1
243-
# shellcheck disable=SC2216
244-
yes | rm -r "$artifact_dir"
238+
# Remove artifacts from dev builds. They are not really needed and take up lots of space.
239+
if [[ $version == *"-dev-"* ]]; then
240+
# shellcheck disable=SC2216
241+
yes | rm -r "$artifact_dir"
242+
fi
245243

246244
touch "$LAST_BUILT_DIR/$current_hash"
247245

ci/buildserver-config.sh

+5-21
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,13 @@
33
# Buildserver configuration. Runtime values are defined here instead of
44
# the scripts where they are used.
55

6-
# Which gpg key to sign things with
7-
export CODE_SIGNING_KEY_FINGERPRINT="A1198702FC3E0A09A9AE5B75D5A1D4F266DE8DDF"
8-
9-
# Debian codenames we support.
10-
SUPPORTED_DEB_CODENAMES=("sid" "testing" "bookworm" "bullseye")
11-
# Ubuntu codenames we support (latest two LTS) ...
12-
SUPPORTED_DEB_CODENAMES+=("noble" "jammy" "focal")
13-
# ... + latest non-LTS Ubuntu. We officially only support the latest non-LTS.
14-
# But to not cause too much disturbance just when Ubuntu is released, we keep
15-
# the last two codenames working in the repository.
16-
SUPPORTED_DEB_CODENAMES+=("mantic")
17-
export SUPPORTED_DEB_CODENAMES
18-
19-
export SUPPORTED_RPM_ARCHITECTURES=("x86_64" "aarch64")
20-
21-
# Servers to upload Linux deb/rpm repositories and all other build artifacts to.
22-
export DEV_UPLOAD_SERVERS=("cdn.devmole.eu")
23-
export STAGING_UPLOAD_SERVERS=("cdn.stagemole.eu")
6+
# Servers to upload build artifacts to.
247
export PRODUCTION_UPLOAD_SERVERS=("cdn.mullvad.net")
258

26-
export DEV_LINUX_REPOSITORY_PUBLIC_URL="https://repository.devmole.eu"
27-
export STAGING_LINUX_REPOSITORY_PUBLIC_URL="https://repository.stagemole.eu"
28-
export PRODUCTION_LINUX_REPOSITORY_PUBLIC_URL="https://repository.mullvad.net"
9+
# Where to publish new app artifact notification files to, so that
10+
# build-linux-repositories picks it up.
11+
# Keep in sync with build-linux-repositories-config.sh
12+
#export LINUX_REPOSITORY_INBOX_DIR_BASE="PLEASE CONFIGURE ME"
2913

3014
# What container volumes cargo should put caches in.
3115
# Specify differently if running multiple builds in parallel on one machine,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env bash
2+
3+
# The directory to use as inbox. This is where .src files are read
4+
#export LINUX_REPOSITORY_INBOX_DIR_BASE="PLEASE CONFIGURE ME"
5+
6+
# GPG key to sign the repositories with
7+
export CODE_SIGNING_KEY_FINGERPRINT="A1198702FC3E0A09A9AE5B75D5A1D4F266DE8DDF"
8+
9+
# Debian codenames we support.
10+
SUPPORTED_DEB_CODENAMES=("sid" "testing" "bookworm" "bullseye")
11+
# Ubuntu codenames we support. Latest two LTS. But when adding a new
12+
# don't immediately remove the oldest one. Allow for some transition period
13+
# with the last three.
14+
SUPPORTED_DEB_CODENAMES+=("noble" "jammy" "focal")
15+
# ... + latest non-LTS Ubuntu. We officially only support the latest non-LTS.
16+
# But to not cause too much disturbance just when Ubuntu is released, we keep
17+
# the last two codenames working in the repository.
18+
SUPPORTED_DEB_CODENAMES+=("mantic")
19+
export SUPPORTED_DEB_CODENAMES
20+
21+
export SUPPORTED_RPM_ARCHITECTURES=("x86_64" "aarch64")
22+
23+
export REPOSITORIES=("stable" "beta")
24+
25+
export PRODUCTION_LINUX_REPOSITORY_PUBLIC_URL="https://repository.mullvad.net"
26+
export STAGING_LINUX_REPOSITORY_PUBLIC_URL="https://repository.stagemole.eu"
27+
export DEV_LINUX_REPOSITORY_PUBLIC_URL="https://repository.devmole.eu"
28+
29+
# Servers to upload Linux deb/rpm repositories to
30+
export PRODUCTION_REPOSITORY_SERVER="cdn.mullvad.net"
31+
export STAGING_REPOSITORY_SERVER="cdn.stagemole.eu"
32+
export DEV_REPOSITORY_SERVER="cdn.devmole.eu"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Builds linux deb and rpm repositories and uploads them to a repository server.
4+
# One instance of this script targets *one* server environment.
5+
# This means that if you want to publish to development, staging and production servers,
6+
# you need to run three instances of this script.
7+
#
8+
# This script works on an $inbox_dir. In this directory it expects one directory per repository.
9+
# For each repository it will read all files having the .src extension
10+
# These files are expected to contain a single line, a path to some directory where
11+
# it can read new artifacts for that product.
12+
# All deb and rpm files from that directory will be signed and moved over to a folder with
13+
# the same name as the .src file, but with a .latest extension instead.
14+
# So artifacts read from `app.src` will be moved to `app.latest/`.
15+
#
16+
# Then the deb and rpm repositories will be generated and all deb and rpm files in
17+
# $inbox_dir/$repository/*.latest/ will be added to the repository. These repositories are then synced
18+
# to `$repository_server_upload_domain respectively.
19+
20+
set -eu
21+
# nullglob is needed to produce expected results when globing an empty directory
22+
shopt -s nullglob
23+
24+
function usage() {
25+
echo "Usage: $0 <environment>"
26+
echo
27+
echo "Example usage: ./build-linux-repositories.sh production"
28+
echo
29+
echo "This script reads an inbox folder for the corresponding server environment."
30+
echo "It then generates and uploads new Linux repositories for all"
31+
echo "the latest artifacts"
32+
echo
33+
echo "Options:"
34+
echo " -h | --help Show this help message and exit."
35+
exit 1
36+
}
37+
38+
if [[ "$#" == 0 || $1 == "-h" || $1 == "--help" ]]; then
39+
usage
40+
fi
41+
42+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
43+
44+
# shellcheck source=ci/linux-repository-builder/build-linux-repositories-config.sh
45+
source "$SCRIPT_DIR/build-linux-repositories-config.sh"
46+
47+
environment="$1"
48+
case "$environment" in
49+
"production")
50+
repository_server_upload_domain="$PRODUCTION_REPOSITORY_SERVER"
51+
repository_server_public_url="$PRODUCTION_LINUX_REPOSITORY_PUBLIC_URL"
52+
;;
53+
"staging")
54+
repository_server_upload_domain="$STAGING_REPOSITORY_SERVER"
55+
repository_server_public_url="$STAGING_LINUX_REPOSITORY_PUBLIC_URL"
56+
;;
57+
"dev")
58+
repository_server_upload_domain="$DEV_REPOSITORY_SERVER"
59+
repository_server_public_url="$DEV_LINUX_REPOSITORY_PUBLIC_URL"
60+
;;
61+
*)
62+
echo "Unknown environment. Specify production, staging or dev" >&2
63+
exit 1
64+
;;
65+
esac
66+
67+
inbox_dir="$LINUX_REPOSITORY_INBOX_DIR_BASE/$environment"
68+
69+
if [[ ! -d "$inbox_dir" ]]; then
70+
echo "Inbox $inbox_dir does not exist" 1>&2
71+
exit 1
72+
fi
73+
74+
# Process all .src files in the given inbox dir.
75+
# Signs and moves all found artifacts into .latest directories
76+
# Returns 0 if everything went well and there are new artifacts for a product.
77+
# Returns 1 if no new artifacts were found
78+
function process_inbox {
79+
local inbox_dir=$1
80+
echo "[#] Processing inbox at $inbox_dir"
81+
82+
local found_new_artifacts="false"
83+
# Read all notify files and move the artifacts they point to into a local .latest copy
84+
for notify_file in "$inbox_dir"/*.src; do
85+
if [[ ! -f "$notify_file" ]]; then
86+
echo "Ignoring non-file $notify_file" 1>&2
87+
continue
88+
fi
89+
echo "Processing notify file $notify_file"
90+
91+
local src_dir
92+
src_dir=$(cat "$notify_file")
93+
if [[ ! -d "$src_dir" ]]; then
94+
echo "Artifact source dir $src_dir from notify file $notify_file does not exist" 1>&2
95+
continue
96+
fi
97+
98+
# Removing the file before we move the artifacts out of where it points.
99+
# This ensures we don't get stuck in a loop processing it over and over
100+
# if the signing and moving fails.
101+
rm "$notify_file"
102+
103+
local artifact_dir=${notify_file/%.src/.latest}
104+
# Recreate the artifact dir, cleaning it
105+
rm -rf "$artifact_dir" && mkdir -p "$artifact_dir" || exit 1
106+
107+
# The fact that we have processed one .src file is enough tro trigger a repository
108+
# rebuild. Because if a product would like to publish "no artifacts" they should
109+
# be able to create a .src file pointing to an empty directory
110+
found_new_artifacts="true"
111+
112+
echo "Moving artifacts from $src_dir to $artifact_dir"
113+
# Move all deb and rpm files into the .latest dir
114+
for src_deb in "$src_dir"/*.deb; do
115+
echo "Signing and moving $src_deb into $artifact_dir/"
116+
dpkg-sig --sign builder "$src_deb"
117+
mv "$src_deb" "$artifact_dir/"
118+
done
119+
for src_rpm in "$src_dir"/*.rpm; do
120+
echo "Signing and moving $src_rpm into $artifact_dir/"
121+
rpm --addsign "$src_rpm"
122+
mv "$src_rpm" "$artifact_dir/"
123+
done
124+
rm -r "$src_dir" || echo "Failed to remove src dir $src_dir"
125+
done
126+
127+
if [[ $found_new_artifacts == "false" ]]; then
128+
return 1
129+
fi
130+
return 0
131+
}
132+
133+
function rsync_repo {
134+
local local_repo_dir=$1
135+
local remote_repo_dir=$2
136+
137+
echo "Syncing to $repository_server_upload_domain:$remote_repo_dir"
138+
rsync -av --delete --mkpath --rsh='ssh -p 1122' \
139+
"$local_repo_dir"/ \
140+
build@"$repository_server_upload_domain":"$remote_repo_dir"
141+
}
142+
143+
for repository in "${REPOSITORIES[@]}"; do
144+
deb_remote_repo_dir="deb/$repository"
145+
rpm_remote_repo_dir="rpm/$repository"
146+
147+
repository_inbox_dir="$inbox_dir/$repository"
148+
if ! process_inbox "$repository_inbox_dir"; then
149+
echo "Nothing new in inbox at $repository_inbox_dir"
150+
continue
151+
fi
152+
153+
# Read all .latest artifact dirs into array
154+
readarray -d '' artifact_dirs < <(find "$repository_inbox_dir" -maxdepth 1 -name "*.latest" -type d -print0)
155+
if [ "${#artifact_dirs[@]}" -lt 1 ]; then
156+
echo "No artifact directories in $repository_inbox_dir to generate repositories from" >&2
157+
continue
158+
fi
159+
160+
echo "Generating repositories from these artifact directories: ${artifact_dirs[*]}"
161+
162+
# Generate deb repository from all the .latest artifacts
163+
164+
deb_repo_dir="$repository_inbox_dir/repos/deb"
165+
rm -rf "$deb_repo_dir" && mkdir -p "$deb_repo_dir" || exit 1
166+
"$SCRIPT_DIR/prepare-apt-repository.sh" "$deb_repo_dir" "${artifact_dirs[@]}"
167+
168+
# Generate rpm repository from all the .latest artifacts
169+
170+
rpm_repo_dir="$repository_inbox_dir/repos/rpm"
171+
rm -rf "$rpm_repo_dir" && mkdir -p "$rpm_repo_dir" || exit 1
172+
"$SCRIPT_DIR/prepare-rpm-repository.sh" "$rpm_repo_dir" \
173+
"$repository_server_public_url" "$rpm_remote_repo_dir" "$repository" "${artifact_dirs[@]}"
174+
175+
# rsync repositories to repository server
176+
177+
echo "[#] Syncing deb repository to $deb_remote_repo_dir"
178+
rsync_repo "$deb_repo_dir" "$deb_remote_repo_dir"
179+
echo "[#] Syncing rpm repository to $rpm_remote_repo_dir"
180+
rsync_repo "$rpm_repo_dir" "$rpm_remote_repo_dir"
181+
182+
done
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Create separate services with instance names being set to "$server_environment".
2+
# See the build-linux-repositories.sh script for details.
3+
#
4+
# For example:
5+
# * build-linux-repositories@production.service
6+
# * build-linux-repositories@staging.service
7+
# * build-linux-repositories@dev.service
8+
#
9+
# To install this service, do the following:
10+
#
11+
# * Log in to the user with `sudo machinectl shell <build account>@` to get a proper shell
12+
# * Copy this service file and corresponding timer file to ~/.config/systemd/user
13+
# * Edit `ExecStart` path to point to absolute path to script
14+
# * Reload systemd: `systemctl --user daemon-reload`
15+
# * Start the timers:
16+
# $ systemctl --user enable --now build-linux-repositories@production.timer
17+
# ... Repeat for other environments.
18+
19+
[Unit]
20+
Description=Mullvad Linux repository generation and upload service
21+
22+
[Service]
23+
Type=oneshot
24+
ExecStart=./build-linux-repositories.sh %i
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[Unit]
2+
Description=Run Mullvad Linux repository generation and upload service periodically
3+
4+
[Timer]
5+
OnCalendar=*-*-* *:*:30
6+
Persistent=true
7+
8+
[Install]
9+
WantedBy=timers.target

0 commit comments

Comments
 (0)