Eric Guo's blog.cloud-mes.com

Hoping writing JS, Ruby & Rails and Go article, but fallback to DevOps note

Install Open Project V16 in a Rocky Linux 9

• Permalink

I already install an open project instance one and half year ago, but it's retired to sync with the production server OS version, which is Rocky Linux 8.10. After Ruby 3.4 released, I found the nokogiri v1.18 version and grpc v1.71 version both need a new GLIBC_2.29 version which is unavailable in Rocky Linux 8 series.

So I decide install a new Open Project instance server for the new production.

Update (September 2026): This server now runs OpenProject 17.9 with Node.js 24. The Hocuspocus section below documents the working collaborative document editing setup. The original v16 installation notes, including the Ruby setup, remain as historical context; use the Ruby version specified by the deployed release's .ruby-version.

Disable SELinux

Disable SELinux
vi /etc/selinux/config
grubby --update-kernel ALL --args selinux=0

Install htop and atop

Install htop and atop
sudo dnf update
sudo dnf install epel-release
sudo dnf install htop
sudo dnf install atop

Install nginx

Install nginx
sudo dnf install nginx

Install Node.js 24

Using the NodeSource distribution. For this OpenProject 17.9 deployment, use Node.js 24, version 24.15.0 or later within the 24.x series.

Install Node.js 24
curl -fsSL https://rpm.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo dnf install -y nodejs
sudo yum groupinstall 'Development Tools'
node --version
npm --version

Install yarn

Install yarn
curl -sL https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
sudo yum install yarn

Install postgresql 16 client

Following DO manual

Install postgresql 16 client
dnf module list postgresql
sudo dnf module enable postgresql:16
sudo dnf install postgresql-devel glibc-all-langpacks
sudo dnf install postgresql-contrib # pg_trgm btree_gist require by open project
sudo dnf install mysql-devel # if need to link to mysql server

Setup open_project user account

Setup open_project user account
adduser open_project
cd /etc/sudoers.d/
echo "open_project ALL=(ALL) NOPASSWD:ALL" > 30-open_project-user
sudo su - open_project
mkdir .ssh
chmod 700 .ssh
vi .ssh/authorized_keys # and paste your public key
chmod 600 .ssh/authorized_keys

Install rbenv and ruby-build

Install rbenv and ruby-build
whoami # should run as a open_project
git clone https://git.thape.com.cn/rails/rbenv.git .rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
~/.rbenv/bin/rbenv init # also edit ~/.bash_profile
# As an rbenv plugin
mkdir -p "$(rbenv root)"/plugins
git clone https://git.thape.com.cn/rails/ruby-build.git "$(rbenv root)"/plugins/ruby-build
git clone https://git.thape.com.cn/rails/rbenv-china-mirror.git "$(rbenv root)"/plugins/rbenv-china-mirror

Install Ruby 3.3.8

Ruby 3.3.8 need Rust to build JIT.

Install Rust and Ruby build dependencies
dnf config-manager --enable crb
dnf install libyaml-devel
yum install -y rust # version 1.79.0
dnf install clang-devel # for some gem like autocorrect-rb
Install and configure Ruby 3.3.8
rbenv install -l
rbenv install 3.3.8
rbenv global 3.3.8
rbenv shell 3.3.8
echo "gem: --no-document" > ~/.gemrc
gem update --system

Prepare the capistrano deploy folder

Prepare the capistrano deploy folder
whoami # should run as a open_project
cd /var/www
sudo mkdir open_project
sudo chown open_project:open_project open_project/

Using mirror when deploy

Run in the release rails root folder

Using mirror when deploy
bundle config mirror.https://rubygems.org https://gems.ruby-china.com

Setting the open project settings

/etc/environment
OPENPROJECT_EDITION=bim
OPENPROJECT_APP__TITLE=天华项目全生命周期管理
OPENPROJECT_APP__SHORT__TITLE=PLM
OPENPROJECT_HOST__NAME=plm-staging.thape.com.cn
OPENPROJECT_EMAIL__DELIVERY__METHOD="smtp"
OPENPROJECT_SMTP__ADDRESS="smtp.thape.com.cn"
OPENPROJECT_SMTP__PORT="25"
OPENPROJECT_SMTP__DOMAIN="thape.com.cn"
OPENPROJECT_SMTP__AUTHENTICATION="login"
OPENPROJECT_SMTP__USER__NAME="plm"
OPENPROJECT_SMTP__PASSWORD=""
OPENPROJECT_SMTP__ENABLE__STARTTLS__AUTO="true"
OPENPROJECT_SMTP__OPENSSL__VERIFY__MODE="none"
OPENPROJECT_ENTERPRISE__TRIAL__CREATION__HOST="https://www.google-analytics.com"
GRUF_OP_SERVER="172.17.1.1:10009"
WX_TEMPLATE_ID=""
WX_WORK_PACKAGE_DETAIL="https://plm.thape.com.cn/work_packages/:id"
MP_QRCODE_ABS_PATH="/var/www/open_project/shared/public/static/mp_qrcode.jpg"
LOGO_ABS_PATH="/var/www/open_project/shared/public/static/logo_plm.png"
CSP_FRAME_SRC="https://ith-workspace.thape.com.cn"
CSP_CONNECT_SRC="https://analytics.thape.com.cn"
WECHAT_AUTH_JWT_SECERT=""
WECHAT_AUTH_ITH_URL="/ith/wechat/ppm/login"
/etc/systemd/system/puma_plm.service
[Unit]
Description=Puma HTTP Server for open_project (staging)
After=syslog.target network.target
[Service]
Type=simple
WatchdogSec=10
User=open_project
EnvironmentFile=/etc/environment
WorkingDirectory=/var/www/open_project/current
ExecStart=/home/open_project/.rbenv/bin/rbenv exec bundle exec puma -e production
ExecReload=/bin/kill -SIGUSR1 $MAINPID
# if we crash, restart
RestartSec=10
Restart=on-failure
StandardOutput=append:/var/www/open_project/shared/log/puma.log
StandardError=append:/var/www/open_project/shared/log/puma.log
SyslogIdentifier=puma_plm
[Install]
WantedBy=multi-user.target
Setting the open project settings
sudo systemctl daemon-reload
bundle exec rake openproject:plugins:register_frontend
bundle exec rake i18n:js:export
bundle exec rake db:seed
sudo journalctl -u puma_plm # check system log and fix errors
sudo systemctl start puma_plm

Configure Hocuspocus for OpenProject 17.9

OpenProject uses Hocuspocus for real-time collaborative editing in the Documents module. The server source is bundled in extensions/op-blocknote-hocuspocus in the deployed OpenProject release. Use that copy so it stays aligned with the application version.

The working PPP setup uses this connection path:

Collaborative editing connection
Browser -> wss://plm-ppp.thape.com.cn/hocuspocus (Nginx, port 443)
-> ws://127.0.0.1:1234 (Hocuspocus)

Install the Hocuspocus dependencies

Run as open_project, using Node.js 24. Install the pnpm version specified by packageManager in the deployed extension's package.json, then install its production dependencies:

Install Hocuspocus dependencies
cd /var/www/open_project/current/extensions/op-blocknote-hocuspocus
node --version
pnpm_package=$(node -p "require('./package.json').packageManager")
sudo npm install --global "$pnpm_package"
pnpm --version
pnpm install --prod

Node must also be available to systemd through /usr/local/bin:/usr/bin:/bin. If using a version manager, adjust the service's PATH to include the installed Node.js 24 binary directory.

Configure the shared environment

Add these entries to /var/www/open_project/shared/.env. Replace the placeholder with a secret generated using openssl rand -hex 32:

/var/www/open_project/shared/.env
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__URL=wss://plm-ppp.thape.com.cn/hocuspocus
OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET=REPLACE_WITH_GENERATED_SECRET
OPENPROJECT_REAL__TIME__TEXT__COLLABORATION__ENABLED=true

Keep this file readable only by the deployment account and root. The explicit enablement setting also overrides a previously saved disabled setting.

Puma and GoodJob must load this file after /etc/environment:

Environment files in the application service units
EnvironmentFile=/etc/environment
EnvironmentFile=/var/www/open_project/shared/.env

The earlier puma_plm.service example only loads /etc/environment, so add the second line when following that example. The PPP deployment uses puma_ppp.service; substitute the actual service name in the commands below if yours differs.

Create the Hocuspocus systemd service

Create /etc/systemd/system/hocuspocus_ppp.service:

/etc/systemd/system/hocuspocus_ppp.service
[Unit]
Description=Hocuspocus collaborative editing server for OpenProject PPP
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=open_project
WorkingDirectory=/var/www/open_project/current/extensions/op-blocknote-hocuspocus
Environment=NODE_ENV=production
Environment=PATH=/usr/local/bin:/usr/bin:/bin
EnvironmentFile=/etc/environment
EnvironmentFile=/var/www/open_project/shared/.env
ExecStart=/bin/sh -c 'export SECRET="$${OPENPROJECT_COLLABORATIVE__EDITING__HOCUSPOCUS__SECRET:?Hocuspocus secret is required}"; exec /usr/bin/env node --import tsx src/index.ts'
Restart=on-failure
RestartSec=5
TimeoutStopSec=60
StandardOutput=journal
StandardError=journal
SyslogIdentifier=hocuspocus_ppp
[Install]
WantedBy=multi-user.target

Hocuspocus expects SECRET, so the service maps the OpenProject secret variable to that name. Keep the double dollar sign in ExecStart: systemd passes a literal dollar sign to the shell for expansion.

Start Hocuspocus
sudo systemd-analyze verify /etc/systemd/system/hocuspocus_ppp.service
sudo systemctl daemon-reload
sudo systemctl enable --now hocuspocus_ppp.service
sudo systemctl status hocuspocus_ppp.service
sudo journalctl -u hocuspocus_ppp.service -n 100 --no-pager

Proxy secure WebSockets through Nginx

Add this location inside the existing HTTPS server block for plm-ppp.thape.com.cn. This assumes Nginx and Hocuspocus run on the same host and the HTTPS virtual host already has a valid certificate.

Hocuspocus location in the HTTPS server block
location /hocuspocus {
proxy_pass http://127.0.0.1:1234;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
Reload Nginx and the application
sudo nginx -t && sudo systemctl reload nginx
sudo systemctl restart puma_ppp.service

Restart the existing GoodJob service in its configured system or user service scope as well, so it reloads the shared environment. Keep direct external access to port 1234 blocked; browsers connect through HTTPS port 443. Hocuspocus must also be able to reach OpenProject's public URL to authenticate users and save document content.

Verify collaboration and troubleshoot

  1. Check Administration -> Documents to confirm collaboration is enabled.
  2. Create a new document under Project -> Documents and open it as two users. Confirm live edits and cursors, then close and reopen it to verify persistence.
  3. In the browser's developer tools, check Network -> WS. The connection should use wss://plm-ppp.thape.com.cn/hocuspocus and return 101 Switching Protocols.

Existing documents with kind: classic continue using CKEditor. Enabling collaboration does not convert their content, and this feature does not replace CKEditor in work package fields.

An HTTPS page cannot connect to ws:// because browsers block mixed content. Merely changing the URL to wss://plm-ppp.thape.com.cn:1234 does not enable TLS on Hocuspocus. Use the Nginx endpoint on port 443 shown above.

If the browser still attempts the old ws:// URL, restart Puma and hard-refresh the document page. Restarting Hocuspocus alone does not change the URL Rails renders. Inspect the rendered setting in the browser console:

Check the WebSocket URL rendered by Rails
document.querySelector(
'[data-documents--init-yjs-provider-hocuspocus-url-value]'
)?.getAttribute('data-documents--init-yjs-provider-hocuspocus-url-value')

If it still shows the old value, confirm Puma loads the edited environment file and remove duplicate URL assignments:

Check Puma environment file locations
sudo systemctl show puma_ppp.service -p FragmentPath -p EnvironmentFiles

For future deployments, install the Hocuspocus dependencies in each release and restart hocuspocus_ppp.service after switching the current symlink.

Upload custom fonts

Upload custom fonts
/var/www/open_project/shared/public
gzip -9r op_public_files.zip fonts/ static/ WW_verify_*.txt

Change IP

Rocky 8 network change:

Edit the Rocky Linux 8 network configuration
vi /etc/sysconfig/network-scripts/ifcfg-ens192

Rocky 9 network change:

Reload the Rocky Linux 9 NetworkManager connection
vi /etc/NetworkManager/system-connections/ens192.nmconnection
nmcli connection reload /etc/NetworkManager/system-connections/ens192.nmconnection
nmcli connection up /etc/NetworkManager/system-connections/ens192.nmconnection

Install the dependency

Install the dependency
sudo yum install ImageMagick

Open the firewall

Open the firewall
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

Comments