2026-06-07 に公開
この記事は, Giteaをアップグレードする際にCodexと相談しながら進めたものを、最終的にブログ調にしてもらっています。
ところどころ変な言い回しがあるかもしれませんが、内容は確認しているのでご容赦ください。
こんにちは
長らく運用していたGiteaを、ようやく重い腰を上げてアップグレードしました。
もともとは、Giteaのバージョンアップに加えて、DBをMySQLからPostgreSQLへ移行したいというところが発端です。
ただし、アプリケーションのアップグレードとDBエンジンの移行を同時にやると、何か起きたときに切り分けが難しくなります。
そのため今回は、まずGitea本体を 1.17.x から 1.26.x まで上げるところに集中することにしました。
PostgreSQLへの移行は別フェーズです。
なお、今回の記事はきれいな手順書というより、実際に作業しながら踏んだ罠の記録です。
最終的に 1.26.2 までは到達しましたが、途中でいくつか方針を変えています。
特に gitea dump をアップグレード中のバックアップとして使う方針は途中でやめました。
GiteaはDockerではなく、binaryを配置してsystemdで起動している構成です。
Giteaは、バージョンごとにディレクトリを作成し、currentというシンボリックリンクで切り替える構成にしていました。
/usr/local/gitea/
1.17.1/
bin/gitea
ini/app.ini
current -> /usr/local/gitea/1.17.1
systemd側はcurrentを参照しています。
ExecStart=/usr/local/gitea/current/bin/gitea web -c /usr/local/gitea/current/ini/app.ini
WorkingDirectory=/var/lib/gitea
データ類は以下のような感じでした。
/var/lib/gitea/custom
/var/lib/gitea/data
/var/lib/gitea/log
/tank/data/app/gitea/gitea-repositories
/tank/data/app/gitea/lfs
/tankはZFSで、定期的にスナップショットを取得してS3に転送しています。
そのため、最終的にはlog以外の永続データを/tank側に寄せたほうがよさそうです。
ただ、アップグレード中にデータ配置まで変えると、何か起きたときに原因がわからなくなります。
今回は既存の配置を維持したまま進めることにしました。
Giteaは起動時にDB migrationが走ります。
つまり、binaryを差し替えて起動した時点でDBスキーマが更新される可能性があります。
currentのリンクを古いバージョンに戻しただけではロールバックできません。
そこで、最初は以下のようにマイナーバージョンを刻んで上げていく方針にしました。
1.17.1
-> 1.18.5
-> 1.19.4
-> 1.20.6
-> 1.21.11
-> 1.22.6
-> 1.23.8
-> 1.24.7
-> 1.25.5
-> 1.26.2
Ansibleでは、おおよそ以下を繰り返すようにしました。
currentのリンク切り替えは便利ですが、ロールバック手段ではありません。
DB migrationが走った後に戻すには、DB dumpやスナップショットから戻す必要があります。
今回の作業はAnsible化しました。
PythonやAnsible自体はuvで管理しています。
uv add ansible ansible-lint
uv run ansible-playbook --version
作成したファイル構成はだいたいこんな感じです。
ansible.cfg
group_vars/gitea.yml
inventory/hosts.example.yml
playbooks/preflight.yml
playbooks/upgrade.yml
roles/gitea_upgrade/
preflight.ymlは、その名の通り事前検証用です。
Giteaの停止やアップグレードは行わず、次のような確認だけをします。
実際にアップグレードするのはupgrade.ymlです。
最初に、変数が未定義で落ちました。
Error while resolving value for 'path': 'gitea_current_link' is undefined
group_vars/gitea.ymlをプロジェクト直下に置いていたのですが、playbookはplaybooks/配下にありました。
この構成だと、期待していたようにgroup_varsが読まれていませんでした。(Ansibleに詳しい人には自明の理かもしれませんね)
ひとまず、playbook側で明示的に読み込むようにしました。
vars_files:
- ../group_vars/gitea.yml
次に、ダウンロードしたGitea binaryを実行しようとして失敗しました。
Permission denied: /usr/local/gitea/1.18.5/bin/gitea
get_urlで mode: "0755"を指定していたのですが、既存ファイルがある場合なども考えると、後段で明示的に補正したほうが安全そうでした。
- name: Ensure target Gitea binary is executable
ansible.builtin.file:
path: "{{ gitea_upgrade_target_binary }}"
owner: root
group: root
mode: "0755"
これで再実行時にも権限が補正されるようになりました。
Gitea関連のコマンドは、rootではなくGitea実行ユーザーで実行したいです。
今回の環境ではgiteaユーザーです。
ところが、become_user: giteaで実行した際にAnsibleの一時ディレクトリへ書き込めずに失敗しました。
Permission denied: /tmp/.ansible-tmp/...
対処として、ansible.cfgでremote tmpを/tmp配下に寄せ、preflightで1777のディレクトリとして作成するようにしました。
remote_tmp = /tmp/.ansible-tmp
system_tmpdirs = /tmp, /var/tmp
- name: Ensure Ansible remote tmp is usable by become users
ansible.builtin.file:
path: "{{ gitea_ansible_remote_tmp }}"
state: directory
owner: root
group: root
mode: "1777"
当初、各ステップの直前にgitea dumpを取るようにしていました。
しかし、CLIからgitea dumpを実行すると、以下のように/usr/local/gitea/current/bin/dataを見に行って失敗しました。
Creating new Local Storage at /usr/local/gitea/current/bin/data/attachments
mkdir /usr/local/gitea/current/bin/data: permission denied
systemdで動いているときはWorkingDirectory=/var/lib/giteaが効いています。
一方で、Ansibleから直接binaryを実行すると、systemdのWorkingDirectoryは当然効きません。
つまり、CLI実行時のwork pathがsystemd起動時とズレていました。
対処として、Gitea CLIには--work-path /var/lib/giteaを明示するようにしました。
/usr/local/gitea/current/bin/gitea \
--work-path /var/lib/gitea \
dump \
-c /usr/local/gitea/current/ini/app.ini
Ansible側では変数として持たせています。
gitea_work_path: /var/lib/gitea
APP_DATA_PATHを書けばよいのでは、と思いましたが、今回の問題はCLI実行時のwork path差分でした。
systemd経由で起動した場合と、手元から直接binaryを叩いた場合で見える世界が違う、という話です。
--work-pathを直したので、これでgitea dumpが通るかと思ったのですが、次はDB dumpで落ちました。
Failed to dump database: Error 1146: Table 'gitea.system_setting' doesn't exist
system_settingは、Gitea内部のDB-backed system settings用のテーブルです。
ざっくり言うと、Giteaの設定の一部をDB側にも持つためのkey-value storeです。
ただ、今回の問題はsystem_settingそのものというより、古いスキーマに対して新しいGiteaのdumpが存在しないテーブルを参照してしまうことでした。
GiteaのdumpはGitea/XORM経由でDB dumpを行います。
そのため、バージョン境界の古いスキーマと相性が悪い場面がありそうでした。
ここで方針を変えました。
gitea_dump_before_each_step: false
アップグレード中のバックアップとしてgitea dumpを使うのをやめ、以下に寄せます。
必要であれば、各ステップ直前に任意のコマンドを挟めるようにしました。
gitea_extra_backup_commands:
- "mysqldump --single-transaction --routines --triggers --events --default-character-set=utf8mb4 gitea > /var/backups/gitea/mysql-before-{{ gitea_version }}-{{ ansible_facts.date_time.iso8601_basic_short }}.sql"
最初は「毎回gitea dumpを取れば安心」と思っていましたが、今回のような大幅アップグレードでは逆に足を引っ張る場面がありました。
DBはDBのnative dumpを使うほうが素直そうです。
1.26.2まで到達したあと、最後にadmin regenerate hooksを実行したところ失敗しました。
Unknown column 'default_wiki_branch' in 'SELECT'
default_wiki_branchはrepository table側のカラムで、wiki repositoryのdefault branchを扱うためのものです。
この時点でアップグレード本体は1.26.2まで進んでいたのですが、hooks再生成のタイミングでDB schemaの不整合を踏んでしまいました。
少なくとも、hooks再生成をplaybookの最後で自動実行すると、migration状態を確認する前に別のエラーで落ちてしまいます。
そのため、hooks再生成は自動実行しない方針に変更しました。
gitea_regenerate_hooks_after_upgrade: false
DB migrationとWeb起動確認が終わった後に、必要に応じて手動で実行することにします。
sudo -u gitea /usr/local/gitea/current/bin/gitea \
--work-path /var/lib/gitea \
admin regenerate hooks \
-c /usr/local/gitea/current/ini/app.ini
最終的に、今回のアップグレード方針は以下のようになりました。
最終的に到達したら、まずsystemdとversionを確認します。
sudo systemctl status gitea
/usr/local/gitea/current/bin/gitea --version
sudo journalctl -u gitea -n 200 --no-pager
起動ログでmigration errorがないことを確認します。
sudo systemctl restart gitea
sudo journalctl -u gitea -f
問題なさそうであれば、必要に応じてhooksを再生成します。
sudo -u gitea /usr/local/gitea/current/bin/gitea \
--work-path /var/lib/gitea \
admin regenerate hooks \
-c /usr/local/gitea/current/ini/app.ini
こまめにアップグレードしよう