testsAndMisc/linux_configuration/features/setup_syncyomi.sh
Krzysztof kuhy Rudnicki f15f00f3fe Re-point paths for the ~ reorganisation (home-tidy)
Also bumps the 16 meta/requirements pins the freshness gate flagged
(anthropic 1.5.0, numpy 2.5.3, pytest-randomly 5, flake8-bugbear 26.9.9,
ruff 0.16.7, pyright 1.1.414, selenium 4.49.0, hypothesis 6.168.0, lxml
6.1.3, pipdeptree 4.2.5, the two types-* stubs) and Flutter 3.47.3 in
meta/.fvmrc. One test still expected the pre-move ~/mcp-servers path.

The pre-commit hook fed .zsh files to shfmt and shellcheck, neither of
which parses zsh (SC2148/SC1071), so no zsh file could be committed since
the hook was tightened on 2026-08-22; those now get `zsh -n` instead. The
hook's own shfmt pass also staged its reformat of 8 shell files.

CI_GREEN_SKIP because main is red on exactly the dependency-freshness
finding this commit fixes, which is the documented use of that flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
2026-09-11 20:50:19 +02:00

151 lines
5.8 KiB
Bash
Executable File

#!/bin/bash
# setup_syncyomi.sh — Self-hosted SyncYomi manga-sync server on this Arch PC.
#
# Stands up SyncYomi (github.com/SyncYomi/SyncYomi) in Docker, bound to
# 127.0.0.1:8282, and fronts it with the EXISTING Caddy reverse proxy (from
# setup_gitea.sh) for automatic public HTTPS at a new DuckDNS subdomain, so
# TachiyomiSY on the phone can sync its library from a mobile network.
#
# It reuses the host's existing exposure stack — it does NOT add a second Caddy,
# a second DuckDNS updater, or hand-edit /etc/nftables.conf. It also creates the
# first SyncYomi account and mints an API key over the local REST API, then
# prints the host address + API key ready to paste into TachiyomiSY.
#
# Run as your normal user (NOT root). It uses sudo only for pacman, the firewall
# flag and enabling docker. Idempotent — safe to re-run.
#
# Usage:
# ./setup_syncyomi.sh Install / update everything (default)
# ./setup_syncyomi.sh setup Same as above
# ./setup_syncyomi.sh status Show health / exposure diagnostics
# ./setup_syncyomi.sh help Show this help
set -euo pipefail
# --- SyncYomi image ----------------------------------------------------------
# Runs a local build of the personal fork by default, so server-side changes
# actually reach the service instead of sitting unbuilt in a clone. The
# published image stays one variable away:
# SYNCYOMI_IMAGE=ghcr.io/syncyomi/syncyomi:v1.1.11 ./setup_syncyomi.sh
# Any value containing a "/" is treated as a registry image and pulled as-is.
readonly SYNCYOMI_IMAGE="${SYNCYOMI_IMAGE:-syncyomi-kuhy:local}"
readonly SYNCYOMI_SRC="${SYNCYOMI_SRC:-${HOME}/vendor/syncyomi-src}"
readonly SYNCYOMI_FORK_URL="https://github.com/kuhyx/syncyomi.git"
readonly SYNCYOMI_PORT=8282
readonly SYNCYOMI_LOCAL="http://127.0.0.1:${SYNCYOMI_PORT}"
# --- Data dir + secrets (outside the git repo, like ~/services/gitea) -----------------
readonly DATA_DIR="${HOME}/services/syncyomi"
readonly COMPOSE_FILE="${DATA_DIR}/docker-compose.yml"
readonly CONFIG_TOML="${DATA_DIR}/config/config.toml"
readonly CONF_FILE="${DATA_DIR}/.syncyomi.conf"
readonly TOKEN_FILE="${DATA_DIR}/.api_token"
# --- Existing host infrastructure (from setup_gitea.sh / install_joplin.sh) --
readonly CADDY_CONTAINER="gitea-caddy"
readonly CADDYFILE="${HOME}/services/gitea/Caddyfile"
readonly CADDY_SITES_DIR="${HOME}/services/gitea/sites"
readonly CADDYFILE_IN_CONTAINER="/etc/caddy/Caddyfile"
readonly DUCKDNS_UPDATER="${HOME}/.joplin-server/duckdns-update.sh"
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
readonly SCRIPT_DIR
readonly WG_SCRIPT="${SCRIPT_DIR}/setup_wireguard_ssh.sh"
# shellcheck source=../lib/common.sh
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/../lib/common.sh"
# Config values (populated from CONF_FILE / defaults).
SYNCYOMI_USER=""
SYNCYOMI_PASSWORD=""
SYNCYOMI_SUBDOMAIN=""
# Local timezone for the container (falls back if timedatectl is unavailable).
TZ_VALUE="$(timedatectl show -p Timezone --value 2>/dev/null || echo 'Europe/Warsaw')"
readonly TZ_VALUE
die() {
log_error "$1"
exit 1
}
usage() {
grep -E '^#( |$)' "$0" | sed -E 's/^# ?//'
exit 0
}
# --- Step 1: preflight -------------------------------------------------------
preflight() {
log_info "Preflight checks"
[[ ${EUID} -ne 0 ]] || die "Run as your normal user, not root (it will sudo where needed)."
# Runtime deps; install any that are missing from the official repos.
# docker-buildx: the fork's Dockerfile uses $BUILDPLATFORM, which only the
# BuildKit builder defines; the legacy builder rejects the empty value.
install_missing_pacman_packages docker docker-compose jq curl openssl git docker-buildx
# Ensure the docker daemon is enabled+running (auto-start after reboot). Only
# touch systemd via sudo when it is not already active, to stay a no-op on the
# common case where docker is already up (Gitea/dufs run on it).
has_cmd docker || die "docker not found — run setup_gitea.sh first."
if ! is_service_active docker; then
log_info "Enabling and starting docker.service"
sudo systemctl enable --now docker
fi
# The public-exposure stack must already exist (Caddy from setup_gitea.sh).
docker ps --format '{{.Names}}' | grep -qx "${CADDY_CONTAINER}" ||
die "Caddy container '${CADDY_CONTAINER}' not running — run setup_gitea.sh first."
[[ -f ${CADDYFILE} ]] || die "Caddyfile ${CADDYFILE} missing — run setup_gitea.sh first."
[[ -x ${DUCKDNS_UPDATER} ]] || die "DuckDNS updater ${DUCKDNS_UPDATER} missing — run install_joplin.sh first."
log_ok "Host exposure stack present (docker + ${CADDY_CONTAINER} + DuckDNS updater)"
}
# --- Step 2: config (auto-generate credentials, store 0600) ------------------
load_config() {
ensure_dir "${DATA_DIR}"
chmod 700 "${DATA_DIR}"
if [[ -f ${CONF_FILE} ]]; then
# shellcheck source=/dev/null
source "${CONF_FILE}"
fi
SYNCYOMI_USER="${SYNCYOMI_USER:-$(get_actual_user)}"
SYNCYOMI_SUBDOMAIN="${SYNCYOMI_SUBDOMAIN:-kuhy-sync.duckdns.org}"
# Generate the web/API password once; never regenerate on re-run.
if [[ -z ${SYNCYOMI_PASSWORD:-} ]]; then
SYNCYOMI_PASSWORD="$(openssl rand -base64 24)"
fi
save_config
}
save_config() {
# 0600 — holds the plaintext SyncYomi password so re-runs stay
# non-interactive. Lives under ~/services/syncyomi (outside the git repo).
umask 077
{
printf 'SYNCYOMI_USER=%q\n' "${SYNCYOMI_USER}"
printf 'SYNCYOMI_PASSWORD=%q\n' "${SYNCYOMI_PASSWORD}"
printf 'SYNCYOMI_SUBDOMAIN=%q\n' "${SYNCYOMI_SUBDOMAIN}"
} >"${CONF_FILE}"
chmod 600 "${CONF_FILE}"
log_ok "Saved config to ${CONF_FILE} (0600)"
}
# shellcheck source=lib/syncyomi_stack.sh
source "$SCRIPT_DIR/lib/syncyomi_stack.sh"
# shellcheck source=lib/syncyomi_expose.sh
source "$SCRIPT_DIR/lib/syncyomi_expose.sh"
main() {
local cmd="${1:-setup}"
case "${cmd}" in
setup) setup_cmd ;;
status) status_cmd ;;
help | -h | --help) usage ;;
*) die "Unknown command '${cmd}' (use: setup | status | help)" ;;
esac
}
main "$@"