From ce15f30c0bd09a33509149133527acee601444b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E4=BD=B3=E6=81=A9?=
Date: Tue, 20 Oct 2026 14:26:13 +0800
Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E9=87=8D=E8=A8=AD=E7=AE=A1?=
=?UTF-8?q?=E7=90=86=E5=93=A1=E5=AF=86=E7=A2=BC=E6=8C=87=E4=BB=A4;?=
=?UTF-8?q?=E5=AE=89=E8=A3=9D=E8=AA=AA=E6=98=8E=E8=A3=9C=E4=B8=8A=20.env?=
=?UTF-8?q?=20=E5=AF=86=E7=A2=BC=E6=B3=A8=E6=84=8F=E4=BA=8B=E9=A0=85;?=
=?UTF-8?q?=E7=99=BC=E5=B8=83=20v1.3.2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
使用者來信:照手冊手動安裝,先啟動過一次才改 .env 的密碼,管理員帳號早已依
舊密碼建立,怎麼登都是「帳號或密碼錯誤」並被鎖 15 分鐘。以前只能刪資料重裝
或還原備份。
- docker compose exec api python -m app.scripts.reset_admin:產生英數臨時密碼、
解除鎖定、重新啟用,登入後須立即改密碼;只限系統管理員帳號;記入操作軌跡
- 安裝指南、FAQ、手冊、.env.example、安裝程式結尾提示補上說明
- v1.3.1 發布日期更正為 2026-10-20
Co-Authored-By: Claude Opus 5.5
---
.env.example | 5 +-
CHANGELOG.md | 28 +++++++-
README.md | 6 +-
backend/app/scripts/reset_admin.py | 109 +++++++++++++++++++++++++++++
backend/tests/test_reset_admin.py | 93 ++++++++++++++++++++++++
docs/deploy/faq.html | 16 ++++-
docs/deploy/faq.md | 24 ++++++-
docs/deploy/install.html | 6 ++
docs/deploy/install.md | 12 ++++
docs/deploy/upgrade.html | 4 +-
docs/deploy/upgrade.md | 4 +-
docs/features.html | 13 ++--
docs/index.html | 4 +-
docs/roadmap.html | 9 ++-
docs/roadmap.md | 5 +-
install.ps1 | 1 +
install.sh | 1 +
17 files changed, 316 insertions(+), 24 deletions(-)
create mode 100644 backend/app/scripts/reset_admin.py
create mode 100644 backend/tests/test_reset_admin.py
diff --git a/.env.example b/.env.example
index 0ed6363..f6c5fac 100644
--- a/.env.example
+++ b/.env.example
@@ -3,6 +3,9 @@
# ── 必改 ─────────────────────────────
# 系統管理員預設帳密(首次登入後會強制改密碼)
+# ⚠️ 只在系統「第一次啟動」時用來建立帳號,之後再改這裡不會生效。
+# 密碼請只用英文與數字:$ 會被 docker compose 當成變數截掉,# 前有空白會被當成註解。
+# 登不進去時:docker compose exec api python -m app.scripts.reset_admin
ADMIN_USERNAME=admin
ADMIN_PASSWORD=changeme
# 學校名稱(顯示於介面與匯出課表)。這只是「初始值」——系統啟用後可於
@@ -29,7 +32,7 @@ HTTP_PORT=80
# ── 預建映像版本(僅「拉取映像」部署方式需要)──────────
# 用 docker compose pull 拉官方映像時,決定要拉哪一版。
# 預設 latest(最新);正式部署建議釘選版本號以利可控升級,例如:
-# IMAGE_TAG=v1.3.1
+# IMAGE_TAG=v1.3.2
# 若你是從原始碼 docker compose up -d 自行建置,此項可忽略。
IMAGE_TAG=latest
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8186fc..0be657f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,11 +4,35 @@
破壞性變更(需人工介入才能升級)以 ⚠️ 標註。
-> **新使用者請直接安裝 v1.3.1**(見 [README](README.md) 快速開始)。v1.0.0 ~ v1.3.0 是先前版本;以下條目保留作為變更紀錄。
+> **新使用者請直接安裝 v1.3.2**(見 [README](README.md) 快速開始)。v1.0.0 ~ v1.3.1 是先前版本;以下條目保留作為變更紀錄。
## [Unreleased]
-## [1.3.1] — 2026-10-19
+## [1.3.2] — 2026-10-20
+
+**系統管理員忘了密碼、或剛裝好就登不進去,現在有指令可以重設。** 來自使用者來信:照手冊手動安裝,先啟動過一次才改 `.env` 的密碼,結果怎麼登都是「帳號或密碼錯誤」,接著被鎖 15 分鐘。管理員帳號只在系統第一次啟動時依 `.env` 建立,之後改 `.env` 不會生效;以前只能刪資料重裝或還原備份。
+
+**本版沒有資料表結構變更**,升級只需換映像;若要退回 v1.3.1,把 `IMAGE_TAG` 改回即可。
+
+官方映像(amd64 + arm64 雙架構)已發布於 GHCR:
+`ghcr.io/begin0808/course_scheduling_system-{api,worker,web}:v1.3.2`
+
+### 新增
+- **重設管理員密碼指令**:在系統資料夾執行 `docker compose exec api python -m app.scripts.reset_admin`。
+ - 產生一組只含英文與數字的**臨時密碼**印在畫面上(不必把密碼打在指令裡),同時**解除登入鎖定、重新啟用帳號**;用它登入後必須立即改密碼,原本各裝置的登入都會失效。資料完全不受影響。
+ - 預設重設 `.env` 的 `ADMIN_USERNAME`(通常是 `admin`);其他管理員帳號加 `--username`。帳號打錯時列出現有的系統管理員帳號。
+ - 只能重設**系統管理員**帳號;教師、組長的密碼由系統管理員到「帳號管理」重設。
+ - 重設會記入操作軌跡(操作者顯示為「主機指令」,不記密碼)。
+
+### 文件
+- 安裝指南第 2 步:`.env` 的管理員密碼**只在第一次啟動時有效**,以及手動編輯時 `$`、`#` 會被 docker compose 截掉;第 5 步新增「登不進去怎麼辦」。
+- 常見問題:新增「第一次登入就一直帳號或密碼錯誤、被鎖 15 分鐘」(與網路、IP、所在國家無關);「忘記管理員密碼」改用新指令。
+- 手冊 FAQ「忘記密碼了?」、`.env.example` 的管理員密碼說明一併更新;安裝程式結束時提示這個指令。
+
+### 修正
+- 變更紀錄等文件中 v1.3.1 的發布日期更正為 2026-10-20。
+
+## [1.3.1] — 2026-10-20
**帳號管理:教學組長、教務主任的帳號終於有地方設定。** 來自使用者詢問:「行政職稱」填了教學組長,登入後仍是教師,找不到設定角色的地方。
diff --git a/README.md b/README.md
index 5d1ed04..4894c5d 100644
--- a/README.md
+++ b/README.md
@@ -132,11 +132,11 @@ docker compose up -d # 首次會建置映像,需數分鐘
## 專案狀態
-**v1.3.1 已發行(2026-10-19)。** 六大里程碑 M0–M5 全部完成,功能齊備並經完整驗收(後端 663 項單元/整合測試、51 項 Playwright 端對端測試涵蓋全部 20 個頁面,每次提交皆對真實 Docker 全棧跑過)。官方映像(amd64 + arm64)已發布於 GHCR。
+**v1.3.2 已發行(2026-10-20)。** 六大里程碑 M0–M5 全部完成,功能齊備並經完整驗收(後端 670 項單元/整合測試、51 項 Playwright 端對端測試涵蓋全部 20 個頁面,每次提交皆對真實 Docker 全棧跑過)。官方映像(amd64 + arm64)已發布於 GHCR。
-v1.3.1 新增**帳號管理**(系統管理員可設定教學組長、教務主任的帳號);v1.3.0 支援**單雙週課程**(同一節單週、雙週輪流上不同的課,排課、請假代課、調課、巡堂表、鐘點都分單雙週;⚠️ 首次改動資料表結構,升級前請先備份);v1.2.19 支援**日薪代課**(國小常見:月結統計把鐘點與日薪分開列)並可**整批指派代課**(整張假單或整天一次派給同一位老師);v1.2.18 可**檢查課表**(發布前列出衝堂、教師不可排時段等衝突;事後修改不可排時段時當場提示已排的課);v1.2.17 修正登入按一次送出兩筆請求(輸錯密碼約第 3 次就被鎖定);v1.2.15、v1.2.16 修正跑班群組的兩個問題(同一位老師在群組裡重複配課時自動排課會排出撞課的課表;同一格有多筆同科目配課時,手動排課把每日節數重複計算);v1.2.12~v1.2.14 可列印**巡堂表**並匯出 Excel(依當天的調代課結果產生,社團另出分組巡堂單);v1.2.10、v1.2.11 讓**國中小、完全中學**跨部上課的老師,課表與通知單都標出實際上課時間,兩部節數不同(國中七節、高中八節)也不會漏課;v1.2.9 可一次匯出**全校教師課表**(總表與批次打包);v1.2.8 可用 Excel **批次更新既有教師**(新學年職務異動),並能**停用/啟用教師登入帳號**(離職教師保留歷史紀錄又進不來);v1.2.7 補上**學期起訖日期**的填寫與修改(先前畫面上無處可填,導致請假被擋);v1.2.6 修掉第一批外部使用者回報的三個問題(教師帳號首次登入卡死、無法事後補開教師帳號、未排課務清單過長時拖不動),並可在畫面上自行修改與重設密碼;v1.2.5 可列印**教師代課通知單與班級代課通知單**(照學校紙本格式,代課時可選計費方式);v1.2.4 的調代課處理會寫出處置方式(代課或調課、哪天補課);v1.2.3 起系統會顯示目前版本,並在有新版時提醒系統管理員(只提醒、不自動升級);v1.2.2 可列印**教師調課通知單與班級調課通知單**(照學校紙本格式);v1.2.1 補上**調課**的操作畫面——請假(含研習)的節次可直接列出雙方都有空的對調節次,支援跨週(最多四週)與連堂,補課日也會出現在今日看板。v1.2.0 起另有**一鍵示範資料**,裝好後兩分鐘內就能看到一張排好的全校課表。
+v1.3.2 新增**重設管理員密碼**的指令(忘了密碼或第一次就登不進去時,不必刪資料或還原備份);v1.3.1 新增**帳號管理**(系統管理員可設定教學組長、教務主任的帳號);v1.3.0 支援**單雙週課程**(同一節單週、雙週輪流上不同的課,排課、請假代課、調課、巡堂表、鐘點都分單雙週;⚠️ 首次改動資料表結構,升級前請先備份);v1.2.19 支援**日薪代課**(國小常見:月結統計把鐘點與日薪分開列)並可**整批指派代課**(整張假單或整天一次派給同一位老師);v1.2.18 可**檢查課表**(發布前列出衝堂、教師不可排時段等衝突;事後修改不可排時段時當場提示已排的課);v1.2.17 修正登入按一次送出兩筆請求(輸錯密碼約第 3 次就被鎖定);v1.2.15、v1.2.16 修正跑班群組的兩個問題(同一位老師在群組裡重複配課時自動排課會排出撞課的課表;同一格有多筆同科目配課時,手動排課把每日節數重複計算);v1.2.12~v1.2.14 可列印**巡堂表**並匯出 Excel(依當天的調代課結果產生,社團另出分組巡堂單);v1.2.10、v1.2.11 讓**國中小、完全中學**跨部上課的老師,課表與通知單都標出實際上課時間,兩部節數不同(國中七節、高中八節)也不會漏課;v1.2.9 可一次匯出**全校教師課表**(總表與批次打包);v1.2.8 可用 Excel **批次更新既有教師**(新學年職務異動),並能**停用/啟用教師登入帳號**(離職教師保留歷史紀錄又進不來);v1.2.7 補上**學期起訖日期**的填寫與修改(先前畫面上無處可填,導致請假被擋);v1.2.6 修掉第一批外部使用者回報的三個問題(教師帳號首次登入卡死、無法事後補開教師帳號、未排課務清單過長時拖不動),並可在畫面上自行修改與重設密碼;v1.2.5 可列印**教師代課通知單與班級代課通知單**(照學校紙本格式,代課時可選計費方式);v1.2.4 的調代課處理會寫出處置方式(代課或調課、哪天補課);v1.2.3 起系統會顯示目前版本,並在有新版時提醒系統管理員(只提醒、不自動升級);v1.2.2 可列印**教師調課通知單與班級調課通知單**(照學校紙本格式);v1.2.1 補上**調課**的操作畫面——請假(含研習)的節次可直接列出雙方都有空的對調節次,支援跨週(最多四週)與連堂,補課日也會出現在今日看板。v1.2.0 起另有**一鍵示範資料**,裝好後兩分鐘內就能看到一張排好的全校課表。
-**請直接從最新版開始安裝**(見上方快速開始);`v1.3.1` 是目前建議使用的版本。各版變更見 [CHANGELOG](CHANGELOG.md),開發歷程見 [docs/tasks.md](docs/tasks.md)。
+**請直接從最新版開始安裝**(見上方快速開始);`v1.3.2` 是目前建議使用的版本。各版變更見 [CHANGELOG](CHANGELOG.md),開發歷程見 [docs/tasks.md](docs/tasks.md)。
系統仍在實際校園環境試用中,若你是第一批使用者,歡迎透過 [Issues](https://github.com/begin0808/Course_Scheduling_System/issues) 回報任何問題。
diff --git a/backend/app/scripts/reset_admin.py b/backend/app/scripts/reset_admin.py
new file mode 100644
index 0000000..c550cc9
--- /dev/null
+++ b/backend/app/scripts/reset_admin.py
@@ -0,0 +1,109 @@
+"""重設系統管理員密碼(v1.3.2):忘了密碼、或第一次就登不進去時,不必刪資料、也不必還原備份。
+
+起因(使用者來信):照手冊手動安裝,先啟動過一次才改 `.env` 的密碼。管理員帳號只在系統
+**第一次啟動**時依 `.env` 建立,之後改 `.env` 不會生效,結果怎麼登都是「帳號或密碼錯誤」,
+接著被鎖 15 分鐘。
+
+做法:產生一組只含英文與數字的臨時密碼(避開 `.env` 裡 `$`、`#` 的問題,也不必把密碼打在
+指令上而留在終端機紀錄),同時解除登入鎖定、重新啟用帳號。對方登入後必須立即改密碼;
+密碼一換,原本各裝置的登入都會失效。只能重設**系統管理員**帳號,其他帳號由系統管理員登入後
+到「帳號管理」重設。
+
+用法(在系統資料夾執行):
+ docker compose exec api python -m app.scripts.reset_admin
+ docker compose exec api python -m app.scripts.reset_admin --username 其他管理員帳號
+"""
+
+import argparse
+import secrets
+import sys
+from typing import TextIO
+
+from sqlalchemy import select
+from sqlalchemy.orm import Session
+
+# 不易看錯的字元:去掉 0/O、1/l/I
+_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"
+_LENGTH = 12
+
+
+class ResetError(Exception):
+ """找不到帳號或不是系統管理員;訊息直接給使用者看。"""
+
+
+def temp_password() -> str:
+ return "".join(secrets.choice(_ALPHABET) for _ in range(_LENGTH))
+
+
+def reset_admin_password(db: Session, username: str, password: str | None = None) -> str:
+ """把系統管理員帳號的密碼重設為臨時密碼並解除鎖定,回傳臨時密碼。"""
+ from app.core.security import hash_password
+ from app.models.audit import AuditLog
+ from app.models.user import Role, User
+
+ user = db.scalar(select(User).where(User.username == username))
+ if user is None:
+ admins = sorted(
+ u.username for u in db.scalars(select(User)) if Role.admin.value in u.role_names
+ )
+ hint = f"系統管理員帳號有:{'、'.join(admins)},請用 --username 指定。" if admins else ""
+ raise ResetError(f"找不到帳號「{username}」。{hint}")
+ if Role.admin.value not in user.role_names:
+ raise ResetError(
+ f"「{username}」不是系統管理員帳號。其他帳號請由系統管理員登入後到「帳號管理」重設密碼。"
+ )
+ temp = password or temp_password()
+ user.password_hash = hash_password(temp)
+ user.must_change_password = True
+ user.failed_login_attempts = 0
+ user.locked_until = None
+ user.is_active = True
+ db.add(AuditLog(
+ user_id=None, username="主機指令", action="reset_admin_password",
+ target_type="user", target_id=user.id, detail=f"重設「{user.username}」的密碼",
+ ))
+ db.commit()
+ return temp
+
+
+def run(db: Session, argv: list[str], out: TextIO, err: TextIO) -> int:
+ from app.core.config import settings
+
+ parser = argparse.ArgumentParser(
+ prog="python -m app.scripts.reset_admin", description="重設系統管理員密碼"
+ )
+ parser.add_argument(
+ "--username", default=settings.admin_username,
+ help=f"要重設的系統管理員帳號(預設 {settings.admin_username})",
+ )
+ args = parser.parse_args(argv)
+ try:
+ temp = reset_admin_password(db, args.username)
+ except ResetError as exc:
+ print(str(exc), file=err)
+ return 1
+ print(f"已重設「{args.username}」的密碼,並解除登入鎖定。", file=out)
+ print(f"臨時密碼:{temp}", file=out)
+ print(
+ f"請用帳號 {args.username} 和這組臨時密碼登入,系統會要求立即改成新密碼;"
+ "原本已登入的裝置都要重新登入。",
+ file=out,
+ )
+ print(".env 裡的 ADMIN_PASSWORD 只在系統第一次啟動時使用,不必修改。", file=out)
+ return 0
+
+
+def main() -> None:
+ import app.models # noqa: F401 - 載入全部 model,關聯才完整
+ from app.core.db import SessionLocal
+
+ db = SessionLocal()
+ try:
+ code = run(db, sys.argv[1:], sys.stdout, sys.stderr)
+ finally:
+ db.close()
+ sys.exit(code)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/backend/tests/test_reset_admin.py b/backend/tests/test_reset_admin.py
new file mode 100644
index 0000000..d10d571
--- /dev/null
+++ b/backend/tests/test_reset_admin.py
@@ -0,0 +1,93 @@
+"""重設系統管理員密碼的指令(v1.3.2)。
+
+起因(使用者來信):先啟動過一次才改 `.env` 的密碼,管理員帳號早已依舊密碼建立,
+怎麼登都進不去、接著被鎖 15 分鐘。以前只能刪資料重裝或還原備份。
+"""
+
+import io
+from datetime import UTC, datetime, timedelta
+
+import pytest
+
+from app.core.security import verify_password
+from app.models.audit import AuditLog
+from app.models.user import Role
+from app.scripts.reset_admin import ResetError, reset_admin_password, run, temp_password
+from tests.conftest import make_user
+
+PW = "password123"
+
+
+def test_locked_out_admin_can_log_in_with_the_temp_password_and_must_change_it(env):
+ client, db = env
+ admin = make_user(db, "admin", PW, roles=[Role.admin])
+ for _ in range(5):
+ client.post("/api/auth/login", json={"username": "admin", "password": "wrong-pass"})
+ r = client.post("/api/auth/login", json={"username": "admin", "password": PW})
+ assert r.status_code == 423 # 已被鎖
+
+ temp = reset_admin_password(db, "admin")
+
+ db.refresh(admin)
+ assert admin.locked_until is None and admin.failed_login_attempts == 0
+ r = client.post("/api/auth/login", json={"username": "admin", "password": temp})
+ assert r.status_code == 200
+ assert r.json()["must_change_password"] is True
+ old = client.post("/api/auth/login", json={"username": "admin", "password": PW})
+ assert old.status_code == 401
+
+
+def test_reset_ends_existing_sessions(env):
+ client, db = env
+ make_user(db, "admin", PW, roles=[Role.admin])
+ client.post("/api/auth/login", json={"username": "admin", "password": PW})
+ assert client.get("/api/auth/me").status_code == 200
+ reset_admin_password(db, "admin")
+ assert client.get("/api/auth/me").status_code == 401
+
+
+def test_deactivated_admin_is_enabled_again(db):
+ admin = make_user(db, "admin", PW, roles=[Role.admin])
+ admin.is_active = False
+ admin.locked_until = datetime.now(UTC) + timedelta(minutes=10)
+ db.commit()
+ temp = reset_admin_password(db, "admin")
+ assert admin.is_active is True
+ assert verify_password(temp, admin.password_hash)
+
+
+def test_unknown_username_lists_the_admin_accounts(db):
+ make_user(db, "root", PW, roles=[Role.admin])
+ make_user(db, "boss", PW, roles=[Role.admin, Role.director])
+ make_user(db, "s", PW, roles=[Role.scheduler])
+ with pytest.raises(ResetError, match="找不到帳號「admin」。系統管理員帳號有:boss、root"):
+ reset_admin_password(db, "admin")
+
+
+def test_only_admin_accounts_can_be_reset(db):
+ s = make_user(db, "s", PW, roles=[Role.scheduler])
+ with pytest.raises(ResetError, match="不是系統管理員帳號"):
+ reset_admin_password(db, "s")
+ assert verify_password(PW, s.password_hash)
+
+
+def test_temp_password_is_plain_letters_and_digits():
+ for _ in range(50):
+ p = temp_password()
+ assert len(p) == 12 and p.isalnum() and p.isascii()
+ assert not set(p) & set("0O1lI")
+
+
+def test_command_prints_the_temp_password_and_records_it(db):
+ make_user(db, "admin", PW, roles=[Role.admin])
+ out, err = io.StringIO(), io.StringIO()
+ assert run(db, [], out, err) == 0
+ temp = out.getvalue().split("臨時密碼:")[1].split()[0]
+ assert len(temp) == 12
+ log = db.query(AuditLog).one()
+ assert (log.action, log.username) == ("reset_admin_password", "主機指令")
+ assert temp not in log.detail
+
+ out, err = io.StringIO(), io.StringIO()
+ assert run(db, ["--username", "nobody"], out, err) == 1
+ assert "找不到帳號「nobody」" in err.getvalue() and out.getvalue() == ""
diff --git a/docs/deploy/faq.html b/docs/deploy/faq.html
index 21d28c6..9dcdab9 100644
--- a/docs/deploy/faq.html
+++ b/docs/deploy/faq.html
@@ -195,12 +195,26 @@ 安裝與啟動
常見:.env 的 DATABASE_URL 與 POSTGRES_* 帳密不一致;SECRET_KEY 未設。
Q:80 埠被占用,啟動失敗(port is already allocated)。
改 .env 的 HTTP_PORT(例如 8080)再 docker compose up -d,改用 http://<主機IP>:8080。
+ Q:裝好後第一次登入就一直「帳號或密碼錯誤」,接著被鎖 15 分鐘。
+ 會鎖定代表管理員帳號已經建好,只是資料庫裡的密碼和你輸入的不同;跟網路、IP、手機熱點或在國外都無關(用 localhost 登入時,連線根本不出這台電腦)。常見原因:
+
+ - 先啟動過一次才改
.env 的密碼。 管理員帳號只在第一次啟動時依 .env 建立,之後改 .env 不會生效。這時的密碼是當時 .env 的值,通常是預設的 changeme。
+ - 密碼裡有
$ 或 #。 docker compose 會把 $ 當成變數截掉,# 前有空白會被當成註解。
+
+ 另外,鎖定是鎖帳號,不是鎖網路:被鎖的 15 分鐘內,換網路、換 IP、甚至密碼打對都進不去。
+ 解法:在系統資料夾執行下面這行,畫面會印出一組臨時密碼並解除鎖定,用 admin 和它登入後再改成自己的密碼(v1.3.2 起):
+ docker compose exec api python -m app.scripts.reset_admin
+
Q:校內其他電腦連不到。
確認用的是主機的區網 IP(非 localhost),且主機防火牆放行該埠。手機/平板需與主機同一網段(同一 Wi-Fi)。
帳號與登入
Q:忘記管理員密碼怎麼辦?
- 若你還記得 .env 的初始 ADMIN_PASSWORD,那是首次登入用的;登入後改過的密碼存在資料庫。若連改過的都忘了,目前需由具資料庫存取權者重設。最務實的做法是還原一份記得密碼的舊備份(見備份指南),或聯繫維護者。請妥善保管管理員密碼。
+ 在主機上、系統資料夾裡執行:
+ docker compose exec api python -m app.scripts.reset_admin
+
+ 畫面會印出一組臨時密碼,並解除登入鎖定、重新啟用帳號;用它登入後系統會要求立即改密碼,原本各裝置的登入都會失效。資料完全不受影響。管理員帳號不是 admin 時加上 --username 你的帳號;帳號打錯時,指令會列出現有的系統管理員帳號。(v1.3.2 起;更早的版本請先升級。)
+ .env 的 ADMIN_PASSWORD 只在系統第一次啟動時使用,改它不會改到現有帳號的密碼。教師、組長忘記密碼,由系統管理員登入後到「帳號管理」重設即可,不需要這個指令。
Q:還原備份後大家被登出了。
這是預期行為。還原會替換整個資料庫(含帳號),為安全起見系統會強制所有人以還原時點的帳密重新登入。
Q:老師收不到調代課 Email。
diff --git a/docs/deploy/faq.md b/docs/deploy/faq.md
index bcc0b3c..f93defd 100644
--- a/docs/deploy/faq.md
+++ b/docs/deploy/faq.md
@@ -22,6 +22,20 @@ docker compose logs --tail=50 <服務名> # 例如 api / worker / postgres
**Q:80 埠被占用,啟動失敗(port is already allocated)。**
改 `.env` 的 `HTTP_PORT`(例如 `8080`)再 `docker compose up -d`,改用 `http://<主機IP>:8080`。
+**Q:裝好後第一次登入就一直「帳號或密碼錯誤」,接著被鎖 15 分鐘。**
+會鎖定代表管理員帳號已經建好,只是資料庫裡的密碼和你輸入的不同;跟網路、IP、手機熱點或在國外都無關(用 `localhost` 登入時,連線根本不出這台電腦)。常見原因:
+
+- **先啟動過一次才改 `.env` 的密碼。** 管理員帳號只在第一次啟動時依 `.env` 建立,之後改 `.env` 不會生效。這時的密碼是當時 `.env` 的值,通常是預設的 `changeme`。
+- **密碼裡有 `$` 或 `#`。** docker compose 會把 `$` 當成變數截掉,`#` 前有空白會被當成註解。
+
+另外,鎖定是鎖**帳號**,不是鎖網路:被鎖的 15 分鐘內,換網路、換 IP、甚至密碼打對都進不去。
+
+解法:在系統資料夾執行下面這行,畫面會印出一組臨時密碼並解除鎖定,用 `admin` 和它登入後再改成自己的密碼(v1.3.2 起):
+
+```bash
+docker compose exec api python -m app.scripts.reset_admin
+```
+
**Q:校內其他電腦連不到。**
確認用的是主機的**區網 IP**(非 `localhost`),且主機防火牆放行該埠。手機/平板需與主機同一網段(同一 Wi-Fi)。
@@ -30,7 +44,15 @@ docker compose logs --tail=50 <服務名> # 例如 api / worker / postgres
## 帳號與登入
**Q:忘記管理員密碼怎麼辦?**
-若你還記得 `.env` 的初始 `ADMIN_PASSWORD`,那是**首次登入**用的;登入後改過的密碼存在資料庫。若連改過的都忘了,目前需由具資料庫存取權者重設。最務實的做法是**還原一份記得密碼的舊備份**(見[備份指南](backup.md)),或聯繫維護者。請妥善保管管理員密碼。
+在主機上、系統資料夾裡執行:
+
+```bash
+docker compose exec api python -m app.scripts.reset_admin
+```
+
+畫面會印出一組臨時密碼,並解除登入鎖定、重新啟用帳號;用它登入後系統會要求立即改密碼,原本各裝置的登入都會失效。資料完全不受影響。管理員帳號不是 `admin` 時加上 `--username 你的帳號`;帳號打錯時,指令會列出現有的系統管理員帳號。(v1.3.2 起;更早的版本請先升級。)
+
+`.env` 的 `ADMIN_PASSWORD` 只在系統第一次啟動時使用,改它不會改到現有帳號的密碼。教師、組長忘記密碼,由系統管理員登入後到「帳號管理」重設即可,不需要這個指令。
**Q:還原備份後大家被登出了。**
這是預期行為。還原會替換整個資料庫(含帳號),為安全起見系統會強制所有人以還原時點的帳密重新登入。
diff --git a/docs/deploy/install.html b/docs/deploy/install.html
index 05c414d..0bfd20c 100644
--- a/docs/deploy/install.html
+++ b/docs/deploy/install.html
@@ -335,6 +335,8 @@
步驟 2:修改 .env(至
把印出來的那 64 個字元貼到 SECRET_KEY= 後面即可。
+ ⚠️ 改完 .env 再啟動。 管理員帳號只在系統第一次啟動時,依當時 .env 的 ADMIN_PASSWORD 建立;之後再改 .env 的密碼、重新啟動,都不會改到已經建立的帳號。如果先啟動過一次才改,請用預設密碼 changeme 登入,或見下方「登不進去怎麼辦」。
+ 密碼請只用英文字母與數字。 docker compose 會把 .env 值裡的 $ 當成變數展開(my$ecret123 實際變成 my),# 前面有空白會被當成註解切掉,而且都沒有任何錯誤訊息。第一次登入時系統會要求你改密碼,那時就可以用含符號的密碼了。(用安裝程式 install.sh / install.ps1 安裝的不受影響,它會自動處理。)
沒改 SECRET_KEY 不會讓系統起不來——程式偵測到仍是範例值時會自動換一把隨機金鑰。
但那把金鑰只存在記憶體裡,容器一重啟所有人就被登出,所以還是設一個固定值為宜。
@@ -364,6 +366,10 @@ 驗證安裝成功
校內其他電腦:http://<主機的區網IP>(例如 http://192.168.1.50,IP 用 ipconfig / ip a 查)
以 .env 設定的 ADMIN_USERNAME / ADMIN_PASSWORD 登入,系統會要求你首次改密碼,接著進入設定精靈,依畫面五步驟建立學期、教師、班級、科目即可開始使用。
+ 登不進去怎麼辦? 一直顯示「帳號或密碼錯誤」、接著被鎖 15 分鐘,代表帳號已經建好(帳號不存在時不會鎖定),只是資料庫裡的密碼和你輸入的不同——跟網路、IP、在不在國外都無關。在系統資料夾執行:
+ docker compose exec api python -m app.scripts.reset_admin
+
+ 畫面會印出一組臨時密碼,同時解除鎖定。用帳號 admin 和這組臨時密碼登入,系統會要求你立刻改成新密碼。資料完全不受影響。(v1.3.2 起;更早的版本請先升級。)
想先試看看再決定?載入示範資料
還沒建任何學期時,到「系統管理 → 示範資料」按一下,系統會建出一所完整的虛構國中(臺南市市立敦品國中:18 班、48 位教師、384 筆配課),十幾秒就能跑出一張全校課表。適合評估階段先把整套流程走過一遍,不必先手 key 幾百筆資料。
只有在系統完全沒有學期時才能載入,以免蓋掉正式資料。試用完要正式啟用時,建議重新安裝一套乾淨的系統,不要把示範資料與真實資料混在一起。
diff --git a/docs/deploy/install.md b/docs/deploy/install.md
index a831d13..b5fee0e 100644
--- a/docs/deploy/install.md
+++ b/docs/deploy/install.md
@@ -198,6 +198,10 @@ $b = New-Object byte[] 32
把印出來的那 64 個字元貼到 `SECRET_KEY=` 後面即可。
+> ⚠️ **改完 `.env` 再啟動。** 管理員帳號只在系統**第一次啟動**時,依當時 `.env` 的 `ADMIN_PASSWORD` 建立;之後再改 `.env` 的密碼、重新啟動,都**不會**改到已經建立的帳號。如果先啟動過一次才改,請用預設密碼 `changeme` 登入,或見下方「登不進去怎麼辦」。
+>
+> **密碼請只用英文字母與數字。** docker compose 會把 `.env` 值裡的 `$` 當成變數展開(`my$ecret123` 實際變成 `my`),`#` 前面有空白會被當成註解切掉,而且都沒有任何錯誤訊息。第一次登入時系統會要求你改密碼,那時就可以用含符號的密碼了。(用安裝程式 `install.sh` / `install.ps1` 安裝的不受影響,它會自動處理。)
+
> 沒改 `SECRET_KEY` 不會讓系統起不來——程式偵測到仍是範例值時會自動換一把隨機金鑰。
> 但那把金鑰只存在記憶體裡,**容器一重啟所有人就被登出**,所以還是設一個固定值為宜。
@@ -241,6 +245,14 @@ curl http://localhost/api/health
以 `.env` 設定的 `ADMIN_USERNAME` / `ADMIN_PASSWORD` 登入,系統會要求你**首次改密碼**,接著進入**設定精靈**,依畫面五步驟建立學期、教師、班級、科目即可開始使用。
+**登不進去怎麼辦?** 一直顯示「帳號或密碼錯誤」、接著被鎖 15 分鐘,代表帳號已經建好(帳號不存在時不會鎖定),只是資料庫裡的密碼和你輸入的不同——跟網路、IP、在不在國外都無關。在系統資料夾執行:
+
+```bash
+docker compose exec api python -m app.scripts.reset_admin
+```
+
+畫面會印出一組**臨時密碼**,同時解除鎖定。用帳號 `admin` 和這組臨時密碼登入,系統會要求你立刻改成新密碼。資料完全不受影響。(v1.3.2 起;更早的版本請先升級。)
+
### 想先試看看再決定?載入示範資料
還沒建任何學期時,到「系統管理 → 示範資料」按一下,系統會建出一所完整的虛構國中(臺南市市立敦品國中:18 班、48 位教師、384 筆配課),十幾秒就能跑出一張全校課表。適合評估階段先把整套流程走過一遍,不必先手 key 幾百筆資料。
diff --git a/docs/deploy/upgrade.html b/docs/deploy/upgrade.html
index 264906e..70f268e 100644
--- a/docs/deploy/upgrade.html
+++ b/docs/deploy/upgrade.html
@@ -213,7 +213,7 @@ 方式 A:拉取映像部署(最常
# https://raw.githubusercontent.com/begin0808/Course_Scheduling_System/main/docker-compose.yml
# 3) 選擇版本:編輯 .env
- # 釘選版本(可控):IMAGE_TAG=v1.3.1
+ # 釘選版本(可控):IMAGE_TAG=v1.3.2
# 永遠最新: IMAGE_TAG=latest
# 4) 拉新映像並重啟
@@ -233,7 +233,7 @@ 方式 B:從原始碼建置部署
關於版本釘選
- - 正式環境建議
IMAGE_TAG=v1.3.1 這樣釘住特定版本,你才能決定何時升級、升到哪一版,而不是每次 pull 都可能變動。
+ - 正式環境建議
IMAGE_TAG=v1.3.2 這樣釘住特定版本,你才能決定何時升級、升到哪一版,而不是每次 pull 都可能變動。
- 想升級時,把
IMAGE_TAG 改成新版本號,再 docker compose pull && up -d。
- 各版本的變更內容見專案根目錄的 CHANGELOG.md;破壞性變更(若有)會在該版本明確標註 ⚠️ 與對應處置。
diff --git a/docs/deploy/upgrade.md b/docs/deploy/upgrade.md
index e426d22..3fb69fd 100644
--- a/docs/deploy/upgrade.md
+++ b/docs/deploy/upgrade.md
@@ -40,7 +40,7 @@ v1.2.19 三個版本的實際資料上驗證過:課表、配課、鐘點、請
# https://raw.githubusercontent.com/begin0808/Course_Scheduling_System/main/docker-compose.yml
# 3) 選擇版本:編輯 .env
-# 釘選版本(可控):IMAGE_TAG=v1.3.1
+# 釘選版本(可控):IMAGE_TAG=v1.3.2
# 永遠最新: IMAGE_TAG=latest
# 4) 拉新映像並重啟
@@ -70,7 +70,7 @@ docker compose up -d --build # 重新建置並重啟
## 關於版本釘選
-- **正式環境建議 `IMAGE_TAG=v1.3.1` 這樣釘住特定版本**,你才能決定何時升級、升到哪一版,而不是每次 `pull` 都可能變動。
+- **正式環境建議 `IMAGE_TAG=v1.3.2` 這樣釘住特定版本**,你才能決定何時升級、升到哪一版,而不是每次 `pull` 都可能變動。
- 想升級時,把 `IMAGE_TAG` 改成新版本號,再 `docker compose pull && up -d`。
- 各版本的變更內容見專案根目錄的 [CHANGELOG.md](../../CHANGELOG.md);破壞性變更(若有)會在該版本明確標註 ⚠️ 與對應處置。
diff --git a/docs/features.html b/docs/features.html
index 596c116..7c84a65 100644
--- a/docs/features.html
+++ b/docs/features.html
@@ -3,7 +3,7 @@
-
+
功能總覽 · 排課與調代課系統