T Toutadmin문서

Node 판 설치하기

Node 판은 상시 도는 서비스로 동작합니다. 프로세스 하나, 디스크에 놓인 SQLite 데이터베이스 하나, 그 밖에 설치할 것은 없습니다 — 외부 데이터베이스도, 캐시도, 큐도 필요 없습니다. 이 안내서는 빈 디렉터리에서 운영 중인 설치본까지를 다루며, 대부분의 안내서가 건너뛰는 부분도 담았습니다. 시스템 서비스, 역방향 프록시, TLS, 백업, 갱신.

무엇이 필요한가#

항목
Node.js22 이상제품은 내장 테스트 실행기와 비교적 새로운 인터페이스에 기댑니다.
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 포트로 뜨고 당신을 설치 마법사로 보냅니다.

변수기본값무엇을 하는가
PORT3000귀 기울이는 포트.
NODE_ENV운영에서는 production. 쿠키가 엄격해지고 자세한 흔적은 나오지 않습니다.
SESSION_SECRET자동 생성세션을 봉인합니다. 비워 두면 data/session.key에 만들어집니다.
INSTALL_TOKEN설치 마법사가 설치 전에 요구합니다. 외부에 노출된 서버라면 권합니다.
TRUST_PROXY믿을 만한 프록시 뒤에서만 1. 그 밖에는 절대 켜지 마십시오.
DB_PATHdata/app.sqlite데이터베이스.
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIRdata/ 아래파일 폴더: 사진, 이력서, 금고, 결재함, 받은 문서, 보관 파일.
LOGIN_RATE_LIMIT10주소마다 십오 분당 로그인 시도.
GLOBAL_RATE_LIMIT300주소마다 분당 요청.
API_RATE_LIMIT토큰마다 분당 API 호출.
SESSION_IDLE_MINUTES60이만큼 놀려 두면 세션이 끊깁니다.
SESSION_MAX_HOURS12어떤 활동으로도 늘어나지 않는 절대 시간.
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_EMAILADMIN_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.yeje.kr;

    ssl_certificate     /etc/letsencrypt/live/intranet.yeje.kr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.yeje.kr/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.yeje.kr;
    return 301 https://$host$request_uri;
}

아울러 서비스가 되돌이 주소에서만 귀 기울이게 하십시오. 그러면 3000 포트에 닿는 것은 프록시뿐이 됩니다.

TLS#

sudo certbot --nginx -d intranet.yeje.kr

제품은 연결이 암호화되는 즉시 Strict-Transport-Security 헤더를 붙이고, 평문에서는 결코 붙이지 않습니다. 암호화되지 않은 쪽에서 알려 봐야 읽히지 않을뿐더러, 로컬 시험을 여섯 달 동안 https 안에 가둬 버리기 때문입니다.

TRUST_PROXY는 프록시와 한 쌍입니다 — 그 밖에는 쓰지 마십시오

이 변수는 헤더에 실린 출발지 주소를 서버가 곧이듣게 만듭니다. nginx 뒤에서는 그래야 합니다 — 그러지 않으면 모든 요청이 127.0.0.1에서 온 것처럼 보이고, 주소별 한도가 아무것도 지키지 못합니다. 앞에 프록시가 없으면 반대입니다. 누구나 원하는 주소를 내세워 한도를 빠져나갑니다.

6. 설치 마법사#

당신의 도메인을 여십시오. 계정이 하나도 없는 동안에는 모든 주소가 /installation으로 넘어갑니다. 다섯 단계입니다.

  1. 이 설치본의 언어. 16개 가운데에서.
  2. 준비물. 확인해서 화면에 보여 줍니다.
  3. 회사: 여기저기 나타날 이름.
  4. 프리랜서가 아닌 새 직원에게 주는 연차 일수.
  5. 관리자 계정: 주소와 열두 자 이상의 비밀번호.

계정이 하나 생기는 순간 /installation은 로그인 화면으로 넘깁니다. 마법사가 스스로 닫힌 것이라, 손으로 지울 파일은 없습니다.

7. 주기적인 훑기#

PHP 판과 달리 따로 걸어 둘 것이 없습니다. 서버가 자체 스케줄러를 지니고, 한 시간마다 깨어나 여덟 가지 일을 합니다.

훑기가 하는 일보장
계약이 끝난 계정을 닫습니다로그인할 때와 대시보드를 열 때도 확인합니다
기한이 된 구독의 청구서를 냅니다고유 색인이 두 번 청구되는 것을 막습니다
기한을 알림으로 바꿉니다중복을 거르는 키가 있어 한 기한은 한 번만 알립니다
읽은 알림과 감사 기록을 정리합니다정한 보관 기간에 따라
경리 우편함을 가져옵니다(IMAP)가져오기가 설정되어 있으면
웹훅 대기열을 재시도까지 포함해 비웁니다다섯 번 시도한 뒤 포기
자동 백업을 만들어 바깥으로 보냅니다정한 주기가 지났을 때

모든 작업이 몇 번을 해도 같은 결과입니다. 훑기를 다시 돌려도 두 번 청구하지 않고 두 번 알리지 않습니다. 그래서 아무 때나 마음 놓고 서비스를 다시 띄울 수 있습니다.

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에 웹훅을 걸어 두십시오. 자세한 내용은 백업과 복원 문서에 있습니다.

먼저 로컬에서 해 보기#

npm install
cp .env.example .env
npm run dev        # 고칠 때마다 다시 뜹니다

http://localhost:3000을 여십시오. 로컬에서는 NODE_ENV를 비워 두십시오. production에서는 쿠키가 '보안' 표시를 달아 평문 연결에서는 보관되지 않고, 로그인 화면에서 제자리를 맴돌게 됩니다.

PHP 판으로 옮기거나, 거기서 오거나#

두 판본은 같은 구조 — 테이블 142개, 열 1311개 — 와 같은 비밀번호 형식을 씁니다. 한쪽을 멈추고 app.sqlite와 파일 폴더를 복사한 뒤 다른 쪽을 켜면 됩니다. 변환은 없습니다. 두 판본을 보십시오.

Toutadmin 문서 —— 2026-09-13에 생성. 소프트웨어와 분리된 독립 사이트입니다.