Compare commits
2 commits
63ad19eb12
...
6f4958ba89
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f4958ba89 | ||
|
|
2f73367c6b |
2 changed files with 67 additions and 2 deletions
15
app.py
15
app.py
|
|
@ -22,7 +22,7 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|||
# Docker tab caches stale container IDs, so the footer is the reliable way
|
||||
# to confirm a `docker compose up -d --build` actually took effect.
|
||||
# Both are env-overridable if the deploy pipeline ever injects them.
|
||||
APP_VERSION = os.environ.get("APP_VERSION", "1.6.0")
|
||||
APP_VERSION = os.environ.get("APP_VERSION", "1.6.1")
|
||||
APP_VERSION_DATE = os.environ.get("APP_VERSION_DATE", "2026-08-27")
|
||||
|
||||
# Must point at a path that survives `docker compose up --build` — the
|
||||
|
|
@ -45,6 +45,19 @@ def save_settings(data):
|
|||
with open(SETTINGS_FILE, "w") as f:
|
||||
json.dump(existing, f, indent=2)
|
||||
|
||||
def delete_setting(key):
|
||||
"""Remove a key outright. save_settings() only ever merges, so it can't
|
||||
take something away — used to retire the plaintext shared password once
|
||||
real accounts exist. Returns True if the key was there."""
|
||||
settings = load_settings()
|
||||
if key not in settings:
|
||||
return False
|
||||
del settings[key]
|
||||
os.makedirs(os.path.dirname(SETTINGS_FILE) or ".", exist_ok=True)
|
||||
with open(SETTINGS_FILE, "w") as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
return True
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.environ.get("SECRET_KEY") or secrets.token_hex(32)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,17 @@ Run it inside the container, where Werkzeug and the real settings.json are:
|
|||
docker exec -it arr-summary python3 manage_users.py add <username>
|
||||
docker exec -it arr-summary python3 manage_users.py passwd <username>
|
||||
docker exec -it arr-summary python3 manage_users.py remove <username>
|
||||
docker exec -it arr-summary python3 manage_users.py retire-shared-password
|
||||
|
||||
The first `add` switches the dashboard from its single shared password to
|
||||
per-user logins. Until then the old LOGIN_PASSWORD keeps working, so you
|
||||
can't lock yourself out by deploying this.
|
||||
|
||||
Once accounts exist and you've confirmed they sign in,
|
||||
`retire-shared-password` deletes the old plaintext login_password from
|
||||
settings.json. It refuses to run while no accounts exist, because with
|
||||
nothing configured at all the dashboard treats that as dev mode and lets
|
||||
anyone in without a password.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
@ -24,7 +31,10 @@ import getpass
|
|||
from app import (
|
||||
SETTINGS_FILE,
|
||||
PASSWORD_HASH_METHOD,
|
||||
LOGIN_PASSWORD,
|
||||
save_settings,
|
||||
delete_setting,
|
||||
load_settings,
|
||||
load_users,
|
||||
generate_password_hash,
|
||||
)
|
||||
|
|
@ -122,7 +132,49 @@ def cmd_remove(args):
|
|||
return 0
|
||||
|
||||
|
||||
COMMANDS = {"list": cmd_list, "add": cmd_add, "passwd": cmd_passwd, "remove": cmd_remove}
|
||||
def cmd_retire_shared_password(_args):
|
||||
"""Delete the plaintext login_password from settings.json.
|
||||
|
||||
Guarded, because removing it while nothing else is configured makes
|
||||
get_active_password() empty — and the login route reads "no credentials
|
||||
at all" as dev mode and lets anyone straight in.
|
||||
"""
|
||||
users = load_users()
|
||||
if not users:
|
||||
print("Refusing: no accounts exist yet, so the shared password is still",
|
||||
file=sys.stderr)
|
||||
print("the only way in. Run `add <username>` first and check you can",
|
||||
file=sys.stderr)
|
||||
print("sign in with it, then run this again.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
if "login_password" not in load_settings():
|
||||
print("Nothing to do — settings.json has no login_password.")
|
||||
if LOGIN_PASSWORD:
|
||||
print()
|
||||
print("Note: LOGIN_PASSWORD is still set in the environment. It's")
|
||||
print("ignored while accounts exist, but clear it from .env too if")
|
||||
print("you want the old shared password gone entirely.")
|
||||
return 0
|
||||
|
||||
delete_setting("login_password")
|
||||
print(f"Removed the plaintext login_password from {SETTINGS_FILE}.")
|
||||
print(f"{len(users)} account(s) remain in use: {', '.join(sorted(users))}")
|
||||
if LOGIN_PASSWORD:
|
||||
print()
|
||||
print("LOGIN_PASSWORD is still set in the environment. It's ignored while")
|
||||
print("accounts exist, but clear it from .env and restart to be rid of it.")
|
||||
print("Don't clear it before you've confirmed the accounts work.")
|
||||
return 0
|
||||
|
||||
|
||||
COMMANDS = {
|
||||
"list": cmd_list,
|
||||
"add": cmd_add,
|
||||
"passwd": cmd_passwd,
|
||||
"remove": cmd_remove,
|
||||
"retire-shared-password": cmd_retire_shared_password,
|
||||
}
|
||||
|
||||
|
||||
def main(argv):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue