Hướng dẫn cài đặt Forem (self-host) trên DigitalOcean
Forem là một nền tảng mã nguồn mở để xây dựng cộng đồng hiện đại, độc lập và an toàn. Hiện tại 1 trong số các cộng đồng cho developers là dev.to cũng được xây dựng bằng Forem.
Hoặc mọi người có thể vào đây để tham khảo 1 số cộng đồng đang sử dụng Forem: discover.forem.com
Cá nhân mình thấy thiết kế cũng như chức năng của Forem khá đẹp và thoáng. Nhưng trong quá trình setup nó thì gặp rất nhiều vấn đề, vì trước đây mình chỉ quen dùng wordpress. Bài viết này mình sẽ hướng dẫn chi tiết và những điều lưu ý trong quá trình cài đặt. Hi vọng sẽ giúp ích được cho ai đó đang muốn xây dựng cộng đồng của riêng mình.
Có 1 vài cách cài đặt như sử dung Docker, cài trực tiếp trên Ubuntu, hay sử dụng t2d (Talk to Dokku), nhưng cuối cùng mình chọn cài bằng hướng dẫn của team Forem (được gọi là Self-host) vì nó ổn định, nhiều tài liệu hỗ trợ, được cập nhật thường xuyên.
Lưu ý
- Forem sẽ được cài đặt trên Fedora CoreOS VM chạy trên một trong số các nhà cung cấp đám mây phổ biến (hiện tại chỗ trợ cho DigitalOcean , AWS và Google Cloud )
- Mình chọn cài đặt trên DigitalOcean, mọi người có thể cài đặt trên AWS, Google Cloud.
- Các câu lệnh được thực hiện trên macOS.
- Cấu hình VPS tối thiểu là 1vCPU, 2GB RAM, vì nó dùng rất nhiều RAM nên cấu hình càng cao càng tốt nhé.
Chuẩn bị
Mở Terminal trên macOS và cài đặt 1 số phần mềm cần thiết
- Cài đặt HomeBrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- Cài đặt Git:
brew install git
- Cài đặt Python 3.x và pip3
brew install python3
- Cài đặt Ansible:
ansible-core 2.11
trở lên (được cung cấp bởi Ansible 4.0.0)
python3 -m pip install --user ansible
python3 -m pip install --user ansible-core==2.12.3
- Cài đặt Butan:
brew install butane
- Cài đặt pwgen:
brew install pwgen
Hướng dẫn
1/ Mở Terminal trên macOS để clone bản repo selfhost về máy
git clone https://github.com/forem/selfhost.git
- Di chuyển vào thư mục selfhost:
cd selfhost
2/ Chạy lệnh sau để tạo tự động 1 số biến:
./setup
- Sau khi chạy lệnh trên xong, bạn cần lưu lại các giá trị
vault
kiểu dạng như sau:
vault_secret_key_base: !vault |
$ANSIBLE_VAULT;1.1;AES256
31626639326433353437623836636431303161363438396661636130646434396430633032343264
3433343031316634636133636663666130303330636366350a616333366666656633353136363865
32333739623836623362343862623963333834656236333433333665666531373534316238633039
3136396237363839350a633764313365343033623061316364646135356336373062313433383866
62613738336463366639323230626465353630646161323931396333333764633633303532656632
33653839363465313863303533613062666364363563353264613439306539366665383462663234
62313161333566373962396561376166333766366233396533356539393738623666656635373436
36323064393461393836626537366239363433393261383137366664343734663161323162613634
35353863356462326435656435373261386230356631396464653937643463323536656538313036
346661356161386132643837386161376337
Encryption successful
Reading plaintext input from stdin. (ctrl-d to end input, twice if your content does not already have a newline)
vault_imgproxy_key: !vault |
$ANSIBLE_VAULT;1.1;AES256
35646264656466303662316162353030343266366562633733623133326663656430356138306266
6564343661366430383637633433343364363538316633340a336436653162363032646430333861
33643061636336613361373430396332663964333230626661336637623336666366623839323564
6432363731363339360a383361323863343131323837636633643261383034316534663634613835
32316565343937306536343232313530383935386231333830343339653838303533383037616333
33613732366236653466373233366234646437353166326164313764626439393165333861653538
61343135373966303931656633363331313838653039626264396136623438626261356632356463
64643666613930383938373337363238373032323166333730653734353463656139623838313939
34626539626339353263376231623731656362636666636435366531346232363836613739386464
303435353334366338646161333636643062
Encryption successful
Reading plaintext input from stdin. (ctrl-d to end input, twice if your content does not already have a newline)
vault_imgproxy_salt: !vault |
$ANSIBLE_VAULT;1.1;AES256
32353437333561633733306239333164363165386437313632373761663535373661633261343833
3534303539333235643530613530323964373530353437330a656636626432336633636132616430
62316331396436626662303134343964366635316435653264386437653238643964363935643637
3433623566346265640a343539303166333439626136343336643232643930393261313035313933
36353833376139306266623261623561373235633432333462323230623665633562333565323235
36366338646134633738323661656530663261336430633235643938383236353832626138356434
30663337353235303038336239343934383065613532343137313038643330346436306261666130
34323137633531393665343564613131343431373835336436656239313738303033333065623964
30643262313833396234623937616632623561656664393739663266313531366332623434336565
316634303133623165643138643831373630
Encryption successful
Reading plaintext input from stdin. (ctrl-d to end input, twice if your content does not already have a newline)
vault_forem_postgres_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
32316238646635623832303464343262313831326131376662653037633265316166653439346163
6231366436316431303164323634663137663866303036390a323736346236633835613962613634
64616233353663643832636435396461393962616264623866613031633931396464346238646564
3030336139303735640a646133313066383535643763353938663865363361356463623162366234
37656661613334376361353331343437636633646331366466646130383731613939616639313638
66613131333735383763656335393762346464346234626633313138376439633965363030616337
65643663306266623764643732376535336339383334363131636537376531613738653764343865
39316264633764383264396530393532333639643062333838373531626263623965366462633534
33343834346333393737353432303065386433303065336563393634393065303838313162653035
32306565616362346466643366356463656639333162343030323338656532613132303164373134
343463383266383361303634356133326431
Encryption successful
3/ Thiết lập quyền truy cập SSH cho VPS
Trên Terminal gõ lệnh sau để tạo ssh-key:
ssh-keygen
Để các giá trị mặc định bằng cách nhấn Enter vài lần, kết quả sẽ tương tự như sau:
duyasia@mac-mini selfhost % ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/duyasia/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/duyasia/.ssh/id_rsa
Your public key has been saved in /Users/duyasia/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:TK5GxqZgf0HQWFnr8IIjRek1TYlCSfqOvOnlacI+MY0 [email protected]
The key's randomart image is:
+---[RSA 3072]----+
| o=*.*o. |
| o=.* o. |
| ...oo.o |
| o.+ B |
| +o+ B S |
| oE*.* + |
| .oo= + |
| +=.+ |
| o=+o |
+----[SHA256]-----+
4/ Chỉnh sửa file inventory/forem/setup.yml
(a). Mở file inventory/forem/setup.yml
bằng VSCode hoặc Sublime Text và thêm vào giá trị cho các biến Ansible inventory sau:
# CHANGE_REQUIRED — forem_domain_name: example.com
forem_domain_name: REPLACEME
# CHANGE_REQUIRED — default_email: [email protected]
default_email: REPLACEME
forem_subdomain_name: www # can be subdomain, i.e. "community" in community.mainwebsite.com
forem_server_hostname: host # You may change to something else if you choose (i.e. server, srv, etc)
Note: Nếu không thích dùng subdomain thì hãy sửa lại định dạng cho app_domain:
- Ban đầu sẽ như này:
app_domain: "{{ forem_subdomain_name }}.{{ forem_domain_name }}"
- Sửa lại thành:
app_domain: "{{ forem_domain_name }}"
Mình dùng subdomain nên mình sẽ sửa thành như sau (sau khi cài đặt xong thì domain của Forem sẽ là community.duy.asia)
forem_domain_name: duy.asia
default_email: [email protected]
forem_subdomain_name: community
forem_server_hostname: host
(b). Thêm tiếp giá trị cho mỗi biến sau (các giá trị này mình đã lưu lại ở bước 2)
vault_secret_key_base
vault_imgproxy_key
vault_imgproxy_salt
vault_forem_postgres_password
- Nhớ thụt lề cho các giá trị như sau (thụt vào 1 tab/hoặc 2 dấu cách so với chữ
vault_
):
(c). Sửa phần ssh_key:
- Ban đầu sẽ như này:
ssh_key: "{{ lookup('file', '~/.ssh/forem.pub') }}"
- Sửa thành như này:
ssh_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
~/.ssh/id_rsa.pub
chính là đường dẫn đến file ssh key mình vừa tạo ở bước 3
5/ Chỉnh sửa file playbooks/providers/digitalocean.yml
Tiếp tục mở file
bằng VSCode hoặc Sublime Text, sửa lại cấu hình VPS tại 2 biến sau:playbooks/providers/digitalocean.yml
forem_do_region: nyc3
forem_do_size: s-2vcpu-2gb
Ở đây mình dự định cài trên Droplets có cấu hình 1 vCPU, 2GB RAM (12$/tháng) và datacenter đặt ở Singapore nên mình sẽ sửa thành:
forem_do_region: sgp1
forem_do_size: s-1vcpu-2gb
Với các cấu hình khác, mọi người có thể lấy 1 cách chính xác bằng cách:
- Đăng ký tài khoản DigitalOcean
- Truy cập vào trang cloud.digitalocean.com
- Click vào Create > Droplets
- Chọn cấu hình và region bạn muốn sử dụng
- Kéo xuống dưới xem tên gợi ý của droplet tại mục hostname. (VD: ubuntu-s-4vcpu-8gb-amd-lon1-01)
- Như ảnh trên thì:
forem_do_region
sẽ làlon1
(tức là London)forem_do_size
sẽ làs-4vcpu-8gb-amd
(tức Premium AMD with NVMe SSD, 8GB / 4 AMD CPUs, 56$/tháng)
6/ Cài đặt DigitalOcean Ansible collection bằng lệnh sau:
ansible-galaxy collection install community.digitalocean
7/ Cài đặt doctl
bằng lệnh sau:
brew install doctl
8/ Tạo Personal Access Token trên tài khoản DigitalOcean của bạn
- Đăng nhập vào DigitalOcean Control Panel.
- Nhấp vào API trong cột menu bên trái > nó sẽ chuyển đến trang Applications & API > trên tab Tokens/Keys . Trong phần Person access tokens, nhấp vào nút Generate New Token
- Lưu lại token (dòng dop_v1_49aa…..)
9/ Trên Terminal, gõ vào lệnh sau, sau đó paste access token vừa lưu ở bước 8 vào:
doctl auth init
Nếu bạn đã thêm 1 token trước đây (hoặc token đã hết hạn), bạn có thể thay token mới bằng cách: mở Finder của macOS > nhấn tổ hợp phím Command + Shift + G > nhập vào đường dẫn /Users/<user>/Library/Application Support/doctl
(thay <user> bằng user bạn đang dùng trên macOS) > mở file config.yaml
> thay đổi access token ở dòng access-token
và lưu lại
Test lại token bằng lệnh sau:
doctl account get
Kết quả tương tự như sau là ok
User Email Team Droplet Limit Email Verified User UUID Status
[email protected] My Team 10 true 89g35441a1c5937f2489c6d821a31614f09 active
10/ Chạy playbook của DigitalOcean để thiết lập Forem
ansible-playbook -i inventory/forem/setup.yml playbooks/providers/digitalocean.yml
DigitalOcean không hỗ trợ Fedora CoreOS nên 1 images tùy chỉnh sẽ được tải về tài khoản của bạn thông qua Ansible. Nếu Wait for fcos-{{ fcos_download_release }} to be created
báo time out thì hãy kiểm tra phần Custom Images trên tài khoản DigitalOcean của bạn để xem hình ảnh của bạn có còn ở trạng thái chờ xử lý hay không. Chờ quá trình xử lý hoàn tất và chạy lại lệnh playbook trên.
Nếu tất cả ok thì sẽ log sẽ như này:
11/ Copy IP trên log (hoặc lấy trong droplets mới được tạo trên tài khoản DigitalOcean của bạn) > tạo DNS với record A và gán sub-domain và IP của bạn vào. Ở đây mình dùng DNS của CloudFlare, nên nó sẽ như này:
Nhớ tắt mục Proxy status để tý tạo ssl không bị lỗi
12/ Kết nối vào VPS để chạy 1 lệnh tạo TLS cert
- Kết nối vào VPS bằng lệnh sau trên Terminal:
ssh core@<IP của droplet>
Gõ yes để xác nhận key fingerprint
Kết quả sẽ tương tự như sau:
duyasia@mac-mini % ssh [email protected]
The authenticity of host '159.223.80.234 (159.223.80.234)' can't be established.
ED25519 key fingerprint is SHA256:BzNrZH/EJzDHdZAJpGm+QWotVyBU3t9fcRXTkoA2ZsU.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '159.223.80.234' (ED25519) to the list of known hosts.
Fedora CoreOS 36.20220703.3.1
Tracker: https://github.com/coreos/fedora-coreos-tracker
Discuss: https://discussion.fedoraproject.org/tag/coreos
[core@community (community.duy.asia) ~]$
- Tạo TLS cert bằng lệnh sau:
sudo systemctl restart forem-traefik.service
13/ Chuyển đến tên miền Forem của bạn và tạo tài khoản đầu tiên (tài khoản admin). Xem thêm Admin Documents để biết thêm thông tin về cách thiết lập Forem.
Trên đây là các bước để mình cài đặt Forem (selfhost) trên DigitalOcean. Nếu chỗ nào mình diễn tả khó hiểu thì hãy để lại comment để mình giải thích thêm nhé. Nếu thấy bài viết hữu ích thì hãy share giúp mình để nhiều người cùng biết đến Forem. Thank
Một số lỗi hay gặp
- Lỗi
Expired SSL Certificate
hoặcSSL Handshake failed
: Do SSL được tạo bằng Let’s Encrypt nên phải tuân theo Giới hạn chứng chỉ trùng lặp là 5 mỗi tuần. Việc cài đi cài lại Forem nhiều lần dẫn đến lỗi này. Khắc phục bằng cách thay đổi sang sub-domain khác hoặc chờ qua 7 ngày. - Khắc phục lỗi SMTP không hoạt động: SMTP được setup và thêm vào tại trang Admin, nhưng nó sẽ không hoạt động luôn, bạn cần kết nối vào VPS bằng
ssh
và chạy lệnh sau để khởi động lại Forem thì nó mới hoạt động:sudo systemctl restart forem.service
Một số command nên biết
Restart để tạo ssl:
sudo systemctl restart forem-traefik.service
Xem log của forem-traefik.service
:
journalctl -u forem-traefik.service > forem_traefik_service_logs.txt
nano forem_traefik_service_logs.txt
systemd: Forem được chạy với các container được cung cấp thông qua Podman và systemd. Các tệp file systemd nằm trong /etc/systemd/system
cd /etc/systemd/system
ls -lah forem*
Forem configs: Tất cả dữ liệu và cấu hình Forem của bạn đều nằm trong đó /opt/forem
. Đây là thư mục quan trọng nhất trên Forem của bạn. Bạn nên sao lưu thư mục này thường xuyên.
ll /opt/forem/
Thư mục ‘configs‘ chứa tệp cấu hình OpenResty (Nginx) và tệp TOML cấu hình Traefik cùng với tệp acme.json
chứa chứng chỉ TLS từ Let’s Encrypt.
ll
total 4
-rw-r--r--. 1 root root 2375 Jun 29 17:16 nginx.conf
drwxr-x---. 2 root root 63 Jun 29 17:16 traefik
ll traefik/
total 12
-rw-------. 1 root root 3524 Jun 29 17:20 acme.json
-rw-r--r--. 1 root root 1928 Jun 29 17:16 dynamic.toml
-rw-r--r--. 1 root root 740 Jun 29 17:16 traefik.toml
Thư mục ‘data‘ chứa các thư mục postgresql, redis và upload của bạn. Thư mục này chứa tất cả nội dung Forem của bạn bao gồm cả các thành viên của bạn.
ll data/
total 4
drwx------. 19 polkitd root 4096 Jun 29 17:19 postgresql
drwxr-x---. 2 polkitd root 28 Jun 29 17:19 redis
drwxr-x---. 2 core core 6 Jun 29 17:16 uploads
Thư mục ‘envs’ chứa tất cả các tệp biến môi trường định cấu hình các dịch vụ Forem sau:
forem-imgproxy.service với imgproxy.env
forem-postgresql.service với postgresql.env
forem-rails.service, forem-worker.service với rails.env
forem-redis.service với redis.env
Nếu bạn phải tạo cấu hình cho một dịch vụ, bạn có thể chỉnh sửa tệp ENV tương ứng và khởi động lại dịch vụ thông qua systemd. Ví dụ, systemctl restart forem.service
sau khi chỉnh sửa ‘rails.env’.
foremctl: Chúng tôi có một tập lệnh trợ giúp (Forem Control) được gọi foremctl
. Nó được sử dụng để điều khiển Forem của bạn thông qua CLI.
Cập nhật Forem lên phiên bản mới nhất và khởi động lại
sudo foremctl deploy
foremimg: Có một tập lệnh trợ giúp (Forem Image) được gọi foremimg
. Nó được sử dụng để kiểm soát phiên bản Forem của bạn và áp dụng các bản cập nhật. Nó phải được chạy với tư cách là người dùng root
.
sudo su
Cập nhật Forem lên phiên bản mới nhất mà không cần khởi động lại
sudo foremimg update
Quay lại báo trước về phiên bản đang chạy cuối cùng và khởi động lại
sudo foremimg rollback
sudo foremctl restart
Cập nhật Fedora CoreOS lên phiên bản ổn định mới nhất
sudo rpm-ostree upgrade --check
Xem trước các bản cập nhật:
sudo rpm-ostree upgrade --preview
Tải xuống các gói mà không cần triển khai chúng:
sudo rpm-ostree upgrade --download-only
Để áp dụng các bản cập nhật và khởi động lại:
sudo rpm-ostree upgrade
systemctl reboot
Sao lưu dữ liệu Forem của bạn
foremctl stop
sudo tar czpf ~core/"$(date '+%Y-%m-%d')-forem-data.tar.gz" /opt/forem
foremctl start
Lưu ý: Chạy foremctl stop sẽ gây ra thời gian chết cho Forem của bạn!
Thay đổi tên miền
Thay đổi tên miền sẽ là một quá trình khá thủ công mà bạn sẽ phải thực hiện thông qua SSH. Bạn có thể dễ dàng khởi chạy Forem mới với tên miền phù hợp và xóa tên miền cũ nếu bạn không quan tâm đến nội dung bên trong tên miền hiện tại mà bạn đang dùng.
Điều đang được nói ở đây là nơi bạn sẽ phải chỉnh sửa tên miền trên máy chủ hiện tại và các service sẽ cần khởi động lại. Tên miền thử nghiệm của tôi là forum.duy.asia và bạn sẽ cần thay đổi tên miền trong hai tệp này với user root:
sudo su
fgrep forum.duy.asia * -R configs/traefik/dynamic.toml: rule = "Host(forum.duy.asia) && Method(GET, POST, PUT, DELETE, PATCH, HEAD)"envs/rails.env:APP_DOMAIN=forum.duy.asia
Và chạy 2 lệnh sau:
systemctl restart forem.service
systemctl restart forem-traefik.service
Và điều đó sẽ xử lý cài đặt cấu hình phía máy chủ xử lý tên miền. Bạn cần đảm bảo rằng DNS của bạn được trỏ đến máy chủ này và nó phân giải chính xác trước khi bạn khởi động lại forem-traefik.service.
Convert ảnh sang định dạng webp
Bạn cần sửa file imgproxy.env trong thư mục /opt/forem/envs.
Tất cả những gì bạn cần làm là mở file lên và dán dòng sau vào cuối file.
IMGPROXY_ENFORCE_WEBP=true
Sau đó, khởi động lại imgproxy bằng cách chạy mã bên dưới:
sudo systemctl restart forem-imgproxy.service
và khởi động lại rails.
sudo systemctl restart forem-rails.service
Nguồn: DUY Blog