site/public/install.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
# Fresh, loopback-only quickstart using the verified Docker Hub release.
set +x
set -euo pipefail
fail() { printf 'Feedbacks: %s\n' "$*" >&2; exit 1; }
progress() { printf '\n[%s/7] %s\n' "$1" "$2"; }
download() {
curl --fail --silent --show-error --location --connect-timeout 10 --max-time 90 \
--retry 2 --retry-delay 1 --proto '=https' --proto-redir '=https' "$@"
}
read_release() {
local key value
version= image= checksum= revision=
while IFS='=' read -r key value || [[ -n "$key" ]]; do
case "$key" in
version) [[ -z "$version" ]] || fail 'Duplicate release version.'; version=$value ;;
image) [[ -z "$image" ]] || fail 'Duplicate release image.'; image=$value ;;
compose_sha256) [[ -z "$checksum" ]] || fail 'Duplicate Compose checksum.'; checksum=$value ;;
compose_revision) [[ -z "$revision" ]] || fail 'Duplicate Compose revision.'; revision=$value ;;
*) fail 'Invalid stable release metadata. No containers were started.' ;;
esac
done < "$directory/release.txt"
[[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || fail 'Invalid stable version.'
[[ "$image" =~ ^softinator/feedbacks@sha256:[a-f0-9]{64}$ ]] || fail 'Invalid release image.'
[[ "$checksum" =~ ^[a-f0-9]{64}$ && "$revision" =~ ^[a-f0-9]{40}$ ]] || fail 'Invalid Compose release pin.'
}
management_commands() {
printf 'Stop: cd %q && docker compose --project-name %q stop\n' "$directory" "$project"
printf 'Start: cd %q && docker compose --project-name %q up -d --wait\n' "$directory" "$project"
printf 'Status: cd %q && docker compose --project-name %q ps\n' "$directory" "$project"
printf 'Logs: cd %q && docker compose --project-name %q logs -f --tail=50\n' "$directory" "$project"
}
setup_stopped() {
local directory=$feedbacks_setup_directory project=$feedbacks_setup_project
printf '\nSetup stopped. Files and volumes are preserved in %s.\n' "$directory" >&2
if [[ -n "${project:-}" && -f "$directory/.env" ]]; then management_commands >&2; fi
}
compose() {
env -u FEEDBACKS_IMAGE -u APP_ORIGIN -u POSTGRES_PASSWORD -u ORGANIZATION_ID \
-u FEEDBACKS_PORT -u COMPOSE_PROJECT_NAME \
docker compose --project-name "$project" --project-directory "$directory" \
--env-file "$directory/.env" -f "$directory/compose.yaml" "$@"
}
main() {
local directory="$PWD/feedbacks-server" port=3000 project= image checksum actual version revision
local database_password owner_password random_id organization_id origin
while [[ $# -gt 0 ]]; do
case "$1" in
--help)
printf 'Usage: bash install.sh [--directory NEW_DIRECTORY] [--port 3000]\n'
printf 'Requires Docker Compose v2, curl and OpenSSL. Starts on localhost with persistent database and image volumes.\n'
return ;;
--directory|--port)
[[ $# -ge 2 && -n "$2" ]] || fail 'Missing option value. Use --help.'
if [[ "$1" == --directory ]]; then directory=$2; else port=$2; fi
shift 2 ;;
*) fail 'Unknown option. Use --help.' ;;
esac
done
[[ "$port" =~ ^[1-9][0-9]{3,4}$ && "$port" -ge 1024 && "$port" -le 65535 ]] || fail 'Use a port from 1024 to 65535.'
[[ "$directory" == /* ]] || directory="$PWD/$directory"
[[ ! -e "$directory" && ! -L "$directory" ]] || fail "Existing path preserved: $directory. Choose a new directory; this is not an upgrade command."
for tool in docker curl openssl; do command -v "$tool" >/dev/null || fail "Install $tool first."; done
progress 1 'Checking Docker and the requested local port…'
if (: < /dev/tcp/127.0.0.1/"$port") 2>/dev/null; then
fail "Port $port is already in use. Nothing was installed. Retry with --port 3300 --directory feedbacks-3300 (or choose another free port)."
fi
docker compose version >/dev/null || fail 'Docker Compose v2 is required.'
docker info >/dev/null 2>&1 || fail 'Start Docker and check your Docker access first.'
umask 077
mkdir -m 700 "$directory" || fail 'Could not create the installation directory.'
feedbacks_setup_directory=$directory
feedbacks_setup_project=
trap setup_stopped EXIT
printf '*\n' > "$directory/.gitignore"
progress 2 'Finding the latest verified stable release…'
download --max-filesize 4096 https://feedbacks.softinator.ai/quickstart-stable.txt --output "$directory/release.txt"
read_release
printf 'Selected Feedbacks %s (verified image digest).\n' "$version"
progress 3 'Downloading and verifying the Compose configuration…'
download "https://raw.githubusercontent.com/Softinator-TechLabs/feedbacks-oss/$revision/site/public/compose.quickstart.yaml" \
--output "$directory/compose.yaml.download"
actual=$(openssl dgst -sha256 "$directory/compose.yaml.download")
[[ ${actual##* } == "$checksum" ]] || fail 'Compose checksum did not match. No containers were started.'
mv "$directory/compose.yaml.download" "$directory/compose.yaml"
progress 4 'Creating private login and persistent-volume configuration…'
database_password=$(openssl rand -hex 32)
owner_password=$(openssl rand -hex 24)
random_id=$(openssl rand -hex 16)
organization_id="${random_id:0:8}-${random_id:8:4}-4${random_id:13:3}-8${random_id:17:3}-${random_id:20:12}"
project="feedbacks-${random_id:0:12}"
feedbacks_setup_project=$project
origin="http://localhost:$port"
{
printf 'COMPOSE_PROJECT_NAME=%s\n' "$project"
printf 'FEEDBACKS_IMAGE=%s\n' "$image"
printf 'FEEDBACKS_PORT=%s\nAPP_ORIGIN=%s\n' "$port" "$origin"
printf 'POSTGRES_PASSWORD=%s\nORGANIZATION_ID=%s\n' "$database_password" "$organization_id"
} > "$directory/.env"
printf '%s' "$owner_password" > "$directory/owner-password"
chmod 600 "$directory/.env" "$directory/owner-password"
compose config --quiet
progress 5 'Pulling images from Docker Hub (the first download can take several minutes)…'
compose pull
progress 6 'Starting PostgreSQL and Feedbacks; waiting for health checks…'
compose up -d --wait --wait-timeout 180
progress 7 'Creating the owner account and checking readiness…'
printf '%s' "$owner_password" | compose exec -T -e [email protected] \
-e BOOTSTRAP_NAME=Owner app node dist/cli/bootstrap.js
compose exec -T app node -e \
"fetch('http://127.0.0.1:3000/readyz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
trap - EXIT
printf '\nFeedbacks is running.\nURL: %s\nEmail: [email protected]\n' "$origin"
if [[ -t 1 ]]; then printf 'Password: %s\n' "$owner_password"; fi
printf 'Password saved privately: %s/owner-password\n' "$directory"
printf 'Database and screenshots use persistent Docker volumes. No S3 account is needed.\n'
management_commands
printf 'For shared HTTPS hosting: https://feedbacks.softinator.ai/docs/guide/self-host#bring-the-server-online\n'
unset owner_password database_password
}
# Run only after the complete downloaded script has been parsed.
main "$@"