Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

Release/install scripts, if changed:

- [ ] `bash -n scripts/*.sh`, `sh -n scripts/install.sh`, and `shellcheck -S warning scripts/*.sh`
- [ ] `bash -n` for every `scripts/*.sh` file, `sh -n scripts/install.sh`, and `shellcheck -S warning scripts/*.sh`
- [ ] `python3 -B -m unittest -v scripts/mirror_receiver_test.py`
- [ ] Invalid version/tag rejection and static amd64/arm64 builds pass

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ jobs:
go test -mod=readonly -count=1 -race -p 1 -tags integration ./...
test -z "$(gofmt -l .)"
sh -n scripts/install.sh
bash -n scripts/*.sh
for script in scripts/*.sh; do
bash -n "$script"
done
python3 -B -m unittest -v scripts/mirror_receiver_test.py
if ! command -v shellcheck >/dev/null 2>&1; then
sudo apt-get update
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ jobs:
- name: Shell syntax
run: |
sh -n scripts/install.sh
bash -n scripts/*.sh
for script in scripts/*.sh; do
bash -n "$script"
done

- name: ShellCheck
run: shellcheck -S warning scripts/*.sh
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ All notable changes to this project are documented here.

## Unreleased

## v2.10.8 - 2026-09-26

- Keep automatic cleanup scheduled until absent-account database, sudoers and
sshd cleanup has succeeded, so a failed step remains retryable.
- Require persistent systemd timer enablement, and distinguish a booted systemd
manager from an installed systemctl when reporting the init system.
- Exit the menu after an uninstall visibly removes the command even if a later
durability or audit-log cleanup step fails. Track visible implicit installs
before reporting their durability failure.
- Bound the mirror installer scan through its final validation, skip hashing
unrelated release binaries, and propagate termination signals through scans.
- Fix Linux executable ownership inspection and integration account-file parsing;
check shell syntax one file at a time in CI and contributor instructions.
- Group invite choices in one plan; remove unused account-creation wrappers,
output fields, registry accessors and single-key configuration. Split CLI,
account lifecycle and self-management files by responsibility, and keep release
tooling tests in their own package without changing public commands, state
formats, signing-key rotation, supported backends or recovery checks.
- Align both operator-guide languages with explicit registry compaction,
interactive sshd-change consent and successful quarantine handoffs.
- Read each account once per status display, sharing that observation between
table and narrow-terminal layouts while refreshing it on the next display.
- Return explicit upgrade outcomes and exit the menu after a visible executable
Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ system paths. Root installer tests that create temporary destinations below
**Release/install scripts**, if you touch `scripts/`:

```bash
bash -n scripts/*.sh
for script in scripts/*.sh; do
bash -n "$script" || exit
done
sh -n scripts/install.sh
python3 -B -m unittest -v scripts/mirror_receiver_test.py
shellcheck -S warning scripts/*.sh
Expand Down
4 changes: 3 additions & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ The real private key is shown only once. Never put an invite bundle in a group c
/usr/bin/sudo /usr/local/sbin/linux-temp-admin revoke --user xxvcc-a1b2c3d4e5
```

By default, automatic revocation is scheduled after 24 hours. On a normal systemd host, a manual or expiry revoke immediately removes sudo/sshd grants, disables login, terminates processes, and clears personal jobs before returning. The passwd name and UID/GID remain quarantined for at least one scheduler polling cycle; a persistent timer repeats the checks and finishes account, Home, and mail deletion in the background, so the terminal does not wait 65 seconds for safety. Without systemd, the same safety drain and deletion remain synchronous. If the account disappeared outside the tool without a deletion-recovery witness, revoke cleans name-scoped grants and tasks it can identify safely, but removes the registry row only after proving there is no same-name group or subuid/subgid residue; it never guesses at removing those artifacts or Home/mail. When an already-authorized teardown persisted a witness first, recovery permits an owner-checked conventional single-file mail-spool sweep and, only when `SequentialID` proves the former private GID, cleanup of that residual private group. subuid/subgid residue is verified but never guessed away, and the absent account's old Home path is never removed recursively. If a safety check or cleanup fails, the command returns nonzero, retains the account when it still exists and the registry witness, and attempts to disable any surviving account for a systemd retry or manual recovery. Revoke access immediately when work is finished even when automatic revocation is enabled.
By default, automatic revocation is scheduled after 24 hours. On a normal systemd host, revoke removes sudo/sshd grants, disables login, and clears processes and personal jobs before establishing identity quarantine and returning. The name and UID/GID remain reserved for at least 65 seconds while a persistent timer repeats the checks and finishes deletion in the background. Without systemd, the wait and deletion remain synchronous. Revoke access as soon as work is finished, even when automatic revocation is enabled.

Once access is revoked and quarantine is established, the command returns `0`. It may still warn and return `0` if the durable handoff or cleanup of the old auto-delete task was not fully confirmed; this does not mean background deletion has finished. A safety check or required cleanup error that prevents safe quarantine or completion of synchronous revoke returns nonzero, retains the registry witness needed for recovery, and attempts to disable any surviving account. Read warnings and check subsequent state with `status` and `doctor`. For absent accounts, old records, and recovery details, see [Revoke an account in the operator guide](docs/operator-guide.en.md#revoke-an-account).

## Everyday commands

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ ssh -i ./xxvcc-a1b2c3d4e5.key -p 22 xxvcc-a1b2c3d4e5@203.0.113.10
/usr/bin/sudo /usr/local/sbin/linux-temp-admin revoke --user xxvcc-a1b2c3d4e5
```

默认会安排在 24 小时后自动撤销。正常 systemd 主机上的手动或到期撤销会立即删除 sudo/sshd 授权、禁用登录、终止进程并清理个人任务,然后在前台返回;passwd 中的用户名和 UID/GID 会继续隔离至少一个任务轮询周期,由持久化 timer 在后台复查并完成账号、Home 和 mail 删除,避免为了安全而让终端等待 65 秒。systemd 不可用时仍同步等待并完成同样的安全清场。若账号已在程序外消失且没有删除恢复见证,程序会清理可安全识别的按用户名授权和任务,但只有在确认没有同名组或 subuid/subgid 残留后才移除登记;它不会猜测删除这些残留或 Home/mail。已经授权的撤销若提前持久化了恢复见证,则允许继续核对 UID 属主的传统单文件 mail spool 清扫;仅当 `SequentialID` 证明了原私有 GID 时,才可清理对应的残留私有组。subuid/subgid 只会验证无残留,Home 绝不会按缺失账号的旧路径恢复性删除。若安全检查或清理失败,程序会返回非零,保留账号(若仍存在)和登记,并尽力禁用仍存在的账号,供 systemd 重试或人工处理。即使启用了自动撤销,用完后也应立即手动撤销。
默认会安排在 24 小时后自动撤销。正常 systemd 主机上的撤销会先移除 sudo/sshd 授权、禁用登录、清理进程和个人任务,再建立身份隔离并返回。用户名和 UID/GID 继续保留至少 65 秒,由持久化 timer 在后台复查并完成删除;systemd 不可用时仍同步等待并完成删除。即使启用了自动撤销,用完后也应立即手动撤销。

已撤权并建立隔离时,命令返回 `0`;持久化交接或旧自动删除任务清理未完整确认时,仍可能警告并返回 `0`,这不表示后台终删已经完成。若安全检查或必需清理错误导致无法安全建立隔离或完成同步撤销,命令返回非零,保留恢复所需登记,并尽力禁用仍存在的账号。请留意警告并用 `status`、`doctor` 核对后续状态;账号已消失、旧记录等恢复细节见[操作指南的撤销章节](docs/operator-guide.md#撤销账号)。

## 常用命令

Expand Down
8 changes: 5 additions & 3 deletions docs/operator-guide.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ Status reports account identity, UID, expiry, auto-delete task, identity-quarant
/usr/bin/sudo /usr/local/sbin/linux-temp-admin revoke --user xxvcc-a1b2c3d4e5
```

When the stable identity can still be checked, normal systemd revoke first removes and confirms sudoers/sshd grants, disables login, and performs two personal-crontab, target-UID `at`/`batch`, and process cleanup passes. It then establishes persistent identity quarantine and returns immediately. The current format requires the registered name, UID/GID, deterministic Home, and fifth-field `lta-m=` generation witness to remain exact. Supported standard account tools give ordinary users no path to rewrite that fifth field, so self-service full-name, room, work/home phone, or nonempty-shell changes, including a concurrent loop, do not hold revoke open. A stable-field or fifth-field-witness change fails closed. A local root that can directly rewrite the account database or program state remains inside the trust boundary. The list shows “access revoked; quarantined”; the passwd entry holds the name and UID/GID for at least 65 seconds (less than 125 seconds after minute rounding) while remaining expired, password-locked, and without a tool-managed sudo/sshd entry. At the deadline, a background timer repeats the checks and then removes the account, deterministic `/home/<username>` directory, UID-matched conventional mail spool, public key, and tasks. A persistent timer catches up after the host was down across the deadline. Without systemd, revoke still waits 65 seconds in the foreground and completes final deletion synchronously. Uninstall also finalizes synchronously so it cannot remove the command still needed by a background task. If the account disappeared outside the tool, revoke first cleans name-scoped grants and tasks that remain safely identifiable, but removes the registry row only after account-database inspection proves there is no same-name group or subuid/subgid residue. An existing deletion-recovery witness authorizes the narrow mail-spool sweep; only `SequentialID` additionally authorizes cleanup of a residual private group proved by the complete database-shape checks, while subuid/subgid residue is verified but never guessed away. Recursive Home cleanup proceeds only for a real directory owned by the registered account's UID/GID with no mount boundary underneath. Home cleanup uses directory descriptors and rejects a symlink at the Home root; an internal symlink is unlinked without following its target. Traversal checks cooperative budgets of 100,000 entries, 128 levels, and two minutes between filesystem calls, so the deadline cannot interrupt one blocked filesystem call. Cron/at and process results are repeated snapshots, not an atomic freeze. If a safety condition, resource limit, job/process inventory, or name-scoped grant cannot be confirmed, revoke attempts to disable the account, retains any surviving account and the registry witness, and returns nonzero so username reuse cannot inherit old data, deferred work, or privilege.
When the stable identity can still be checked, normal systemd revoke first removes and confirms sudoers/sshd grants, disables login, and performs two personal-crontab, target-UID `at`/`batch`, and process cleanup passes. It then establishes persistent identity quarantine and returns immediately. The current format requires the registered name, UID/GID, deterministic Home, and fifth-field `lta-m=` generation witness to remain exact. Supported standard account tools give ordinary users no path to rewrite that fifth field, so self-service full-name, room, work/home phone, or nonempty-shell changes, including a concurrent loop, do not hold revoke open. A stable-field or fifth-field-witness change fails closed. A local root that can directly rewrite the account database or program state remains inside the trust boundary. The list shows “access revoked; quarantined”; the passwd entry holds the name and UID/GID for at least 65 seconds (less than 125 seconds after minute rounding) while remaining expired, password-locked, and without a tool-managed sudo/sshd entry. At the deadline, a background timer repeats the checks and then removes the account, deterministic `/home/<username>` directory, UID-matched conventional mail spool, public key, and tasks. A persistent timer catches up after the host was down across the deadline. Without systemd, revoke still waits 65 seconds in the foreground and completes final deletion synchronously. Uninstall also finalizes synchronously so it cannot remove the command still needed by a background task. If the account disappeared outside the tool, revoke first cleans name-scoped grants and tasks that remain safely identifiable, but removes the registry row only after account-database inspection proves there is no same-name group or subuid/subgid residue. An existing deletion-recovery witness authorizes the narrow mail-spool sweep; only `SequentialID` additionally authorizes cleanup of a residual private group proved by the complete database-shape checks, while subuid/subgid residue is verified but never guessed away. Recursive Home cleanup proceeds only for a real directory owned by the registered account's UID/GID with no mount boundary underneath. Home cleanup uses directory descriptors and rejects a symlink at the Home root; an internal symlink is unlinked without following its target. Traversal checks cooperative budgets of 100,000 entries, 128 levels, and two minutes between filesystem calls, so the deadline cannot interrupt one blocked filesystem call. Cron/at and process results are repeated snapshots, not an atomic freeze. If an unconfirmed safety condition, resource limit, job/process inventory, or name-scoped grant prevents safe quarantine or completion of synchronous revoke, the command returns nonzero, attempts to disable the account, and retains any surviving account and the registry witness for recovery so username reuse cannot inherit old data, deferred work, or privilege.

Once access revocation and quarantine are confirmed, the command returns `0`. If the registry change is visible but its durability was not fully confirmed, or cleanup of the old auto-delete task failed, it warns about the handoff or cleanup, retains quarantine and the background finalizer, and still returns `0`. Exit code `0` does not mean background deletion or every cleanup step has finished; read warnings and check state with `status` and `doctor`.

Mail-specific cleanup handles only a traditional single-file mbox at `/var/mail/<username>` or `/var/spool/mail/<username>`. An existing mail spool must be root-owned and have no setuid bit; a world-writable spool must have sticky protection. Thus `root:mail 3777` and Arch Linux's `root:root 1777` work, while mode `0777`/`2777` without sticky and a non-root owner such as `mail:mail` fail closed. The target mailbox must also be a non-symlink regular file owned by the captured UID. `invite` checks the spool before `useradd` and again after the UID is known. A preflight failure leaves no account; a post-helper failure attempts rollback and retains an expired, locked, credential-less account plus its registry witness when cleanup cannot be confirmed. This specialized path neither searches nor traverses Maildir and never touches aaPanel's `/www/vmail`; a Maildir inside a tool-managed Home is still removed with the whole Home under the normal complete-revocation rules.

Expand All @@ -143,7 +145,7 @@ Deleting an unregistered account requires explicit `--force` and an additional u
/usr/bin/sudo /usr/local/sbin/linux-temp-admin cleanup-expired --compact
```

`linux-temp-admin cleanup-expired` removes stale registry rows and orphaned sudoers files, sshd exceptions, and revoke tasks. It **never deletes an account**. Use `linux-temp-admin revoke` to delete an account and `linux-temp-admin status` to list them.
`linux-temp-admin cleanup-expired --compact` removes stale registry rows and orphaned sudoers files, sshd exceptions, and revoke tasks. It **never deletes an account**. Without `--compact`, the command only displays registry entries and guidance; it performs no cleanup. Use `linux-temp-admin revoke` to delete an account and `linux-temp-admin status` to list them.

### Recover a missing identity sequence

Expand Down Expand Up @@ -227,4 +229,4 @@ The audit log remains at `/var/log/linux-temp-admin/audit.log` by default. The l
/home/USER/.ssh/authorized_keys
```

An `at` queue entry may also exist when systemd is unavailable. The sshd file exists only after explicit `--fix-sshd`, and the sudoers file exists only for a sudo-enabled account.
An `at` queue entry may also exist when systemd is unavailable. An sshd exception is written only with explicit `--fix-sshd` or explicit consent to the per-account repair during interactive use; `--no-fix-sshd` prevents it. The sudoers file exists only for a sudo-enabled account.
Loading
Loading