3 年ぶりに Redmine のサーバを引越しした。

ライフログのために Redmine を愛用している。何かと忙しく複雑な日々のスケジュールや買い物をチケットとして管理する。一言でいえばクラウド日記帳のような使い方ということだが…チケットに様々な情報を記録し、それをどんどん関連付けていくことで、過去のイベントや行動、反省点を振り返りながら行動できる。難しいことは Redmine に記録する。その後は、また必要になるまで忘れてしまって良い。その分だけ目の前のできごとに集中して楽しむことができる。これが私のスタイルに適合して暮らしの必需品になっている。

引越し元のサーバは Amazon EC2 である。 その時の作業メモは本ブログに残してある (自分用 Redmine を Amazon EC2 + Docker 環境に移行した)。 3 年前の当時 EC2 の Saving Plans を買って運用してきた。 それを最近になって使い切ったので、より安い VPS へまた移すことにする。

もちろん、当時の移行の記録や、サーバ見直しのスケジュールも Redmine で管理してきた。

引越し先は WebARENA Indigo。他には Vultr や Linode 等も検討したが、円安もあってそれほど安くないし、WebARENA Indigo のリソースが丁度良いと考えた。WebARENA Indigo では、RAM 1 GB, 1 vCPU, 20 GB SSD の構成を月額 450 円で使える。 しかも、ちょうど 「eKYC」本人確認キャンペーン を開催している。最大 3 ヶ月間は 35 % OFF で使えるらしい。月額 300 円。これはお得だ。

この記事では Redmine サーバを引越しするための手順を記録する。 引越し後のサーバでは OS に初めて AlmaLinux を使ってみた。 加えて、せっかくなので Podman に挑戦した。 これまで使ってきた docker-compose.yml を活用し、かつユーザ権限で Redmine サーバを常時起動させるには少し工夫が必要だった。

1. バックアップ

何をするにもまずはバックアップ。移行元のサーバにログインしてバックアップを取得する。

1.1. 旧サーバの様子を確認

ログインする:

> ssh fg-redmine
Last login: Mon Sep 25 17:06:47 2023 from 172.135.178.217.shared.user.transix.jp

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
54 package(s) needed for security, out of 71 available
Run "sudo yum update" to apply all updates.

サーバの状態を確認する:

$ w
 17:10:03 up 81 days, 23:49,  1 user,  load average: 0.08, 0.02, 0.01
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
ec2-user pts/0    172.135.178.217. 17:09    1.00s  0.01s  0.00s w
$ uname -a
Linux ip-172-31-12-243.ap-northeast-1.compute.internal 4.14.318-241.531.amzn2.x86_64 #1 SMP Tue Jun 27 21:49:00 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
$ docker --version
Docker version 20.10.23, build 7155243

Docker Compose で起動していた:

$ cd redmine/
$ docker-compose ps
      Name                     Command               State                                   Ports
-----------------------------------------------------------------------------------------------------------------------------------
redmine_certbot_1   /bin/sh -c trap exit TERM; ...   Up      443/tcp, 80/tcp
redmine_db_1        docker-entrypoint.sh postgres    Up      5432/tcp
redmine_nginx_1     /docker-entrypoint.sh /bin ...   Up      0.0.0.0:443->443/tcp,:::443->443/tcp, 0.0.0.0:80->80/tcp,:::80->80/tcp
redmine_redmine_1   /docker-entrypoint.sh rail ...   Up      3000/tcp

docker-compose.yml は下記のようにしていた:

version: '3.1'
services:
  certbot:
    image: certbot/certbot
    restart: always
    volumes:
      - ./data/letsencrypt:/etc/letsencrypt
      - certbot:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot; sleep 12h & wait $${!}; done;'"
  nginx:
    image: nginx:1-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./data/nginx/conf:/etc/nginx/conf.d
      - ./data/nginx/ssl:/etc/ssl/private
      - ./data/letsencrypt:/etc/letsencrypt
      - certbot:/var/www/letsencrypt
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
  redmine:
    image: redmine:5.0.5-alpine
    restart: always
    volumes:
      - ./data/redmine/conf/configuration.yml:/usr/src/redmine/config/configuration.yml
      - ./data/redmine/files:/usr/src/redmine/files
      - ./data/redmine/plugins:/usr/src/redmine/plugins
      - ./data/redmine/themes/bleuclair:/usr/src/redmine/public/themes/bleuclair
    environment:
      REDMINE_DB_POSTGRES: db
      REDMINE_DB_DATABASE: redmine
      REDMINE_DB_USERNAME: postgres
      REDMINE_DB_PASSWORD: ************
  db:
    image: postgres:9.5-alpine
    restart: always
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./backup/db:/backup
    environment:
      POSTGRES_DB: redmine
      POSTGRES_PASSWORD: ************
volumes:
  db-data:
  certbot:

1.2. バックアップ生成

では早速、データベースのバックアップを生成していく。 Docker ボリュームを使用し、./backup ディレクトリをデータベースのコンテナの /backup にマウントしてある。

PostgreSQL サーバ上で pg_dump コマンドを実行し、その出力を /backup へ出力した:

$ BKNAME="redmine-2023926"
$ docker-compose exec db pg_dump -U postgres -Fc -h localhost -f "/backup/$BKNAME.sqlc" redmine
$ ls backup/db/
redmine-20230705.sqlc  redmine-2023926.sqlc

次に、先程の SQL ファイルと、Redmine のファイル群をローカル PC へ一気に回収する。 Redmine のファイル群は rsync で直接ダウンロードする。王道ならばサーバ上で一度 GZIP とか BZIP2 とかにまとめてしまうところ。しかし、そうするとディスク容量がファイル実体の 2 倍必要になってしまう。ディスク容量の費用をケチるために直接 rsync する。

下記はローカル PC 上で実行した:

> cd backup/web/redmine.xaxxi.net/
> bash
$ BKNAME="redmine-20230926"
$ scp "fg-redmine:/home/ec2-user/redmine/backup/db/$BKNAME.sqlc" .
$ rsync -av "fg-redmine:/home/ec2-user/redmine/data/redmine/*" "$BKNAME"
$ tar zcf "$BKNAME.tar.gz" "$BKNAME"
$ ls -lh
合計 2.3G
(中略)
drwxr-xr-x. 2 fujii fujii    0  9月 26 02:12 redmine-2023926
-rwxr-xr-x. 1 fujii fujii 3.0M  9月 26 02:12 redmine-2023926.sqlc
-rwxr-xr-x. 1 fujii fujii 237M  9月 26 02:13 redmine-2023926.tar.gz

1.3. 以前のバックアップを削除

最後に、前回のメンテナンスで作った古いバックアップを削除した。

どうせ AWS 側のサーバは畳むのだから、この作業は不要だ。 しかし次回のメンテナンスも、今書いているこの手順を見て行う。 その時に忘れないために残しておく。

$ rm backup/db/redmine-20230705.sqlc

2. Redmine サーバの更新

移行前のサーバの Redmine を最新に更新し、その Redmine で動作することを確認しておく。 これで新しいサーバでは最新の Redmine バージョンから始めることができる。 データの復元が少し安全になるだろう。

更新前の Redmine バージョンは 5.0.4:

Redmine version                5.0.4.stable
Ruby version                   3.1.3-p185 (2022-11-24) [x86_64-linux-musl]
Rails version                  6.1.7
Environment                    production
Database adapter               PostgreSQL
Mailer queue                   ActiveJob::QueueAdapters::AsyncAdapter
Mailer delivery                smtp

2.1. パッケージ更新

パッケージを更新する:

$ sudo yum update
(中略)
Removed:
  kernel.x86_64 0:4.14.301-224.520.amzn2

Installed:
  grub2.x86_64 1:2.06-14.amzn2.0.1                                 grub2-pc.x86_64 1:2.06-14.amzn2.0.1
  grub2-tools.x86_64 1:2.06-14.amzn2.0.1                           grub2-tools-efi.x86_64 1:2.06-14.amzn2.0.1
  grub2-tools-extra.x86_64 1:2.06-14.amzn2.0.1                     grub2-tools-minimal.x86_64 1:2.06-14.amzn2.0.1
  kernel.x86_64 0:4.14.322-246.539.amzn2

Updated:
  amazon-ssm-agent.x86_64 0:3.2.1377.0-1.amzn2                   bind-export-libs.x86_64 32:9.11.4-26.P2.amzn2.13.4
  bind-libs.x86_64 32:9.11.4-26.P2.amzn2.13.4                    bind-libs-lite.x86_64 32:9.11.4-26.P2.amzn2.13.4
  bind-license.noarch 32:9.11.4-26.P2.amzn2.13.4                 bind-utils.x86_64 32:9.11.4-26.P2.amzn2.13.4
  ca-certificates.noarch 0:2021.2.50-72.amzn2.0.8                containerd.x86_64 0:1.6.19-1.amzn2.0.3
  curl.x86_64 0:8.2.1-1.amzn2.0.3                                dhclient.x86_64 12:4.2.5-79.amzn2.1.5
  dhcp-common.x86_64 12:4.2.5-79.amzn2.1.5                       dhcp-libs.x86_64 12:4.2.5-79.amzn2.1.5
  ec2-hibinit-agent.noarch 0:1.0.2-5.amzn2                       elfutils-default-yama-scope.noarch 0:0.176-2.amzn2.0.2
  elfutils-libelf.x86_64 0:0.176-2.amzn2.0.2                     elfutils-libs.x86_64 0:0.176-2.amzn2.0.2
  glibc.x86_64 0:2.26-63.amzn2.0.1                               glibc-all-langpacks.x86_64 0:2.26-63.amzn2.0.1
  glibc-common.x86_64 0:2.26-63.amzn2.0.1                        glibc-locale-source.x86_64 0:2.26-63.amzn2.0.1
  glibc-minimal-langpack.x86_64 0:2.26-63.amzn2.0.1              grub2-common.noarch 1:2.06-14.amzn2.0.1
  grub2-pc-modules.noarch 1:2.06-14.amzn2.0.1                    kernel-tools.x86_64 0:4.14.322-246.539.amzn2
  krb5-libs.x86_64 0:1.15.1-55.amzn2.2.6                         libcap.x86_64 0:2.54-1.amzn2.0.2
  libcrypt.x86_64 0:2.26-63.amzn2.0.1                            libcurl.x86_64 0:8.2.1-1.amzn2.0.3
  libgcc.x86_64 0:7.3.1-17.amzn2                                 libgomp.x86_64 0:7.3.1-17.amzn2
  libicu.x86_64 0:50.2-4.amzn2.0.1                               libidn.x86_64 0:1.28-4.amzn2.0.5
  libidn2.x86_64 0:2.3.0-1.amzn2.0.3                             libjpeg-turbo.x86_64 0:2.0.90-2.amzn2.0.6
  libnghttp2.x86_64 0:1.41.0-1.amzn2.0.3                         libpng.x86_64 2:1.5.13-8.amzn2.0.5
  libssh2.x86_64 0:1.4.3-12.amzn2.2.6                            libstdc++.x86_64 0:7.3.1-17.amzn2
  libtasn1.x86_64 0:4.10-1.amzn2.0.6                             libtiff.x86_64 0:4.0.3-35.amzn2.0.14
  libxml2.x86_64 0:2.9.1-6.amzn2.5.12                            libxml2-python.x86_64 0:2.9.1-6.amzn2.5.12
  microcode_ctl.x86_64 2:2.1-47.amzn2.2.15                       openldap.x86_64 0:2.4.44-25.amzn2.0.7
  openssh.x86_64 0:7.4p1-22.amzn2.0.5                            openssh-clients.x86_64 0:7.4p1-22.amzn2.0.5
  openssh-server.x86_64 0:7.4p1-22.amzn2.0.5                     openssl.x86_64 1:1.0.2k-24.amzn2.0.9
  openssl-libs.x86_64 1:1.0.2k-24.amzn2.0.9                      python-configobj.noarch 0:4.7.2-7.amzn2.0.1
  python-ipaddress.noarch 0:1.0.16-2.amzn2.0.2                   python-pillow.x86_64 0:2.0.0-23.gitd1c6db8.amzn2.0.7
  python-requests.noarch 0:2.6.0-10.amzn2.0.1                    python2-rsa.noarch 0:3.4.1-1.amzn2.0.4
  python3.x86_64 0:3.7.16-1.amzn2.0.4                            python3-libs.x86_64 0:3.7.16-1.amzn2.0.4
  python3-pip.noarch 0:20.2.2-1.amzn2.0.4                        python3-setuptools.noarch 0:49.1.3-1.amzn2.0.3
  runc.x86_64 0:1.1.7-3.amzn2                                    shadow-utils.x86_64 2:4.1.5.1-24.amzn2.0.3
  sudo.x86_64 0:1.8.23-10.amzn2.3.4                              system-release.x86_64 1:2-15.amzn2
  tcpdump.x86_64 14:4.9.2-4.amzn2.1.0.1                          yajl.x86_64 0:2.0.4-4.amzn2.0.3

Replaced:
  grub2.x86_64 1:2.06-9.amzn2.0.3                                grub2-tools.x86_64 1:2.06-9.amzn2.0.3

Complete!

Redmine 稼働状態を調べる。 なぜわざわざ確認するかというと、パッケージ更新によりプロセスが停止してしまうことがあったからだ。 サーバ費用をケチったせいだ。

$ docker-compose ps
      Name                     Command               State                                   Ports
-----------------------------------------------------------------------------------------------------------------------------------
redmine_certbot_1   /bin/sh -c trap exit TERM; ...   Up      443/tcp, 80/tcp
redmine_db_1        docker-entrypoint.sh postgres    Up      5432/tcp
redmine_nginx_1     /docker-entrypoint.sh /bin ...   Up      0.0.0.0:443->443/tcp,:::443->443/tcp, 0.0.0.0:80->80/tcp,:::80->80/tcp
redmine_redmine_1   /docker-entrypoint.sh rail ...   Up      3000/tcp

でも今回はちゃんと生き延びたようだ。

2.2. Redmine 本体の更新

Redmine イメージを更新する。更新後のバージョンは v5.0.5。 docker-compose.yml を編集して Redmine のイメージを変更した:

$ vim docker-compose.yml
$ grep redmine: docker-compose.yml
  redmine:
    image: redmine:5.0.5-alpine
$ docker-compose pull

Redmine コンテナを再起動:

$ docker-compose down
$ docker-compose up -d

このあと無事に Redmine が動作することを確認した。 バージョンはほとんど同じなのでマイグレーションは不要。 それでも再度バックアップを取っておくのが望ましい。

更新後のバージョン:

Redmine version                5.0.5.stable
Ruby version                   3.1.4-p223 (2023-03-30) [x86_64-linux-musl]
Rails version                  6.1.7.2
Environment                    production
Database adapter               PostgreSQL
Mailer queue                   ActiveJob::QueueAdapters::AsyncAdapter
Mailer delivery                smtp

3. 新規サーバの準備

引越し先のサーバは先述の通り、WebARENA Indigo である。 OS には AlmaLinux 9.0 を選択した。AlmaLinux をインストールして使うのは初めて。楽しみだ。

インスタンスは作成済みの状態から作業を始める。

SSH 接続する。 fg-indigo2~/.ssh/config に登録してある:

> ssh fg-indigo2
[alma@i-19100000524547 ~]$

3.1. サーバ構成を眺める

まず記念に w:

$ w
17:33:48 up 7 min,  2 users,  load average: 0.00, 0.03, 0.01
USER     TTY        LOGIN@   IDLE   JCPU   PCPU WHAT
alma     pts/0     17:30   52.00s  0.00s  0.00s -bash
alma     pts/1     17:32    3.00s  0.01s  0.00s w

これも記念で OS 確認:

$ uname -a
Linux i-19100000524547 5.14.0-70.13.1.el9_0.x86_64 #1 SMP PREEMPT Tue May 17 15:53:11 EDT 2022 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/almalinux-release
AlmaLinux release 9.0 (Emerald Puma)

ストレージ。ルートパーティションは 20 GB:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        453M     0  453M   0% /dev
tmpfs           481M     0  481M   0% /dev/shm
tmpfs           193M  2.9M  190M   2% /run
/dev/vda4        20G  908M   19G   5% /
/dev/vda3       495M   99M  397M  20% /boot
/dev/vda2       200M  6.2M  194M   4% /boot/efi
tmpfs            97M     0   97M   0% /run/user/1000

CPU。1 vCPU だけ。個人で使うには問題無いと思っている:

$ lscpu
Architecture:            x86_64
  CPU op-mode(s):        32-bit, 64-bit
  Address sizes:         46 bits physical, 48 bits virtual
  Byte Order:            Little Endian
CPU(s):                  1
  On-line CPU(s) list:   0
Vendor ID:               GenuineIntel
  Model name:            Intel Xeon E312xx (Sandy Bridge)
    CPU family:          6
    Model:               42
    Thread(s) per core:  1
    Core(s) per socket:  1
    Socket(s):           1
    Stepping:            1
    BogoMIPS:            4399.98
    Flags:               fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm
                         constant_tsc rep_good nopl cpuid tsc_known_freq pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 x2apic popcnt tsc_dea
                         dline_timer aes xsave avx hypervisor lahf_lm pti xsaveopt
Virtualization features:
  Hypervisor vendor:     KVM
  Virtualization type:   full
Caches (sum of all):
  L1d:                   32 KiB (1 instance)
  L1i:                   32 KiB (1 instance)
  L2:                    4 MiB (1 instance)
NUMA:
  NUMA node(s):          1
  NUMA node0 CPU(s):     0
Vulnerabilities:
  Itlb multihit:         KVM: Mitigation: VMX unsupported
  L1tf:                  Mitigation; PTE Inversion
  Mds:                   Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
  Meltdown:              Mitigation; PTI
  Spec store bypass:     Vulnerable
  Spectre v1:            Mitigation; usercopy/swapgs barriers and __user pointer sanitization
  Spectre v2:            Mitigation; Retpolines, STIBP disabled, RSB filling
  Srbds:                 Not affected
  Tsx async abort:       Not affected

RAM は 1 GB くらい:

$ cat /proc/meminfo
MemTotal:         983688 kB
MemFree:          702468 kB
MemAvailable:     695292 kB
Buffers:            5052 kB
Cached:           102960 kB
SwapCached:            0 kB
Active:            54432 kB
Inactive:         102552 kB
Active(anon):        936 kB
Inactive(anon):    52528 kB
Active(file):      53496 kB
Inactive(file):    50024 kB
Unevictable:        1536 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         50376 kB
Mapped:            47452 kB
Shmem:              4492 kB
KReclaimable:      24392 kB
Slab:              50264 kB
SReclaimable:      24392 kB
SUnreclaim:        25872 kB
KernelStack:        1868 kB
PageTables:         1652 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      491844 kB
Committed_AS:     282632 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       14148 kB
VmallocChunk:          0 kB
Percpu:              476 kB
HardwareCorrupted:     0 kB
AnonHugePages:      2048 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:       90092 kB
DirectMap2M:      958464 kB

ネットワーク。eth0。IP アドレスはなんとなく隠して貼っておく:

$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
    valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff
    altname enp0s10
    altname ens10
    inet ***.***.**.***/24 brd ***.***.**.*** scope global noprefixroute eth0
    valid_lft forever preferred_lft forever
    inet6 ****::***:****:****:***/64 scope link
    valid_lft forever preferred_lft forever

4.2. 基本的な設定

とりあえずシステム更新する:

$ sudo su -
# dnf --refresh update
# reboot

VIM だけは入れておく:

# dnf install vim-minimal
# vi --version | head -1
VIM - Vi IMproved 8.2 (2019 Dec 12, compiled Feb 09 2023 00:00:00)

ホスト名を設定して気持ちをスッキリさせる:

# hostname fg-indigo2
# vi /etc/hostname

後に邪魔になるので SELinux は無効化してしまう:

# getenforce
Enforcing
# vi /etc/selinux/config
# grep -e ^SELINUX= /etc/selinux/config
SELINUX=disabled

4. Podman の導入

以前のサーバでは Docker を使っていた。今回はせっかくなので Podman を使う。

4.1. Podman

Podman は Redhat 社が中心のオープンソースコミュニティで開発しているコンテナ管理ツール。Pods はコンテナをまとめたもの。Pods を manage するので Podman という名称らしい。

Docker と互換性を持ち、OCI (Open Container Initiative) コンテナに対応している。 そのため Docker HUB に登録されたイメージを使用することができる。 CLI コマンドも似せてあり、同じような考え方で操作できる。

Docker との大きな違いは、Podman がデーモンレスであること。 デーモンを介さずに起動することでルート権限を悪用されるセキュリティ上の問題を回避する。 ルートレスコンテナ、ユーザ権限で起動するコンテナに対応している。 Dockerd のようなデーモンに依存しないため、Dockerd が終了したら全コンテナが停止するということもない。

4.2. Podman の準備

Podman をインストールした:

# dnf install podman
# podman --version
podman version 4.4.1

podman-docker パッケージをインストールすると docker コマンドで Podman を使用できるようになる。 この記事を書いている今なら「初めから podman コマンドを使えば良くね」と考えるが、 作業当時はよくわかっていなかったこともあり、基本 docker コマンドで操作した。

この後に続く手順も dockerdocker-compose を前提にした操作となる。

# dnf install podman-docker
# docker --version
Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg.
podman version 4.4.1

言われた通りに /etc/containers/nodocker を作成するとメッセージが静かになる:

# touch /etc/containers/nodocker

Podman API Socket を有効化する。Docker コマンド経由で使用するには必要:

# systemctl enable podman.socket
Created symlink /etc/systemd/system/sockets.target.wants/podman.socket → /usr/lib/systemd/system/podman.socket.
# systemctl status podman.socket
● podman.socket - Podman API Socket
     Loaded: loaded (/usr/lib/systemd/system/podman.socket; enabled; preset: disabled)
     Active: active (listening) since Mon 2023-09-25 18:02:20 UTC; 7min ago
      Until: Mon 2023-09-25 18:02:20 UTC; 7min ago
   Triggers: ● podman.service
       Docs: man:podman-system-service(1)
     Listen: /run/podman/podman.sock (Stream)
     CGroup: /system.slice/podman.socket

Sep 25 18:02:20 fg-indigo2 systemd[1]: Listening on Podman API Socket.

起動したソケットに接続してテストする。OK が返ってきた:

# curl -w "\n" -H "Content-Type: application/json" --unix-socket /var/run/docker.sock http://localhost/_ping
OK

カーネルパラメータの net.ipv4.ip_unprivileged_port_start を変更した。 ユーザ権限で 80 番ポートを LISTEN できるようになる:

# vi /etc/sysctl.d/99-sysctl.conf
# sysctl -p
net.ipv4.ip_unprivileged_port_start = 80

ここからは alma ユーザに戻り、ユーザ権限で作業する。

ユーザモードの Podman ソケットも生成:

# exit
$ systemctl --user enable podman.socket
Created symlink /home/alma/.config/systemd/user/sockets.target.wants/podman.socket → /usr/lib/systemd/user/podman.socket.

ユーザモードの Podman ソケットを使うよう、環境変数を設定する:

$ export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock
$ vi ~/.bash_profile
$ grep DOCKER_HOST ~/.bash_profile
export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock

ようやく hello-world を実行できるようになった:

$ docker run --rm hello-world
Resolved "hello-world" as an alias (/etc/containers/registries.conf.d/000-shortnames.conf)
Trying to pull quay.io/podman/hello:latest...
Getting image source signatures
Copying blob d08b40be6878 done
Copying config e2b3db5d4f done
Writing manifest to image destination
Storing signatures
!... Hello Podman World ...!

         .--"--.
       / -     - \
      / (O)   (O) \
   ~~~| -=(,Y,)=- |
    .---. /`  \   |~~
 ~/  o  o \~~~~.----. ~~
  | =(X)= |~  / (O (O) \
   ~~~~~~~  ~| =(Y_)=-  |
  ~~~~    ~~~|   U      |~~

Project:   https://github.com/containers/podman
Website:   https://podman.io
Documents: https://docs.podman.io
Twitter:   @Podman_io

ダグトリオみたいなのが現われた。Podman のマスコットキャラクターである。ありがてえ。

4.3. Docker compose のインストール

以前のサーバと同じような手順で Redmine を起動したいと考え、Docker Compose をインストールした。 Docker と共通のものを使うことができる:

# curl -SL https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
# chmod 0755 /usr/local/bin/docker-compose
# docker-compose --version
Docker Compose version v2.20.3

実際のところ直接 Pods を使うべきだったのか否か、調べていない。

5. Redmine の構築

5.1. SSL 証明書の発行

Let’s Encrypt から証明書を取得する。certbot を Docker で実行した。 コマンド実行前に、取得対象のドメインの DNS レコードを設定しておく必要がある。

慎重に作業するなら、以前と異なるドメインを使うと良い。 この手順では記述を割愛したが、私は一旦仮ドメインの証明書を取得し構築後、うまくいったのを見届けてから本来のドメインで同じ作業をやり直した。

$ mkdir -p ~/redmine/data/letsencrypt
$ cd ~/redmine
$ docker run --rm -ti -v $PWD/data/letsencrypt:/etc/letsencrypt -p 80:80 certbot/certbot certonly --standalone
Requesting a certificate for redmine.xaxxi.net

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/redmine.xaxxi.net/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/redmine.xaxxi.net/privkey.pem
This certificate expires on 2023-12-25.
These files will be updated when the certificate renews.

NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
$ ls data/letsencrypt
accounts  archive  live  renewal  renewal-hooks

5.2. Nginx の設定ファイル作成

Nginx の設定ファイルを作成する:

$ mkdir -p ~/redmine/data/nginx/{conf,ssl}
$ vi data/nginx/conf/proxy.conf
$ cat data/nginx/ssl/dhparam.pem

proxy.conf の内容は下記とした:

server {
    listen        80;
    server_name   redmine.xaxxi.net;

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    return 301    https://redmine.xaxxi.net$request_uri;
}
server {
    listen        443 ssl;
    server_name   redmine.xaxxi.net;

    ssl_certificate          /etc/letsencrypt/live/redmine.xaxxi.net/fullchain.pem;
    ssl_certificate_key      /etc/letsencrypt/live/redmine.xaxxi.net/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    ssl_trusted_certificate  /etc/letsencrypt/live/redmine.xaxxi.net/chain.pem;
    ssl_dhparam              /etc/ssl/private/dhparam.pem;

    add_header Strict-Transport-Security "max-age=63072000" always;

    ssl_session_cache shared:SSL:10m;

    ssl_prefer_server_ciphers on;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        proxy_set_header     X-Forward-For $remote_addr;
        proxy_pass           http://redmine:3000/;
        client_max_body_size 500M;
    }
}

5.4. Redmine を起動

ここまでやると、とりあえず真っ新な状態の Redmine を起動できた。

$ docker-compose pull
$ docker-compose up -d

5.5. Redmine のデータ復元

Redmine のバックアップデータをアップロードする。 アップロード先のディレクトリを用意:

$ mkdir -p ~/redmine/backup/data/redmine
$ mkdir -p ~/redmine/backup/db

ローカル PC からバックアップファイルをアップロードした。 手順の初めに用意したものだ:

> rsync -aP redmine-20230926/* fg-indigo2:/home/alma/redmine/data/redmine/
> rsync -aP redmine-20230926.sqlc fg-indigo2:/home/alma/redmine/backup/db

PostgreSQL のデータを復元:

$ docker-compose exec db pg_restore -U postgres -d redmine -c -n public /backup/redmine-20230926.sqlc

なんか大量のエラーを出力した。全部プラグイン関係のもののようだ。使わないので見捨てた。

pg_restore: [archiver (db)] Error while PROCESSING TOC:
pg_restore: [archiver (db)] Error from TOC entry 2781; 1259 37130 INDEX unique_schema_migrations postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "unique_schema_migrations" does not exist
    Command was: DROP INDEX public.unique_schema_migrations;

pg_restore: [archiver (db)] Error from TOC entry 2763; 1259 37315 INDEX index_projects_on_easy_baseline_for_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_projects_on_easy_baseline_for_id" does not exist
    Command was: DROP INDEX public.index_projects_on_easy_baseline_for_id;

pg_restore: [archiver (db)] Error from TOC entry 2846; 1259 37328 INDEX index_easy_baseline_sources_on_source_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_easy_baseline_sources_on_source_id" does not exist
    Command was: DROP INDEX public.index_easy_baseline_sources_on_source_id;

pg_restore: [archiver (db)] Error from TOC entry 2845; 1259 37329 INDEX index_easy_baseline_sources_on_destination_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_easy_baseline_sources_on_destination_id" does not exist
    Command was: DROP INDEX public.index_easy_baseline_sources_on_destination_id;

pg_restore: [archiver (db)] Error from TOC entry 2844; 1259 37327 INDEX index_easy_baseline_sources_on_baseline_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_easy_baseline_sources_on_baseline_id" does not exist
    Command was: DROP INDEX public.index_easy_baseline_sources_on_baseline_id;

pg_restore: [archiver (db)] Error from TOC entry 2679; 1259 37053 INDEX index_dmsf_wrkfl_step_assigns_on_wrkfl_step_id_and_frev_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_dmsf_wrkfl_step_assigns_on_wrkfl_step_id_and_frev_id" does not exist
    Command was: DROP INDEX public.index_dmsf_wrkfl_step_assigns_on_wrkfl_step_id_and_frev_id;

pg_restore: [archiver (db)] Error from TOC entry 2682; 1259 37052 INDEX index_dmsf_workflow_steps_on_dmsf_workflow_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_dmsf_workflow_steps_on_dmsf_workflow_id" does not exist
    Command was: DROP INDEX public.index_dmsf_workflow_steps_on_dmsf_workflow_id;

pg_restore: [archiver (db)] Error from TOC entry 2673; 1259 37051 INDEX index_dmsf_locks_on_entity_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_dmsf_locks_on_entity_id" does not exist
    Command was: DROP INDEX public.index_dmsf_locks_on_entity_id;

pg_restore: [archiver (db)] Error from TOC entry 2670; 1259 37050 INDEX index_dmsf_links_on_project_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_dmsf_links_on_project_id" does not exist
    Command was: DROP INDEX public.index_dmsf_links_on_project_id;

pg_restore: [archiver (db)] Error from TOC entry 2667; 1259 37049 INDEX index_dmsf_folders_on_project_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_dmsf_folders_on_project_id" does not exist
    Command was: DROP INDEX public.index_dmsf_folders_on_project_id;

pg_restore: [archiver (db)] Error from TOC entry 2664; 1259 37048 INDEX index_dmsf_files_on_project_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_dmsf_files_on_project_id" does not exist
    Command was: DROP INDEX public.index_dmsf_files_on_project_id;

pg_restore: [archiver (db)] Error from TOC entry 2661; 1259 37047 INDEX index_dmsf_file_revisions_on_dmsf_file_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_dmsf_file_revisions_on_dmsf_file_id" does not exist
    Command was: DROP INDEX public.index_dmsf_file_revisions_on_dmsf_file_id;

pg_restore: [archiver (db)] Error from TOC entry 2611; 1259 37030 INDEX index_agile_data_on_position postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_agile_data_on_position" does not exist
    Command was: DROP INDEX public.index_agile_data_on_position;

pg_restore: [archiver (db)] Error from TOC entry 2610; 1259 37029 INDEX index_agile_data_on_issue_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_agile_data_on_issue_id" does not exist
    Command was: DROP INDEX public.index_agile_data_on_issue_id;

pg_restore: [archiver (db)] Error from TOC entry 2607; 1259 37028 INDEX index_agile_colors_on_container_type postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_agile_colors_on_container_type" does not exist
    Command was: DROP INDEX public.index_agile_colors_on_container_type;

pg_restore: [archiver (db)] Error from TOC entry 2606; 1259 37027 INDEX index_agile_colors_on_container_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "index_agile_colors_on_container_id" does not exist
    Command was: DROP INDEX public.index_agile_colors_on_container_id;

pg_restore: [archiver (db)] Error from TOC entry 2676; 1259 37026 INDEX idx_dmsf_wfstepact_on_wfstepassign_id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  index "idx_dmsf_wfstepact_on_wfstepassign_id" does not exist
    Command was: DROP INDEX public.idx_dmsf_wfstepact_on_wfstepassign_id;

pg_restore: [archiver (db)] Error from TOC entry 2843; 2606 37326 CONSTRAINT easy_baseline_sources_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.easy_baseline_sources" does not exist
    Command was: ALTER TABLE ONLY public.easy_baseline_sources DROP CONSTRAINT easy_baseline_sources_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2684; 2606 36941 CONSTRAINT dmsf_workflows_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflows" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_workflows DROP CONSTRAINT dmsf_workflows_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2681; 2606 36939 CONSTRAINT dmsf_workflow_steps_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflow_steps" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_workflow_steps DROP CONSTRAINT dmsf_workflow_steps_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2678; 2606 36937 CONSTRAINT dmsf_workflow_step_assignments_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflow_step_assignments" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_workflow_step_assignments DROP CONSTRAINT dmsf_workflow_step_assignments_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2675; 2606 36935 CONSTRAINT dmsf_workflow_step_actions_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflow_step_actions" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_workflow_step_actions DROP CONSTRAINT dmsf_workflow_step_actions_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2672; 2606 36933 CONSTRAINT dmsf_locks_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_locks" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_locks DROP CONSTRAINT dmsf_locks_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2669; 2606 36931 CONSTRAINT dmsf_links_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_links" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_links DROP CONSTRAINT dmsf_links_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2666; 2606 36929 CONSTRAINT dmsf_folders_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_folders" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_folders DROP CONSTRAINT dmsf_folders_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2663; 2606 36927 CONSTRAINT dmsf_files_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_files" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_files DROP CONSTRAINT dmsf_files_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2660; 2606 36925 CONSTRAINT dmsf_file_revisions_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_file_revisions" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_file_revisions DROP CONSTRAINT dmsf_file_revisions_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2658; 2606 36923 CONSTRAINT dmsf_file_revision_accesses_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_file_revision_accesses" does not exist
    Command was: ALTER TABLE ONLY public.dmsf_file_revision_accesses DROP CONSTRAINT dmsf_file_revision_accesses_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2609; 2606 36901 CONSTRAINT agile_data_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.agile_data" does not exist
    Command was: ALTER TABLE ONLY public.agile_data DROP CONSTRAINT agile_data_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2605; 2606 36899 CONSTRAINT agile_colors_pkey postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.agile_colors" does not exist
    Command was: ALTER TABLE ONLY public.agile_colors DROP CONSTRAINT agile_colors_pkey;

pg_restore: [archiver (db)] Error from TOC entry 2603; 2604 37321 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.easy_baseline_sources" does not exist
    Command was: ALTER TABLE public.easy_baseline_sources ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2470; 2604 36428 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflows" does not exist
    Command was: ALTER TABLE public.dmsf_workflows ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2469; 2604 36427 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflow_steps" does not exist
    Command was: ALTER TABLE public.dmsf_workflow_steps ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2468; 2604 36426 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflow_step_assignments" does not exist
    Command was: ALTER TABLE public.dmsf_workflow_step_assignments ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2467; 2604 36425 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_workflow_step_actions" does not exist
    Command was: ALTER TABLE public.dmsf_workflow_step_actions ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2466; 2604 36424 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_locks" does not exist
    Command was: ALTER TABLE public.dmsf_locks ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2464; 2604 36423 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_links" does not exist
    Command was: ALTER TABLE public.dmsf_links ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2463; 2604 36422 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_folders" does not exist
    Command was: ALTER TABLE public.dmsf_folders ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2460; 2604 36421 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_files" does not exist
    Command was: ALTER TABLE public.dmsf_files ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2457; 2604 36420 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_file_revisions" does not exist
    Command was: ALTER TABLE public.dmsf_file_revisions ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2453; 2604 36419 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.dmsf_file_revision_accesses" does not exist
    Command was: ALTER TABLE public.dmsf_file_revision_accesses ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2403; 2604 36409 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.agile_data" does not exist
    Command was: ALTER TABLE public.agile_data ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 2402; 2604 36408 DEFAULT id postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  relation "public.agile_colors" does not exist
    Command was: ALTER TABLE public.agile_colors ALTER COLUMN id DROP DEFAULT;

pg_restore: [archiver (db)] Error from TOC entry 302; 1259 37316 SEQUENCE easy_baseline_sources_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "easy_baseline_sources_id_seq" does not exist
    Command was: DROP SEQUENCE public.easy_baseline_sources_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 303; 1259 37318 TABLE easy_baseline_sources postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "easy_baseline_sources" does not exist
    Command was: DROP TABLE public.easy_baseline_sources;

pg_restore: [archiver (db)] Error from TOC entry 228; 1259 36024 SEQUENCE dmsf_workflows_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_workflows_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_workflows_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 227; 1259 36017 TABLE dmsf_workflows postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_workflows" does not exist
    Command was: DROP TABLE public.dmsf_workflows;

pg_restore: [archiver (db)] Error from TOC entry 226; 1259 36015 SEQUENCE dmsf_workflow_steps_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_workflow_steps_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_workflow_steps_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 225; 1259 36012 TABLE dmsf_workflow_steps postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_workflow_steps" does not exist
    Command was: DROP TABLE public.dmsf_workflow_steps;

pg_restore: [archiver (db)] Error from TOC entry 224; 1259 36010 SEQUENCE dmsf_workflow_step_assignments_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_workflow_step_assignments_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_workflow_step_assignments_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 223; 1259 36007 TABLE dmsf_workflow_step_assignments postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_workflow_step_assignments" does not exist
    Command was: DROP TABLE public.dmsf_workflow_step_assignments;

pg_restore: [archiver (db)] Error from TOC entry 222; 1259 36005 SEQUENCE dmsf_workflow_step_actions_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_workflow_step_actions_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_workflow_step_actions_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 221; 1259 35999 TABLE dmsf_workflow_step_actions postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_workflow_step_actions" does not exist
    Command was: DROP TABLE public.dmsf_workflow_step_actions;

pg_restore: [archiver (db)] Error from TOC entry 220; 1259 35997 SEQUENCE dmsf_locks_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_locks_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_locks_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 219; 1259 35994 TABLE dmsf_locks postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_locks" does not exist
    Command was: DROP TABLE public.dmsf_locks;

pg_restore: [archiver (db)] Error from TOC entry 218; 1259 35992 SEQUENCE dmsf_links_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_links_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_links_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 217; 1259 35985 TABLE dmsf_links postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_links" does not exist
    Command was: DROP TABLE public.dmsf_links;

pg_restore: [archiver (db)] Error from TOC entry 216; 1259 35983 SEQUENCE dmsf_folders_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_folders_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_folders_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 215; 1259 35975 TABLE dmsf_folders postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_folders" does not exist
    Command was: DROP TABLE public.dmsf_folders;

pg_restore: [archiver (db)] Error from TOC entry 214; 1259 35973 SEQUENCE dmsf_files_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_files_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_files_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 213; 1259 35965 TABLE dmsf_files postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_files" does not exist
    Command was: DROP TABLE public.dmsf_files;

pg_restore: [archiver (db)] Error from TOC entry 212; 1259 35963 SEQUENCE dmsf_file_revisions_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_file_revisions_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_file_revisions_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 211; 1259 35955 TABLE dmsf_file_revisions postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_file_revisions" does not exist
    Command was: DROP TABLE public.dmsf_file_revisions;

pg_restore: [archiver (db)] Error from TOC entry 210; 1259 35953 SEQUENCE dmsf_file_revision_accesses_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "dmsf_file_revision_accesses_id_seq" does not exist
    Command was: DROP SEQUENCE public.dmsf_file_revision_accesses_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 209; 1259 35949 TABLE dmsf_file_revision_accesses postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "dmsf_file_revision_accesses" does not exist
    Command was: DROP TABLE public.dmsf_file_revision_accesses;

pg_restore: [archiver (db)] Error from TOC entry 184; 1259 35814 SEQUENCE agile_data_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "agile_data_id_seq" does not exist
    Command was: DROP SEQUENCE public.agile_data_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 183; 1259 35811 TABLE agile_data postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "agile_data" does not exist
    Command was: DROP TABLE public.agile_data;

pg_restore: [archiver (db)] Error from TOC entry 182; 1259 35809 SEQUENCE agile_colors_id_seq postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  sequence "agile_colors_id_seq" does not exist
    Command was: DROP SEQUENCE public.agile_colors_id_seq;

pg_restore: [archiver (db)] Error from TOC entry 181; 1259 35803 TABLE agile_colors postgres
pg_restore: [archiver (db)] could not execute query: ERROR:  table "agile_colors" does not exist
    Command was: DROP TABLE public.agile_colors;

WARNING: errors ignored on restore: 69

これで Redmine を再起動する。 以前と同じチケットが表示されたら、移行は完了。めでたし、めでたし。

$ docker-compose down
$ docker-compose up -d

6. Redmine のサービス化

ここまでで docker-compose を使って Redmine を起動できた。 しかし一晩経つと勝手にコンテナを終了してしまうことが判明した。 どうやらルート権限で起動していると終了しないが、ユーザ権限では駄目な模様。 Podman 自体がデーモンレスなので、コマンドだけ docker-compose を使ったところで止められてしまうようだ。

6.1. Podman Compose のユニットファイル作成

Systemd にサービスとして登録すれば常時起動させることができる。 ここで Podman Compose を利用する。 Podman Compose には、docker-compose.yml に従い Pods を生成、さらに Systemd のユニットファイルを作成する便利な機能がある。

podman-compose をインストールするために Pip が必要:

$ sudo su -
# dnf install python3-pip
# exit

podman-compose をインストール:

# pip3 install podman-compose
Collecting podman-compose
  Using cached podman_compose-1.0.6-py2.py3-none-any.whl (34 kB)
Requirement already satisfied: pyyaml in /usr/lib64/python3.9/site-packages (from podman-compose) (5.4.1)
Requirement already satisfied: python-dotenv in /usr/local/lib/python3.9/site-packages (from podman-compose) (1.0.0)
Installing collected packages: podman-compose
Successfully installed podman-compose-1.0.6
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

Podman Compose のユニットファイルを作成。 Pod を起動するユニットファイルの基本になる。これはルート権限で行う必要がある:

# podman-compose systemd -a create-unit
podman-compose version: 1.0.6
['podman', '--version', '']
using podman version: 4.4.1
writing [/etc/systemd/user/podman-compose@.service]: ...
writing [/etc/systemd/user/podman-compose@.service]: done.

while in your project type `podman-compose systemd -a register`

# exit

6.2. Redmine Pod のユニットファイル登録

ここからは再びユーザ権限で操作する。 ユーザ向けにも Podman Compose をインストール:

$ pip3 install podman-compose

docker-compose.yml の内容で Redmine Pod を生成。 Pod とは先述の通り、複数コンテナをまとめたものである。 podman-compose systemd -a register で Pod 生成だけでなく Systemd へ登録してくれる。 当然 docker-compose.yml の置いてあるディレクトリでコマンドを実行する:

$ podman-compose systemd -a register
podman-compose version: 1.0.6
['podman', '--version', '']
using podman version: 4.4.1
writing [/home/alma/.config/containers/compose/projects/redmine.env]: ...
writing [/home/alma/.config/containers/compose/projects/redmine.env]: done.


creating the pod without starting it: ...


podman-compose version: 1.0.6
['podman', '--version', '']
using podman version: 4.4.1
** excluding:  set()
['podman', 'ps', '--filter', 'label=io.podman.compose.project=redmine', '-a', '--format', '']
podman volume inspect redmine_certbot || podman volume create redmine_certbot
['podman', 'volume', 'inspect', 'redmine_certbot']
['podman', 'network', 'exists', 'redmine_default']
podman create --name=redmine_certbot_1 --label io.podman.compose.config-hash=3092854c4b61817d29f8e447c6d1a0f169e19c51c5ff2dae715d109ad8e3fc3e --label io.podman.compose.project=redmine --label io.podman.compose.version=1.0.6 --label PODMAN_SYSTEMD_UNIT=podman-compose@redmine.service --label com.docker.compose.project=redmine --label com.docker.compose.project.working_dir=/home/alma/redmine --label com.docker.compose.project.config_files=docker-compose.yml --label com.docker.compose.container-number=1 --label com.docker.compose.service=certbot -v /home/alma/redmine/data/letsencrypt:/etc/letsencrypt -v redmine_certbot:/var/www/certbot --net redmine_default --network-alias certbot --restart always --entrypoint ["/bin/sh", "-c", "trap exit TERM; while :; do certbot renew --webroot -w /var/www/certbot; sleep 12h & wait ${!}; done;"] certbot/certbot
cbecc79821ef0751c8889abd171be3f6add8188167f2124d4493566fa7ad3b0c
exit code: 0
podman volume inspect redmine_certbot || podman volume create redmine_certbot
['podman', 'volume', 'inspect', 'redmine_certbot']
['podman', 'network', 'exists', 'redmine_default']
podman create --name=redmine_nginx_1 --label io.podman.compose.config-hash=3092854c4b61817d29f8e447c6d1a0f169e19c51c5ff2dae715d109ad8e3fc3e --label io.podman.compose.project=redmine --label io.podman.compose.version=1.0.6 --label PODMAN_SYSTEMD_UNIT=podman-compose@redmine.service --label com.docker.compose.project=redmine --label com.docker.compose.project.working_dir=/home/alma/redmine --label com.docker.compose.project.config_files=docker-compose.yml --label com.docker.compose.container-number=1 --label com.docker.compose.service=nginx -v /home/alma/redmine/data/nginx/conf:/etc/nginx/conf.d -v /home/alma/redmine/data/nginx/ssl:/etc/ssl/private -v /home/alma/redmine/data/letsencrypt:/etc/letsencrypt -v redmine_certbot:/var/www/letsencrypt --net redmine_default --network-alias nginx -p 80:80 -p 443:443 --restart always nginx:1-alpine /bin/sh -c while :; do sleep 6h & wait ${!}; nginx -s reload; done & nginx -g "daemon off;"
5ceaa3c1f4391c2df34fc38561b2acf34fe5463fa8bd319f634d4f572213136e
exit code: 0
['podman', 'network', 'exists', 'redmine_default']
podman create --name=redmine_redmine_1 --label io.podman.compose.config-hash=3092854c4b61817d29f8e447c6d1a0f169e19c51c5ff2dae715d109ad8e3fc3e --label io.podman.compose.project=redmine --label io.podman.compose.version=1.0.6 --label PODMAN_SYSTEMD_UNIT=podman-compose@redmine.service --label com.docker.compose.project=redmine --label com.docker.compose.project.working_dir=/home/alma/redmine --label com.docker.compose.project.config_files=docker-compose.yml --label com.docker.compose.container-number=1 --label com.docker.compose.service=redmine -e REDMINE_DB_POSTGRES=db -e REDMINE_DB_DATABASE=redmine -e REDMINE_DB_USERNAME=postgres -e REDMINE_DB_PASSWORD=hyc96br1askf -v /home/alma/redmine/data/redmine/conf/configuration.yml:/usr/src/redmine/config/configuration.yml -v /home/alma/redmine/data/redmine/files:/usr/src/redmine/files -v /home/alma/redmine/data/redmine/plugins:/usr/src/redmine/plugins -v /home/alma/redmine/data/redmine/themes/bleuclair:/usr/src/redmine/public/themes/bleuclair --net redmine_default --network-alias redmine --restart always redmine:5.0.5-alpine
efa924520752682e46537dfb9f7b4b0eb8021353c7ddc1991fcbf868b4894c25
exit code: 0
podman volume inspect redmine_db-data || podman volume create redmine_db-data
['podman', 'volume', 'inspect', 'redmine_db-data']
['podman', 'network', 'exists', 'redmine_default']
podman create --name=redmine_db_1 --label io.podman.compose.config-hash=3092854c4b61817d29f8e447c6d1a0f169e19c51c5ff2dae715d109ad8e3fc3e --label io.podman.compose.project=redmine --label io.podman.compose.version=1.0.6 --label PODMAN_SYSTEMD_UNIT=podman-compose@redmine.service --label com.docker.compose.project=redmine --label com.docker.compose.project.working_dir=/home/alma/redmine --label com.docker.compose.project.config_files=docker-compose.yml --label com.docker.compose.container-number=1 --label com.docker.compose.service=db -e POSTGRES_DB=redmine -e POSTGRES_PASSWORD=hyc96br1askf -v redmine_db-data:/var/lib/postgresql/data -v /home/alma/redmine/backup/db:/backup --net redmine_default --network-alias db --restart always postgres:9.5-alpine
fe430ad70d25ceb813e9435a7af986d1ce2a6aa793e6a9c383a3447742cf1aa7
exit code: 0

final exit code is  0

you can use systemd commands like enable, start, stop, status, cat
all without `sudo` like this:

                systemctl --user enable --now 'podman-compose@redmine'
                systemctl --user status 'podman-compose@redmine'
                journalctl --user -xeu 'podman-compose@redmine'

and for that to work outside a session
you might need to run the following command *once*

                sudo loginctl enable-linger 'alma'

you can use podman commands like:

                podman pod ps
                podman pod stats 'pod_redmine'
                podman pod logs --tail=10 -f 'pod_redmine'

なんか色々と自動的に進めてくれたようだ。

しかしこのままではうまく起動しない。 podman-commpose\@.service を編集し、--in-pod 1 を追加した。 既に Issue が出ているので、きっと近い内に直ると思う。

変更後のユニットファイル:

$ cat /etc/xdg/systemd/user/podman-compose\@.service
# /etc/systemd/user/podman-compose@.service

[Unit]
Description=%i rootless pod (podman-compose)

[Service]
Type=simple
EnvironmentFile=%h/.config/containers/compose/projects/%i.env
ExecStartPre=-/usr/local/bin/podman-compose --in-pod 1 up --no-start
ExecStartPre=/usr/bin/podman pod start pod_%i
ExecStart=/usr/local/bin/podman-compose wait
ExecStop=/usr/bin/podman pod stop pod_%i

[Install]
WantedBy=default.target

変更の差分:

$ diff -u /tmp/podman-compose.service /etc/xdg/systemd/user/podman-compose\@.service
--- /tmp/podman-compose.service 2023-09-28 18:43:23.705503890 +0000
+++ /etc/xdg/systemd/user/podman-compose@.service       2023-09-28 18:39:54.297475319 +0000
@@ -6,7 +6,7 @@
 [Service]
 Type=simple
 EnvironmentFile=%h/.config/containers/compose/projects/%i.env
-ExecStartPre=-/usr/local/bin/podman-compose up --no-start
+ExecStartPre=-/usr/local/bin/podman-compose --in-pod 1 up --no-start
 ExecStartPre=/usr/bin/podman pod start pod_%i
 ExecStart=/usr/local/bin/podman-compose wait
 ExecStop=/usr/bin/podman pod stop pod_%i

変更を適用するにはお馴染 daemon-reload が必要:

$ systemctl --user daemon-reload

6.3. Redmine Pod の起動

これで準備は整った。Systemctl で Pod を有効化する:

$ systemctl --user enable --now 'podman-compose@redmine'

起動できたみたいだ!!

$ systemctl --no-pager --full --user status 'podman-compose@redmine.service'
● podman-compose@redmine.service - redmine rootless pod (podman-compose)
     Loaded: loaded (/etc/xdg/systemd/user/podman-compose@.service; enabled; preset: disabled)
     Active: active (running) since Thu 2023-09-28 18:40:57 UTC; 6min ago
   Main PID: 164783 (podman)
      Tasks: 7 (limit: 5637)
     Memory: 32.8M
        CPU: 3.206s
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/app-podman\x2dcompose.slice/podman-compose@redmine.service
             └─164783 podman wait -- redmine_certbot_1 redmine_nginx_1 redmine_redmine_1 redmine_db_1

Sep 28 18:40:56 fg-indigo2 podman-compose[164701]: ['podman', 'network', 'exists', 'redmine_default']
Sep 28 18:40:56 fg-indigo2 podman-compose[164701]: podman create --name=redmine_db_1 --pod=pod_redmine --label io.podman.compose.config-hash=3092854c4b61817d29f8e447c6d1a0f169e19c51c5ff2dae715d109ad8e3fc3e --label io.podman.compose.project=redmine --label io.podman.compose.version=1.0.6 --label PODMAN_SYSTEMD_UNIT=podman-compose@redmine.service --label com.docker.compose.project=redmine --label com.docker.compose.project.working_dir=/home/alma/redmine --label com.docker.compose.project.config_files=docker-compose.yml --label com.docker.compose.container-number=1 --label com.docker.compose.service=db -e POSTGRES_DB=redmine -e POSTGRES_PASSWORD=hyc96br1askf -v redmine_db-data:/var/lib/postgresql/data -v /home/alma/redmine/backup/db:/backup --net redmine_default --network-alias db --restart always postgres:9.5-alpine
Sep 28 18:40:56 fg-indigo2 podman-compose[164771]: Error: creating container storage: the container name "redmine_db_1" is already in use by b5dcabc6bc8ed2124e9822a5f14db90868a69156d8f0af2a77a3058a474ab435. You have to remove that container to be able to reuse that name: that name is already in use
Sep 28 18:40:56 fg-indigo2 podman-compose[164701]: exit code: 125
Sep 28 18:40:57 fg-indigo2 podman[164777]: 1a3e7d4c10caa60ffcc6d0b441ded2d75481b7584d0d7e008ffb9c2d433e8a09
Sep 28 18:40:57 fg-indigo2 systemd[143293]: Started redmine rootless pod (podman-compose).
Sep 28 18:40:57 fg-indigo2 podman-compose[164783]: podman-compose version: 1.0.6
Sep 28 18:40:57 fg-indigo2 podman-compose[164783]: ['podman', '--version', '']
Sep 28 18:40:57 fg-indigo2 podman-compose[164783]: using podman version: 4.4.1
Sep 28 18:40:57 fg-indigo2 podman-compose[164783]: podman wait -- redmine_certbot_1 redmine_nginx_1 redmine_redmine_1 redmine_db_1

あとは布団に入るだけ。目が覚めても Redmine にアクセス可能であれば、構築は成功だ。

8. まとめ

旅行へ行く直前に作業したので、常時起動できるようになるのが間に合って良かった。 勿論、旅行中も Redmine には存分に活躍して頂いた。

文中で「ダグトリオみたいなキャラクタ」と書いたが、カラーで見るとアザラシのように見える。 本当はセルキーという、アイルランドの人魚的な存在らしい。 普段、海ではアザラシの皮を被っていて、陸にあがるときに人間の姿になるとのこと。 セルキーのグループを Pod と呼ぶそうな。ということはセルキーはコンテナなのか…?

9. 参考リンク

Redmine

Certbot

Podman