Merge branch 'code/majormac/wiki-forgejo-recovery'
This commit is contained in:
commit
3f94ebb963
2 changed files with 106 additions and 0 deletions
105
05-troubleshooting/forgejo-mailer-and-cli-recovery.md
Normal file
105
05-troubleshooting/forgejo-mailer-and-cli-recovery.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
title: "Forgejo: Account Recovery & CLI Admin When Locked Out of the GUI"
|
||||
domain: troubleshooting
|
||||
category: general
|
||||
tags: [forgejo, gitea, smtp, docker, account-recovery, self-hosting]
|
||||
status: published
|
||||
created: 2026-06-12
|
||||
updated: 2026-06-12
|
||||
---
|
||||
# Forgejo: Account Recovery & CLI Admin When Locked Out of the GUI
|
||||
|
||||
Two related problems on a single-admin self-hosted **Forgejo** (or Gitea): the GUI *"Forgot password"* is disabled, and you can't log in to fix it. Here's how to (1) enable account recovery properly, and (2) recover from the command line when you're already locked out.
|
||||
|
||||
## Symptoms
|
||||
|
||||
- The *Forgot password* page shows: **"Account recovery is only available when email is set up. Please set up email to enable account recovery."**
|
||||
- You can't log in (wrong/forgotten password), so you can't add an SSH key or change settings in the GUI either.
|
||||
|
||||
## Part 1 — Enable account recovery (configure the mailer)
|
||||
|
||||
Account recovery needs SMTP. If you already run a mail server on your tailnet, relay through it — **no app password needed** when the Forgejo host is `mynetworks`-trusted by that mail server.
|
||||
|
||||
Edit `app.ini` (in the data volume, e.g. `/data/gitea/conf/app.ini`):
|
||||
|
||||
```ini
|
||||
[mailer]
|
||||
ENABLED = true
|
||||
PROTOCOL = smtp+starttls
|
||||
SMTP_ADDR = 100.x.y.z ; mail server's tailnet IP
|
||||
SMTP_PORT = 587
|
||||
FROM = forgejo@example.com
|
||||
FORCE_TRUST_SERVER_CERT = true ; required when connecting by IP (cert CN won't match)
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `FORCE_TRUST_SERVER_CERT = true` is needed when you target the relay by **IP** — the TLS cert is issued for a hostname, not the IP, so verification would otherwise fail. Acceptable on a trusted internal hop.
|
||||
- Omit `USER`/`PASSWD` if the relay accepts your host via `mynetworks` (no SASL). Otherwise add SMTP auth.
|
||||
- `app.ini` lives in the persistent volume, so the change **survives container re-creation** (e.g. Watchtower's nightly pull).
|
||||
|
||||
Apply and verify:
|
||||
|
||||
```bash
|
||||
docker restart forgejo
|
||||
docker logs forgejo 2>&1 | grep -i "Mail Service Enabled" # confirms the mailer loaded
|
||||
```
|
||||
|
||||
Test the SMTP path **before** trusting it (run from the host, mimicking Forgejo's connection):
|
||||
|
||||
```bash
|
||||
python3 - <<'EOF'
|
||||
import smtplib, ssl
|
||||
ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
|
||||
s = smtplib.SMTP("100.x.y.z", 587, timeout=15)
|
||||
s.ehlo(); s.starttls(context=ctx); s.ehlo()
|
||||
s.sendmail("forgejo@example.com", ["you@example.com"],
|
||||
"Subject: test\r\n\r\nForgejo relay path test")
|
||||
s.quit(); print("SENT_OK")
|
||||
EOF
|
||||
```
|
||||
|
||||
`SENT_OK` means the relay accepted the message. `/user/forgot_password` should now show the reset form instead of the email error.
|
||||
|
||||
> **Container can't reach the tailnet IP?** Docker bridge networks usually route to Tailscale via the host (SNAT to the host's tailnet IP). Confirm with:
|
||||
> `docker exec forgejo nc -w5 100.x.y.z 587 </dev/null && echo REACHABLE`
|
||||
|
||||
## Part 2 — Recover from the CLI (already locked out)
|
||||
|
||||
Forgejo's admin CLI runs inside the container as the git user (UID 1000) and needs no login.
|
||||
|
||||
**Reset a password:**
|
||||
|
||||
```bash
|
||||
docker exec -u 1000 forgejo forgejo admin user change-password -u <user> -p '<newpass>'
|
||||
```
|
||||
|
||||
> ⚠️ **Gotcha:** `change-password` sets `must_change_password=true` by default. That **forces a change on next GUI login _and_ returns HTTP 403 on the API** (`"You must change your password"`). Clear it:
|
||||
> ```bash
|
||||
> docker exec -u 1000 forgejo forgejo admin user must-change-password --unset <user>
|
||||
> ```
|
||||
|
||||
**Add an SSH key without the GUI** (basic-auth API — works only if 2FA is off):
|
||||
|
||||
```bash
|
||||
curl -u <user>:'<pass>' -X POST -H 'Content-Type: application/json' \
|
||||
-d '{"title":"laptop","key":"ssh-ed25519 AAAA... you@host"}' \
|
||||
http://localhost:3004/api/v1/user/keys
|
||||
# HTTP 201 = created
|
||||
```
|
||||
|
||||
Forgejo regenerates the git user's `authorized_keys` from the database, so `ssh -p <port> git@host` authenticates immediately afterward — no restart needed.
|
||||
|
||||
## "The password keeps changing" — it (probably) isn't
|
||||
|
||||
If a self-hosted Forgejo admin password *seems* to reset itself, a stock Forgejo container does **not** reset admin passwords. Rule out the server first:
|
||||
|
||||
- the compose has **no** admin/password env and no custom entrypoint;
|
||||
- **no** cron, systemd timer, or script runs `forgejo admin user change-password`;
|
||||
- the data volume is persistent (re-creation keeps the DB, password included).
|
||||
|
||||
If all three hold, nothing server-side is changing it — the "changing" password is a **client-side** artifact: a duplicate or stale entry in your password manager autofilling different values. Delete the duplicates and keep one.
|
||||
|
||||
## See also
|
||||
|
||||
- Forgejo — [Config Cheat Sheet → mailer](https://forgejo.org/docs/latest/admin/config-cheat-sheet/)
|
||||
|
|
@ -102,6 +102,7 @@ updated: 2026-05-15T09:00
|
|||
* [Gemini CLI Manual Update](05-troubleshooting/gemini-cli-manual-update.md)
|
||||
* [MajorWiki Setup & Publishing Pipeline](05-troubleshooting/majwiki-setup-and-pipeline.md)
|
||||
* [Gitea Actions Runner: Boot Race Condition Fix](05-troubleshooting/gitea-runner-boot-race-network-target.md)
|
||||
* [Forgejo: Account Recovery & CLI Admin When Locked Out of the GUI](05-troubleshooting/forgejo-mailer-and-cli-recovery.md)
|
||||
* [Cron Heartbeat False Alarm: /var/run Cleared by Reboot](05-troubleshooting/cron-heartbeat-tmpfs-reboot-false-alarm.md)
|
||||
* [SELinux: Fixing Dovecot Mail Spool Context (/var/vmail)](05-troubleshooting/selinux-dovecot-vmail-context.md)
|
||||
* [SELinux: Wrong /etc/localtime Label Silently Breaks Timezone Changes](05-troubleshooting/selinux-localtime-label-breaks-timezone.md)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue