Add Anki Sync Server addon (self-hosted flashcard sync)
Installs the official self-hosted anki-sync-server (python -m anki.syncserver from PyPI's anki package, no Qt/GUI deps) as a systemd service under $KIOSK_USER, same shape as the Web UI addon: venv under $ANKI_SYNC_DIR, EnvironmentFile under $ANKI_SYNC_ENV_DIR (chmod 600 since it holds plaintext sync passwords), multi-user support via SYNC_USER1/2/... Lets Anki desktop/AnkiDroid clients on the LAN sync flashcards against this kiosk instead of AnkiWeb. Added to the existing Addons menu rather than a new top-level category - Addons is already a flat mix of unrelated self-hosted services (printing, telephony, media server, remote access), so one more service fits that pattern instead of fragmenting the menu structure for a single item. Wired into complete_uninstall.sh, clone_settings.sh's addon detection/checklist, and the webui helper script's source list (which documents itself as mirroring install.sh's sources exactly). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NoqDrsMq2dazAwGU1AodNc
This commit is contained in:
@@ -1291,6 +1291,17 @@ terminal menu and the web UI, so they can't drift apart).
|
|||||||
steps, so any code or hardware-config change picked up by the pull
|
steps, so any code or hardware-config change picked up by the pull
|
||||||
actually takes effect. Also offers an on-demand Electron version
|
actually takes effect. Also offers an on-demand Electron version
|
||||||
check. See "Recent Updates (v2.15.0)" below.
|
check. See "Recent Updates (v2.15.0)" below.
|
||||||
|
- `menus/addon_anki_sync.sh` — **Anki Sync Server** (Addons): installs
|
||||||
|
the official self-hosted Anki sync server (`python -m
|
||||||
|
anki.syncserver`, from the `anki` PyPI package, no Qt/GUI deps) in
|
||||||
|
its own venv under `$ANKI_SYNC_DIR`, run as a systemd service under
|
||||||
|
`$KIOSK_USER`, same shape as the Web UI's Node app. Lets Anki desktop/
|
||||||
|
AnkiDroid clients on the LAN sync flashcards (simple vocab/math-fact
|
||||||
|
decks or complex custom note types) against this kiosk instead of
|
||||||
|
AnkiWeb; supports multiple sync accounts (`SYNC_USER1`, `SYNC_USER2`,
|
||||||
|
...). Quizlet sets still go through Anki's own tab-separated import
|
||||||
|
first — this server only syncs the resulting collection, same as it
|
||||||
|
would with AnkiWeb.
|
||||||
- `webui/` + `menus/addon_webui.sh` — **Web UI** (Addons, installed by
|
- `webui/` + `menus/addon_webui.sh` — **Web UI** (Addons, installed by
|
||||||
default during provisioning): a small Node/Express app running as a
|
default during provisioning): a small Node/Express app running as a
|
||||||
systemd service under `$KIOSK_USER` with zero ambient `sudo`, giving
|
systemd service under `$KIOSK_USER` with zero ambient `sudo`, giving
|
||||||
|
|||||||
+8
-3
@@ -37,7 +37,10 @@
|
|||||||
# Display/Lockout, the webui/ Node app installed as a systemd
|
# Display/Lockout, the webui/ Node app installed as a systemd
|
||||||
# service; no login of its own, put it behind your own reverse proxy
|
# service; no login of its own, put it behind your own reverse proxy
|
||||||
# with Authelia forward-auth if it needs to be reachable beyond a
|
# with Authelia forward-auth if it needs to be reachable beyond a
|
||||||
# trusted LAN - see the file header).
|
# trusted LAN - see the file header), Anki Sync Server
|
||||||
|
# (menus/addon_anki_sync.sh - self-hosted flashcard sync via the
|
||||||
|
# official `python -m anki.syncserver`, for Anki desktop/AnkiDroid
|
||||||
|
# clients on the LAN instead of AnkiWeb).
|
||||||
# Advanced: Diagnostics (menus/diagnostics.sh - system status/logs/
|
# Advanced: Diagnostics (menus/diagnostics.sh - system status/logs/
|
||||||
# audio/network), Electron Maintenance (menus/advanced_electron.sh -
|
# audio/network), Electron Maintenance (menus/advanced_electron.sh -
|
||||||
# manual update, fix blank screen), Factory Reset
|
# manual update, fix blank screen), Factory Reset
|
||||||
@@ -94,6 +97,8 @@ source "$SCRIPT_DIR/menus/addon_lms_squeezelite.sh"
|
|||||||
source "$SCRIPT_DIR/menus/addon_asterisk_intercom.sh"
|
source "$SCRIPT_DIR/menus/addon_asterisk_intercom.sh"
|
||||||
# shellcheck source=menus/addon_webui.sh
|
# shellcheck source=menus/addon_webui.sh
|
||||||
source "$SCRIPT_DIR/menus/addon_webui.sh"
|
source "$SCRIPT_DIR/menus/addon_webui.sh"
|
||||||
|
# shellcheck source=menus/addon_anki_sync.sh
|
||||||
|
source "$SCRIPT_DIR/menus/addon_anki_sync.sh"
|
||||||
# shellcheck source=menus/advanced_electron.sh
|
# shellcheck source=menus/advanced_electron.sh
|
||||||
source "$SCRIPT_DIR/menus/advanced_electron.sh"
|
source "$SCRIPT_DIR/menus/advanced_electron.sh"
|
||||||
# shellcheck source=menus/advanced_upgrade.sh
|
# shellcheck source=menus/advanced_upgrade.sh
|
||||||
@@ -174,8 +179,8 @@ core_settings_menu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addons_menu_builder() {
|
addons_menu_builder() {
|
||||||
MENU_LABELS=("CUPS Printing" "Authelia Auto-Login" "Remote Access" "LMS Server / Squeezelite Player" "Asterisk Intercom (SIP Extension)" "Web UI")
|
MENU_LABELS=("CUPS Printing" "Authelia Auto-Login" "Remote Access" "LMS Server / Squeezelite Player" "Asterisk Intercom (SIP Extension)" "Web UI" "Anki Sync Server")
|
||||||
MENU_HANDLERS=(addon_cups_menu addon_authelia_menu remote_access_menu addon_lms_squeezelite_menu addon_asterisk_intercom_menu addon_webui_menu)
|
MENU_HANDLERS=(addon_cups_menu addon_authelia_menu remote_access_menu addon_lms_squeezelite_menu addon_asterisk_intercom_menu addon_webui_menu addon_anki_sync_menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
addons_menu() {
|
addons_menu() {
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
: "${WIREGUARD_DIR:=/etc/wireguard}"
|
: "${WIREGUARD_DIR:=/etc/wireguard}"
|
||||||
: "${WEBUI_DIR:=/opt/kiosk-webui}"
|
: "${WEBUI_DIR:=/opt/kiosk-webui}"
|
||||||
: "${WEBUI_ENV_DIR:=/etc/kiosk-webui}"
|
: "${WEBUI_ENV_DIR:=/etc/kiosk-webui}"
|
||||||
|
: "${ANKI_SYNC_DIR:=/opt/kiosk-anki-sync}"
|
||||||
|
: "${ANKI_SYNC_ENV_DIR:=/etc/kiosk-anki-sync}"
|
||||||
: "${SUDOERS_D_DIR:=/etc/sudoers.d}"
|
: "${SUDOERS_D_DIR:=/etc/sudoers.d}"
|
||||||
# The one root-owned script the Web UI's addon-install/Update actions are
|
# The one root-owned script the Web UI's addon-install/Update actions are
|
||||||
# allowed to invoke via passwordless sudo (menus/addon_webui.sh writes it
|
# allowed to invoke via passwordless sudo (menus/addon_webui.sh writes it
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/addon_anki_sync.sh - "Anki Sync Server" addon.
|
||||||
|
#
|
||||||
|
# Self-hosted flashcard sync, as an alternative to AnkiWeb - lets Anki
|
||||||
|
# desktop and AnkiDroid clients on the LAN sync their collection (any
|
||||||
|
# note type: simple vocab/math-fact cards or complex custom templates)
|
||||||
|
# against this kiosk instead of ankiweb.net. Standard Anki-format decks
|
||||||
|
# import/export as .apkg from the desktop client; Quizlet sets round-trip
|
||||||
|
# through Anki via tab-separated text (Quizlet's own export/import), so
|
||||||
|
# this server doesn't need to know anything about that format itself -
|
||||||
|
# it only syncs whatever collection the client already has.
|
||||||
|
#
|
||||||
|
# Installed the officially documented way (docs.ankiweb.net/sync-server.html):
|
||||||
|
# the real "anki" package from PyPI, run headless as `python -m
|
||||||
|
# anki.syncserver` - no Qt/GUI deps, since that module never imports the
|
||||||
|
# desktop app's aqt package. Runs in its own venv under $ANKI_SYNC_DIR
|
||||||
|
# (lib/config.sh) as $KIOSK_USER, same shape as menus/addon_webui.sh's
|
||||||
|
# Node app: an EnvironmentFile under $ANKI_SYNC_ENV_DIR feeding a
|
||||||
|
# systemd unit's ExecStart, both parameterized so tests can point them
|
||||||
|
# at a scratch directory instead of the real system.
|
||||||
|
#
|
||||||
|
# Unlike webui.env, anki-sync.env holds plaintext sync passwords
|
||||||
|
# (SYNC_USER1=user:pass, ... - the only form the upstream server
|
||||||
|
# accepts), so it's written chmod 600/root:root rather than left at the
|
||||||
|
# default `sudo tee` mode - systemd reads EnvironmentFile as root before
|
||||||
|
# dropping to User=$KIOSK_USER, so the service never needs read access
|
||||||
|
# to it itself.
|
||||||
|
#
|
||||||
|
# No login/HTTPS beyond that account+password, same caveat as every
|
||||||
|
# other plain self-hosted addon here (CUPS, LMS, Web UI): fine on a
|
||||||
|
# trusted LAN, put it behind a reverse proxy with TLS if it needs to be
|
||||||
|
# reachable beyond one.
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
anki_sync_is_installed() {
|
||||||
|
[[ -f "$SYSTEMD_DIR/kiosk-anki-sync.service" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
anki_sync_is_active() {
|
||||||
|
systemctl is-active --quiet kiosk-anki-sync
|
||||||
|
}
|
||||||
|
|
||||||
|
anki_sync_env_file() {
|
||||||
|
echo "$ANKI_SYNC_ENV_DIR/anki-sync.env"
|
||||||
|
}
|
||||||
|
|
||||||
|
anki_sync_port() {
|
||||||
|
sudo grep -oP '^SYNC_PORT=\K.*' "$(anki_sync_env_file)" 2>/dev/null || echo "8080"
|
||||||
|
}
|
||||||
|
|
||||||
|
# No `|| echo 0` here: `grep -c` on a file with zero matches still
|
||||||
|
# prints "0" to stdout while exiting non-zero, so chaining `||` would
|
||||||
|
# run *both* branches and return "0\n0" - the pattern menus/
|
||||||
|
# addon_webui.sh's webui_port() uses safely only works because `-oP`
|
||||||
|
# prints nothing at all on no match.
|
||||||
|
anki_sync_user_count() {
|
||||||
|
local count
|
||||||
|
count=$(sudo grep -c '^SYNC_USER[0-9]\+=' "$(anki_sync_env_file)" 2>/dev/null)
|
||||||
|
echo "${count:-0}"
|
||||||
|
}
|
||||||
|
|
||||||
|
addon_anki_sync_status() {
|
||||||
|
if anki_sync_is_installed && anki_sync_is_active; then
|
||||||
|
echo "Anki Sync Server: running at http://$(get_ip_address):$(anki_sync_port)/"
|
||||||
|
echo " Sync users: $(anki_sync_user_count)"
|
||||||
|
elif anki_sync_is_installed; then
|
||||||
|
echo "Anki Sync Server: installed but not running"
|
||||||
|
else
|
||||||
|
echo "Anki Sync Server: not installed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
addon_anki_sync_menu_builder() {
|
||||||
|
if anki_sync_is_installed; then
|
||||||
|
MENU_LABELS=("Add another sync user" "Change port" "Restart service" "Uninstall")
|
||||||
|
MENU_HANDLERS=(action_anki_sync_add_user action_anki_sync_reconfigure_port action_anki_sync_restart action_anki_sync_uninstall)
|
||||||
|
else
|
||||||
|
MENU_LABELS=("Install Anki Sync Server")
|
||||||
|
MENU_HANDLERS=(action_anki_sync_install)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
addon_anki_sync_menu() {
|
||||||
|
run_menu "ANKI SYNC SERVER" addon_anki_sync_menu_builder addon_anki_sync_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Shared install helpers
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
anki_sync_ask_username() {
|
||||||
|
local default="$1"
|
||||||
|
local uname
|
||||||
|
while true; do
|
||||||
|
uname=$(ask_text "Username" "$default")
|
||||||
|
if [[ -z "$uname" ]]; then
|
||||||
|
echo "❌ Username cannot be empty" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$uname" == *:* ]]; then
|
||||||
|
echo "❌ Username cannot contain ':'" >&2
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "$uname"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prompts twice (masked) and returns the password once both entries
|
||||||
|
# match - same shape as menus/lockout.sh's prompt_and_hash_password,
|
||||||
|
# but returned as plain text (not hashed): the upstream sync server
|
||||||
|
# authenticates against the plaintext SYNC_USERn value itself.
|
||||||
|
anki_sync_ask_password() {
|
||||||
|
local pass1 pass2
|
||||||
|
while true; do
|
||||||
|
read -r -s -p "Password for $1: " pass1
|
||||||
|
echo
|
||||||
|
read -r -s -p "Confirm password: " pass2
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -z "$pass1" ]]; then
|
||||||
|
echo "❌ Password cannot be empty"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ "$pass1" != "$pass2" ]]; then
|
||||||
|
echo "❌ Passwords do not match"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "$pass1"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
anki_sync_write_base_env() {
|
||||||
|
local port="$1"
|
||||||
|
sudo mkdir -p "$ANKI_SYNC_ENV_DIR"
|
||||||
|
sudo tee "$(anki_sync_env_file)" > /dev/null <<EOF
|
||||||
|
SYNC_BASE=${ANKI_SYNC_DIR}/data
|
||||||
|
SYNC_HOST=0.0.0.0
|
||||||
|
SYNC_PORT=${port}
|
||||||
|
EOF
|
||||||
|
sudo chown root:root "$(anki_sync_env_file)"
|
||||||
|
sudo chmod 600 "$(anki_sync_env_file)"
|
||||||
|
}
|
||||||
|
|
||||||
|
anki_sync_append_user_line() {
|
||||||
|
local index="$1" username="$2" password="$3"
|
||||||
|
echo "SYNC_USER${index}=${username}:${password}" | sudo tee -a "$(anki_sync_env_file)" > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
anki_sync_write_unit_file() {
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-anki-sync.service" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Anki Sync Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=${KIOSK_USER}
|
||||||
|
EnvironmentFile=${ANKI_SYNC_ENV_DIR}/anki-sync.env
|
||||||
|
ExecStart=${ANKI_SYNC_DIR}/bin/python -m anki.syncserver
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Actions
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_anki_sync_install() {
|
||||||
|
echo
|
||||||
|
echo "This installs a self-hosted Anki sync server (the official"
|
||||||
|
echo "'python -m anki.syncserver', from the 'anki' package on PyPI)"
|
||||||
|
echo "so Anki desktop / AnkiDroid on your LAN can sync flashcards to"
|
||||||
|
echo "this kiosk instead of AnkiWeb. Plain vocab/math-fact cards and"
|
||||||
|
echo "complex custom note types both sync fine - it just stores"
|
||||||
|
echo "whatever collection the client already has. Quizlet decks"
|
||||||
|
echo "still need to go through Anki's own tab-separated import first"
|
||||||
|
echo "(File > Import in the desktop app) - this server only syncs"
|
||||||
|
echo "the resulting collection."
|
||||||
|
echo
|
||||||
|
ask_yes_no "Install the Anki Sync Server?" "n" || { echo "Cancelled"; return; }
|
||||||
|
|
||||||
|
echo "Installing python3-venv..."
|
||||||
|
if ! sudo apt-get update -qq || ! sudo apt-get install -y python3-venv python3-pip; then
|
||||||
|
log_error "Failed to install python3-venv/python3-pip"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Creating virtual environment..."
|
||||||
|
sudo mkdir -p "$ANKI_SYNC_DIR"
|
||||||
|
sudo chown -R "$KIOSK_USER:$KIOSK_USER" "$ANKI_SYNC_DIR"
|
||||||
|
if ! sudo -u "$KIOSK_USER" python3 -m venv "$ANKI_SYNC_DIR"; then
|
||||||
|
log_error "Failed to create virtual environment"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Debian/Ubuntu's python3-venv doesn't always bootstrap pip into a
|
||||||
|
# fresh venv on its own (ensurepip behavior varies by release) - fall
|
||||||
|
# back to ensurepip explicitly rather than failing the whole install
|
||||||
|
# over it.
|
||||||
|
if ! sudo -u "$KIOSK_USER" test -x "$ANKI_SYNC_DIR/bin/pip"; then
|
||||||
|
sudo -u "$KIOSK_USER" "$ANKI_SYNC_DIR/bin/python" -m ensurepip --upgrade
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing the 'anki' package (this can take a minute)..."
|
||||||
|
if ! sudo -u "$KIOSK_USER" "$ANKI_SYNC_DIR/bin/pip" install --upgrade pip anki; then
|
||||||
|
log_error "pip install anki failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Set up the first sync account (used by Anki clients to log in):"
|
||||||
|
local username password port
|
||||||
|
username=$(anki_sync_ask_username "kiosk")
|
||||||
|
password=$(anki_sync_ask_password "$username")
|
||||||
|
port=$(ask_integer "Port to listen on" "8080" 1024 65535)
|
||||||
|
|
||||||
|
sudo mkdir -p "$ANKI_SYNC_DIR/data"
|
||||||
|
sudo chown "$KIOSK_USER:$KIOSK_USER" "$ANKI_SYNC_DIR/data"
|
||||||
|
|
||||||
|
anki_sync_write_base_env "$port"
|
||||||
|
anki_sync_append_user_line 1 "$username" "$password"
|
||||||
|
anki_sync_write_unit_file
|
||||||
|
|
||||||
|
if enable_and_start_units kiosk-anki-sync; then
|
||||||
|
sudo ufw allow "${port}/tcp" comment 'Anki Sync Server' 2>/dev/null || true
|
||||||
|
log_success "Anki Sync Server installed: http://$(get_ip_address):${port}/"
|
||||||
|
echo " In Anki desktop: Preferences > Network > Self-hosted sync server"
|
||||||
|
echo " Log in with: $username"
|
||||||
|
else
|
||||||
|
log_error "Installed but failed to start - check: sudo journalctl -u kiosk-anki-sync -n 50"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
action_anki_sync_add_user() {
|
||||||
|
echo
|
||||||
|
local next_index
|
||||||
|
next_index=$(( $(anki_sync_user_count) + 1 ))
|
||||||
|
|
||||||
|
local username password
|
||||||
|
username=$(anki_sync_ask_username "user${next_index}")
|
||||||
|
password=$(anki_sync_ask_password "$username")
|
||||||
|
|
||||||
|
anki_sync_append_user_line "$next_index" "$username" "$password"
|
||||||
|
sudo systemctl restart kiosk-anki-sync
|
||||||
|
log_success "Added sync user: $username"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_anki_sync_reconfigure_port() {
|
||||||
|
echo
|
||||||
|
local current_port
|
||||||
|
current_port=$(anki_sync_port)
|
||||||
|
local port
|
||||||
|
port=$(ask_integer "Port to listen on" "$current_port" 1024 65535)
|
||||||
|
|
||||||
|
if [[ "$port" == "$current_port" ]]; then
|
||||||
|
echo "No change"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo sed -i "s/^SYNC_PORT=.*/SYNC_PORT=${port}/" "$(anki_sync_env_file)"
|
||||||
|
sudo systemctl restart kiosk-anki-sync
|
||||||
|
sudo ufw allow "${port}/tcp" comment 'Anki Sync Server' 2>/dev/null || true
|
||||||
|
log_success "Anki Sync Server now listening on port ${port}"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_anki_sync_restart() {
|
||||||
|
sudo systemctl restart kiosk-anki-sync
|
||||||
|
sleep 1
|
||||||
|
if anki_sync_is_active; then
|
||||||
|
log_success "Anki Sync Server restarted"
|
||||||
|
else
|
||||||
|
log_error "Anki Sync Server failed to restart - check: sudo journalctl -u kiosk-anki-sync -n 50"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
action_anki_sync_uninstall() {
|
||||||
|
echo
|
||||||
|
ask_yes_no "Uninstall the Anki Sync Server? This deletes the synced collection data on this kiosk." "n" || { echo "Cancelled"; return; }
|
||||||
|
anki_sync_do_uninstall
|
||||||
|
}
|
||||||
|
|
||||||
|
# The actual removal, no prompt - shared with Complete Uninstall so that
|
||||||
|
# operation doesn't need to re-implement teardown a second time.
|
||||||
|
anki_sync_do_uninstall() {
|
||||||
|
sudo systemctl stop kiosk-anki-sync 2>/dev/null || true
|
||||||
|
sudo systemctl disable kiosk-anki-sync 2>/dev/null || true
|
||||||
|
sudo rm -f "$SYSTEMD_DIR/kiosk-anki-sync.service"
|
||||||
|
sudo systemctl daemon-reload 2>/dev/null || true
|
||||||
|
sudo rm -rf "$ANKI_SYNC_ENV_DIR" "$ANKI_SYNC_DIR"
|
||||||
|
log_success "Anki Sync Server removed"
|
||||||
|
}
|
||||||
@@ -172,6 +172,7 @@ source "$SCRIPT_DIR/menus/addon_remote_access.sh"
|
|||||||
source "$SCRIPT_DIR/menus/addon_lms_squeezelite.sh"
|
source "$SCRIPT_DIR/menus/addon_lms_squeezelite.sh"
|
||||||
source "$SCRIPT_DIR/menus/addon_asterisk_intercom.sh"
|
source "$SCRIPT_DIR/menus/addon_asterisk_intercom.sh"
|
||||||
source "$SCRIPT_DIR/menus/addon_webui.sh"
|
source "$SCRIPT_DIR/menus/addon_webui.sh"
|
||||||
|
source "$SCRIPT_DIR/menus/addon_anki_sync.sh"
|
||||||
source "$SCRIPT_DIR/menus/advanced_electron.sh"
|
source "$SCRIPT_DIR/menus/advanced_electron.sh"
|
||||||
source "$SCRIPT_DIR/menus/advanced_upgrade.sh"
|
source "$SCRIPT_DIR/menus/advanced_upgrade.sh"
|
||||||
source "$SCRIPT_DIR/menus/advanced_factory_reset.sh"
|
source "$SCRIPT_DIR/menus/advanced_factory_reset.sh"
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ clone_detect_addons() {
|
|||||||
command -v netbird &>/dev/null && addons+=("netbird")
|
command -v netbird &>/dev/null && addons+=("netbird")
|
||||||
baresip_is_installed 2>/dev/null && addons+=("asterisk_intercom")
|
baresip_is_installed 2>/dev/null && addons+=("asterisk_intercom")
|
||||||
webui_is_installed 2>/dev/null && addons+=("webui")
|
webui_is_installed 2>/dev/null && addons+=("webui")
|
||||||
|
anki_sync_is_installed 2>/dev/null && addons+=("anki_sync")
|
||||||
|
|
||||||
if sudo -u "$KIOSK_USER" test -f "$CONFIG_PATH" 2>/dev/null; then
|
if sudo -u "$KIOSK_USER" test -f "$CONFIG_PATH" 2>/dev/null; then
|
||||||
local authelia_url
|
local authelia_url
|
||||||
@@ -243,6 +244,13 @@ action_apply_clone_settings() {
|
|||||||
fi ;;
|
fi ;;
|
||||||
asterisk_intercom)
|
asterisk_intercom)
|
||||||
echo " [!] Asterisk Intercom - needs its own SIP extension on this device" ;;
|
echo " [!] Asterisk Intercom - needs its own SIP extension on this device" ;;
|
||||||
|
anki_sync)
|
||||||
|
if anki_sync_is_installed; then
|
||||||
|
echo " [x] Anki Sync Server - already installed"
|
||||||
|
else
|
||||||
|
echo " [ ] Anki Sync Server - not installed, install via Addons"
|
||||||
|
missing_any=true
|
||||||
|
fi ;;
|
||||||
authelia)
|
authelia)
|
||||||
echo " [!] Authelia - needs a fresh login on this device" ;;
|
echo " [!] Authelia - needs a fresh login on this device" ;;
|
||||||
*)
|
*)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
# (cups_do_uninstall, vnc_do_uninstall, wireguard_do_uninstall,
|
# (cups_do_uninstall, vnc_do_uninstall, wireguard_do_uninstall,
|
||||||
# tailscale_do_uninstall, netbird_do_uninstall, lms_do_uninstall,
|
# tailscale_do_uninstall, netbird_do_uninstall, lms_do_uninstall,
|
||||||
# squeezelite_do_uninstall, asterisk_intercom_do_uninstall,
|
# squeezelite_do_uninstall, asterisk_intercom_do_uninstall,
|
||||||
# webui_do_uninstall, power_schedule_do_remove_all,
|
# webui_do_uninstall, anki_sync_do_uninstall, power_schedule_do_remove_all,
|
||||||
# emergency_hotspot_do_disable) instead of
|
# emergency_hotspot_do_disable) instead of
|
||||||
# re-implementing removal logic for each addon a second time here - if an
|
# re-implementing removal logic for each addon a second time here - if an
|
||||||
# addon's uninstall logic changes, this picks it up automatically. Only
|
# addon's uninstall logic changes, this picks it up automatically. Only
|
||||||
@@ -59,6 +59,7 @@ action_complete_uninstall() {
|
|||||||
echo " • Remote access (VNC, WireGuard, Tailscale, Netbird)"
|
echo " • Remote access (VNC, WireGuard, Tailscale, Netbird)"
|
||||||
echo " • Asterisk Intercom (Baresip)"
|
echo " • Asterisk Intercom (Baresip)"
|
||||||
echo " • Web UI"
|
echo " • Web UI"
|
||||||
|
echo " • Anki Sync Server"
|
||||||
echo " • LightDM and Openbox"
|
echo " • LightDM and Openbox"
|
||||||
echo " • All kiosk schedules and services"
|
echo " • All kiosk schedules and services"
|
||||||
echo " • Emergency hotspot configuration"
|
echo " • Emergency hotspot configuration"
|
||||||
@@ -89,6 +90,7 @@ action_complete_uninstall() {
|
|||||||
squeezelite_do_uninstall
|
squeezelite_do_uninstall
|
||||||
asterisk_intercom_do_uninstall purge
|
asterisk_intercom_do_uninstall purge
|
||||||
webui_do_uninstall
|
webui_do_uninstall
|
||||||
|
anki_sync_do_uninstall
|
||||||
|
|
||||||
echo "[3/12] Removing schedules and emergency hotspot..."
|
echo "[3/12] Removing schedules and emergency hotspot..."
|
||||||
power_schedule_do_remove_all
|
power_schedule_do_remove_all
|
||||||
|
|||||||
Reference in New Issue
Block a user