#!/usr/bin/env bash # FromChat one-click server installer # https://github.com/fromchat-messenger/app set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DEPLOYMENT_ROOT="${SCRIPT_DIR}" # shellcheck source=scripts/lib.sh source "${DEPLOYMENT_ROOT}/scripts/lib.sh" DEFAULT_BACKEND_REPO="${FROMCHAT_BACKEND_REPO:-https://github.com/fromchat-messenger/backend.git}" DEFAULT_WEB_REPO="${FROMCHAT_WEB_REPO:-https://github.com/fromchat-messenger/web.git}" DEFAULT_APP_REPO="${FROMCHAT_APP_REPO:-https://github.com/fromchat-messenger/app.git}" INSTALL_DIR="" FROMCHAT_VERSION="" BACKEND_REPO="${DEFAULT_BACKEND_REPO}" WEB_REPO="${DEFAULT_WEB_REPO}" GENERATE_ONLY=false GENERATE_COMPONENTS="" GENERATE_TAG="" GENERATE_OUTPUT="" usage() { cat </dev/null | cut -d: -f6 || true)" if [[ -z "${home}" ]]; then home="${HOME}" fi echo "${home}/fromchat-server" } configure_caddyfile() { local install_dir="$1" local caddyfile="${install_dir}/Caddyfile" if [[ ! -f "${caddyfile}" ]]; then cp "${DEPLOYMENT_ROOT}/templates/Caddyfile" "${caddyfile}" fi step "Opening Caddyfile in nano (save with Ctrl+O, exit with Ctrl+X)…" "${EDITOR:-nano}" "${caddyfile}" || nano "${caddyfile}" || vi "${caddyfile}" } setup_updater() { local install_dir="$1" local tag="$2" local updater_dir="${install_dir}/updater" mkdir -p "${updater_dir}" step "Git token for the auto-updater (GitHub or Gitea: read packages + repo metadata)." info "GitHub token:" printf '%b%s%b\n' "$BLUE" \ " https://github.com/settings/tokens/new?description=FromChat%20Updater&scopes=read:packages,repo" \ "$NC" info "Gitea token: Settings → Applications → Generate New Token (read:repository, read:package)" prompt "Paste token (input hidden):" local token IFS= read -rs token || true printf '\n' >&2 [[ -n "${token}" ]] || die "GitHub token is required when updater is selected." fetch_raw_file "${DEFAULT_APP_REPO}" "${tag}" "updater/compose.yml" \ "${updater_dir}/compose.yml" 2>/dev/null \ || fetch_raw_file "${DEFAULT_APP_REPO}" "main" "updater/compose.yml" \ "${updater_dir}/compose.yml" 2>/dev/null \ || cp "${DEPLOYMENT_ROOT}/../updater/compose.yml" "${updater_dir}/compose.yml" local components_csv components_csv="$(IFS=,; echo "${SELECTED[*]}")" cat > "${updater_dir}/.env" </dev/null || true SELECTED=() select_components prompt "Use custom git repositories? [y/N]:" local custom IFS= read -r custom || true if [[ "${custom:-}" =~ ^[Yy] ]]; then prompt "Backend repository URL [${DEFAULT_BACKEND_REPO}]:" IFS= read -r line || true [[ -n "${line}" ]] && BACKEND_REPO="${line}" prompt "Web repository URL [${DEFAULT_WEB_REPO}]:" IFS= read -r line || true [[ -n "${line}" ]] && WEB_REPO="${line}" fi step "Resolving latest common semver tag…" local repos_for_tag=() component_selected backend && repos_for_tag+=("${BACKEND_REPO}") component_selected frontend && repos_for_tag+=("${WEB_REPO}") if ((${#repos_for_tag[@]} == 0)); then repos_for_tag+=("${BACKEND_REPO}" "${WEB_REPO}") fi step "GitHub / Gitea token for private repos (leave empty if public):" prompt "Paste token (input hidden):" local gh_token="" IFS= read -rs gh_token || true printf '\n' >&2 export GITHUB_TOKEN="${gh_token}" export GIT_TOKEN="${gh_token}" if [[ -n "${GITHUB_TOKEN}" ]]; then FROMCHAT_VERSION="$(python3 "${DEPLOYMENT_ROOT}/scripts/resolve-tag.py" "${GITHUB_TOKEN}" "${repos_for_tag[@]}")" else FROMCHAT_VERSION="$(python3 "${DEPLOYMENT_ROOT}/scripts/resolve-tag.py" "${repos_for_tag[@]}")" fi [[ -n "${FROMCHAT_VERSION}" ]] || die "Could not resolve a semver tag." success "Using tag ${FROMCHAT_VERSION}" local components_csv components_csv="$(IFS=,; echo "${SELECTED[*]}")" local compose_components="" local c for c in "${SELECTED[@]}"; do [[ "${c}" == "updater" ]] && continue compose_components="${compose_components:+$compose_components,}${c}" done run_generate_compose "${INSTALL_DIR}" "${compose_components}" "${FROMCHAT_VERSION}" if component_selected caddy; then configure_caddyfile "${INSTALL_DIR}" fi if [[ ! -f "${INSTALL_DIR}/.env" ]]; then warn "No .env in ${INSTALL_DIR}. Create one before production use (see backend generate:env)." touch "${INSTALL_DIR}/.env" chown "${real_user}:${real_user}" "${INSTALL_DIR}/.env" 2>/dev/null || true fi step "Starting FromChat stack…" ( cd "${INSTALL_DIR}" docker compose --env-file .env up -d --wait --timeout 600 ) if component_selected updater; then export GITHUB_TOKEN="${gh_token:-${GITHUB_TOKEN:-}}" export GIT_TOKEN="${gh_token:-${GIT_TOKEN:-${GITHUB_TOKEN:-}}}" setup_updater "${INSTALL_DIR}" "${FROMCHAT_VERSION}" fi chown -R "${real_user}:${real_user}" "${INSTALL_DIR}" 2>/dev/null || true print_success_banner "${INSTALL_DIR}" } main() { parse_args "$@" if ${GENERATE_ONLY}; then generate_config_mode else full_install "$@" fi } main "$@"