Add the configure app
This commit is contained in:
Executable
+315
@@ -0,0 +1,315 @@
|
||||
#!/bin/bash
|
||||
|
||||
TITLE="Federated Computer Core"
|
||||
DESCRIPTION="Superpanel, Tailscale, Nextcloud, email and files"
|
||||
|
||||
# What you entered last time, so running this again only does what's missing
|
||||
CORE_CONF="$HOME/.config/coredesktop/core.conf"
|
||||
GOA_ACCOUNT="$COREDESKTOP/configure.d/lib/goa-account"
|
||||
[ -f "$CORE_CONF" ] && source "$CORE_CONF"
|
||||
|
||||
# CORE_DOMAIN, CORE_EMAIL and CORE_NAME come from core.conf; the rest follow
|
||||
core_names() {
|
||||
CORE_USER="${CORE_EMAIL%@*}"
|
||||
NEXTCLOUD="https://nextcloud.$CORE_DOMAIN"
|
||||
WEBDAV="$NEXTCLOUD/remote.php/dav/files/$CORE_USER"
|
||||
MOUNT="/mnt/$CORE_DOMAIN"
|
||||
SUPERPANEL_DESKTOP="$HOME/.local/share/applications/core-$CORE_DOMAIN-superpanel.desktop"
|
||||
}
|
||||
|
||||
superpanel_done() {
|
||||
[ -f "$SUPERPANEL_DESKTOP" ]
|
||||
}
|
||||
|
||||
# Tailscale has no status field naming its login server, so core.conf
|
||||
# records that it was set up for this Core
|
||||
tailscale_done() {
|
||||
[ "$CORE_TAILSCALE" = "$CORE_DOMAIN" ] \
|
||||
&& tailscale status --json 2> /dev/null | jq -e '.BackendState == "Running"' > /dev/null
|
||||
}
|
||||
|
||||
nextcloud_done() {
|
||||
"$GOA_ACCOUNT" exists owncloud "$CORE_USER@nextcloud.$CORE_DOMAIN" 2> /dev/null
|
||||
}
|
||||
|
||||
email_done() {
|
||||
"$GOA_ACCOUNT" exists imap_smtp "$CORE_EMAIL" 2> /dev/null
|
||||
}
|
||||
|
||||
files_done() {
|
||||
grep -qs " $MOUNT davfs " /etc/fstab && grep -qs "^$MOUNT " "$HOME/.davfs2/secrets"
|
||||
}
|
||||
|
||||
is_configured() {
|
||||
[ -n "$CORE_DOMAIN" ] || return 1
|
||||
core_names
|
||||
superpanel_done && tailscale_done && nextcloud_done && email_done && files_done
|
||||
}
|
||||
|
||||
save_conf() {
|
||||
mkdir -p "$(dirname "$CORE_CONF")"
|
||||
{
|
||||
printf 'CORE_DOMAIN=%q\n' "$CORE_DOMAIN"
|
||||
printf 'CORE_EMAIL=%q\n' "$CORE_EMAIL"
|
||||
printf 'CORE_NAME=%q\n' "$CORE_NAME"
|
||||
printf 'CORE_TAILSCALE=%q\n' "$CORE_TAILSCALE"
|
||||
} > "$CORE_CONF"
|
||||
}
|
||||
|
||||
# Basic auth header for curl, read from stdin (-H @-) so the password isn't
|
||||
# in the process list
|
||||
auth_header() {
|
||||
printf 'Authorization: Basic %s\n' "$(printf '%s:%s' "$1" "$2" | base64 -w0)"
|
||||
}
|
||||
|
||||
# Create a Nextcloud app password for the Online Accounts account and the
|
||||
# files mount, so they don't keep your Core password. It shows in Nextcloud
|
||||
# under Settings > Security as "CoreDesktop on <this computer>", where it can
|
||||
# be revoked. Returns 2 if Nextcloud says the password is wrong.
|
||||
nextcloud_app_password() {
|
||||
local response code
|
||||
response="$(auth_header "$CORE_USER" "$PASSWORD" | curl -s -w '\n%{http_code}' -H @- \
|
||||
-H 'OCS-APIRequest: true' -A "CoreDesktop on $HOSTNAME" \
|
||||
"$NEXTCLOUD/ocs/v2.php/core/getapppassword?format=json")"
|
||||
code="${response##*$'\n'}"
|
||||
case "$code" in
|
||||
200) APP_PASSWORD="$(jq -r '.ocs.data.apppassword // empty' <<< "${response%$'\n'*}")"
|
||||
[ -n "$APP_PASSWORD" ] ;;
|
||||
401) return 2 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
desktop_entry() {
|
||||
local file="$1" name="$2" comment="$3" url="$4" icon="$5"
|
||||
mkdir -p "$(dirname "$file")"
|
||||
cat > "$file" << EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=$name
|
||||
Comment=$comment
|
||||
Exec=xdg-open $url
|
||||
Icon=$icon
|
||||
Categories=Network;
|
||||
EOF
|
||||
}
|
||||
|
||||
setup_superpanel() {
|
||||
local url="https://superpanel.$CORE_DOMAIN"
|
||||
local icon="$HOME/.local/share/icons/core-$CORE_DOMAIN-superpanel.png"
|
||||
|
||||
# The login page carries Superpanel's icon inline
|
||||
mkdir -p "$(dirname "$icon")"
|
||||
if ! curl -s "$url/" | grep -o 'rel="icon"[^>]*base64,[^"]*' | head -n 1 \
|
||||
| sed 's/.*base64,//' | base64 -d > "$icon" 2> /dev/null || [ ! -s "$icon" ]; then
|
||||
rm -f "$icon"
|
||||
icon="applications-internet"
|
||||
fi
|
||||
desktop_entry "$SUPERPANEL_DESKTOP" "Superpanel" "Manage your Federated Computer Core ($CORE_DOMAIN)" "$url" "$icon"
|
||||
echo "Added Superpanel to your applications."
|
||||
}
|
||||
|
||||
setup_tailscale() {
|
||||
# --reset and --force-reauth move the machine from any other tailnet;
|
||||
# --operator lets you, and the Tailscale GNOME extension, use it without sudo
|
||||
echo "Open the link below to sign in to your Core's Tailscale network."
|
||||
sudo tailscale up --reset --force-reauth --login-server "https://headscale.$CORE_DOMAIN" \
|
||||
--accept-routes --operator="$USER" || return 1
|
||||
CORE_TAILSCALE="$CORE_DOMAIN"
|
||||
save_conf
|
||||
}
|
||||
|
||||
setup_nextcloud() {
|
||||
[ -n "$APP_PASSWORD" ] || { echo "This needs your Core password, and Nextcloud didn't accept it."; return 1; }
|
||||
"$GOA_ACCOUNT" add << EOF || return 1
|
||||
$(jq -n --arg user "$CORE_USER" --arg host "nextcloud.$CORE_DOMAIN" --arg url "$NEXTCLOUD" \
|
||||
--arg password "$APP_PASSWORD" '{
|
||||
provider: "owncloud",
|
||||
identity: $user,
|
||||
presentation_identity: "\($user)@\($host)",
|
||||
credentials: {password: $password},
|
||||
details: {
|
||||
Uri: "\($url)/remote.php/webdav",
|
||||
FilesEnabled: "true",
|
||||
CalendarEnabled: "true",
|
||||
CalDavUri: "\($url)/remote.php/dav",
|
||||
ContactsEnabled: "true",
|
||||
CardDavUri: "\($url)/remote.php/dav",
|
||||
AcceptSslErrors: "false"
|
||||
}}')
|
||||
EOF
|
||||
echo "Added Nextcloud to Online Accounts (calendar, contacts and files)."
|
||||
}
|
||||
|
||||
# The mail server publishes its settings (Thunderbird's autoconfig format).
|
||||
# Prints: imap_host imap_port imap_socket smtp_host smtp_port smtp_socket username
|
||||
mail_settings() {
|
||||
curl -sf --max-time 15 \
|
||||
"https://autoconfig.$CORE_DOMAIN/mail/config-v1.1.xml?emailaddress=$CORE_EMAIL" \
|
||||
| python3 -I -c '
|
||||
import sys, xml.etree.ElementTree as ET
|
||||
provider = ET.parse(sys.stdin).getroot().find("emailProvider")
|
||||
imap = provider.find("incomingServer[@type=\"imap\"]")
|
||||
smtp = provider.find("outgoingServer[@type=\"smtp\"]")
|
||||
print(*(s.findtext(f) for s in (imap, smtp) for f in ("hostname", "port", "socketType")),
|
||||
imap.findtext("username"))'
|
||||
}
|
||||
|
||||
# Online Accounts takes host:port, and only the host for the usual port
|
||||
host_port() {
|
||||
if [ "$2" = "$3" ]; then echo "$1"; else echo "$1:$2"; fi
|
||||
}
|
||||
|
||||
setup_email() {
|
||||
[ -n "$PASSWORD" ] || { echo "This needs your Core password."; return 1; }
|
||||
local imap_host imap_port imap_socket smtp_host smtp_port smtp_socket username
|
||||
if ! read -r imap_host imap_port imap_socket smtp_host smtp_port smtp_socket username < <(mail_settings) \
|
||||
|| [ -z "$smtp_socket" ]; then
|
||||
echo "The mail server didn't give its settings; using mail.$CORE_DOMAIN with the usual ports."
|
||||
imap_host="mail.$CORE_DOMAIN" imap_port=993 imap_socket=SSL
|
||||
smtp_host="mail.$CORE_DOMAIN" smtp_port=587 smtp_socket=STARTTLS
|
||||
username="%EMAILADDRESS%"
|
||||
fi
|
||||
case "$username" in
|
||||
%EMAILLOCALPART%) username="$CORE_USER" ;;
|
||||
*) username="$CORE_EMAIL" ;;
|
||||
esac
|
||||
[ "$imap_socket" = SSL ] && imap_std=993 || imap_std=143
|
||||
[ "$smtp_socket" = SSL ] && smtp_std=465 || smtp_std=587
|
||||
|
||||
"$GOA_ACCOUNT" add << EOF || return 1
|
||||
$(jq -n --arg email "$CORE_EMAIL" --arg name "$CORE_NAME" --arg user "$username" \
|
||||
--arg password "$PASSWORD" \
|
||||
--arg imap "$(host_port "$imap_host" "$imap_port" "$imap_std")" --arg imap_socket "$imap_socket" \
|
||||
--arg smtp "$(host_port "$smtp_host" "$smtp_port" "$smtp_std")" --arg smtp_socket "$smtp_socket" '{
|
||||
provider: "imap_smtp",
|
||||
identity: $email,
|
||||
presentation_identity: $email,
|
||||
credentials: {"imap-password": $password, "smtp-password": $password},
|
||||
details: {
|
||||
Enabled: "true",
|
||||
EmailAddress: $email,
|
||||
Name: $name,
|
||||
ImapHost: $imap,
|
||||
ImapUserName: $user,
|
||||
ImapUseSsl: (if $imap_socket == "SSL" then "true" else "false" end),
|
||||
ImapUseTls: (if $imap_socket == "STARTTLS" then "true" else "false" end),
|
||||
ImapAcceptSslErrors: "false",
|
||||
SmtpHost: $smtp,
|
||||
SmtpUseAuth: "true",
|
||||
SmtpUserName: $user,
|
||||
SmtpAuthLogin: "false",
|
||||
SmtpAuthPlain: "true",
|
||||
SmtpUseSsl: (if $smtp_socket == "SSL" then "true" else "false" end),
|
||||
SmtpUseTls: (if $smtp_socket == "STARTTLS" then "true" else "false" end),
|
||||
SmtpAcceptSslErrors: "false"
|
||||
}}')
|
||||
EOF
|
||||
echo "Added $CORE_EMAIL to Online Accounts."
|
||||
}
|
||||
|
||||
# Mount your Nextcloud files at /mnt/<domain> with davfs2. You can mount it
|
||||
# without sudo once it's in /etc/fstab with "user" and you're in Arch's davfs2
|
||||
# group (network). It's mounted now and each time you log in.
|
||||
setup_files() {
|
||||
[ -n "$APP_PASSWORD" ] || { echo "This needs your Core password, and Nextcloud didn't accept it."; return 1; }
|
||||
local secret
|
||||
|
||||
sudo install -d -o "$USER" -g "$(id -gn)" "$MOUNT" || return 1
|
||||
if ! grep -qs " $MOUNT davfs " /etc/fstab; then
|
||||
echo "$WEBDAV $MOUNT davfs rw,user,noauto,uid=$USER,_netdev 0 0" | sudo tee -a /etc/fstab > /dev/null || return 1
|
||||
fi
|
||||
if ! id -nG "$USER" | grep -qw network; then
|
||||
sudo usermod -aG network "$USER" || return 1
|
||||
fi
|
||||
|
||||
# davfs2 only reads this file if no one else can
|
||||
mkdir -p -m 700 "$HOME/.davfs2"
|
||||
touch "$HOME/.davfs2/secrets"
|
||||
chmod 600 "$HOME/.davfs2/secrets"
|
||||
secret="${APP_PASSWORD//\\/\\\\}"
|
||||
secret="${secret//\"/\\\"}"
|
||||
sed -i "\\#^$MOUNT #d" "$HOME/.davfs2/secrets"
|
||||
printf '%s %s "%s"\n' "$MOUNT" "$CORE_USER" "$secret" >> "$HOME/.davfs2/secrets"
|
||||
|
||||
mkdir -p "$HOME/.config/autostart"
|
||||
cat > "$HOME/.config/autostart/core-$CORE_DOMAIN-files.desktop" << EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Nextcloud files ($CORE_DOMAIN)
|
||||
Comment=Mount your Nextcloud files at $MOUNT
|
||||
Exec=sh -c 'mountpoint -q $MOUNT || mount $MOUNT'
|
||||
X-GNOME-Autostart-enabled=true
|
||||
NoDisplay=true
|
||||
EOF
|
||||
|
||||
# Shown in the Files sidebar as the domain
|
||||
local bookmarks="$HOME/.config/gtk-3.0/bookmarks"
|
||||
mkdir -p "$(dirname "$bookmarks")"
|
||||
grep -qs "^file://$MOUNT " "$bookmarks" || echo "file://$MOUNT $CORE_DOMAIN" >> "$bookmarks"
|
||||
|
||||
# sg picks up the network group now, without logging out and in again
|
||||
if ! mountpoint -q "$MOUNT"; then
|
||||
sg network -c "mount $MOUNT" || return 1
|
||||
fi
|
||||
echo "Your Nextcloud files are at $MOUNT (\"$CORE_DOMAIN\" in the Files sidebar)."
|
||||
}
|
||||
|
||||
# Run a step unless it's done; a failed step doesn't stop the ones after it
|
||||
step() {
|
||||
local name="$1" title="$2"
|
||||
if "${name}_done"; then
|
||||
echo " $title: already set up"
|
||||
return 0
|
||||
fi
|
||||
if ! confirm_yes "Set up $title?"; then
|
||||
return 0
|
||||
fi
|
||||
"setup_$name" || { echo -e "\e[31m$title was not set up.\e[0m"; STATUS=1; }
|
||||
}
|
||||
|
||||
run_configure() {
|
||||
local url
|
||||
STATUS=0
|
||||
|
||||
read -erp "Your Superpanel's address (e.g. https://superpanel.example.com): " \
|
||||
-i "${CORE_DOMAIN:+https://superpanel.$CORE_DOMAIN}" url
|
||||
url="${url#*://}"
|
||||
url="${url%%/*}"
|
||||
CORE_DOMAIN="${url#superpanel.}"
|
||||
if [ -z "$CORE_DOMAIN" ] || ! curl -s -o /dev/null --max-time 15 "https://superpanel.$CORE_DOMAIN/"; then
|
||||
echo "Couldn't reach https://superpanel.$CORE_DOMAIN."
|
||||
return 1
|
||||
fi
|
||||
|
||||
read -erp "Your Core email: " -i "${CORE_EMAIL:-$USER@$CORE_DOMAIN}" CORE_EMAIL
|
||||
read -erp "Your name, as shown on email you send: " \
|
||||
-i "${CORE_NAME:-$(getent passwd "$USER" | cut -d: -f5 | cut -d, -f1)}" CORE_NAME
|
||||
[[ "$CORE_EMAIL" == *@* ]] || { echo "That isn't an email address."; return 1; }
|
||||
core_names
|
||||
save_conf
|
||||
|
||||
# Nextcloud, email and files need your Core password; Nextcloud checks it
|
||||
if ! nextcloud_done || ! email_done || ! files_done; then
|
||||
local tries
|
||||
for tries in 1 2 3; do
|
||||
read -rsp "Your Core password: " PASSWORD
|
||||
echo
|
||||
nextcloud_app_password
|
||||
case $? in
|
||||
0) break ;;
|
||||
2) echo "Nextcloud says that password is wrong."; PASSWORD="" ;;
|
||||
*) echo "Couldn't reach Nextcloud at $NEXTCLOUD; trying the password for email anyway."; break ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
echo
|
||||
step superpanel "Superpanel in your applications"
|
||||
step tailscale "Tailscale with your Core (headscale.$CORE_DOMAIN)"
|
||||
step nextcloud "Nextcloud in Online Accounts"
|
||||
step email "Email in Online Accounts"
|
||||
step files "Nextcloud files at $MOUNT"
|
||||
unset PASSWORD APP_PASSWORD
|
||||
return $STATUS
|
||||
}
|
||||
Reference in New Issue
Block a user