T Toutadminドキュメント

PHP 版のインストール

PHP 版は、そのままレンタルサーバーに置けば動きます。Composer も、パッケージマネージャーも、 常駐させるサービスも必要ありません。ディレクトリがひとつ、設定ファイルがひとつ、cron がひとつ。 このチュートリアルは、空のディレクトリから本番稼働までを通しで扱います。多くの手引きが飛ばす ところ、つまり権限、TLS、cron、バックアップ、更新も含めて。

必要なもの#

項目バージョンなぜ必要か
PHP8.1 以上ユニオン型、enumreadonlynever。コードが使っています。
pdo_sqliteデータベースそのもの。これがなければ何も動きません。
mbstring16 言語での文字列の正しい扱い。
openssl推奨データベース内の秘密情報(SMTP パスワード、遠隔保管先のトークン)の暗号化。
curl推奨Webhook の送出と、バックアップの外部送信。
intl推奨Node 版と同じ日付表記。なければ月名は辞書から取られます。

インストーラーはこれらをひとつずつ確認し、画面に表示してから先へ進ませます。必須の 項目が欠けていればインストールは中断し、任意の項目は知らせるだけで妨げません。

依存ゼロ

composer で入れるものは何もありません。IMAP の読み取り、tar.gz の書庫、 PDF、CSV、QR コード、HTTP クライアントはすべて製品の中に書かれています。だからこそ、 コマンドラインすら使えないサーバーにも置けるのです。

1. ファイルを置く#

PHP 版の書庫を入手して転送します。構成はこうなっています。

toutadmin/
├── app/            コード(Web サーバーは決して配信しません)
│   ├── Core/       カーネル:ルーティング、DB、セッション、セキュリティ
│   ├── Modules/    業務:人事、経理、案件、品質……
│   ├── Controllers/
│   ├── views/      テンプレート
│   ├── locales/    16 の辞書
│   └── schema.sql  Node 版とまったく同じ DB 構造
├── public/         ← ドキュメントルートはここ、他のどこでもありません
│   ├── index.php   フロントコントローラー
│   ├── css/ js/
├── data/           DB、金庫、署名綴じ、受領書類、バックアップ
├── tools/          cron.php、check-keys.php
├── tests/
├── config.sample.php
└── VERSION
ドキュメントルートは public/、ディレクトリ全体ではありません

このページで一番大事な一点です。サーバーがディレクトリ全体を配信すると、 data/app.sqlite は誰でもダウンロードできてしまいます。パスワードのハッシュも 金庫も含めたデータベースまるごとです。data/public/ の外にあるのは そのためです。

レンタルサーバーの場合#

二つの場合があります。ドメインのルートを選べる事業者(cPanel、Plesk、「ルートディレクトリ」を 指定できるサーバー全般)なら、ディレクトリ全体を www/ の外に置き、ドメインを toutadmin/public に向けてください。

ルートが固定されている場合——多くは www/public_html/ —— public/ の中身をそこに置き、残りをその一階層上に置いて、 public/index.php の先頭にあるパスを書き換えます。

www/                  ← 固定されたルート
├── index.php
├── css/  js/
toutadmin/            ← 残り、Web から届かない場所
├── app/
├── data/
└── config.php
// www/index.php —— 直す一行
require_once __DIR__ . '/../toutadmin/app/bootstrap.php';

どうしてもルートの外に何も置けないときのために、data/ に同梱の .htaccess がアクセスを拒否します。これは安全帯であって解決策ではありません。 置けるなら必ずルートの外へ。

2. 設定を書く#

cp config.sample.php config.php

そして config.php を開きます。

<?php
return [
    // SQLite の DB とデータ用ディレクトリ。ドキュメントルートの外に置きます。
    'db_path'  => __DIR__ . '/data/app.sqlite',
    'data_dir' => __DIR__ . '/data',

    // このインスタンス固有の鍵。一度だけ生成します:
    //   php -r "echo bin2hex(random_bytes(32));"
    'session_secret' => '…',

    // インストール用トークン。次の手順を参照。
    'install_token' => '',

    // メールに入るリンクで使う公開アドレス。
    'base_url' => 'https://intranet.mise.co.jp',

    // 信頼できるプロキシの背後にあるときだけ有効にします。
    'trust_proxy' => false,

    // 上限:アドレスごと 15 分あたりのログイン試行回数と、
    // アドレスごと 1 分あたりのリクエスト数。
    'login_rate_limit'  => 10,
    'global_rate_limit' => 300,

    // 期限:無操作での時間と、何をしても延びない絶対的な上限。
    'session_idle_minutes' => 60,
    'session_max_hours'    => 12,
];
インスタンスの鍵

署名綴じの署名を封じ、データベース内の秘密情報を暗号化します。失うと既存の署名は検証 できなくなります。見本の値のままにすると、製品が data/.instance-key に自分で 生成します(権限 0600)。その場合はこのファイルもバックアップに含めてください。

設定は別の場所にも置けます。環境変数 TOUTADMIN_CONFIG で別のファイルを指せます。

3. インストール用トークンを決める#

ファイルを置いてからあなたがインストーラーを開くまでの間、このインスタンスは先に 見つけた者のものです。最初に着いた人が管理者アカウントを作ります。短い隙間ですが、 それで十分です。だから公開する前にランダムな値を入れておきます。

php -r "echo bin2hex(random_bytes(16));"
# そして config.php に:
'install_token' => 'c3f1…',

インストーラーはこれを求め、一定時間で照合し——素朴な比較ではトークンが 一文字ずつ言い当てられます——拒否のたびに記録します。トークンがなければ何も作られません。 インストールが済むとインストーラーは自分で閉じます。トークンは残しておいて構いません。 もう何の役にも立ちません。

4. 権限#

パス権限誰が書くか
data/0770Web サーバー、それだけ
data/app.sqlite0660インストール時に作られます
config.php0640誰も——サーバーからは読むだけ
app/public/0755誰も——読むだけ
# PHP が 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 は DB の隣に付随ファイル(-wal-shm)も書きます。書き込み可能で なければならないのはディレクトリであって、DB ファイルだけではありません。

5. Web サーバー#

Nginx#

server {
    listen 443 ssl http2;
    server_name intranet.mise.co.jp;

    root /var/www/toutadmin/public;   # 他のどこでもなく
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/intranet.mise.co.jp/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.mise.co.jp/privkey.pem;

    client_max_body_size 20M;          # 金庫への預け入れ、受領書類

    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;
    }

    # 預けられたファイルは決して直接配信しません。セッションと
    # ファイルのハッシュを確かめるルートを必ず通ります。
    location ~ ^/(data|app|tools|tests)/ { deny all; }
}

server {
    listen 80;
    server_name intranet.mise.co.jp;
    return 301 https://$host$request_uri;
}

Apache#

<VirtualHost *:443>
    ServerName intranet.mise.co.jp
    DocumentRoot /var/www/toutadmin/public

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/intranet.mise.co.jp/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/intranet.mise.co.jp/privkey.pem

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

同梱の public/.htaccess がすべてのアドレスを index.php に回します。 Apache のバーチャルホストなら、これで足ります。

TLS#

接続が暗号化されているときだけ、製品は Strict-Transport-Security ヘッダーを付け、 平文のときは決して付けません。暗号化されていないページから宣言してもブラウザーは受け取らず、 かえって手元の試用を半年ものあいだ https に縛りかねないからです。自分で管理する サーバーなら:

sudo certbot --nginx -d intranet.mise.co.jp

前段のプロキシやロードバランサーが TLS を終端している場合は 'trust_proxy' => true にします——その場合だけです。そうでないと、誰でもヘッダーで好きな IP とプロトコルを名乗れて しまいます。

6. cron#

PHP のサイトはリクエストがあるときしか動きません。Node 版が毎時の巡回でこなす 8 つの仕事は、 ここではサーバーの cron が引き受けます。

* * * * * /usr/bin/php /var/www/toutadmin/tools/cron.php >> /var/log/toutadmin-cron.log 2>&1
cron がすること頻度
契約が終わったアカウントを閉じる毎回
期日の来た定期契約を請求する毎回、二重請求はしません
期限を通知に変える毎回、重複は除きます
既読の通知と監査記録を片づける設定した保存期間に従って
経理のメールボックスを読む(IMAP)取り込みを設定していれば
Webhook の待ち行列を再試行ごと流す毎回
自動バックアップを作り外部へ送る設定した間隔が来たとき

設定した間隔より頻繁にスクリプトを呼んでも、バックアップが増えるわけではありません。時が来て いなければ何も起きないだけです。サーバーが cron を 15 分おきに制限しているなら * * * * **/15 * * * * にしてください。Webhook がその分だけ遅れて 出ていくだけです。

cron のないサーバーでは

入門向けのサーバーには無いことがあります。外部サービス(死活監視など)からの HTTP 呼び出しは 使えません。cron.php はコマンドライン以外からの実行を拒みます。公開アドレスから バックアップを起こさせないためです。その場合はバックアップ画面から手で実行してください。

7. インストーラー#

ドメインを開きます。アカウントがひとつも無いあいだ、すべてのアドレスは /installation に向きます。全体がひとつのページに収まっています。

  1. 前提条件を一つずつ確認して表示——必須のものは明示されます。
  2. 設定してあればインストール用トークン
  3. 会社:あちこちに出てくる名称と、(フリーランス以外の)新入社員に与える年次有給休暇の日数。
  4. インスタンスの既定の言語。16 言語から選びます。
  5. 管理者アカウント:メールアドレスと、12 文字以上のパスワード。

すべてがひとつのトランザクションで書き込まれます。アカウントができると /installation はログイン画面へ向きます。インストーラーは自分で閉じるので、 消すファイルはありません。

8. 動いていることを確かめる#

# テスト。手元で動かします(あなたの DB には書きません)
php tests/run.php

# 翻訳:辞書に無い鍵を使っている画面がひとつも無いこと
php tools/check-keys.php

それから画面で。メンバーを作り、そのアカウントでログインし、添付ファイルを預け、バックアップを 手で実行してバックアップ画面で検証します。この四つで DB、ファイル、権限、書庫に触れます。 つまりサーバーに求められることのすべてです。

9. 更新する#

# 1. まずバックアップ。いつでも
php tools/cron.php                     # または「バックアップ」画面から

# 2. データではなくコードを入れ替えます
#    app/ public/ tools/ tests/ VERSION  ← 入れ替え
#    data/ config.php                    ← そのまま

# 3. DB は最初のリクエストで自分を移行します

DB 構造は冪等な移行で進みます。更新を二度走らせても何も壊れません。Node 版のインスタンスと PHP 版のインスタンスは同じ構造——142 表、1311 列——と 同じパスワードを共有します。データベースは変換なしにどちらへでも移せます。

10. 本当にバックアップする#

tar.gz の書庫には、データベース(VACUUM INTO で複製するので書き込みの 最中でも整合します)と、五つのファイル用ディレクトリが入ります。顔写真、受け取った履歴書、 金庫、署名綴じ、受領書類です。書庫の各ファイルには SHA-256 が添えられ、復元時に一つずつ 照合されます。

守っているはずのサーバーに置いたままの書庫は、何も守りません。バックアップ画面で 外部の保管先(FTPS または Google ドライブ)を設定してください。詳しくは バックアップと復元のページへ。

まず手元で試す#

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

PHP 内蔵のサーバーで製品を一通り見ることはできます。本番向きではありません。プロセスはひとつ、 TLS なし、流量の制限もありません。

Toutadmin ドキュメント —— 2026-09-13 に生成。 ソフトウェアから独立した単体のサイトです。