T ToutadminTài liệu

Cài bản Node

Bản Node chạy như một dịch vụ thường trực: một tiến trình, một cơ sở dữ liệu SQLite nằm trên đĩa, ngoài ra không phải cài gì thêm — không cơ sở dữ liệu ngoài, không bộ nhớ đệm, không hàng đợi. Hướng dẫn này đi từ một thư mục trống đến bản cài đang chạy thật, gồm cả những phần mà phần lớn hướng dẫn bỏ qua: dịch vụ hệ thống, proxy ngược, TLS, sao lưu và nâng cấp.

Cần những gì#

Thành phầnPhiên bảnVì sao
Node.js22 trở lênSản phẩm dựa vào bộ chạy kiểm thử có sẵn và vào các giao diện mới.
Một trình biên dịch Cbuild-essentialbetter-sqlite3 được biên dịch lúc cài, trừ khi đã có sẵn tệp nhị phân cho nền tảng của bạn.
Một ổ đĩa cục bộCho SQLite và các tệp được tải lên. Tuyệt đối không dùng ổ chia sẻ qua mạng: xem bên dưới.

Một máy một nhân với 512 MB bộ nhớ là đủ cho vài chục người. Điều quan trọng không phải sức mạnh mà là cái đĩa: nó phải là đĩa cục bộ và phải được sao lưu.

1. Lấy về và cài#

git clone <kho-của-bạn> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev

Dùng npm ci chứ không phải npm install: nó cài đúng những gì tệp khoá mô tả, và không bao giờ viết lại tệp ấy. Trên máy chủ, một phiên bản trượt đi là sự cố không ai hiểu nổi.

Nếu biên dịch thất bại

better-sqlite3 là phụ thuộc duy nhất biên dịch mã C. sudo apt install -y build-essential python3 gỡ được gần như mọi trường hợp.

2. Cấu hình#

cp .env.example .env

Mọi thứ đặt bằng biến môi trường — đọc từ .env, hoặc do trình quản lý dịch vụ của bạn đặt. Không biến nào bắt buộc: không có .env thì bản cài vẫn khởi động ở cổng 3000 và đưa bạn tới trình cài đặt.

BiếnMặc địnhLàm gì
PORT3000Cổng lắng nghe.
NODE_ENVỞ môi trường thật đặt production: cookie nghiêm ngặt, không in vết chi tiết.
SESSION_SECRETtự sinhNiêm phong các phiên. Bỏ trống thì nó được sinh trong data/session.key.
INSTALL_TOKENTrình cài đặt đòi trước khi cài. Nên đặt trên máy chủ phơi ra internet.
TRUST_PROXYĐặt 1 khi có proxy tin cậy phía trước, và chỉ khi ấy.
DB_PATHdata/app.sqliteCơ sở dữ liệu.
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIRnằm dưới data/Các thư mục tệp: ảnh, hồ sơ ứng tuyển, két, cặp ký, chứng từ nhận được, kho lưu.
LOGIN_RATE_LIMIT10Số lần thử đăng nhập mỗi mười lăm phút, tính theo địa chỉ.
GLOBAL_RATE_LIMIT300Số yêu cầu mỗi phút, tính theo địa chỉ.
API_RATE_LIMITSố lần gọi API mỗi phút, tính theo mã thông báo.
SESSION_IDLE_MINUTES60Không hoạt động quá lâu thì phiên rớt.
SESSION_MAX_HOURS12Thời hạn tuyệt đối, không hoạt động nào kéo dài thêm được.
ADMIN_EMAIL, ADMIN_PASSWORDCài không cần giao diện: tạo quản trị viên ngay khi khởi động.
Khoá bí mật của phiên

Đổi SESSION_SECRET là đá tất cả mọi người ra cùng lúc. Hãy đặt một lần, sinh ngẫu nhiên (node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"), và lưu nó cùng phần còn lại. Đừng bao giờ để nó nằm trong kho git.

Đặt mã cài đặt#

Giữa lần khởi động đầu tiên và lúc bạn vào trình cài đặt, bản cài thuộc về ai tìm ra nó trước: người đến trước sẽ tạo tài khoản quản trị. Vậy hãy đặt INSTALL_TOKEN trước khi mở cổng:

node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# rồi trong .env:
INSTALL_TOKEN=c3f1…

Trình cài đặt so nó trong thời gian không đổi — so sánh thường sẽ để lộ mã theo từng ký tự — và ghi nhật ký mỗi lần từ chối. Cài xong rồi thì trình cài đặt tự đóng lại.

Cài không cần giao diện#

Với triển khai tự động, ADMIN_EMAILADMIN_PASSWORD tạo quản trị viên ngay khi khởi động, bỏ qua trình cài đặt. Sau đó hãy gỡ chúng đi: mật khẩu nằm trong môi trường của một dịch vụ thì ai đọc được dịch vụ ấy đều đọc được.

3. Quyền truy cập và nơi để dữ liệu#

sudo useradd --system --home /var/www/toutadmin --shell /usr/sbin/nologin toutadmin
sudo chown -R toutadmin:toutadmin /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/data
sudo chmod 640 /var/www/toutadmin/.env

Mã nguồn có thể để chỉ đọc; thứ duy nhất phải ghi được là data/. SQLite còn ghi các tệp đi kèm bên cạnh (-wal, -shm): cái đáng kể là thư mục, không riêng gì tệp cơ sở dữ liệu.

Tuyệt đối không đặt trên ổ chia sẻ qua mạng

NFS và SMB nói dối về việc khoá tệp. SQLite dựa vào chính cái khoá ấy để ngăn hai bên ghi cùng lúc: trên ổ chia sẻ, cơ sở dữ liệu sẽ hỏng dần mà không báo trước. Luôn dùng đĩa cục bộ — thứ đi ra ngoài là bản sao lưu, chứ không phải cơ sở dữ liệu.

4. Dịch vụ#

Chạy bằng tay thì đóng cửa sổ lệnh là sản phẩm dừng. Hãy giao nó cho systemd:

# /etc/systemd/system/toutadmin.service
[Unit]
Description=Toutadmin
After=network.target

[Service]
Type=simple
User=toutadmin
WorkingDirectory=/var/www/toutadmin
EnvironmentFile=/var/www/toutadmin/.env
ExecStart=/usr/bin/node src/server.js
Restart=always
RestartSec=5

# Dịch vụ chỉ cần ghi vào data/.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/toutadmin/data

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now toutadmin
sudo systemctl status toutadmin
journalctl -u toutadmin -f

Năm dòng siết chặt ấy không phải để trang trí: ProtectSystem=strict làm cả hệ thống tệp không ghi được, còn ReadWritePaths mở lại đúng một thư mục buộc phải ghi được. Một lỗ hổng cho phép ghi tuỳ ý khi ấy cũng chỉ với tới data/.

5. Proxy ngược#

Đừng bao giờ đưa cổng 3000 thẳng ra internet: nó không làm TLS, và cũng chẳng có lý do gì phải học.

server {
    listen 443 ssl http2;
    server_name intranet.vidu.vn;

    ssl_certificate     /etc/letsencrypt/live/intranet.vidu.vn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.vidu.vn/privkey.pem;

    client_max_body_size 20M;          # tệp gửi vào két, chứng từ nhận được

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name intranet.vidu.vn;
    return 301 https://$host$request_uri;
}

Đồng thời cho dịch vụ chỉ lắng nghe trên địa chỉ vòng lặp, để cổng 3000 chỉ proxy với tới được.

TLS#

sudo certbot --nginx -d intranet.vidu.vn

Ngay khi kết nối đã mã hoá, sản phẩm tự đặt tiêu đề Strict-Transport-Security, và không bao giờ đặt trên kết nối để trần: công bố từ một trang không mã hoá thì nó chẳng được đọc, mà lại nhốt một lần chạy thử cục bộ trong https suốt sáu tháng.

TRUST_PROXY đi cùng proxy, và chỉ cùng proxy

Biến này khiến máy chủ tin lời địa chỉ nguồn ghi trong tiêu đề. Sau nginx thì đúng là cần thế — nếu không, mọi yêu cầu trông như đến từ 127.0.0.1 và các hạn mức theo địa chỉ chẳng còn bảo vệ gì. Không có proxy phía trước thì ngược lại: ai cũng khai địa chỉ tuỳ ý và đi vòng qua hạn mức.

6. Trình cài đặt#

Mở tên miền của bạn. Chừng nào chưa có tài khoản nào, mọi địa chỉ đều chuyển về /installation. Năm bước:

  1. Ngôn ngữ của bản cài, trong 16 ngôn ngữ.
  2. Các yêu cầu hệ thống, được kiểm và hiển thị.
  3. Doanh nghiệp: cái tên sẽ xuất hiện khắp nơi.
  4. Số ngày phép năm cấp cho mỗi nhân viên mới không làm tự do.
  5. Tài khoản quản trị: địa chỉ và mật khẩu ít nhất mười hai ký tự.

Ngay khi có một tài khoản, /installation chuyển về trang đăng nhập: trình cài đặt đã tự đóng, không có tệp nào phải xoá bằng tay.

7. Vòng quét định kỳ#

Khác với bản PHP, ở đây không phải đặt lịch gì cả: máy chủ mang sẵn bộ lập lịch của nó, cứ mỗi giờ lại thức dậy và làm tám việc.

Vòng quét làm gìBảo đảm
Đóng những tài khoản đã hết hợp đồngCũng được kiểm lúc đăng nhập và khi mở bảng điều khiển
Phát hành hoá đơn cho các thuê bao đến kỳMột chỉ mục duy nhất ngăn xuất hoá đơn hai lần
Biến các kỳ hạn thành thông báoCó khoá chống trùng: một kỳ hạn chỉ nhắc một lần
Dọn thông báo đã đọc và nhật ký kiểm toánTheo thời hạn lưu đã chọn
Lấy hộp thư kế toán về (IMAP)Nếu việc lấy thư đã được cấu hình
Xả hàng đợi webhook, kể cả các lần gửi lạiNăm lần thử rồi bỏ
Tạo bản sao lưu tự động và gửi ra ngoàiKhi đã đến kỳ đã định

Mỗi thao tác đều lặp lại vẫn cho cùng kết quả: chạy lại vòng quét không xuất hoá đơn hai lần, không báo hai lần. Chính nhờ vậy mà bạn có thể khởi động lại dịch vụ bất cứ lúc nào mà không phải đắn đo.

8. Kiểm tra rằng mọi thứ chạy#

# các bài kiểm thử (không ghi vào cơ sở dữ liệu của bạn)
npm test

# một bản cài trình diễn, để đi một vòng
node scripts/seed-demo.js

Rồi trong giao diện: tạo một thành viên, đăng nhập bằng người đó, gửi lên một tệp đính kèm, chạy một bản sao lưu bằng tay và kiểm nó từ màn hình Sao lưu. Bốn động tác ấy chạm tới cơ sở dữ liệu, tệp, quyền và kho lưu.

9. Nâng cấp#

# 1. sao lưu trước đã, luôn luôn — từ màn hình Sao lưu

# 2. mã nguồn
cd /var/www/toutadmin
git pull
npm ci --omit=dev

# 3. khởi động lại; cơ sở dữ liệu tự chuyển đổi khi khởi động
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager

Lược đồ tiến triển bằng các bước chuyển đổi lặp lại vẫn cho cùng kết quả: chạy lại bản nâng cấp không làm hỏng gì. data/.env không bao giờ bị đụng tới.

Quay lại#

Đưa mã nguồn về phiên bản trước rồi khởi động lại. Các bước chuyển đổi không xoá cột: một cơ sở dữ liệu đã chuyển vẫn đọc được bằng phiên bản trước, trừ khi nhật ký thay đổi nói rõ khác đi. Nếu còn ngờ, hãy phục hồi bản lưu đã lấy ở bước 1.

10. Sao lưu cho ra sao lưu#

Kho lưu tar.gz mang theo cơ sở dữ liệu và năm thư mục tệp. Cơ sở dữ liệu được chép bằng cơ chế sao lưu trực tuyến của SQLite, cho ra một bản chép nhất quán ngay cả khi đang có ghi — bản PHP đạt cùng kết quả bằng VACUUM INTO. Mỗi tệp mang mã băm SHA-256 của nó, được đối chiếu lúc phục hồi.

Một kho lưu nằm lại trên chính máy chủ mà nó bảo vệ thì chẳng bảo vệ được gì: hãy cấu hình nơi lưu từ xa (FTPS hoặc Google Drive) từ màn hình Sao lưu, và đăng ký một webhook cho sauvegarde.echec để biết khi việc gửi ra ngoài thất bại. Chi tiết nằm ở trang Sao lưu và phục hồi.

Thử ở máy mình trước đã#

npm install
cp .env.example .env
npm run dev        # khởi động lại sau mỗi thay đổi

Mở http://localhost:3000. Ở máy mình, hãy để NODE_ENV trống: trong production, cookie bị đánh dấu «chỉ dùng kết nối an toàn» và sẽ không được giữ trên kết nối để trần — bạn sẽ quẩn quanh mãi ở trang đăng nhập.

Chuyển sang bản PHP, hoặc từ đó sang đây#

Hai bản dùng chung một lược đồ — 142 bảng, 1311 cột — và cùng một định dạng mật khẩu. Dừng bên này, chép app.sqlite cùng các thư mục tệp, khởi động bên kia: không có bước chuyển đổi nào. Xem Hai bản.

Tài liệu Toutadmin — dựng ngày 2026-09-13. Trang độc lập, tách rời phần mềm.