-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
214 lines (181 loc) · 5.94 KB
/
Copy pathinstall.sh
File metadata and controls
214 lines (181 loc) · 5.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env bash
set -Eeuo pipefail
# LiberoStack production installer.
# Designed for a fresh, low-resource VPS where Docker is the only required
# runtime dependency. Secrets are written to .env, which must stay out of Git.
COMPOSE_FILE="docker-compose.prod.yml"
ENV_FILE=".env"
ENV_EXAMPLE=".env.example"
DEFAULT_POSTGRES_PASSWORD="sovereign_secure_pass_2026"
DEFAULT_ADMIN_TOKEN="liberostack_master_secret_2026"
log() {
printf '\033[1;32m[liberostack]\033[0m %s\n' "$*"
}
warn() {
printf '\033[1;33m[warning]\033[0m %s\n' "$*" >&2
}
die() {
printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2
exit 1
}
require_project_root() {
[[ -f "$COMPOSE_FILE" ]] || die "Run this script from the LiberoStack project root."
[[ -f "$ENV_EXAMPLE" ]] || die "Missing $ENV_EXAMPLE."
}
install_docker_if_missing() {
if command -v docker >/dev/null 2>&1; then
log "Docker is already installed."
else
warn "Docker is not installed."
read -r -p "Install Docker now using https://get.docker.com? [Y/n] " answer
answer="${answer:-Y}"
if [[ "$answer" =~ ^[Yy]$ ]]; then
command -v curl >/dev/null 2>&1 || die "curl is required to install Docker automatically."
curl -fsSL https://get.docker.com | sh
log "Docker installation script completed."
else
die "Install Docker manually, then re-run ./install.sh."
fi
fi
if ! docker compose version >/dev/null 2>&1; then
die "Docker Compose v2 plugin is missing. Install Docker Compose, then re-run ./install.sh."
fi
if ! docker info >/dev/null 2>&1; then
die "Docker daemon is not reachable. Start Docker or run this script with a user allowed to access Docker."
fi
}
ensure_env_file() {
if [[ -f "$ENV_FILE" ]]; then
log "$ENV_FILE already exists; preserving existing values where safe."
else
cp "$ENV_EXAMPLE" "$ENV_FILE"
chmod 600 "$ENV_FILE"
log "Created $ENV_FILE from $ENV_EXAMPLE."
fi
}
get_env_var() {
local key="$1"
grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true
}
set_env_var() {
local key="$1"
local value="$2"
local tmp_file
touch "$ENV_FILE"
tmp_file="$(mktemp)"
if grep -qE "^${key}=" "$ENV_FILE"; then
awk -v key="$key" -v value="$value" '
BEGIN { prefix = key "=" }
index($0, prefix) == 1 { $0 = prefix value }
{ print }
' "$ENV_FILE" > "$tmp_file"
else
cat "$ENV_FILE" > "$tmp_file"
printf '%s=%s\n' "$key" "$value" >> "$tmp_file"
fi
cat "$tmp_file" > "$ENV_FILE"
rm -f "$tmp_file"
}
generate_secret() {
if command -v openssl >/dev/null 2>&1; then
openssl rand -hex 16
return
fi
# Fallback for minimal VPS images without openssl. Temporarily relax pipefail
# because head intentionally closes the pipe after reading 32 bytes.
set +o pipefail
LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32
printf '\n'
set -o pipefail
}
ensure_secure_postgres_password() {
local current_password
local generated_password
current_password="$(get_env_var "POSTGRES_PASSWORD")"
if [[ -z "$current_password" || "$current_password" == "$DEFAULT_POSTGRES_PASSWORD" ]]; then
generated_password="$(generate_secret)"
set_env_var "POSTGRES_PASSWORD" "$generated_password"
log "Generated a secure POSTGRES_PASSWORD in $ENV_FILE."
else
log "Existing POSTGRES_PASSWORD is non-default; preserving it."
fi
}
ensure_secure_admin_token() {
local current_token
local generated_token
current_token="$(get_env_var "ADMIN_TOKEN")"
if [[ -z "$current_token" || "$current_token" == "$DEFAULT_ADMIN_TOKEN" ]]; then
# 32 random bytes encoded as hex gives a 64-character owner token.
if command -v openssl >/dev/null 2>&1; then
generated_token="$(openssl rand -hex 32)"
else
set +o pipefail
generated_token="$(LC_ALL=C tr -dc 'a-f0-9' < /dev/urandom | head -c 64)"
set -o pipefail
fi
set_env_var "ADMIN_TOKEN" "$generated_token"
log "Generated a secure ADMIN_TOKEN in $ENV_FILE."
else
log "Existing ADMIN_TOKEN is non-default; preserving it."
fi
}
ensure_secure_telegram_webhook_secret() {
local current_secret
local generated_secret
current_secret="$(get_env_var "TELEGRAM_WEBHOOK_SECRET")"
if [[ -z "$current_secret" ]]; then
if command -v openssl >/dev/null 2>&1; then
generated_secret="$(openssl rand -hex 32)"
else
set +o pipefail
generated_secret="$(LC_ALL=C tr -dc 'a-f0-9' < /dev/urandom | head -c 64)"
set -o pipefail
fi
set_env_var "TELEGRAM_WEBHOOK_SECRET" "$generated_secret"
log "Generated a secure TELEGRAM_WEBHOOK_SECRET in $ENV_FILE."
else
log "Existing TELEGRAM_WEBHOOK_SECRET found; preserving it."
fi
}
configure_production_runtime() {
set_env_var "ENV" "production"
set_env_var "POSTGRES_SERVER" "postgres:5432"
set_env_var "TELEGRAM_BOT_MODE" "webhook"
}
prompt_for_domain() {
local domain
domain="$(get_env_var "APP_DOMAIN")"
if [[ -n "$domain" ]]; then
log "Using existing APP_DOMAIN=$domain."
else
while [[ -z "${domain:-}" ]]; do
read -r -p "Enter your production domain, e.g. libero.yourdomain.com: " domain
domain="${domain#http://}"
domain="${domain#https://}"
domain="${domain%%/*}"
done
set_env_var "APP_DOMAIN" "$domain"
log "Saved APP_DOMAIN=$domain."
fi
# Keep FastAPI-generated absolute URLs and OAuth redirects aligned with Caddy.
set_env_var "APP_BASE_URL" "https://$domain"
}
deploy_stack() {
log "Building and starting production containers."
docker compose -f "$COMPOSE_FILE" up -d --build
log "Running database migrations inside the web container."
docker compose -f "$COMPOSE_FILE" exec web alembic upgrade head
log "Deployment complete. Caddy will issue TLS automatically once DNS points to this VPS."
}
main() {
require_project_root
install_docker_if_missing
ensure_env_file
configure_production_runtime
ensure_secure_postgres_password
ensure_secure_admin_token
ensure_secure_telegram_webhook_secret
prompt_for_domain
deploy_stack
}
main "$@"