Trong khối này là cách lệnh tải về file cài từ nguồn chính thức, giải nén nó ra thư mục phù hợp, và phân quyền để đảm bảo trang hoạt động cũng như hạn chế rủi ro về bảo mật.
# Phần 4: Cai dat WordPress
# --- KIỂM TRA MÔI TRƯỜNG ---
# Script này yêu cầu Caddy và PHP đã được cài trước đó
echo -e "${GREEN}>>> Dang kiem tra moi truong he thong...${NC}"
if ! id "caddy" &>/dev/null; then
echo -e "${RED}Loi: User 'caddy' chua ton tai.${NC}"
echo -e "${YELLOW}Goi y: Cai dat Caddy Web Server truoc do chua thanh cong.${NC}"
exit 1
fi
if ! id "www-data" &>/dev/null; then
echo -e "${RED}Loi: User 'www-data' chua ton tai.${NC}"
echo -e "${YELLOW}Goi y: Hay cai dat PHP-FPM.${NC}"
exit 1
fi
# --- BƯỚC 1: NHẬP VÀ XỬ LÝ TÊN MIỀN ---
echo -e "${GREEN}>>> Vui long nhap ten mien cua ban (vi du: example.com):${NC}"
read -p "Domain: " INPUT_DOMAIN < /dev/tty
# Xử lý chuỗi: Xóa toàn bộ khoảng trắng và chuyển về chữ thường
DOMAIN=$(echo "$INPUT_DOMAIN" | tr -d ' ' | tr '[:upper:]' '[:lower:]')
# Kiểm tra dữ liệu đầu vào
if [[ -z "$DOMAIN" ]]; then
echo -e "${RED}Loi: Ten mien khong duoc de trong!${NC}"
exit 1
fi
# Kiểm tra sơ bộ định dạng domain (phải có dấu chấm)
if [[ "$DOMAIN" != *"."* ]]; then
echo -e "${RED}Loi: Ten mien '$DOMAIN' khong hop le (thieu dau cham).${NC}"
exit 1
fi
echo -e "${GREEN}>>> Dang tien hanh cai dat cho domain: ${YELLOW}$DOMAIN${NC}"
# --- BƯỚC 2: TẠO CẤU TRÚC THƯ MỤC ---
echo -e "${GREEN}[1/5] Dang tao thu muc chua ma nguon...${NC}"
# Tạo thư mục web root (-p giúp không báo lỗi nếu thư mục đã tồn tại)
sudo mkdir -p /var/www/$DOMAIN/public_html
echo -e "${GREEN}[2/5] Dang tao thu muc logs va cap quyen...${NC}"
# Tạo thư mục logs
sudo mkdir -p /var/www/$DOMAIN/logs
# Cấp quyền cho user caddy để ghi được log truy cập
sudo chown -R caddy:caddy /var/www/$DOMAIN/logs
# --- BƯỚC 3: TẢI VÀ GIẢI NÉN WORDPRESS ---
echo -e "${GREEN}[3/5] Dang tai WordPress phien ban moi nhat...${NC}"
# Di chuyển vào thư mục tên miền
cd /var/www/$DOMAIN
# Tải file về (thêm cờ -f để báo lỗi nếu link hỏng/404)
# Xóa file cũ nếu tồn tại để tránh lỗi permission
sudo rm -f latest.tar.gz
sudo curl -fLO https://wordpress.org/latest.tar.gz
echo -e "${GREEN}[4/5] Dang giai nen ma nguon...${NC}"
# Giải nén thẳng vào thư mục đích, bỏ qua lớp vỏ 'wordpress' bên ngoài
sudo tar xzf latest.tar.gz -C /var/www/$DOMAIN/public_html --strip-components=1
# Dọn dẹp file nén
sudo rm -f latest.tar.gz
# --- BƯỚC 4: PHÂN QUYỀN (PERMISSIONS) ---
echo -e "${GREEN}[5/5] Dang thiet lap quyen han chuan cho WordPress...${NC}"
WP_ROOT="/var/www/$DOMAIN/public_html"
PARENT_DIR="/var/www/$DOMAIN"
WP_OWNER="www-data"
WP_GROUP="www-data"
# Gán chủ sở hữu: www-data (để PHP có thể ghi file, cài plugin, upload ảnh)
sudo chown -R $WP_OWNER:$WP_GROUP $WP_ROOT
# Gán chủ sở hữu thư mục cha, không đệ quy, không -R
sudo chown $WP_OWNER:$WP_GROUP $PARENT_DIR
# Chuẩn hóa quyền theo khuyến nghị bảo mật của WordPress:
# - Thư mục: 755 (rwxr-xr-x)
# - File: 644 (rw-r--r--)
sudo find $WP_ROOT -type d -exec chmod 755 {} \;
sudo find $WP_ROOT -type f -exec chmod 644 {} \;
# Đảm bảo Caddy có thể "đi xuyên qua" thư mục /var/www để đọc file
sudo chmod +x /var/www
# Khởi động lại để tránh phân quyền bị cache
sudo systemctl reload php8.3-fpm
# --- HOÀN TẤT ---
echo -e "${GREEN}=============================================${NC}"
echo -e "${GREEN} Cai Dat Ma Nguon WordPress Hoan Tat! ${NC}"
echo -e "${GREEN}=============================================${NC}"
echo -e "Domain: ${YELLOW}$DOMAIN${NC}"
echo -e "Web Root: ${YELLOW}$WP_ROOT${NC}"
echo -e "Logs Directory: ${YELLOW}/var/www/$DOMAIN/logs${NC}"
echo -e "${GREEN}>>> Buoc tiep theo: Cau hinh Caddyfile.${NC}"
sleep 2
a. Kiểm tra môi trường
Trước khi cài WordPress, cần có Caddy & PHP-FPM nên các yếu tố này được kiểm tra trước:
echo -e "${GREEN}>>> Dang kiem tra moi truong he thong...${NC}"
if ! id "caddy" &>/dev/null; then
echo -e "${RED}Loi: User 'caddy' chua ton tai.${NC}"
echo -e "${YELLOW}Goi y: Cai dat Caddy Web Server truoc do chua thanh cong.${NC}"
exit 1
fi
if ! id "www-data" &>/dev/null; then
echo -e "${RED}Loi: User 'www-data' chua ton tai.${NC}"
echo -e "${YELLOW}Goi y: Hay cai dat PHP-FPM.${NC}"
exit 1
fi
b. Nhập vào tên miền và tiền xử lý cơ bản
echo -e "${GREEN}>>> Vui long nhap ten mien cua ban (vi du: example.com):${NC}"
read -p "Domain: " INPUT_DOMAIN < /dev/tty
# Xử lý chuỗi: Xóa toàn bộ khoảng trắng và chuyển về chữ thường
DOMAIN=$(echo "$INPUT_DOMAIN" | tr -d ' ' | tr '[:upper:]' '[:lower:]')
# Kiểm tra dữ liệu đầu vào
if [[ -z "$DOMAIN" ]]; then
echo -e "${RED}Loi: Ten mien khong duoc de trong!${NC}"
exit 1
fi
# Kiểm tra sơ bộ định dạng domain (phải có dấu chấm)
if [[ "$DOMAIN" != *"."* ]]; then
echo -e "${RED}Loi: Ten mien '$DOMAIN' khong hop le (thieu dau cham).${NC}"
exit 1
fi
Tuy nhiên chuỗi lệnh trên có vấn đề, nó không cho cơ hội người dùng sửa sai nếu chẳng may họ sơ ý gõ sai, chỉ một lần sai chương trình sẽ thoát. Điều này nhìn chung là không ổn ngay cả với người dùng có kinh nghiệm. Vì vậy tôi sẽ sửa nó lại, với việc cho phép người dùng nhập sai tối đa 3 lần:
# Cấu hình số lần thử tối đa
MAX_RETRIES=3
COUNT=0
DOMAIN=""
# Bắt đầu vòng lặp
while [[ $COUNT -lt $MAX_RETRIES ]]; do
((COUNT++)) # Tăng biến đếm lên 1
# Hiển thị prompt có kèm số lần thử để user biết
if [[ $COUNT -eq 1 ]]; then
read -p "Nhap Domain: " INPUT_DOMAIN < /dev/tty
else
echo -e "${RED}Ban vua nhap sai! Hay chu y nhap lai dung nhe.${NC}"
read -p "Nhap Domain: " INPUT_DOMAIN < /dev/tty
fi
# Xử lý chuỗi, bỏ khoảng trắng, chuyển chữ hoa thành chữ thường
TEMP_DOMAIN=$(echo "$INPUT_DOMAIN" | tr -d ' ' | tr '[:upper:]' '[:lower:]')
# 2. Gọt bỏ http, https và trailing slash
# Input: https://Example.com/
# Output: example.com
DOMAIN=$(echo "$TEMP_DOMAIN" | sed -e 's|^https\?://||' -e 's|/.*$||')
# --- KIỂM TRA LOGIC ---
if [[ -z "$DOMAIN" ]]; then
echo -e "${RED}Loi: Ten mien khong duoc de trong!${NC}"
elif [[ "$DOMAIN" != *"."* ]]; then
echo -e "${RED}Loi: Ten mien '$DOMAIN' khong hop le (thieu dau cham).${NC}"
else
# Nếu đã sửa xong mà hợp lệ -> Chấp nhận luôn
# Có thể in ra thông báo để người dùng biết script đã tự sửa giúp họ
if [[ "$INPUT_DOMAIN" != "$DOMAIN" ]]; then
echo -e "${GREEN}Script da tu dong chuan hoa input '${INPUT_DOMAIN}' thanh '${DOMAIN}'${NC}"
fi
break
fi
# Nếu mã chạy xuống đây nghĩa là nhập sai
if [[ $COUNT -eq $MAX_RETRIES ]]; then
echo -e "${RED}Ban da nhap sai qua 3 lan. Script se dung lai de bao ve he thong.${NC}"
exit 1
else
echo "Vui long thu lai..."
echo "-------------------------"
fi
done
# --- Script tiếp tục chạy từ đây khi dữ liệu đã đúng ---
echo -e "Thanh cong! Domain duoc chap nhan: $DOMAIN"
Ngoài ra lệnh trên còn xử lý trường hợp mà người dùng thông thường dễ lầm tưởng, chẳng hạn họ nghĩ trang chủ kèm https (hoặc http) và dấu / ở cuối là tên miền, ví dụ: https://kiencang.net/
Khi đó chương trình âm thầm xử lý, chuyển https://kiencang.net/ thành định dạng đúng kiencang.net
c. Tạo thư mục web
echo -e "${GREEN}[1/5] Dang tao thu muc chua ma nguon...${NC}"
# Tạo thư mục web root (-p giúp không báo lỗi nếu thư mục đã tồn tại)
sudo mkdir -p /var/www/$DOMAIN/public_html
echo -e "${GREEN}[2/5] Dang tao thu muc logs va cap quyen...${NC}"
# Tạo thư mục logs
sudo mkdir -p /var/www/$DOMAIN/logs
# Cấp quyền cho user caddy để ghi được log truy cập
sudo chown -R caddy:caddy /var/www/$DOMAIN/logs
–
sudo mkdir -p /var/www/$DOMAIN/public_html
Phần này tạo thư mục chứa mã nguồn của web, cấu trúc là cấu trúc truyền thống và tốt dành cho tên miền. $DOMAIN nằm trong cấu trúc giúp dễ thêm các tên miền khác nếu muốn (phân biệt thư mục rất tiện theo tên miền). /var/www/$DOMAIN/public_html-p có tác dụng chống lỗi liên quan đến thư mục cha đã được tạo, vì các tên miền sau (nếu được thêm vào) thì thư mục /var/www đã được tạo rồi, nên nếu không có -p lệnh trên sẽ báo lỗi thư mục cha đã tồn tại.
Chương trình cũng tách thư mục logs dành riêng của user caddy (webserver) ra ngoài public_html, điều này bảo mật hơn vì ngăn tình trạng logs bị lộ ra ngoài.
Cuối cùng là câu lệnh gán quyền sở hữu cho user và group caddy:
sudo chown -R caddy:caddy /var/www/$DOMAIN/logs
d. Tải về & giải nén WordPress
echo -e "${GREEN}[3/5] Dang tai WordPress phien ban moi nhat...${NC}"
# Di chuyển vào thư mục tên miền
cd /var/www/$DOMAIN
# Tải file về (thêm cờ -f để báo lỗi nếu link hỏng/404)
# Xóa file cũ nếu tồn tại để tránh lỗi permission
sudo rm -f latest.tar.gz
sudo curl -fLO https://wordpress.org/latest.tar.gz
echo -e "${GREEN}[4/5] Dang giai nen ma nguon...${NC}"
# Giải nén thẳng vào thư mục đích, bỏ qua lớp vỏ 'wordpress' bên ngoài
sudo tar xzf latest.tar.gz -C /var/www/$DOMAIN/public_html --strip-components=1
# Dọn dẹp file nén
sudo rm -f latest.tar.gz
e. Phân quyền
Đây là các thiết lập đặc biệt quan trọng, các web server có các người dùng mặc định, ví dụ như www-data được Ubuntu cấp sẵn để thực hiện các nhiệm vụ liên quan đến web và user này cần có quyền vào thư mục gốc đọc, ghi và thực thi. Caddy Web Server còn có thêm người dùng caddy để làm nhiệm vụ riêng của nó, chẳng hạn như quản lý file logs để ghi lại nhật ký hoạt động trên Caddy, và nó phải được cấp quyền cho việc ghi chép thông tin ấy vào thư mục cụ thể.
–
Trong Linux quyền Đọc (read) được gán giá trị 4, quyền Ghi (write) được gán giá trị 2, và quyền Thực thi (execution) được gán giá trị 1.
Nếu một user được gán đủ 3 quyền trên thì nó được gán giá trị quyền là 7 (4 + 2 + 1), nếu chỉ có quyền read & execution nó sẽ được gán giá trị 5 (4 + 1). Hoặc bạn sẽ thấy nó biểu diễn dưới dạng ký tự viết tắt, ví dụ quyền 7 là rwx, quyền 5 sẽ là r-x (dấu – biểu diễn nó khuyết quyền đó).
Một file, hoặc một thư mục bất kỳ sẽ có 3 đối tượng được xét quyền thao tác với nó, gồm:
- user sở hữu;
- group (nhóm) sở hữu;
- other chỉ đến tất cả các user, group khác;
Ví dụ thư mục X được gán quyền 755 thì có nghĩa là user sở hữu nó có quyền 7 (đọc, ghi, thực thi), group sở hữu & other đều có quyền 5 (đọc, thực thi).
–
Trong phân quyền người ta có khái niệm gọi là đặc quyền tối thiểu (least privilege) để chỉ đến việc quyền chỉ nên được phân vừa đủ để hoàn thành nhiệm vụ. Cấp thiếu quyền sẽ không làm được việc, cấp thừa quyền sẽ gia tăng rủi ro. Ví dụ user caddy có quyền với logs, nhưng nó không cần quyền 7 với thư mục web gốc thì sẽ không được cấp quyền đó. Ngược lại user www-data cần quyền 7 với thư mục web gốc mà chỉ cấp quyền 5 cho nó thì nó không hoàn thành nhiệm vụ.
–
Phân quyền trong Linux cần thời gian để hiểu rõ hơn vì một số khái niệm của nó hơi khác với trực giác thông thường, ví dụ:
- Trong Linux, quyền sẽ được so khớp từ user sở hữu -> group sở hữu-> other, và nó khớp với cái nào trước thì quyền đó được gán ngay lập tức mà không cần xét đến trường hợp sau. Đấy là lý do nếu một user nào đó có quyền user sở hữu (ví dụ 4) có quyền thấp hơn group sở hữu (ví dụ có quyền 6) với một file nào đó thì mặc dù user đấy nằm trong group sở hữu đi chăng nữa thì quyền của user đấy vẫn chỉ là 4 với file đó chứ không phải 6. Cái này gọi là First Match Wins (khớp là dừng). Nó hơi khác với trực giác & thói quen của chúng ta về quyền cộng dồn hoặc thừa hưởng quyền từ nhóm thuộc về.
- Khi một user sở hữu đẩy file lên một thư mục, thì quyền mà nó cấp cho file không bị ảnh hưởng bởi các quyền trước đó đã được cấp cho file bên trong. Ví dụ user sở hữu cấp quyền 644 cho file X, và đẩy nó lên thư mục được cấp quyền 664 cho tất cả file nằm trong, thì file X này vẫn chỉ được cấp quyền 644 mà thôi. Nó không tự động kế thừa quyền 6 của nhóm sở hữu với file vừa mới up lên.
echo -e "${GREEN}[5/5] Dang thiet lap quyen han chuan cho WordPress...${NC}"
WP_ROOT="/var/www/$DOMAIN/public_html"
PARENT_DIR="/var/www/$DOMAIN"
WP_OWNER="www-data"
WP_GROUP="www-data"
# Gán chủ sở hữu: www-data (để PHP có thể ghi file, cài plugin, upload ảnh)
sudo chown -R $WP_OWNER:$WP_GROUP $WP_ROOT
# Gán chủ sở hữu thư mục cha, không đệ quy, không -R
sudo chown $WP_OWNER:$WP_GROUP $PARENT_DIR
# Chuẩn hóa quyền theo khuyến nghị bảo mật của WordPress:
# - Thư mục: 755 (rwxr-xr-x)
# - File: 644 (rw-r--r--)
sudo find $WP_ROOT -type d -exec chmod 755 {} \;
sudo find $WP_ROOT -type f -exec chmod 644 {} \;
# Đảm bảo Caddy có thể "đi xuyên qua" thư mục /var/www để đọc file
sudo chmod +x /var/www
# Khởi động lại để tránh phân quyền bị cache
sudo systemctl reload php8.3-fpm
–
Phần đầu:
WP_ROOT="/var/www/$DOMAIN/public_html"
PARENT_DIR="/var/www/$DOMAIN"
WP_OWNER="www-data"
WP_GROUP="www-data"
Cái này là các biến được gán để tiện thao tác. Hãy để ý đến www-data, bạn đang có 2 đối tượng khác nhau! Khi một user được tạo trong Linux, không chỉ nó sinh ra mà cả group với tên tương ứng cũng sinh ra, chúng khác nhau, nhưng mặc định user thuộc về group, và một group có thể có nhiều user, và Linux có cơ chế để thêm bớt user trong group.
Phần hai:
sudo chown -R $WP_OWNER:$WP_GROUP $WP_ROOT
Gán quyền sở hữu cho user www-data và nhóm www-data. Đây là dòng đặc biệt quan trọng để web hoạt động bình thường. Từ khóa chown là viết tắt của change owner. Từ khóa -R nghĩa là đệ quy, nói cách khác không chỉ thư mục gốc mà mọi thư mục và file nằm trong đó đều được gán user sở hữu www-data và nhóm sở hữu www-data.
sudo chown $WP_OWNER:$WP_GROUP $PARENT_DIR
Tương tự nhưng không đệ quy.
Phần ba:
sudo find $WP_ROOT -type d -exec chmod 755 {} \;
sudo find $WP_ROOT -type f -exec chmod 644 {} \;
Đây là 2 dòng đặc biệt quan trọng liên quan đến phân quyền. Nếu chown chỉ ra ai là user sở hữu, ai là group sở hữu (và từ đó cho phép suy ra luôn ai là other), thì chmod giúp xác định rõ ràng quyền của từng đối tượng này.
Giải thích:
sudo find $WP_ROOT -type d -exec chmod 755 {} \;
Nghĩa là tất cả các thư mục nằm trong thư mục web gốc (và cả chính nó) được cấp quyền 7 (cho user sở hữu), 5 (cho nhóm sở hữu), 5 (cho các đối tượng khác).
–
sudo find $WP_ROOT -type f -exec chmod 644 {} \;
Nghĩa là tất cả các file nằm trong thư mục gốc được cấp quyền 6 (cho user sở hữu/đọc & ghi), 4 (cho nhóm sở hữu/chỉ đọc), 4 (cho các đối tượng khác, chỉ đọc).
–
Như vậy tổng hợp các lệnh trên sẽ cho ta biết:
- Thư mục gốc do user, group nào sở hữu?
- Ai là other với thư mục gốc (ví dụ caddy sẽ là other)?
- Và user, group sở hữu có quyền gì với file và thư mục nằm trong thư mục web gốc?
- Và other thì có quyền gì?
Phần bốn:
sudo chmod +x /var/www
Logs đang nằm trong /var/www/, user caddy có quyền vào và thao tác với logs, nhưng caddy đang thiếu quyền để đi vào thư mục cha /var/www/, đoạn +x /var/www cho phép mọi user (bao gồm caddy) đi qua thư mục cha nhưng không làm gì khác ngoài việc đi qua (không đọc, không ghi).
–
Cuối:
sudo systemctl reload php8.3-fpm
Khởi động lại để áp dụng các quyền ngay lập tức.
f. Thông báo
echo -e "${GREEN}=============================================${NC}"
echo -e "${GREEN} Cai Dat Ma Nguon WordPress Hoan Tat! ${NC}"
echo -e "${GREEN}=============================================${NC}"
echo -e "Domain: ${YELLOW}$DOMAIN${NC}"
echo -e "Web Root: ${YELLOW}$WP_ROOT${NC}"
echo -e "Logs Directory: ${YELLOW}/var/www/$DOMAIN/logs${NC}"
echo -e "${GREEN}>>> Buoc tiep theo: Cau hinh Caddyfile.${NC}"
sleep 2
Giống như các block khác, ở phần này nó đưa ra các thông báo liên quan & màn hình tạm dừng nghỉ 2s.