librelad d3faa2514f feat(backup): SSH key card in the sftp location editor
When a location uses SSH key auth, show a key card: paste an existing private
key, or 'Generate keypair', then the card displays the public key to copy into
the remote server's authorized_keys (with Copy/Delete). Wires to the
ssh-key-set/generate/delete CLI; key mutations refresh locations.json so the
card reflects state immediately. applySshAuthVisibility toggles the card vs the
password field by auth mode. Private key only ever flows in (base64); only the
public key is ever shown.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 16:17:34 +01:00

114 lines
3.2 KiB
Bash

#!/bin/bash
# LibrePortal-managed SSH key store for backup locations. Each location's
# private key lives at configs/backup/locations/<idx>/ssh.key. Public key
# is derived on the fly via ssh-keygen -y, so we never persist it
# separately. The CFG_BACKUP_LOC_<idx>_SSH_AUTH selector (key|password)
# is the only ssh-related value in the location.config file.
# Refresh locations.json so the editor reflects new key state (public key /
# "key set" status) right after a mutation. No-op if the generator isn't loaded.
backupSshKeyRefreshUi()
{
declare -f webuiGenerateBackupLocations >/dev/null 2>&1 && webuiGenerateBackupLocations >/dev/null 2>&1
return 0
}
backupSshKeyFile()
{
local idx="$1"
backupLocationSshKey "$idx"
}
backupSshKeyExists()
{
local idx="$1"
[[ -f "$(backupSshKeyFile "$idx")" ]]
}
# Decode a base64-encoded private key and write it to the location's key
# file. The frontend base64-encodes pasted multi-line keys so we can ship
# them through the existing single-line task command pipeline.
backupSshKeySet()
{
local idx="$1"
local key_b64="$2"
if [[ -z "$idx" || -z "$key_b64" ]]; then
isError "backupSshKeySet requires <idx> <base64-key>"
return 1
fi
backupLocationEnsureDir "$idx"
local owner
owner=$(backupLocationOwner)
local key_file
key_file=$(backupSshKeyFile "$idx")
local decoded
decoded=$(echo "$key_b64" | base64 -d 2>/dev/null)
if [[ -z "$decoded" ]]; then
isError "Failed to base64-decode the supplied key"
return 1
fi
echo "$decoded" | sudo tee "$key_file" >/dev/null
sudo chown "$owner":"$owner" "$key_file"
sudo chmod 0600 "$key_file"
if ! sudo -u "$owner" ssh-keygen -y -f "$key_file" >/dev/null 2>&1; then
isError "Key written but ssh-keygen can't read it — check the format (OpenSSH PEM: ed25519, rsa, ecdsa)"
return 1
fi
isSuccessful "SSH key saved for location $idx"
backupSshKeyRefreshUi
}
backupSshKeyGenerate()
{
local idx="$1"
if [[ -z "$idx" ]]; then
isError "backupSshKeyGenerate requires <idx>"
return 1
fi
backupLocationEnsureDir "$idx"
local owner
owner=$(backupLocationOwner)
local key_file
key_file=$(backupSshKeyFile "$idx")
if [[ -f "$key_file" ]]; then
sudo rm -f "$key_file" "${key_file}.pub"
fi
sudo -u "$owner" ssh-keygen -t ed25519 -f "$key_file" -N "" -C "libreportal-loc-${idx}" -q
sudo chmod 0600 "$key_file"
[[ -f "${key_file}.pub" ]] && sudo rm -f "${key_file}.pub" # we re-derive when needed
isSuccessful "Generated ed25519 keypair for location $idx"
isNotice "Public key (paste into the remote host's ~/.ssh/authorized_keys):"
backupSshKeyPublic "$idx"
backupSshKeyRefreshUi
}
backupSshKeyPublic()
{
local idx="$1"
local key_file
key_file=$(backupSshKeyFile "$idx")
[[ -f "$key_file" ]] || return 1
sudo -u "$(backupLocationOwner)" ssh-keygen -y -f "$key_file" 2>/dev/null
}
backupSshKeyDelete()
{
local idx="$1"
local key_file
key_file=$(backupSshKeyFile "$idx")
[[ -f "$key_file" ]] && sudo rm -f "$key_file" "${key_file}.pub"
isSuccessful "SSH key removed for location $idx"
backupSshKeyRefreshUi
}