Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,24 @@ and on top of that:
Supervised Manual GitLab Update
-------------------------------

It is recommended to always first check the `GitLab documentation`_ prior to
update. It is also recommended that you ensure you have a full backup (TKLBAM
is a good option, but there are other methods). Once you are statisfied,
update to the latest stable release via apt::
Check the installed and eligible versions without changing the appliance::

gitlab-update --check

Before an update, consult the `GitLab upgrade path`_ and the release-specific
`GitLab documentation`_. GitLab requires intermediate upgrade stops. Back up
the appliance, then install the next eligible version explicitly::

apt update
apt install gitlab-ce
apt install gitlab-ce=<version>

Repeat the application acceptance checks before proceeding to another required
stop. Available versions are listed by ``apt-cache madison gitlab-ce`` and the
`GitLab release blog`_.

You can view available versions via the `GitLab 'release' blog tag`_. We also
highly recommend subscribing to receive email notifications.
If APT reports an expired repository key or ``NO_PUBKEY``, follow the
`repository-key rotation procedure`_. It preserves the per-repository
``signed-by`` restriction and verifies GitLab's full published fingerprint.

Credentials *(passwords set at first boot)*
-------------------------------------------
Expand All @@ -53,4 +61,6 @@ Credentials *(passwords set at first boot)*
.. _TurnKey Core: https://www.turnkeylinux.org/core
.. _Omnibus package: https://docs.gitlab.com/omnibus/
.. _GitLab documentation: https://docs.gitlab.com/omnibus/update/README.html
.. _GitLab 'release' blog tag: https://about.gitlab.com/blog/categories/releases/
.. _GitLab upgrade path: https://docs.gitlab.com/update/upgrade_paths/
.. _GitLab release blog: https://about.gitlab.com/blog/categories/releases/
.. _repository-key rotation procedure: docs/update-apt-repo-key.rst
18 changes: 17 additions & 1 deletion changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
turnkey-gitlab-19.0 (1) turnkey; urgency=low

* Install GitLab CE 19.3.0 from its official Debian 13 repository with a
pinned version, repository key fingerprint, and signed package metadata.

* Add a non-mutating update check and document GitLab's supervised required
upgrade stops.

* Verify firstboot failures and keep the root password out of child process
arguments.

* Add v19 application acceptance coverage and README evidence crosswalk.

* Upgrade the base distribution to Debian 13 Trixie.

-- TurnKey Linux release engineering <release-engineering@turnkeylinux.org> Tue, 25 Aug 2026 00:00:00 +0000

turnkey-gitlab-18.1 (1) turnkey; urgency=low

* Update GitLab to latest GitLab-CE v17.3.0- via upstream apt repo.
Expand Down Expand Up @@ -337,4 +354,3 @@ turnkey-gitlab-12.0 (1) turnkey; urgency=low
appliances. Here we only describe changes specific to this appliance.

-- Alon Swartz <alon@turnkeylinux.org> Wed, 01 Aug 2012 08:00:00 +0200

108 changes: 89 additions & 19 deletions conf.d/main
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,51 @@ ADMIN_PASS=Turnkey1
APP_NAME="TurnKey GitLab"
DISPLAY_NAME="$APP_NAME Admin"
CONF=/etc/gitlab/gitlab.rb
GITLAB_VERSION=19.3.0-ce.0
GITLAB_PACKAGE_SHA256=f88f80cd61d6b2beb35aa7207591d4abdfed0e6c2c42e6ed753dd29ea5de076d
GITLAB_KEY_SHA256=003c0ca2fea61767f8c6de7a1c0f49fc88ea3c8db95e3cd1856b32ce9d876e0f
GITLAB_KEY_FINGERPRINT=F6403F6544A38863DAA0B6E03F01618A51312F3F
SOURCE_RECORD=/usr/local/share/turnkey-gitlab/source

[ "$FAB_HTTP_PROXY" ] && export http_proxy=$FAB_HTTP_PROXY

APT_KEY_URL=https://packages.gitlab.com/gpg.key
curl -sS $APT_KEY_URL | apt-key --keyring /usr/share/keyrings/gitlab-ce.gpg add -
key=$(mktemp)
runsvdir_pid=
cleanup() {
if [[ -n $runsvdir_pid ]]; then
kill "$runsvdir_pid" 2>/dev/null || true
wait "$runsvdir_pid" 2>/dev/null || true
fi
find "$key" -maxdepth 0 -type f -delete
}
trap cleanup EXIT
curl -fsSL "$APT_KEY_URL" -o "$key"
echo "$GITLAB_KEY_SHA256 $key" | sha256sum -c -
test "$(gpg --show-keys --with-colons "$key" | awk -F: '$1 == "fpr" { print $10; exit }')" = "$GITLAB_KEY_FINGERPRINT"
gpg --batch --yes --dearmor --output /usr/share/keyrings/gitlab-ce.gpg "$key"

apt-get update
apt-get install gitlab-ce -y

# tweak GitLab defaults for build within TKLDev
metadata_sha256=$(apt-cache show "gitlab-ce=$GITLAB_VERSION" |
awk '$1 == "SHA256:" { print $2; exit }')
test "$metadata_sha256" = "$GITLAB_PACKAGE_SHA256"

# GitLab's package configures itself from its postinst. Stage the verified
# package so its bundled runit can supervise services during that configure.
apt-get install --download-only -y "gitlab-ce=$GITLAB_VERSION"
package=/var/cache/apt/archives/gitlab-ce_${GITLAB_VERSION}_amd64.deb
test -f "$package"
echo "$GITLAB_PACKAGE_SHA256 $package" | sha256sum -c -
dpkg --unpack "$package"

install -d -m 0755 "$(dirname "$CONF")" /opt/gitlab/service
cp /opt/gitlab/etc/gitlab.rb.template "$CONF"
chmod 0600 "$CONF"
sed -i "s|GENERATED_EXTERNAL_URL|http://$DOMAIN|" "$CONF"

# Tweak GitLab defaults before the package's automatic reconfigure. Disabling
# init detection is required inside the TKLDev chroot, where systemd is not PID
# 1. The temporary runit process below provides the supervisor GitLab expects.
sed -i "/^external_url/ s|'.*|'http://$DOMAIN'|" $CONF
sed -i "/postgresql\['dynamic_shared_memory_type'\]/ s|^# *||" $CONF
sed -i "/postgresql\['dynamic_shared_memory_type'\]/ s|=.*|= 'mmap'|" $CONF
Expand All @@ -34,46 +69,82 @@ sed -i "/gitlab_rails\['gitlab_email_subject_suffix'\]/ s|=.*|= '\[$APP_NAME\]'|
echo "package['detect_init'] = false" >> "$CONF"
echo "package['modify_kernel_parameters'] = false" >> "$CONF"

set +x
/opt/gitlab/embedded/bin/runsvdir-start >/tmp/gitlab-runsvdir-build.log 2>&1 &
runsvdir_pid=$!
set -x
kill -0 "$runsvdir_pid"

EXTERNAL_URL="http://$DOMAIN" apt-get install -y "gitlab-ce=$GITLAB_VERSION"
test "$(dpkg-query -W -f='${Version}' gitlab-ce)" = "$GITLAB_VERSION"

mkdir -p "$(dirname "$SOURCE_RECORD")"
cat >"$SOURCE_RECORD" <<EOF
package_source=https://packages.gitlab.com/gitlab/gitlab-ce/debian/trixie
installed_version=$GITLAB_VERSION
package_sha256=$GITLAB_PACKAGE_SHA256
repository_key_fingerprint=$GITLAB_KEY_FINGERPRINT
repository_key_sha256=$GITLAB_KEY_SHA256
EOF

# set up GitLab
export LC_ALL=C.UTF-8 # explicitly set UTF8 to avoid GitLab bitchin...

service gitlab-runsvdir start
gitlab-ctl reconfigure

# disable GitLab WebUI prompting to set 'root' GitLab user password (TurnKey
# sets GitLab 'root' user password via inithook)
echo -e "$ADMIN_PASS\n$ADMIN_PASS\n" | gitlab-rake 'gitlab:password:reset[root]'
set +x
printf '%s\n%s\n' "$ADMIN_PASS" "$ADMIN_PASS" | gitlab-rake 'gitlab:password:reset[root]'
set -x
gitlab-psql -c "UPDATE users SET password_automatically_set='f' WHERE id = 1;"
gitlab-psql -c "UPDATE users SET reset_password_token='' WHERE id = 1;"

# remove initially created files which will be auto-regenerated on new host
rm -f /opt/gitlab/embedded/nodes/tkldev.json
rm -f /etc/gitlab/gitlab-secrets.json
for generated in \
/opt/gitlab/embedded/nodes/tkldev.json \
/etc/gitlab/gitlab-secrets.json; do
if [[ -f $generated ]]; then
find "$generated" -maxdepth 0 -type f -delete
fi
done

# clear secrets (they will be regenerated automatically)
gitlab-ctl start postgresql
echo -e 'ApplicationSetting.first.delete\n' | gitlab-rails console -e production
cat <<EOF | gitlab-rails dbconsole --database main
cat <<EOF | gitlab-psql --set=ON_ERROR_STOP=on
UPDATE projects SET runners_token = null, runners_token_encrypted = null;
UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
UPDATE application_settings SET runners_registration_token_encrypted = null;
UPDATE application_settings SET encrypted_ci_jwt_signing_key = null;
UPDATE ci_runners SET token = null, token_encrypted = null;
TRUNCATE integrations, chat_names, issue_tracker_data, jira_tracker_data, slack_integrations, web_hooks, zentao_tracker_data, web_hook_logs, dingtalk_tracker_data, slack_integrations_scopes;
UPDATE ci_runners SET token_encrypted = null;
TRUNCATE integrations, chat_names, issue_tracker_data, jira_tracker_data, slack_integrations, web_hooks, zentao_tracker_data, dingtalk_tracker_data, slack_integrations_scopes;
EOF

# fix occasional postgres stop timeout
while ! gitlab-ctl stop postgresql | grep -q 'ok: down'; do sleep 1; done
gitlab-ctl stop
# GitLab services occasionally need a second stop wait while shutting down in
# the build chroot. Keep retries bounded and preserve a terminal failure.
stop_gitlab() {
for _ in 1 2 3; do
if gitlab-ctl stop "$@"; then
return 0
fi
sleep 2
done
return 1
}
stop_gitlab postgresql
stop_gitlab
kill "$runsvdir_pid"
wait "$runsvdir_pid" || true
runsvdir_pid=

# remove unneeded default TurnKey Confconsole Let's Encrypt integration files
# GitLab Omnibus provides it's own LE integration - GitLab specific Confconsole
# files provided via overlay
LE_DIR=/usr/lib/confconsole/plugins.d/Lets_Encrypt
FILES="add-water cert_auto_renew.py dehydrated-wrapper *.pyc"
for file in $FILES; do
rm -rf $LE_DIR/$file
done
find "$LE_DIR" -maxdepth 1 -type f \
\( -name add-water -o -name cert_auto_renew.py \
-o -name dehydrated-wrapper -o -name '*.pyc' \) -delete
# GitLab Omnibus includes it's own ACME client
apt-get purge -y dehydrated

Expand All @@ -83,7 +154,6 @@ sed -i "/postgresql\['dynamic_shared_memory_type'\]/ s|= 'mmap'|= nil|" $CONF
sed -i "/postgresql\['shared_buffers'\]/ s|^|# |" $CONF
sed -i "/postgresql\['shared_buffers'\]/ s|=.*|= \"256MB\"|" $CONF

service gitlab-runsvdir stop
# ensure that the overlay turnkey service is the one that is enabled
systemctl disable gitlab-runsvdir.service
systemctl enable gitlab-runsvdir.service
Expand Down
104 changes: 76 additions & 28 deletions docs/update-apt-repo-key.rst
Original file line number Diff line number Diff line change
@@ -1,44 +1,92 @@
TunrKey Linux GitLab - Update GitLab apt repo key
=================================================
TurnKey Linux GitLab - Rotate the GitLab APT repository key
============================================================

.. contents::


Context
=======

This doc details how to fix a `GitLab "NO_PUBKEY" error`_ message when using
apt.
This document explains how to recover from a GitLab repository ``NO_PUBKEY``
or expired-key error. Debian Trixie does not provide ``apt-key``. The GitLab
repository is instead restricted to
``/usr/share/keyrings/gitlab-ce.gpg`` by the source's ``signed-by`` option.

Background
==========
Trust boundary
==============

To ensure that the packages that you download are the ones provided by the
packager, apt repositories are cryptographically signed with a GPG key. From
time to time, these keys are "rotated" (i.e. new keys generated and this new
key used instead of the old one). When this happens, you will need to update
the GPG keyring that apt checks against when downloadng apt package lists.
Obtain the full current repository-metadata signing-key fingerprint from the
official `GitLab Linux package signatures`_ page through a trusted browser.
The fingerprint documented for this appliance release is
``F6403F6544A38863DAA0B6E03F01618A51312F3F``. If GitLab has published a
replacement, substitute its complete 40-character uppercase fingerprint in
the procedure below. Do not trust a short key ID or the downloaded key alone.

GitLab upstream `provide instructions` on how to do that. However, TurnKey
Linux follows the "best practice" convention of specifying which particular
key any 3rd party repository should use. To ensure that this is honored, the
key needs to be stored in a particular location (as defined in the relevant
`sources.list entry`_) and added in a way slightly
different to the upstream instructions.
The procedure verifies the download before changing trust, preserves the
per-repository ``signed-by`` restriction, and updates the appliance source
record consumed by ``gitlab-update --check``. Run it as ``root``::

How to update the GitLab GPG key
================================
set -eu
expected_fingerprint=F6403F6544A38863DAA0B6E03F01618A51312F3F
key_url=https://packages.gitlab.com/gpg.key
keyring=/usr/share/keyrings/gitlab-ce.gpg
source_list=/etc/apt/sources.list.d/gitlab-ce.list
source_record=/usr/local/share/turnkey-gitlab/source
source_line="deb [signed-by=$keyring] https://packages.gitlab.com/gitlab/gitlab-ce/debian/ trixie main"
work=$(mktemp -d /tmp/gitlab-key-rotation.XXXXXXXX)
staged_keyring=
staged_record=
cleanup() {
rm -rf -- "$work"
test -z "$staged_keyring" || rm -f -- "$staged_keyring"
test -z "$staged_record" || rm -f -- "$staged_record"
}
trap cleanup EXIT
trap 'exit 1' HUP INT TERM

Assuming that the new keyfile provided by GitLab is the same as it was when
they rotated their keys (April 2020), then this will resolve the issue::
test "$(id -u)" -eq 0
grep -Fxq "$source_line" "$source_list"
test "$(grep -c '^repository_key_fingerprint=' "$source_record")" -eq 1
test "$(grep -c '^repository_key_sha256=' "$source_record")" -eq 1

curl -o /tmp/gitlab-ce.key https://packages.gitlab.com/gpg.key
apt-key --keyring /usr/share/keyrings/gitlab-ce.gpg add /tmp/gitlab-ce.key
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
"$key_url" --output "$work/gitlab.key"
fingerprint=$(gpg --show-keys --with-colons "$work/gitlab.key" | \
awk -F: '$1 == "fpr" { print $10; exit }')
test "$fingerprint" = "$expected_fingerprint"
key_sha256=$(sha256sum "$work/gitlab.key" | awk '{ print $1 }')

Note that if you are not running as root, 'sudo' will be required for the
second line.
staged_keyring=$(mktemp /usr/share/keyrings/gitlab-ce.gpg.XXXXXXXX)
gpg --batch --yes --dearmor --output "$staged_keyring" \
"$work/gitlab.key"
chmod 0644 "$staged_keyring"
test "$(gpg --show-keys --with-colons "$staged_keyring" | \
awk -F: '$1 == "fpr" { print $10; exit }')" = \
"$expected_fingerprint"

staged_record=$(mktemp /usr/local/share/turnkey-gitlab/source.XXXXXXXX)
sed \
-e "s/^repository_key_fingerprint=.*/repository_key_fingerprint=$expected_fingerprint/" \
-e "s/^repository_key_sha256=.*/repository_key_sha256=$key_sha256/" \
"$source_record" >"$staged_record"
chmod --reference="$source_record" "$staged_record"

.. _provide instructions: https://docs.gitlab.com/omnibus/update/package_signatures.html#fetching-new-keys-after-2020-04-06
.. _GitLab "NO_PUBKEY" error: https://github.com/turnkeylinux/tracker/issues/1441
.. _sources.list entry: https://github.com/turnkeylinux-apps/gitlab/blob/master/overlay/etc/apt/sources.list.d/gitlab-ce.list#L4
mv -f -- "$staged_keyring" "$keyring"
staged_keyring=
mv -f -- "$staged_record" "$source_record"
staged_record=

apt-get update
gitlab-update --check | tee "$work/update-check"
grep -Fxq "integrity=APT-signed-by-$expected_fingerprint" \
"$work/update-check"
grep -Fxq "repository_key_download_sha256=$key_sha256" \
"$work/update-check"

Every trust check occurs before APT refreshes repository metadata. If the
command is interrupted between the two final moves, ``gitlab-update --check``
fails because the keyring and source record disagree. Rerun the complete
procedure rather than weakening the ``signed-by`` restriction.


.. _GitLab Linux package signatures: https://docs.gitlab.com/omnibus/update/package_signatures/
Loading