T ToutadminTài liệu

Cài đặt bản PHP

Bản PHP chạy ngay khi đặt lên một dịch vụ lưu trữ chia sẻ: không Composer, không trình quản lý gói, không dịch vụ nào phải chạy thường trực. Một thư mục, một tệp cấu hình, một cron. Hướng dẫn này đi từ thư mục trống đến một bản chạy thật, kể cả những phần mà phần lớn tài liệu bỏ qua: quyền tệp, TLS, cron, sao lưu và nâng cấp.

Cần những gì#

Thành phầnPhiên bảnVì sao cần
PHP8.1 trở lênKiểu hợp, enum, readonly, never: mã nguồn dùng đến chúng.
pdo_sqliteChính là cơ sở dữ liệu. Thiếu nó thì không gì chạy được.
mbstringXử lý chuỗi cho đúng trong mười sáu thứ tiếng.
opensslNên cóMã hoá các bí mật trong cơ sở dữ liệu (mật khẩu SMTP, token nơi lưu trữ ngoài).
curlNên cóGửi webhook và đẩy bản sao lưu ra bên ngoài.
intlNên cóCách viết ngày giống hệt bản Node. Thiếu nó, tên tháng lấy từ từ điển.

Trình cài đặt kiểm tra từng mục và hiện lên màn hình trước khi cho bạn đi tiếp: thiếu một mục bắt buộc thì dừng lại, còn mục tuỳ chọn chỉ được báo chứ không cản.

Không phụ thuộc gì

Không có gì phải cài bằng composer: đọc IMAP, nén tar.gz, PDF, CSV, mã QR và HTTP client đều được viết bên trong sản phẩm. Chính vì thế mới đặt được nó lên một máy chủ mà bạn thậm chí không có dòng lệnh.

1. Đưa tệp lên#

Lấy gói bản PHP rồi tải lên. Cấu trúc như sau:

toutadmin/
├── app/            mã nguồn (máy chủ web không bao giờ phục vụ)
│   ├── Core/       nhân: định tuyến, CSDL, phiên, bảo mật
│   ├── Modules/    nghiệp vụ: nhân sự, kế toán, dự án, chất lượng…
│   ├── Controllers/
│   ├── views/      khuôn mẫu
│   ├── locales/    mười sáu từ điển
│   └── schema.sql  cấu trúc CSDL, y hệt bản Node
├── public/         ← thư mục gốc của website trỏ vào đây, không vào đâu khác
│   ├── index.php   bộ điều khiển đầu vào
│   ├── css/ js/
├── data/           CSDL, két, cặp chữ ký, chứng từ nhận được, sao lưu
├── tools/          cron.php, check-keys.php
├── tests/
├── config.sample.php
└── VERSION
Thư mục gốc trỏ vào public/, không phải cả thư mục

Đây là điều quan trọng nhất của trang này. Nếu máy chủ phục vụ cả thư mục thì data/app.sqlite ai cũng tải về được: toàn bộ cơ sở dữ liệu, kể cả mã băm mật khẩu và két. data/ nằm ngoài public/ chính là vì vậy.

Trên dịch vụ lưu trữ chia sẻ#

Có hai trường hợp. Nếu nhà cung cấp cho bạn chọn thư mục gốc của tên miền (cPanel, Plesk, và phần lớn dịch vụ có mục «thư mục gốc»), hãy đặt cả thư mục ra ngoài www/ rồi trỏ tên miền vào toutadmin/public.

Nếu thư mục gốc là cố định — thường là www/ hoặc public_html/ — hãy đặt nội dung của public/ vào đó, phần còn lại đặt cao hơn một bậc, rồi sửa đường dẫn ở đầu public/index.php:

www/                  ← thư mục gốc cố định
├── index.php
├── css/  js/
toutadmin/            ← phần còn lại, nơi web không với tới
├── app/
├── data/
└── config.php
// www/index.php —— dòng cần sửa
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';

Cùng đường, nếu không thể đặt gì ra ngoài thư mục gốc, tệp .htaccess đi kèm trong data/ đã từ chối mọi truy cập. Đó là dây an toàn, không phải giải pháp: hãy luôn ưu tiên đặt ra ngoài thư mục gốc.

2. Viết cấu hình#

cp config.sample.php config.php

Rồi mở config.php:

<?php
return [
    // CSDL SQLite và thư mục dữ liệu: nằm ngoài thư mục gốc của website.
    'db_path'  => __DIR__ . '/data/app.sqlite',
    'data_dir' => __DIR__ . '/data',

    // Khoá riêng của bản cài này. Sinh một lần:
    //   php -r "echo bin2hex(random_bytes(32));"
    'session_secret' => '…',

    // Token cài đặt: xem bước sau.
    'install_token' => '',

    // Địa chỉ công khai, dùng cho các liên kết gửi trong thư.
    'base_url' => 'https://noibo.congty.vn',

    // Chỉ bật khi đứng sau một proxy đáng tin.
    'trust_proxy' => false,

    // Giới hạn: số lần thử đăng nhập mỗi địa chỉ trong mười lăm phút,
    // và số yêu cầu mỗi địa chỉ trong một phút.
    'login_rate_limit'  => 10,
    'global_rate_limit' => 300,

    // Hết hạn: thời gian không hoạt động, và thời gian tuyệt đối
    // mà không thao tác nào kéo dài thêm được.
    'session_idle_minutes' => 60,
    'session_max_hours'    => 12,
];
Khoá của bản cài

Nó niêm phong chữ ký trong cặp chữ ký và mã hoá các bí mật trong cơ sở dữ liệu. Mất nó thì các chữ ký đã có không kiểm được nữa. Nếu bạn giữ nguyên giá trị mẫu, sản phẩm tự sinh một khoá trong data/.instance-key (quyền 0600) — khi đó đừng quên đưa tệp này vào bản sao lưu.

Cấu hình có thể đặt ở nơi khác: biến môi trường TOUTADMIN_CONFIG trỏ tới một tệp khác.

3. Đặt token cài đặt#

Từ lúc tệp được đưa lên đến lúc bạn mở trình cài đặt, bản cài này thuộc về người tìm thấy nó trước: ai đến trước, người đó tạo tài khoản quản trị. Khoảng hở ấy ngắn, nhưng đủ. Vậy nên hãy đặt một giá trị ngẫu nhiên trước khi đưa lên mạng:

php -r "echo bin2hex(random_bytes(16));"
# rồi ghi vào config.php:
'install_token' => 'c3f1…',

Trình cài đặt sẽ hỏi token này, so sánh trong thời gian không đổi — so sánh ngây thơ cho phép đoán ra token từng ký tự một — và ghi lại mỗi lần từ chối. Không có token thì không gì được tạo ra. Cài xong, trình cài đặt tự đóng: giữ token lại cũng được, nó không còn tác dụng.

4. Quyền tệp#

Đường dẫnQuyềnAi ghi
data/0770Máy chủ web, và chỉ nó
data/app.sqlite0660Do lúc cài đặt tạo ra
config.php0640Không ai — máy chủ chỉ đọc
app/, public/0755Không ai — chỉ đọc
# Ví dụ với máy chủ chạy PHP dưới tên www-data
sudo chown -R nin:www-data /var/www/toutadmin
sudo find /var/www/toutadmin -type d -exec chmod 750 {} \;
sudo find /var/www/toutadmin -type f -exec chmod 640 {} \;
sudo chmod 770 /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/public

SQLite còn ghi các tệp đi kèm bên cạnh CSDL (-wal, -shm): thứ phải ghi được là thư mục, chứ không chỉ riêng tệp CSDL.

5. Máy chủ web#

Nginx#

server {
    listen 443 ssl http2;
    server_name noibo.congty.vn;

    root /var/www/toutadmin/public;   # không nơi nào khác
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/noibo.congty.vn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/noibo.congty.vn/privkey.pem;

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

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Tệp đã gửi lên không bao giờ được phục vụ trực tiếp: chúng đi qua
    # một tuyến có kiểm phiên và mã băm của tệp.
    location ~ ^/(data|app|tools|tests)/ { deny all; }
}

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

Apache#

<VirtualHost *:443>
    ServerName noibo.congty.vn
    DocumentRoot /var/www/toutadmin/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/noibo.congty.vn/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/noibo.congty.vn/privkey.pem

    <Directory /var/www/toutadmin/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Tệp public/.htaccess đi kèm đã chuyển mọi địa chỉ về index.php. Với một máy chủ ảo Apache, thế là đủ.

TLS#

Sản phẩm chỉ thêm tiêu đề Strict-Transport-Security khi kết nối đã được mã hoá, và không bao giờ thêm khi kết nối để trần: khai báo nó từ một trang chưa mã hoá thì trình duyệt không nhận, mà lại có thể khoá một lần chạy thử ở máy bạn vào https suốt nửa năm. Trên máy chủ bạn tự quản:

sudo certbot --nginx -d noibo.congty.vn

Nếu phía trước có proxy hay bộ cân bằng tải kết thúc TLS, hãy đặt 'trust_proxy' => true — và chỉ trong trường hợp đó: nếu không, ai cũng có thể tự khai địa chỉ IP và giao thức trong tiêu đề.

6. Cron#

Một trang PHP chỉ chạy khi có yêu cầu. Tám việc mà bản Node làm trong vòng quét mỗi giờ, ở đây do cron của máy chủ đảm nhận:

* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
Cron làm gìBao lâu một lần
Đóng các tài khoản có hợp đồng đã hếtMỗi lần chạy
Xuất hoá đơn cho các thuê bao đến kỳMỗi lần chạy, không bao giờ lặp
Chuyển các kỳ hạn thành thông báoMỗi lần chạy, có lọc trùng
Dọn thông báo đã đọc và nhật ký kiểm toánTheo thời hạn lưu đã đặt
Đọc hộp thư kế toán (IMAP)Nếu đã bật thu nhận
Xả hàng đợi webhook, kể cả các lần thử lạiMỗi lần chạy
Tạo bản sao lưu tự động và gửi ra ngoàiKhi đến khoảng thời gian đã đặt

Gọi kịch bản dày hơn khoảng thời gian đã đặt cũng không làm sao lưu nhiều hơn: chưa đến lúc thì không có gì xảy ra. Nếu dịch vụ lưu trữ giới hạn cron mười lăm phút một lần, hãy đổi * * * * * thành */15 * * * * — webhook chỉ đi chậm hơn chừng ấy.

Trên dịch vụ không có cron

Vài gói nhập môn không có. Gọi HTTP từ một dịch vụ bên ngoài (kiểu giám sát tình trạng) không dùng được: cron.php từ chối chạy ngoài dòng lệnh, chính là để một địa chỉ công khai không kích hoạt được việc sao lưu. Khi đó hãy chạy tay ở màn hình Sao lưu.

7. 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 dẫn về /installation. Tất cả gọn trong một trang:

  1. Điều kiện tiên quyết, kiểm và hiện từng mục — mục bắt buộc được ghi rõ.
  2. Token cài đặt, nếu bạn đã đặt.
  3. Công ty: tên sẽ xuất hiện khắp nơi, và số ngày phép mỗi nhân viên mới (không phải cộng tác viên tự do) được hưởng.
  4. Ngôn ngữ mặc định của bản cài, chọn trong 16 thứ tiếng.
  5. Tài khoản quản trị: địa chỉ thư và một mật khẩu ít nhất mười hai ký tự.

Mọi thứ được ghi trong cùng một giao dịch. Khi đã có tài khoản, /installation chuyển về trang đăng nhập: trình cài đặt tự đóng, không phải xoá tệp nào.

8. Kiểm tra mọi thứ chạy đúng#

# Bộ kiểm thử, chạy ở máy bạn (không ghi vào CSDL của bạn)
php tests/run.php

# Bản dịch: không màn hình nào dùng khoá thiếu trong từ điển
php tools/check-keys.php

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

9. Nâng cấp#

# 1. Sao lưu trước, luôn luôn
php tools/cron.php                     # hoặc màn hình « Sao lưu »

# 2. Thay mã nguồn, không đụng dữ liệu
#    app/ public/ tools/ tests/ VERSION  ← thay
#    data/ config.php                    ← giữ nguyên

# 3. CSDL tự chuyển đổi ở yêu cầu đầu tiên

Cấu trúc CSDL tiến hoá qua các bước chuyển đổi lặp lại được: chạy nâng cấp hai lần cũng không hỏng gì. Một bản Node và một bản PHP dùng chung cùng một cấu trúc — 142 bảng, 1311 cột — và cùng cách lưu mật khẩu: chuyển cơ sở dữ liệu giữa hai bên không cần chuyển đổi gì.

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

Gói tar.gz chứa cơ sở dữ liệu (chép bằng VACUUM INTO, nên vẫn nhất quán ngay giữa lúc đang ghi) và năm thư mục tệp: ảnh đại diện, hồ sơ ứng tuyển nhận được, két, cặp chữ ký, chứng từ nhận được. Mỗi tệp trong gói đi kèm mã SHA-256 của nó, và được kiểm từng tệp khi khôi phục.

Một gói sao lưu nằm lại trên chính máy chủ mà nó phải bảo vệ thì chẳng bảo vệ được gì: hãy đặt một nơi lưu trữ bên ngoài (FTPS hoặc Google Drive) ở màn hình Sao lưu. Xem trang Sao lưu và khôi phục.

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

cp config.sample.php config.php
php -S localhost:8000 -t public public/index.php

Máy chủ đi kèm PHP đủ để bạn xem qua sản phẩm. Nó không dành cho chạy thật: một tiến trình, không TLS, không giới hạn lưu lượng.

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