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 登入時,連線根本不出這台電腦)。常見原因:

+ +

另外,鎖定是鎖帳號,不是鎖網路:被鎖的 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:從原始碼建置部署


    關於版本釘選

    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 @@ - + 功能總覽 · 排課與調代課系統