Compare commits
No commits in common. "ef00e0444d4067420eaac3a9a1560867f0fecefa" and "153c90bf68e3dbee969bd6507d5e89b4fad5b2d8" have entirely different histories.
ef00e0444d
...
153c90bf68
@ -1,158 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Linkding stores users in standard Django auth (django.contrib.auth.models.User).
|
|
||||||
# We drive the same User model the web UI uses via `python manage.py shell -c`
|
|
||||||
# from inside the linkding-service container — password hashing and admin flags
|
|
||||||
# come out identical to what Django itself would write.
|
|
||||||
#
|
|
||||||
# Username (not email) is the primary identifier in Linkding's UI, so the Tools
|
|
||||||
# manifest, the EZ_USER\t panel rows, and every lookup here all key on username.
|
|
||||||
# Email is stored when supplied but never required.
|
|
||||||
|
|
||||||
_linkdingManage() {
|
|
||||||
local container="linkding-service"
|
|
||||||
local py_body="$1"
|
|
||||||
shift
|
|
||||||
runFileOp docker exec -i -w /etc/linkding "$@" "$container" \
|
|
||||||
python manage.py shell -c "$py_body" 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
authAdapter_linkding_setPassword() {
|
|
||||||
local username="$1" password="$2"
|
|
||||||
[[ -z "$username" ]] && { isError "Username is required."; return 1; }
|
|
||||||
[[ -z "$password" ]] && password=$(generateRandomPassword)
|
|
||||||
|
|
||||||
local out
|
|
||||||
out=$(_linkdingManage '
|
|
||||||
import os
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
U = get_user_model()
|
|
||||||
u = U.objects.filter(username=os.environ["EZ_USER"]).first()
|
|
||||||
if not u:
|
|
||||||
print("EZ_USER_NOT_FOUND"); raise SystemExit
|
|
||||||
u.set_password(os.environ["EZ_PASS"]); u.save()
|
|
||||||
print("EZ_RESET_OK:" + u.username)
|
|
||||||
' -e "EZ_USER=$username" -e "EZ_PASS=$password")
|
|
||||||
|
|
||||||
if echo "$out" | grep -q EZ_USER_NOT_FOUND; then
|
|
||||||
isError "No Linkding user '$username'."; return 1
|
|
||||||
fi
|
|
||||||
if ! echo "$out" | grep -q EZ_RESET_OK; then
|
|
||||||
isError "Linkding password reset failed: $out"; return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Mirror the admin password back into the .config if we just reset the
|
|
||||||
# account we know about — keeps the WebUI's "Admin credentials" card
|
|
||||||
# honest. Skipped when the user resets a non-admin account.
|
|
||||||
if [[ "$username" == "${CFG_LINKDING_ADMIN_USER:-}" ]]; then
|
|
||||||
authPersistCfg linkding ADMIN_PASSWORD "$password"
|
|
||||||
fi
|
|
||||||
isSuccessful "Linkding password set for $username — New password: $password"
|
|
||||||
}
|
|
||||||
|
|
||||||
authAdapter_linkding_createUser() {
|
|
||||||
local username="$1" password="$2" email="$3" isAdmin="$4"
|
|
||||||
[[ -z "$username" ]] && { isError "Username is required."; return 1; }
|
|
||||||
[[ -z "$password" ]] && password=$(generateRandomPassword)
|
|
||||||
|
|
||||||
local make_super="False"
|
|
||||||
[[ "$isAdmin" == "true" ]] && make_super="True"
|
|
||||||
|
|
||||||
local out
|
|
||||||
out=$(_linkdingManage '
|
|
||||||
import os
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
U = get_user_model()
|
|
||||||
if U.objects.filter(username=os.environ["EZ_USER"]).exists():
|
|
||||||
print("EZ_USER_EXISTS"); raise SystemExit
|
|
||||||
email = os.environ.get("EZ_EMAIL", "")
|
|
||||||
if os.environ["EZ_ADMIN"] == "True":
|
|
||||||
u = U.objects.create_superuser(
|
|
||||||
username=os.environ["EZ_USER"],
|
|
||||||
email=email,
|
|
||||||
password=os.environ["EZ_PASS"],
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
u = U.objects.create_user(
|
|
||||||
username=os.environ["EZ_USER"],
|
|
||||||
email=email,
|
|
||||||
password=os.environ["EZ_PASS"],
|
|
||||||
)
|
|
||||||
print("EZ_USER_CREATED:" + str(u.id))
|
|
||||||
' -e "EZ_USER=$username" -e "EZ_PASS=$password" -e "EZ_EMAIL=$email" -e "EZ_ADMIN=$make_super")
|
|
||||||
|
|
||||||
if echo "$out" | grep -q EZ_USER_EXISTS; then
|
|
||||||
isError "Linkding user '$username' already exists."; return 1
|
|
||||||
fi
|
|
||||||
if ! echo "$out" | grep -q EZ_USER_CREATED; then
|
|
||||||
isError "Linkding user creation failed: $out"; return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we just minted the first admin account, remember it so subsequent
|
|
||||||
# password resets keep the .config in sync (same pattern bookstack uses).
|
|
||||||
if [[ "$isAdmin" == "true" && -z "${CFG_LINKDING_ADMIN_USER:-}" ]]; then
|
|
||||||
authPersistCfg linkding ADMIN_USER "$username"
|
|
||||||
authPersistCfg linkding ADMIN_PASSWORD "$password"
|
|
||||||
fi
|
|
||||||
isSuccessful "Linkding user created — Username: $username — Password: $password"
|
|
||||||
}
|
|
||||||
|
|
||||||
authAdapter_linkding_listUsers() {
|
|
||||||
# The user-list modal in the WebUI uses `email || username` as each row's
|
|
||||||
# display id and pre-fills the tool's `username` field with that value
|
|
||||||
# (via fillIdentifier in tools-manager.js). Linkding's primary id IS the
|
|
||||||
# username and email is optional, so emit username in BOTH slots — the
|
|
||||||
# row click then always sends back the username our other tools expect.
|
|
||||||
# The third column (`roles`) is matched against /admin/i in the JS to
|
|
||||||
# decide whether to show the 👑→👤 demote toggle vs 👤→👑 promote.
|
|
||||||
_linkdingManage '
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
for u in get_user_model().objects.all().order_by("username"):
|
|
||||||
if u.is_superuser: role = "admin"
|
|
||||||
elif u.is_staff: role = "staff"
|
|
||||||
else: role = "user"
|
|
||||||
print("EZ_USER\t%s\t%s\t%s" % (u.username, u.username, role))
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
authAdapter_linkding_deleteUser() {
|
|
||||||
local username="$1"
|
|
||||||
[[ -z "$username" ]] && { isError "Username is required."; return 1; }
|
|
||||||
local out
|
|
||||||
out=$(_linkdingManage '
|
|
||||||
import os
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
u = get_user_model().objects.filter(username=os.environ["EZ_USER"]).first()
|
|
||||||
if not u:
|
|
||||||
print("EZ_USER_NOT_FOUND"); raise SystemExit
|
|
||||||
u.delete()
|
|
||||||
print("EZ_USER_DELETED")
|
|
||||||
' -e "EZ_USER=$username")
|
|
||||||
echo "$out" | grep -q EZ_USER_NOT_FOUND && { isError "No Linkding user '$username'."; return 1; }
|
|
||||||
echo "$out" | grep -q EZ_USER_DELETED || { isError "Linkding delete failed: $out"; return 1; }
|
|
||||||
isSuccessful "Linkding user '$username' deleted."
|
|
||||||
}
|
|
||||||
|
|
||||||
authAdapter_linkding_setAdmin() {
|
|
||||||
local username="$1" isAdmin="$2"
|
|
||||||
[[ -z "$username" ]] && { isError "Username is required."; return 1; }
|
|
||||||
local target="$isAdmin"
|
|
||||||
[[ "$target" != "true" ]] && target="false"
|
|
||||||
|
|
||||||
local out
|
|
||||||
out=$(_linkdingManage '
|
|
||||||
import os
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
u = get_user_model().objects.filter(username=os.environ["EZ_USER"]).first()
|
|
||||||
if not u:
|
|
||||||
print("EZ_USER_NOT_FOUND"); raise SystemExit
|
|
||||||
flag = os.environ["EZ_TARGET"] == "true"
|
|
||||||
u.is_superuser = flag
|
|
||||||
u.is_staff = flag
|
|
||||||
u.save()
|
|
||||||
print("EZ_OK")
|
|
||||||
' -e "EZ_USER=$username" -e "EZ_TARGET=$target")
|
|
||||||
echo "$out" | grep -q EZ_USER_NOT_FOUND && { isError "No Linkding user '$username'."; return 1; }
|
|
||||||
echo "$out" | grep -q EZ_OK || { isError "Linkding admin toggle failed: $out"; return 1; }
|
|
||||||
isSuccessful "Linkding user '$username' admin → $target."
|
|
||||||
}
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
{
|
|
||||||
"tools": [
|
|
||||||
{
|
|
||||||
"id": "reset_password",
|
|
||||||
"category": "users",
|
|
||||||
"label": "Reset User Password",
|
|
||||||
"description": "Reset an existing Linkding user's password. Leave the password field blank to generate a random one — it is shown in the task log.",
|
|
||||||
"icon": "🔑",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"label": "Username",
|
|
||||||
"type": "text",
|
|
||||||
"placeholder": "alice",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "password",
|
|
||||||
"label": "New password (leave blank to generate)",
|
|
||||||
"type": "password",
|
|
||||||
"placeholder": "Leave blank for random"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "create_account",
|
|
||||||
"category": "users",
|
|
||||||
"label": "Create User Account",
|
|
||||||
"description": "Create a new Linkding user. Tick \"Make admin\" to grant full admin (Django superuser) rights. Leave the password blank to generate a random one.",
|
|
||||||
"icon": "👤",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"label": "Username",
|
|
||||||
"type": "text",
|
|
||||||
"placeholder": "alice",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "email",
|
|
||||||
"label": "Email (optional)",
|
|
||||||
"type": "text",
|
|
||||||
"placeholder": "alice@example.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "password",
|
|
||||||
"label": "Password (leave blank to generate)",
|
|
||||||
"type": "password",
|
|
||||||
"placeholder": "Leave blank for random"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"label": "Make admin",
|
|
||||||
"type": "checkbox",
|
|
||||||
"default": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "list_users",
|
|
||||||
"category": "users",
|
|
||||||
"label": "List Users",
|
|
||||||
"description": "Show every Linkding user with their admin status.",
|
|
||||||
"icon": "📋",
|
|
||||||
"fields": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "delete_user",
|
|
||||||
"category": "users",
|
|
||||||
"label": "Delete User Account",
|
|
||||||
"description": "Permanently delete a Linkding user account and all of their bookmarks.",
|
|
||||||
"icon": "🗑",
|
|
||||||
"destructive": true,
|
|
||||||
"confirm": "This cannot be undone — the user and all of their bookmarks will be removed.",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"label": "Username",
|
|
||||||
"type": "text",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "set_admin",
|
|
||||||
"category": "users",
|
|
||||||
"label": "Set Admin Status",
|
|
||||||
"description": "Promote a user to admin (Django is_superuser + is_staff) or demote them to a normal user.",
|
|
||||||
"icon": "👑",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"label": "Username",
|
|
||||||
"type": "text",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "admin",
|
|
||||||
"label": "Make admin",
|
|
||||||
"type": "checkbox",
|
|
||||||
"default": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
appLinkdingCreateAccount() {
|
|
||||||
local args="$1"
|
|
||||||
authAdapterCall linkding createUser \
|
|
||||||
"$(authToolArg "$args" username)" \
|
|
||||||
"$(authToolArg "$args" password)" \
|
|
||||||
"$(authToolArg "$args" email)" \
|
|
||||||
"$(authToolArg "$args" admin)"
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
appLinkdingDeleteUser() {
|
|
||||||
local args="$1"
|
|
||||||
authAdapterCall linkding deleteUser "$(authToolArg "$args" username)"
|
|
||||||
}
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
appLinkdingListUsers() {
|
|
||||||
authAdapterCall linkding listUsers
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
appLinkdingResetPassword() {
|
|
||||||
local args="$1"
|
|
||||||
authAdapterCall linkding setPassword \
|
|
||||||
"$(authToolArg "$args" username)" \
|
|
||||||
"$(authToolArg "$args" password)"
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
appLinkdingSetAdmin() {
|
|
||||||
local args="$1"
|
|
||||||
authAdapterCall linkding setAdmin \
|
|
||||||
"$(authToolArg "$args" username)" \
|
|
||||||
"$(authToolArg "$args" admin)"
|
|
||||||
}
|
|
||||||
29
scripts/menu/tools/manage_linkding.sh
Executable file
29
scripts/menu/tools/manage_linkding.sh
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
linkdingToolsMenu()
|
||||||
|
{
|
||||||
|
# Enable input
|
||||||
|
stty echo
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
isHeader "Linkding Tools"
|
||||||
|
isOption "1. Run Config Updater"
|
||||||
|
isOption "x. Exit to Main Menu"
|
||||||
|
echo ""
|
||||||
|
isQuestion "What is your choice: "
|
||||||
|
read -rp "" linkding_menu_choice
|
||||||
|
|
||||||
|
case $linkding_menu_choice in
|
||||||
|
1)
|
||||||
|
appLinkdingSetupUser;
|
||||||
|
;;
|
||||||
|
x)
|
||||||
|
endStart;
|
||||||
|
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
isNotice "Invalid choice. Please select a valid option."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ menu_scripts=(
|
|||||||
"menu/tools/manage_dashy.sh"
|
"menu/tools/manage_dashy.sh"
|
||||||
"menu/tools/manage_docker.sh"
|
"menu/tools/manage_docker.sh"
|
||||||
"menu/tools/manage_invidious.sh"
|
"menu/tools/manage_invidious.sh"
|
||||||
|
"menu/tools/manage_linkding.sh"
|
||||||
"menu/tools/manage_main.sh"
|
"menu/tools/manage_main.sh"
|
||||||
"menu/tools/manage_mattermost.sh"
|
"menu/tools/manage_mattermost.sh"
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user