improvements to configure
This commit is contained in:
+196
-9
@@ -3,11 +3,30 @@
|
||||
TITLE="Federated Computer Core"
|
||||
DESCRIPTION="Superpanel, Nextcloud, email, files and Element"
|
||||
|
||||
# What you entered last time, so running this again only does what's missing
|
||||
# Each Core is in cores/<domain>.conf, with what you entered last time, so
|
||||
# running this again only does what's missing. core.conf is a copy of the main
|
||||
# Core's: Element, Tailscale and Backup use that one.
|
||||
CORE_CONF="$HOME/.config/coredesktop/core.conf"
|
||||
CORES_DIR="$HOME/.config/coredesktop/cores"
|
||||
TAILSCALE_CONF="$HOME/.config/coredesktop/tailscale.conf"
|
||||
BACKUP_CONF="$HOME/.config/coredesktop/backup.conf"
|
||||
GOA_ACCOUNT="$COREDESKTOP/configure.d/lib/goa-account"
|
||||
source "$COREDESKTOP/configure.d/lib/nextcloud.sh"
|
||||
[ -f "$CORE_CONF" ] && source "$CORE_CONF"
|
||||
MAIN_DOMAIN="$CORE_DOMAIN"
|
||||
|
||||
core_domains() {
|
||||
local file
|
||||
for file in "$CORES_DIR"/*.conf; do
|
||||
[ -f "$file" ] && basename "$file" .conf
|
||||
done
|
||||
}
|
||||
|
||||
load_core() {
|
||||
CORE_DOMAIN="" CORE_EMAIL="" CORE_NAME=""
|
||||
[ -n "$1" ] && source "$CORES_DIR/$1.conf"
|
||||
core_names
|
||||
}
|
||||
|
||||
# CORE_DOMAIN, CORE_EMAIL and CORE_NAME come from core.conf; the rest follow
|
||||
core_names() {
|
||||
@@ -48,19 +67,37 @@ element_done() {
|
||||
"$ELEMENT_CONFIG" &> /dev/null
|
||||
}
|
||||
|
||||
is_configured() {
|
||||
[ -n "$CORE_DOMAIN" ] || return 1
|
||||
core_names
|
||||
superpanel_done && nextcloud_done && email_done && files_done && element_done
|
||||
# Element only follows the main Core
|
||||
core_done() {
|
||||
superpanel_done && nextcloud_done && email_done && files_done \
|
||||
&& { [ "$CORE_DOMAIN" != "$MAIN_DOMAIN" ] || element_done; }
|
||||
}
|
||||
|
||||
# Done when every Core is
|
||||
is_configured() {
|
||||
local domain domains
|
||||
domains="$(core_domains)"
|
||||
[ -n "$domains" ] || return 1
|
||||
for domain in $domains; do
|
||||
(load_core "$domain" && core_done) || return 1
|
||||
done
|
||||
}
|
||||
|
||||
# The first Core you set up is the main one
|
||||
save_conf() {
|
||||
mkdir -p "$(dirname "$CORE_CONF")"
|
||||
mkdir -p "$CORES_DIR"
|
||||
{
|
||||
printf 'CORE_DOMAIN=%q\n' "$CORE_DOMAIN"
|
||||
printf 'CORE_EMAIL=%q\n' "$CORE_EMAIL"
|
||||
printf 'CORE_NAME=%q\n' "$CORE_NAME"
|
||||
} > "$CORE_CONF"
|
||||
} > "$CORES_DIR/$CORE_DOMAIN.conf"
|
||||
if [ -z "$MAIN_DOMAIN" ] || [ "$MAIN_DOMAIN" = "$CORE_DOMAIN" ]; then
|
||||
make_main "$CORE_DOMAIN"
|
||||
fi
|
||||
}
|
||||
|
||||
make_main() {
|
||||
cp "$CORES_DIR/$1.conf" "$CORE_CONF" && MAIN_DOMAIN="$1"
|
||||
}
|
||||
|
||||
desktop_entry() {
|
||||
@@ -263,7 +300,8 @@ step() {
|
||||
"setup_$name" || { echo -e "\e[31m$title was not set up.\e[0m"; STATUS=1; }
|
||||
}
|
||||
|
||||
run_configure() {
|
||||
# Set up the Core loaded with load_core, or a new one
|
||||
setup_core() {
|
||||
local url
|
||||
STATUS=0
|
||||
|
||||
@@ -304,7 +342,156 @@ run_configure() {
|
||||
step nextcloud "Nextcloud in Online Accounts"
|
||||
step email "Email in Online Accounts"
|
||||
step files "Nextcloud files at $MOUNT"
|
||||
element_installed && step element "Element with your Core's Matrix server"
|
||||
[ "$CORE_DOMAIN" = "$MAIN_DOMAIN" ] && element_installed \
|
||||
&& step element "Element with your Core's Matrix server"
|
||||
unset PASSWORD APP_PASSWORD
|
||||
return $STATUS
|
||||
}
|
||||
|
||||
# Tailscale and Backup point at one Core, which can't be removed while they do
|
||||
tailscale_on() {
|
||||
[ "$(source "$TAILSCALE_CONF" 2> /dev/null && echo "$TAILSCALE_DOMAIN")" = "$1" ] \
|
||||
&& tailscale status --json 2> /dev/null | jq -e '.BackendState == "Running"' > /dev/null
|
||||
}
|
||||
|
||||
backup_on() {
|
||||
[[ "$(source "$BACKUP_CONF" 2> /dev/null && echo "$BACKUP_WEBDAV")" == "https://nextcloud.$1/"* ]] \
|
||||
&& systemctl --user is-enabled -q coredesktop-backup.timer 2> /dev/null
|
||||
}
|
||||
|
||||
# Choose one of DOMAINS, other than $2 if given; sets PICKED
|
||||
pick_core() {
|
||||
local choices=() domain choice i
|
||||
for domain in "${DOMAINS[@]}"; do
|
||||
[ "$domain" != "$2" ] && choices+=("$domain")
|
||||
done
|
||||
if [ ${#choices[@]} -eq 1 ]; then
|
||||
PICKED="${choices[0]}"
|
||||
return 0
|
||||
fi
|
||||
for i in "${!choices[@]}"; do
|
||||
printf ' %d) %s\n' $((i + 1)) "${choices[$i]}"
|
||||
done
|
||||
read -rp "$1 " choice
|
||||
[[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#choices[@]} )) || return 1
|
||||
PICKED="${choices[$((choice - 1))]}"
|
||||
}
|
||||
|
||||
# Point Element at the main Core, and say what else follows it
|
||||
main_changed() {
|
||||
load_core "$MAIN_DOMAIN"
|
||||
echo "$MAIN_DOMAIN is now your main Core."
|
||||
if element_installed && ! element_done && confirm_yes "Point Element at $MATRIX?"; then
|
||||
setup_element
|
||||
fi
|
||||
[ -f "$TAILSCALE_CONF" ] && echo "To move this computer to its Tailscale network: configure tailscale"
|
||||
[ -f "$BACKUP_CONF" ] && echo "To back up to it instead: configure backup"
|
||||
return 0
|
||||
}
|
||||
|
||||
# The app password this computer uses for the Core, from davfs2's secrets
|
||||
files_app_password() {
|
||||
grep -s "^$MOUNT " "$HOME/.davfs2/secrets" \
|
||||
| python3 -I -c 'import shlex, sys; print(shlex.split(sys.stdin.read())[2])' 2> /dev/null
|
||||
}
|
||||
|
||||
# Undo what setup_core did. Nothing on the Core itself changes.
|
||||
remove_core() {
|
||||
local domain="$1" new_main="" app_password mount_re
|
||||
if tailscale_on "$domain"; then
|
||||
echo "This computer is on $domain's Tailscale network. Leave it first: configure tailscale"
|
||||
return 1
|
||||
fi
|
||||
if backup_on "$domain"; then
|
||||
echo "Your backups go to $domain. Turn them off first: configure backup"
|
||||
return 1
|
||||
fi
|
||||
if [ "$domain" = "$MAIN_DOMAIN" ] && [ ${#DOMAINS[@]} -gt 1 ]; then
|
||||
echo "$domain is your main Core. Which Core should be the main one instead?"
|
||||
pick_core "Choose:" "$domain" || { echo "Choose a number from the list."; return 1; }
|
||||
new_main="$PICKED"
|
||||
fi
|
||||
echo "This removes $domain's Superpanel, Nextcloud, email and files from this computer."
|
||||
echo "Your files, email and everything else stay on the Core."
|
||||
confirm "Remove $domain?" || return 0
|
||||
|
||||
load_core "$domain"
|
||||
app_password="$(files_app_password)"
|
||||
[ -n "$app_password" ] && nextcloud_revoke_app_password "$app_password"
|
||||
|
||||
mountpoint -q "$MOUNT" && { umount "$MOUNT" 2> /dev/null || sudo umount "$MOUNT"; }
|
||||
mount_re="${MOUNT//./\\.}"
|
||||
if grep -qs " $MOUNT davfs " /etc/fstab; then
|
||||
sudo sed -i "\\# $mount_re davfs #d" /etc/fstab && sudo systemctl daemon-reload
|
||||
fi
|
||||
[ -d "$MOUNT" ] && sudo rmdir "$MOUNT" 2> /dev/null
|
||||
sed -i "\\#^$mount_re #d" "$HOME/.davfs2/secrets" 2> /dev/null
|
||||
sed -i "\\#^file://$mount_re #d" "$HOME/.config/gtk-3.0/bookmarks" 2> /dev/null
|
||||
rm -f "$HOME/.config/autostart/core-$CORE_DOMAIN-files.desktop"
|
||||
|
||||
"$GOA_ACCOUNT" remove owncloud "$CORE_USER@nextcloud.$CORE_DOMAIN"
|
||||
"$GOA_ACCOUNT" remove imap_smtp "$CORE_EMAIL"
|
||||
rm -f "$SUPERPANEL_DESKTOP" "$HOME/.local/share/icons/core-$CORE_DOMAIN-superpanel.png"
|
||||
|
||||
# Element keeps its default server, unless that was this Core
|
||||
if [ "$domain" = "$MAIN_DOMAIN" ] && [ -z "$new_main" ] && [ -f "$ELEMENT_CONFIG" ] \
|
||||
&& jq -e --arg name "matrix.$domain" '.default_server_config["m.homeserver"].server_name == $name' \
|
||||
"$ELEMENT_CONFIG" &> /dev/null; then
|
||||
jq 'del(.default_server_config)' "$ELEMENT_CONFIG" > "$ELEMENT_CONFIG.tmp" \
|
||||
&& mv -f "$ELEMENT_CONFIG.tmp" "$ELEMENT_CONFIG"
|
||||
fi
|
||||
|
||||
rm -f "$CORES_DIR/$domain.conf"
|
||||
echo "Removed $domain from this computer."
|
||||
if [ -n "$new_main" ]; then
|
||||
make_main "$new_main" && main_changed
|
||||
elif [ "$domain" = "$MAIN_DOMAIN" ]; then
|
||||
rm -f "$CORE_CONF"
|
||||
fi
|
||||
}
|
||||
|
||||
run_configure() {
|
||||
local choice i state
|
||||
mapfile -t DOMAINS < <(core_domains)
|
||||
if [ ${#DOMAINS[@]} -eq 0 ]; then
|
||||
load_core ""
|
||||
setup_core
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Your Cores:"
|
||||
for i in "${!DOMAINS[@]}"; do
|
||||
if (load_core "${DOMAINS[$i]}" && core_done); then
|
||||
state="\e[32mdone\e[0m "
|
||||
else
|
||||
state="\e[33mto do\e[0m"
|
||||
fi
|
||||
printf " %d) $state %s%s\n" $((i + 1)) "${DOMAINS[$i]}" \
|
||||
"$([ "${DOMAINS[$i]}" = "$MAIN_DOMAIN" ] && echo " (main)")"
|
||||
done
|
||||
echo
|
||||
echo " Choose a number to finish or change its setup, or:"
|
||||
echo " a) Add a Core"
|
||||
[ ${#DOMAINS[@]} -gt 1 ] && echo " m) Make another Core the main one"
|
||||
echo " r) Remove a Core"
|
||||
echo " q) Leave it as it is"
|
||||
echo
|
||||
read -rp "Choose: " choice
|
||||
case "$choice" in
|
||||
a|A)
|
||||
load_core ""
|
||||
setup_core ;;
|
||||
m|M)
|
||||
[ ${#DOMAINS[@]} -gt 1 ] || return 0
|
||||
pick_core "Which should be the main one?" "$MAIN_DOMAIN" || { echo "Choose a number from the list."; return 1; }
|
||||
make_main "$PICKED" && main_changed ;;
|
||||
r|R)
|
||||
pick_core "Which Core should be removed?" || { echo "Choose a number from the list."; return 1; }
|
||||
remove_core "$PICKED" ;;
|
||||
*)
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#DOMAINS[@]} )); then
|
||||
load_core "${DOMAINS[$((choice - 1))]}"
|
||||
setup_core
|
||||
fi ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -14,12 +14,45 @@ is_installed() {
|
||||
|
||||
# Tailscale has no status field naming its login server, so tailscale.conf
|
||||
# records which Core it was set up for
|
||||
is_configured() {
|
||||
[ -n "$CORE_DOMAIN" ] && [ "$TAILSCALE_DOMAIN" = "$CORE_DOMAIN" ] \
|
||||
joined() {
|
||||
[ -n "$TAILSCALE_DOMAIN" ] \
|
||||
&& tailscale status --json 2> /dev/null | jq -e '.BackendState == "Running"' > /dev/null
|
||||
}
|
||||
|
||||
# Done when on the main Core's network
|
||||
is_configured() {
|
||||
[ -n "$CORE_DOMAIN" ] && [ "$TAILSCALE_DOMAIN" = "$CORE_DOMAIN" ] && joined
|
||||
}
|
||||
|
||||
# Log out rather than only go down, so this computer's key is expired on
|
||||
# headscale and it's off the network until it joins again
|
||||
leave_network() {
|
||||
sudo tailscale logout || return 1
|
||||
rm -f "$TAILSCALE_CONF"
|
||||
echo "This computer has left your Core's Tailscale network. To join again: configure tailscale"
|
||||
}
|
||||
|
||||
run_configure() {
|
||||
local choice
|
||||
if joined &> /dev/null; then
|
||||
echo "This computer is on $TAILSCALE_DOMAIN's Tailscale network (headscale.$TAILSCALE_DOMAIN)."
|
||||
echo
|
||||
if [ "$TAILSCALE_DOMAIN" = "$CORE_DOMAIN" ]; then
|
||||
echo " 1) Join it again"
|
||||
else
|
||||
echo " 1) Join your main Core's network instead (headscale.$CORE_DOMAIN)"
|
||||
fi
|
||||
echo " 2) Leave the network"
|
||||
echo " q) Leave it as it is"
|
||||
echo
|
||||
read -rp "Choose: " choice
|
||||
case "$choice" in
|
||||
1) ;;
|
||||
2) leave_network; return ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -z "$CORE_DOMAIN" ]; then
|
||||
echo "This joins your Core's Tailscale network, so set up your Core first: configure core"
|
||||
return 1
|
||||
|
||||
@@ -13,11 +13,16 @@ is_installed() {
|
||||
command -v restic > /dev/null && command -v rclone > /dev/null && command -v secret-tool > /dev/null
|
||||
}
|
||||
|
||||
is_configured() {
|
||||
backups_on() {
|
||||
source "$COREDESKTOP/system/backup-env.sh" > /dev/null || return 1
|
||||
[ -n "$RESTIC_PASSWORD_COMMAND" ] && systemctl --user is-enabled -q coredesktop-backup.timer
|
||||
}
|
||||
|
||||
# Done when backing up to the main Core
|
||||
is_configured() {
|
||||
backups_on && [ "$BACKUP_WEBDAV" = "https://nextcloud.$CORE_DOMAIN/remote.php/dav/files/${CORE_EMAIL%@*}" ]
|
||||
}
|
||||
|
||||
save_secret() {
|
||||
printf '%s' "$3" | secret-tool store --label "CoreDesktop backup $1 ($2)" \
|
||||
coredesktop backup item "$1" url "$2"
|
||||
@@ -59,8 +64,78 @@ new_passphrase() {
|
||||
save_secret passphrase "$WEBDAV/$BACKUP_DIR" "$pass"
|
||||
}
|
||||
|
||||
# Stop the daily backup and any running now, and remove its units
|
||||
stop_backups() {
|
||||
systemctl --user disable --now coredesktop-backup.timer 2> /dev/null
|
||||
systemctl --user stop coredesktop-backup.service 2> /dev/null
|
||||
rm -f "$UNITS"/coredesktop-backup.{service,timer}
|
||||
systemctl --user daemon-reload
|
||||
}
|
||||
|
||||
# Delete the backup from Nextcloud, then what's on this computer for it:
|
||||
# without the backup, the passphrase and app password are no use
|
||||
delete_backups() {
|
||||
local app_password
|
||||
nextcloud_login || { echo "Couldn't sign in to Nextcloud."; return 1; }
|
||||
echo "Deleting $BACKUP_DIR from your Nextcloud files..."
|
||||
rclone purge ":webdav:$BACKUP_DIR" \
|
||||
|| { echo "rclone couldn't delete $BACKUP_DIR."; return 1; }
|
||||
echo "Deleted. Nextcloud keeps it in its trash bin, where it still uses space, until you empty it."
|
||||
|
||||
app_password="$(backup_secret nextcloud "$WEBDAV")"
|
||||
[ -n "$app_password" ] && nextcloud_revoke_app_password "$app_password"
|
||||
secret-tool clear coredesktop backup item nextcloud url "$WEBDAV"
|
||||
secret-tool clear coredesktop backup item passphrase url "$WEBDAV/$BACKUP_DIR"
|
||||
rm -f "$BACKUP_CONF"
|
||||
}
|
||||
|
||||
turn_off() {
|
||||
local answer
|
||||
WEBDAV="$BACKUP_WEBDAV"
|
||||
CORE_USER="$BACKUP_USER"
|
||||
NEXTCLOUD="${BACKUP_WEBDAV%%/remote.php/*}"
|
||||
|
||||
stop_backups
|
||||
echo "Backups are off."
|
||||
|
||||
echo
|
||||
echo "The backups made so far are still in $BACKUP_DIR in your Nextcloud files."
|
||||
if ! confirm "Delete them? They can't be restored from after that."; then
|
||||
echo "Kept them. To turn backups back on: configure backup"
|
||||
return 0
|
||||
fi
|
||||
read -rp "To delete them, type the folder name ($BACKUP_DIR): " answer
|
||||
if [ "$answer" != "$BACKUP_DIR" ]; then
|
||||
echo "That's not the folder name, so the backups were kept."
|
||||
return 0
|
||||
fi
|
||||
delete_backups
|
||||
}
|
||||
|
||||
run_configure() {
|
||||
local pass
|
||||
local pass choice
|
||||
if backups_on &> /dev/null; then
|
||||
if is_configured &> /dev/null; then
|
||||
echo "Your home folder is backed up daily to $BACKUP_DIR in your Nextcloud files."
|
||||
echo
|
||||
echo " 1) Set it up again"
|
||||
else
|
||||
echo "Your home folder is backed up daily to $BACKUP_DIR on ${BACKUP_WEBDAV%%/remote.php/*},"
|
||||
echo "which isn't your main Core."
|
||||
echo
|
||||
echo " 1) Back up to your main Core ($CORE_DOMAIN) instead; the backups so far stay where they are"
|
||||
fi
|
||||
echo " 2) Turn backups off"
|
||||
echo " q) Leave it as it is"
|
||||
echo
|
||||
read -rp "Choose: " choice
|
||||
case "$choice" in
|
||||
1) ;;
|
||||
2) turn_off; return ;;
|
||||
*) return 0 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ -z "$CORE_DOMAIN" ] || [ -z "$CORE_EMAIL" ]; then
|
||||
echo "Backups go to your Nextcloud files, so set up your Core first: configure core"
|
||||
return 1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add an account to GNOME Online Accounts, the way Settings does.
|
||||
"""Add or remove an account in GNOME Online Accounts, the way Settings does.
|
||||
|
||||
Settings adds accounts by calling AddAccount on the Online Accounts service,
|
||||
which saves the account and keeps its password in the GNOME keyring. This
|
||||
@@ -9,6 +9,7 @@ so its password doesn't show up in the process list.
|
||||
Usage:
|
||||
goa-account exists <provider> <presentation-identity>
|
||||
goa-account add < account.json
|
||||
goa-account remove <provider> <presentation-identity>
|
||||
|
||||
account.json:
|
||||
{"provider": "owncloud", "identity": "...", "presentation_identity": "...",
|
||||
@@ -36,16 +37,20 @@ def accounts(bus):
|
||||
(objects,) = call(bus, "/org/gnome/OnlineAccounts",
|
||||
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects",
|
||||
None, "(a{oa{sa{sv}}})")
|
||||
for interfaces in objects.values():
|
||||
for path, interfaces in objects.items():
|
||||
account = interfaces.get("org.gnome.OnlineAccounts.Account")
|
||||
if account:
|
||||
yield account
|
||||
yield path, account
|
||||
|
||||
|
||||
def find(bus, provider, presentation_identity):
|
||||
return [path for path, account in accounts(bus)
|
||||
if account.get("ProviderType") == provider
|
||||
and account.get("PresentationIdentity") == presentation_identity]
|
||||
|
||||
|
||||
def exists(bus, provider, presentation_identity):
|
||||
return any(account.get("ProviderType") == provider
|
||||
and account.get("PresentationIdentity") == presentation_identity
|
||||
for account in accounts(bus))
|
||||
return bool(find(bus, provider, presentation_identity))
|
||||
|
||||
|
||||
def add(bus, account):
|
||||
@@ -58,6 +63,11 @@ def add(bus, account):
|
||||
"org.gnome.OnlineAccounts.Manager", "AddAccount", parameters, "(o)")
|
||||
|
||||
|
||||
def remove(bus, provider, presentation_identity):
|
||||
for path in find(bus, provider, presentation_identity):
|
||||
call(bus, path, "org.gnome.OnlineAccounts.Account", "Remove", None, "()")
|
||||
|
||||
|
||||
def main():
|
||||
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
if len(sys.argv) == 4 and sys.argv[1] == "exists":
|
||||
@@ -68,6 +78,9 @@ def main():
|
||||
return 0
|
||||
add(bus, account)
|
||||
return 0
|
||||
if len(sys.argv) == 4 and sys.argv[1] == "remove":
|
||||
remove(bus, sys.argv[2], sys.argv[3])
|
||||
return 0
|
||||
print(__doc__.strip().split("\n\n")[2], file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
@@ -25,3 +25,9 @@ nextcloud_app_password() {
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Revoke an app password, signing in with it. Uses CORE_USER and NEXTCLOUD.
|
||||
nextcloud_revoke_app_password() {
|
||||
auth_header "$CORE_USER" "$1" | curl -sf -o /dev/null -X DELETE -H @- \
|
||||
-H 'OCS-APIRequest: true' "$NEXTCLOUD/ocs/v2.php/core/apppassword"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user