255 lines
7.5 KiB
Bash
Executable File
255 lines
7.5 KiB
Bash
Executable File
#!/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 <<EOF
|
|
FromChat server installer
|
|
|
|
curl -fsSL https://raw.githubusercontent.com/fromchat-messenger/app/main/deployment/install.sh | sudo bash
|
|
|
|
Options:
|
|
--generate-config SERVICES Merge compose files only (comma-separated:
|
|
backend,frontend,caddy). Example:
|
|
--generate-config backend,frontend,caddy
|
|
--tag TAG Image / git tag (required with --generate-config)
|
|
--output-dir PATH Output directory (default: ./fromchat-server)
|
|
--backend-repo URL Backend git repository
|
|
--web-repo URL Web frontend git repository
|
|
-h, --help Show this help
|
|
EOF
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--generate-config)
|
|
GENERATE_ONLY=true
|
|
GENERATE_COMPONENTS="${2:-}"
|
|
shift 2
|
|
;;
|
|
--tag)
|
|
GENERATE_TAG="${2:-}"
|
|
shift 2
|
|
;;
|
|
--output-dir)
|
|
GENERATE_OUTPUT="${2:-}"
|
|
shift 2
|
|
;;
|
|
--backend-repo)
|
|
BACKEND_REPO="${2:-}"
|
|
shift 2
|
|
;;
|
|
--web-repo)
|
|
WEB_REPO="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
die "Unknown argument: $1"
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
real_install_dir() {
|
|
local user home
|
|
user="${SUDO_USER:-${USER}}"
|
|
home="$(getent passwd "${user}" 2>/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" <<EOF
|
|
GITHUB_TOKEN=${token}
|
|
GIT_TOKEN=${token}
|
|
BACKEND_REPO=${BACKEND_REPO}
|
|
WEB_REPO=${WEB_REPO}
|
|
DEPLOYMENT_REPO=${DEFAULT_APP_REPO}
|
|
COMPOSE_PROJECT_DIR=${install_dir}
|
|
FROMCHAT_COMPONENTS=${components_csv}
|
|
CHECK_INTERVAL_SECONDS=60
|
|
EOF
|
|
|
|
(
|
|
cd "${updater_dir}"
|
|
docker compose --env-file .env up -d --wait --timeout 120
|
|
)
|
|
success "Updater service started"
|
|
}
|
|
|
|
generate_config_mode() {
|
|
[[ -n "${GENERATE_COMPONENTS}" ]] || die "--generate-config requires a service list"
|
|
[[ -n "${GENERATE_TAG}" ]] || die "--tag is required with --generate-config"
|
|
|
|
local out="${GENERATE_OUTPUT:-$(real_install_dir)}"
|
|
mkdir -p "${out}"
|
|
ensure_python_yaml
|
|
run_generate_compose "${out}" "${GENERATE_COMPONENTS}" "${GENERATE_TAG}"
|
|
success "Generated ${out}/compose.yml"
|
|
}
|
|
|
|
full_install() {
|
|
require_root "$@"
|
|
require_debian
|
|
install_docker_if_needed
|
|
ensure_python_yaml
|
|
ensure_compose_plugin
|
|
|
|
local real_user real_home
|
|
real_user="${SUDO_USER:-${USER}}"
|
|
real_home="$(getent passwd "${real_user}" | cut -d: -f6)"
|
|
INSTALL_DIR="${real_home}/fromchat-server"
|
|
|
|
step "Install directory: ${INSTALL_DIR}"
|
|
mkdir -p "${INSTALL_DIR}"
|
|
chown "${real_user}:${real_user}" "${INSTALL_DIR}" 2>/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 "$@"
|