Node 版を導入する
Node 版は常駐するサービスとして動きます。プロセスが一つ、ディスク上に SQLite のデータベースが 一つ、あとは入れるものがありません。外部のデータベースも、キャッシュも、待ち行列も要りません。 この手引きは空のディレクトリから本番の導入までを通して扱います。多くの手引きが省く部分 ——システムのサービス、リバースプロキシ、TLS、バックアップ、更新——も含めて。
必要なもの#
| 要素 | 版 | なぜ |
|---|---|---|
| Node.js | 22 以上 | 製品は内蔵のテスト実行機能と、新しめの API に依っています。 |
| C コンパイラ | build-essential | お使いの環境向けの出来合いのバイナリがない限り、better-sqlite3 は導入時に組み立てられます。 |
| ローカルのディスク | — | SQLite と受け取ったファイルのため。ネットワーク共有は決して使わないこと。下記参照。 |
一コア・512 MB の機械で数十人分は足ります。効いてくるのは性能ではなくディスクです。 ローカルであること、そして控えが取られていること。
1. 取得して導入する#
git clone <あなたのリポジトリ> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev
npm install ではなく npm ci を。ロックファイルの記述どおりに入り、
それを書き換えることもありません。サーバーで版がずれると、誰にも理由の分からない障害になります。
C を組み立てる依存は better-sqlite3 だけです。
sudo apt install -y build-essential python3 でほぼすべての場合が片づきます。
2. 設定する#
cp .env.example .env
設定はすべて環境変数で行います。.env から読むか、サービス管理の仕組みが与えます。
必須のものは一つもありません。.env がなければ、導入は 3000 番で立ち上がり、
あなたを導入画面へ送ります。
| 変数 | 既定 | 何をするか |
|---|---|---|
PORT | 3000 | 待ち受ける番号。 |
NODE_ENV | — | 本番では production。クッキーは厳格になり、詳しい痕跡は出ません。 |
SESSION_SECRET | 自動生成 | 接続を封じます。空のままなら data/session.key に作られます。 |
INSTALL_TOKEN | — | 導入画面が導入前に求めます。外に出ているサーバーでは推奨。 |
TRUST_PROXY | — | 信頼できる前段がいるときだけ 1。それ以外では絶対に付けない。 |
DB_PATH | data/app.sqlite | データベース。 |
UPLOAD_DIR、CV_DIR、VAULT_DIR、SIGN_DIR、DOCS_DIR、BACKUP_DIR | data/ の下 | ファイルの置き場:顔写真、履歴書、金庫、署名フォルダー、受け取った書類、書庫。 |
LOGIN_RATE_LIMIT | 10 | 一つの発信元から十五分あたりのログイン試行。 |
GLOBAL_RATE_LIMIT | 300 | 一つの発信元から一分あたりの要求。 |
API_RATE_LIMIT | — | 一つの鍵から一分あたりの API 呼び出し。 |
SESSION_IDLE_MINUTES | 60 | これだけ放っておくと接続が切れます。 |
SESSION_MAX_HOURS | 12 | どんな操作でも延びない、上限の時間。 |
ADMIN_EMAIL、ADMIN_PASSWORD | — | 画面を使わない導入。起動時に管理者を作ります。 |
SESSION_SECRET を変えると、全員が一斉に締め出されます。一度だけ決め、無作為に
作り
(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")、
ほかと一緒に控えを取ってください。git のリポジトリに残しては決していけません。
導入用の合鍵を置く#
最初の起動からあなたが導入画面に入るまでのあいだ、その導入は見つけた者のもの
です。先に来た者が管理者アカウントを作れてしまいます。ですから番号を開ける前に
INSTALL_TOKEN を置いてください。
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# そのうえで .env に:
INSTALL_TOKEN=c3f1…
導入画面はこれを一定の時間で照合します。ふつうの比較では、合鍵が一文字ずつ 当てられてしまうからです。拒んだ回数はすべて記録に残ります。導入が済めば、画面は自分で閉じます。
画面を使わずに導入する#
自動の配備では、ADMIN_EMAIL と ADMIN_PASSWORD が導入画面を通さずに
起動時の管理者を作ります。済んだら外してください。サービスの環境にあるパスワードは、その
サービスを読める者みなに読めます。
3. 権限と、データの置き場所#
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
コードは読み取り専用のままで構いません。書き込めなければならないのは data/ だけ
です。SQLite は隣に別のファイルも書きます(-wal、-shm)。効いてくるのは
フォルダーであって、データベースだけではありません。
NFS も SMB も、ファイルの施錠について嘘をつきます。SQLite は二重書き込みを防ぐのにその施錠を 頼みにしています。共有の上では、データベースは何も告げずに壊れていきます。必ずローカルの ディスクへ。外へ出ていくのは控えであって、データベースそのものではありません。
4. サービスにする#
手で起動すると、端末を閉じた時点で止まります。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
# このサービスが書き込む必要があるのは 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
堅くするための五行は飾りではありません。ProtectSystem=strict がファイル体系全体を
書き込めなくし、ReadWritePaths が書き込めるべき唯一のフォルダーだけを開け直します。
任意の書き込みを許す穴があっても、届く先は data/ だけになります。
5. 前段のサーバー#
3000 番をそのままインターネットに出してはいけません。TLS を話しませんし、覚える理由もありません。
server {
listen 443 ssl http2;
server_name intranet.rei.jp;
ssl_certificate /etc/letsencrypt/live/intranet.rei.jp/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/intranet.rei.jp/privkey.pem;
client_max_body_size 20M; # 金庫への預け入れ、受け取った書類
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.rei.jp;
return 301 https://$host$request_uri;
}
あわせて、サービスは折り返しのアドレスだけで待ち受けさせてください。3000 番に届くのが前段だけに なります。
TLS#
sudo certbot --nginx -d intranet.rei.jp
通信が暗号化された時点で、製品は Strict-Transport-Security を自ら付けます。
暗号化されていない経路では決して付けません。暗号化されていないページから告げても読まれませんし、
手元での試用を半年も https に閉じ込めてしまうからです。
TRUST_PROXY は前段とひと組。それ以外では付けない
この変数は、ヘッダーで告げられた発信元をサーバーに信じさせます。nginx の後ろではそれが要ります。
そうしないと、どの要求も 127.0.0.1 から来たように見え、発信元ごとの上限が何も
守らなくなります。前段がいないときは逆です。誰でも好きな発信元を名乗り、上限をすり抜けます。
6. 導入画面#
ご自分のドメインを開きます。アカウントが一つもないあいだは、どのアドレスも
/installation へ送られます。五段階です。
- この導入の言語。16 言語のなかから。
- 前提条件。確かめたうえで画面に出ます。
- 会社。どこにでも出てくる名前です。
- フリーランス以外の新しい従業員に与える年次有給の日数。
- 管理者アカウント。アドレスと、十二文字以上のパスワード。
アカウントが一つできた時点で、/installation はログイン画面へ送るようになります。
導入画面は自分で閉じたので、手で消すファイルはありません。
7. 定期的な見回り#
PHP 版と違って、仕掛けるものは何もありません。サーバーが自前の時計を持ち、一時間ごとに目を覚まして 八つのことをします。
| 見回りがすること | 担保 |
|---|---|
| 契約が満了したアカウントを閉じる | ログイン時と一覧を開いたときにも確かめます |
| 期日の来た継続契約の請求を起こす | 一意の索引が二重の請求を防ぎます |
| 期日をお知らせに変える | 重複を除く鍵があり、一つの期日は一度しか知らせません |
| 読み終えたお知らせと監査記録を整理する | 選んだ保存期間に従って |
| 経理用の受信箱を取りに行く(IMAP) | 取得が設定されていれば |
| Webhook の待ち行列を、再送も含めて片づける | 五回試し、そこで打ち切り |
| 自動のバックアップを作り、外へ送る | 決めた間隔が過ぎたとき |
どの処理も何度やっても同じ結果になります。見回りをやり直しても、二重に請求せず、 二重に知らせません。だからこそ、いつサービスを立て直しても構わないのです。
8. ひととおり動くか確かめる#
# テスト(あなたのデータベースには書きません)
npm test
# 歩き回るための見本の導入
node scripts/seed-demo.js
そのうえで画面から、メンバーを一人作り、その人でログインし、添付を一つ置き、バックアップを手で 取ってバックアップの画面で確かめてください。この四つで、データベース、ファイル、権限、 書庫のすべてに触れたことになります。
9. 更新する#
# 1. まずバックアップ。必ず ——「バックアップ」の画面から
# 2. コード
cd /var/www/toutadmin
git pull
npm ci --omit=dev
# 3. 立て直す。データベースは起動時に自分で移行します
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager
構造は、何度やっても同じ結果になる移行で進みます。更新をやり直しても壊れません。
data/ と .env には一切触れません。
前の版に戻す#
コードを前の版に戻して立て直してください。移行が列を削ることはないので、移行済みのデータベースも 前の版で読めます。変更履歴に断りがある場合を除きます。迷ったら、 手順 1 で取った書庫から戻してください。
10. きちんと控えを取る#
tar.gz の書庫には、データベースと五つのファイル置き場が入ります。データベースは
SQLite の稼働中バックアップで写すので、書き込みの最中でも筋の通った写しになります。PHP 版は
VACUUM INTO で同じ結果を得ます。どのファイルも SHA-256 の指紋を持ち、戻すときに
照合されます。
守る相手と同じサーバーに残る書庫は、何も守りません。バックアップの画面から外部の
預け先(FTPS か Google Drive)を設定し、sauvegarde.echec に Webhook をつないで、
外への送りが失敗したときに知らせを受けてください。詳しくは
バックアップと復元のページに。
まず手元で試す#
npm install
cp .env.example .env
npm run dev # 変更のたびに立て直します
http://localhost:3000 を開いてください。手元では NODE_ENV を空のままに。
production ではクッキーが「安全な経路のみ」と印されるので、暗号化されていない接続では
保たれず、ログイン画面で堂々巡りになります。
PHP 版へ移る、あるいは PHP 版から来る#
二つの版は同じ構造——142 の表、1311 の列——と、同じパスワードの形式を
共有します。片方を止め、app.sqlite とファイルの置き場を写し、もう片方を起動する。
変換はありません。二つの版をご覧ください。
Toutadmin ドキュメント —— 2026-09-13 に生成。 ソフトウェアから独立した単体のサイトです。