mirror of
https://github.com/dartdavros/chatballs.git
synced 2026-10-05 09:14:58 +03:00
fix(ci): harden release and deployment workflow
This commit is contained in:
1 parent
ae98760824
commit
e13542fc45
27 files changed
+1424
-348
No files matched your search
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# common.sh — shared helpers for the CustoCRM deployment CLI.
|
||||
|
||||
CUSTOCRM_NON_INTERACTIVE=0
|
||||
CUSTOCRM_JSON=0
|
||||
|
||||
log() { printf '[custocrm] %s\n' "$*" >&2; }
|
||||
log_ok() { printf '[custocrm] OK: %s\n' "$*" >&2; }
|
||||
log_warn() { printf '[custocrm] WARN: %s\n' "$*" >&2; }
|
||||
log_err() { printf '[custocrm] ERROR: %s\n' "$*" >&2; }
|
||||
|
||||
die() {
|
||||
local msg="$1"
|
||||
local code="${2:-1}"
|
||||
if [[ "$CUSTOCRM_JSON" == "1" ]]; then
|
||||
printf '{"status":"error","error":%s}\n' "$(json_escape "$msg")"
|
||||
else
|
||||
log_err "$msg"
|
||||
fi
|
||||
exit "$code"
|
||||
}
|
||||
|
||||
json_escape() {
|
||||
local s="$1"
|
||||
s="${s//\\/\\\\}"
|
||||
s="${s//\"/\\\"}"
|
||||
s="${s//$'\n'/\\n}"
|
||||
s="${s//$'\r'/\\r}"
|
||||
s="${s//$'\t'/\\t}"
|
||||
printf '"%s"' "$s"
|
||||
}
|
||||
|
||||
require_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" 2
|
||||
}
|
||||
|
||||
release_dir() {
|
||||
printf '%s' "${CUSTOCRM_RELEASE_DIR:?CUSTOCRM_RELEASE_DIR is not set}"
|
||||
}
|
||||
|
||||
instance_dir() {
|
||||
printf '%s' "${CUSTOCRM_INSTANCE_DIR:?CUSTOCRM_INSTANCE_DIR is not set}"
|
||||
}
|
||||
|
||||
instance_env_file() { printf '%s/.env' "$(instance_dir)"; }
|
||||
release_env_file() { printf '%s/release.env' "$(release_dir)"; }
|
||||
release_checksums_file() { printf '%s/checksums.txt' "$(release_dir)"; }
|
||||
compose_file() { printf '%s/compose.yaml' "$(release_dir)"; }
|
||||
state_dir() { printf '%s/state' "$(instance_dir)"; }
|
||||
data_dir() { printf '%s/data' "$(instance_dir)"; }
|
||||
|
||||
ensure_instance_dirs() {
|
||||
local d
|
||||
for d in "$(instance_dir)" "$(state_dir)" "$(data_dir)" \
|
||||
"$(instance_dir)/backups" "$(instance_dir)/logs"; do
|
||||
mkdir -p "$d" || die "cannot create directory: $d"
|
||||
done
|
||||
}
|
||||
|
||||
LOCK_FD=9
|
||||
acquire_lock() {
|
||||
local lock_file
|
||||
lock_file="$(state_dir)/deploy.lock"
|
||||
exec 9>"$lock_file" || die "cannot open lock file: $lock_file"
|
||||
if ! flock -n 9; then
|
||||
die "another operation is in progress (lock held): $lock_file" 3
|
||||
fi
|
||||
}
|
||||
|
||||
release_lock() {
|
||||
flock -u 9 2>/dev/null || true
|
||||
exec 9>&- 2>/dev/null || true
|
||||
}
|
||||
|
||||
env_get() {
|
||||
local file="$1" key="$2"
|
||||
[[ -f "$file" ]] || return 0
|
||||
awk -F= -v k="$key" '$1==k && $0 !~ /^#/ {sub(/^[^=]*=/,""); print; exit}' "$file"
|
||||
}
|
||||
|
||||
verify_release_checksums() {
|
||||
local checksums
|
||||
checksums="$(release_checksums_file)"
|
||||
[[ -f "$checksums" ]] || {
|
||||
log_err "release checksums missing: $checksums"
|
||||
return 1
|
||||
}
|
||||
command -v sha256sum >/dev/null 2>&1 || {
|
||||
log_err "required command not found: sha256sum"
|
||||
return 1
|
||||
}
|
||||
(
|
||||
cd "$(release_dir)" || exit 1
|
||||
sha256sum -c checksums.txt >/dev/null
|
||||
) || {
|
||||
log_err "release checksum verification failed"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
validate_calls_network_boundary() {
|
||||
local web_ip turn_ip
|
||||
web_ip="$(env_get "$(instance_env_file)" CUSTOCRM_WEB_LISTENING_IP)"
|
||||
turn_ip="$(env_get "$(instance_env_file)" HUB_TURN_LISTENING_IP)"
|
||||
|
||||
[[ -n "$web_ip" ]] || {
|
||||
log_err "CUSTOCRM_WEB_LISTENING_IP is required for calls profile"
|
||||
return 1
|
||||
}
|
||||
[[ "$web_ip" != "0.0.0.0" ]] || {
|
||||
log_err "CUSTOCRM_WEB_LISTENING_IP cannot be 0.0.0.0 when calls profile uses TURN TLS on 443"
|
||||
return 1
|
||||
}
|
||||
[[ "$web_ip" != "$turn_ip" ]] || {
|
||||
log_err "web and TURN listeners must use different public IP addresses"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
release_image_keys() {
|
||||
printf '%s\n' \
|
||||
CUSTOCRM_BACKEND_IMAGE \
|
||||
CUSTOCRM_FRONTEND_IMAGE \
|
||||
CUSTOCRM_POSTGRES_IMAGE \
|
||||
CUSTOCRM_REDIS_IMAGE \
|
||||
CUSTOCRM_GATEWAY_IMAGE \
|
||||
CUSTOCRM_COTURN_IMAGE
|
||||
}
|
||||
|
||||
validate_release_image_refs() {
|
||||
local key ref failed=0
|
||||
while IFS= read -r key; do
|
||||
ref="$(env_get "$(release_env_file)" "$key")"
|
||||
if [[ ! "$ref" =~ @sha256:[0-9a-fA-F]{64}$ ]]; then
|
||||
log_err "$key must be an immutable @sha256 reference"
|
||||
failed=1
|
||||
fi
|
||||
done < <(release_image_keys)
|
||||
[[ "$failed" == "0" ]]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# compose.sh — canonical Docker Compose invocation for a release + instance pair.
|
||||
# Release files remain immutable; runtime data and .env stay in INSTANCE_DIR.
|
||||
|
||||
run_compose() {
|
||||
local inst rel
|
||||
inst="$(instance_dir)"
|
||||
rel="$(release_dir)"
|
||||
(
|
||||
cd "$inst" || exit 1
|
||||
docker compose \
|
||||
--project-directory "$inst" \
|
||||
--env-file "$inst/.env" \
|
||||
--env-file "$rel/release.env" \
|
||||
-f "$rel/compose.yaml" \
|
||||
"$@"
|
||||
)
|
||||
}
|
||||
|
||||
compose_config_validate() {
|
||||
run_compose config -q
|
||||
}
|
||||
|
||||
profile_enabled() {
|
||||
local p="$1" profiles
|
||||
profiles="$(env_get "$(instance_env_file)" COMPOSE_PROFILES)"
|
||||
[[ ",$profiles," == *",$p,"* ]]
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy.sh — canonical deployment workflow (ADR-HUB-0028 / SPEC-HUB-0019).
|
||||
|
||||
cmd_deploy() {
|
||||
ensure_instance_dirs
|
||||
acquire_lock
|
||||
trap release_lock EXIT
|
||||
|
||||
log "deploy: validating release and instance config"
|
||||
_deploy_validate || die "deploy: validation failed" 1
|
||||
|
||||
log "deploy: pulling immutable images"
|
||||
run_compose pull || die "deploy: image pull failed" 1
|
||||
|
||||
log "deploy: starting infrastructure (postgres, redis)"
|
||||
run_compose up -d postgres redis || die "deploy: infrastructure start failed" 1
|
||||
_wait_healthy postgres 60 || die "deploy: postgres did not become healthy" 1
|
||||
_wait_healthy redis 30 || die "deploy: redis did not become healthy" 1
|
||||
|
||||
log "deploy: running one-shot init (migrate)"
|
||||
run_compose run --rm init || die "deploy: init (migrate) failed" 1
|
||||
|
||||
log "deploy: starting application services"
|
||||
local app_services=(backend worker frontend gateway)
|
||||
if profile_enabled calls; then
|
||||
app_services+=(coturn)
|
||||
fi
|
||||
run_compose up -d "${app_services[@]}" || die "deploy: application start failed" 1
|
||||
|
||||
log "deploy: waiting for application health"
|
||||
_wait_healthy backend 90 || die "deploy: backend did not become healthy" 1
|
||||
_wait_running frontend 30 || die "deploy: frontend did not start" 1
|
||||
_wait_running gateway 30 || die "deploy: gateway did not start" 1
|
||||
if profile_enabled calls; then
|
||||
_wait_healthy coturn 60 || die "deploy: coturn did not become healthy" 1
|
||||
fi
|
||||
|
||||
log "deploy: running smoke checks"
|
||||
_smoke || die "deploy: smoke checks failed" 1
|
||||
|
||||
_record_release
|
||||
log_ok "deploy: complete (release: $(_applied_version_target))"
|
||||
}
|
||||
|
||||
_deploy_validate() {
|
||||
[[ -f "$(compose_file)" ]] || { log_err "compose.yaml missing"; return 1; }
|
||||
[[ -f "$(instance_env_file)" ]] || { log_err "instance .env missing"; return 1; }
|
||||
[[ -f "$(release_env_file)" ]] || { log_err "release.env missing"; return 1; }
|
||||
|
||||
verify_release_checksums || return 1
|
||||
validate_release_image_refs || return 1
|
||||
|
||||
local domain
|
||||
domain="$(env_get "$(instance_env_file)" CUSTOCRM_DOMAIN)"
|
||||
[[ -n "$domain" ]] || { log_err "CUSTOCRM_DOMAIN not set"; return 1; }
|
||||
|
||||
if profile_enabled calls; then
|
||||
validate_calls_network_boundary || return 1
|
||||
fi
|
||||
|
||||
compose_config_validate >/dev/null 2>&1 || {
|
||||
log_err "compose config invalid"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
_wait_healthy() {
|
||||
local svc="$1" timeout="$2" waited=0 state
|
||||
while [[ "$waited" -lt "$timeout" ]]; do
|
||||
state="$(run_compose ps --format json "$svc" 2>/dev/null | _first_json_service_state)"
|
||||
[[ "$state" == "healthy" ]] && return 0
|
||||
sleep 3
|
||||
waited=$((waited + 3))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_wait_running() {
|
||||
local svc="$1" timeout="$2" waited=0 state
|
||||
while [[ "$waited" -lt "$timeout" ]]; do
|
||||
state="$(run_compose ps --format json "$svc" 2>/dev/null | _first_json_service_state)"
|
||||
case "$state" in
|
||||
healthy|running|Up*) return 0 ;;
|
||||
esac
|
||||
sleep 3
|
||||
waited=$((waited + 3))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_first_json_service_state() {
|
||||
local line
|
||||
IFS= read -r line || return 0
|
||||
if [[ $line =~ \"Health\":\"([^\"]*)\" ]] && [[ -n "${BASH_REMATCH[1]}" ]]; then
|
||||
printf '%s\n' "${BASH_REMATCH[1]}"
|
||||
elif [[ $line =~ \"State\":\"([^\"]*)\" ]]; then
|
||||
printf '%s\n' "${BASH_REMATCH[1]}"
|
||||
elif [[ $line =~ \"Status\":\"([^\"]*)\" ]]; then
|
||||
printf '%s\n' "${BASH_REMATCH[1]}"
|
||||
fi
|
||||
}
|
||||
|
||||
_smoke() {
|
||||
local domain
|
||||
domain="$(env_get "$(instance_env_file)" CUSTOCRM_DOMAIN)"
|
||||
|
||||
run_compose exec -T backend python - <<'PY' >/dev/null 2>&1 || {
|
||||
import os
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
health_host = os.environ.get("HUB_HEALTHCHECK_HOST") or os.environ["CUSTOCRM_DOMAIN"]
|
||||
health_request = urllib.request.Request(
|
||||
"http://127.0.0.1:8000/api/v1/health/ready/",
|
||||
headers={"Host": health_host, "X-Forwarded-Proto": "https"},
|
||||
)
|
||||
with urllib.request.urlopen(health_request, timeout=5) as response:
|
||||
assert response.status == 200
|
||||
|
||||
with urllib.request.urlopen("http://frontend/", timeout=5) as response:
|
||||
assert response.status == 200
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, request, file_pointer, code, message, headers, new_url):
|
||||
return None
|
||||
|
||||
opener = urllib.request.build_opener(NoRedirect)
|
||||
domain = os.environ["CUSTOCRM_DOMAIN"]
|
||||
gateway_request = urllib.request.Request("http://gateway/", headers={"Host": domain})
|
||||
try:
|
||||
opener.open(gateway_request, timeout=5)
|
||||
except urllib.error.HTTPError as error:
|
||||
assert error.code in {301, 302, 303, 307, 308}
|
||||
assert error.headers.get("Location", "").startswith(f"https://{domain}")
|
||||
else:
|
||||
raise AssertionError("gateway did not redirect HTTP to HTTPS")
|
||||
PY
|
||||
log_err "smoke: internal backend/frontend/gateway checks failed"
|
||||
return 1
|
||||
}
|
||||
log_ok "smoke: backend, frontend and gateway"
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
local code
|
||||
code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 "https://$domain/" 2>/dev/null || true)"
|
||||
if [[ "$code" =~ ^(200|30[12378])$ ]]; then
|
||||
log_ok "smoke: public HTTPS endpoint"
|
||||
else
|
||||
log_warn "smoke: public HTTPS endpoint is not reachable yet (HTTP $code)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
_state_file() { printf '%s/applied_release' "$(state_dir)"; }
|
||||
|
||||
_record_release() {
|
||||
local ver
|
||||
ver="$(env_get "$(release_env_file)" CUSTOCRM_VERSION)"
|
||||
printf 'applied_version=%s\napplied_at=%s\n' "${ver:-unknown}" \
|
||||
"$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date)" > "$(_state_file)"
|
||||
}
|
||||
|
||||
_applied_version_target() {
|
||||
env_get "$(release_env_file)" CUSTOCRM_VERSION || printf 'unknown'
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
# doctor.sh — read-only pre-flight checks before deployment.
|
||||
|
||||
cmd_doctor() {
|
||||
local failures=0 inst rel
|
||||
inst="$(instance_dir)"
|
||||
rel="$(release_dir)"
|
||||
|
||||
_doctor_report() {
|
||||
local ok="$1"
|
||||
shift
|
||||
local msg="$*"
|
||||
if [[ "$ok" == "1" ]]; then
|
||||
[[ "$CUSTOCRM_JSON" == "1" ]] || log_ok "$msg"
|
||||
else
|
||||
[[ "$CUSTOCRM_JSON" == "1" ]] || log_err "$msg"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
require_cmd docker
|
||||
_doctor_report 1 "docker found"
|
||||
require_cmd flock
|
||||
_doctor_report 1 "flock found"
|
||||
|
||||
if command -v sha256sum >/dev/null 2>&1; then
|
||||
_doctor_report 1 "sha256sum found"
|
||||
else
|
||||
_doctor_report 0 "sha256sum missing"
|
||||
fi
|
||||
|
||||
if docker info >/dev/null 2>&1; then
|
||||
_doctor_report 1 "docker daemon reachable"
|
||||
else
|
||||
_doctor_report 0 "docker daemon not reachable"
|
||||
fi
|
||||
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
_doctor_report 1 "docker compose plugin available"
|
||||
else
|
||||
_doctor_report 0 "docker compose plugin missing"
|
||||
fi
|
||||
|
||||
local arch
|
||||
arch="$(uname -m 2>/dev/null || echo unknown)"
|
||||
if [[ "$arch" == "x86_64" ]]; then
|
||||
_doctor_report 1 "host arch x86_64"
|
||||
else
|
||||
_doctor_report 0 "unsupported host arch: $arch (target: x86_64)"
|
||||
fi
|
||||
|
||||
if [[ -d "$inst" ]] && [[ -w "$inst" ]]; then
|
||||
_doctor_report 1 "instance dir writable: $inst"
|
||||
else
|
||||
_doctor_report 0 "instance dir not writable: $inst"
|
||||
fi
|
||||
|
||||
if [[ -f "$(compose_file)" ]]; then
|
||||
_doctor_report 1 "compose.yaml present in release: $rel"
|
||||
else
|
||||
_doctor_report 0 "compose.yaml missing in release: $rel"
|
||||
fi
|
||||
|
||||
if [[ -f "$(instance_env_file)" ]]; then
|
||||
_doctor_report 1 "instance .env present"
|
||||
else
|
||||
_doctor_report 0 "instance .env missing: $(instance_env_file)"
|
||||
fi
|
||||
|
||||
if [[ -f "$(release_env_file)" ]]; then
|
||||
_doctor_report 1 "release.env present"
|
||||
else
|
||||
_doctor_report 0 "release.env missing: $(release_env_file)"
|
||||
fi
|
||||
|
||||
if verify_release_checksums; then
|
||||
_doctor_report 1 "release checksums valid"
|
||||
else
|
||||
_doctor_report 0 "release checksums invalid"
|
||||
fi
|
||||
|
||||
if validate_release_image_refs; then
|
||||
_doctor_report 1 "release image references are immutable"
|
||||
else
|
||||
_doctor_report 0 "release image references are invalid"
|
||||
fi
|
||||
|
||||
local domain
|
||||
domain="$(env_get "$(instance_env_file)" CUSTOCRM_DOMAIN)"
|
||||
if [[ -n "$domain" ]]; then
|
||||
_doctor_report 1 "CUSTOCRM_DOMAIN set: $domain"
|
||||
else
|
||||
_doctor_report 0 "CUSTOCRM_DOMAIN not set"
|
||||
fi
|
||||
|
||||
local pg_pwd
|
||||
pg_pwd="$(env_get "$(instance_env_file)" POSTGRES_PASSWORD)"
|
||||
if [[ -n "$pg_pwd" ]]; then
|
||||
_doctor_report 1 "POSTGRES_PASSWORD set"
|
||||
else
|
||||
_doctor_report 0 "POSTGRES_PASSWORD empty"
|
||||
fi
|
||||
|
||||
local secret
|
||||
secret="$(env_get "$(instance_env_file)" HUB_SECRET_KEY)"
|
||||
if [[ -n "$secret" ]] && [[ "$secret" != "change-me-long-random-secret" ]]; then
|
||||
_doctor_report 1 "HUB_SECRET_KEY set"
|
||||
else
|
||||
_doctor_report 0 "HUB_SECRET_KEY default/empty"
|
||||
fi
|
||||
|
||||
if profile_enabled calls; then
|
||||
local missing=0 k
|
||||
for k in HUB_CALL_TURN_SECRET HUB_CALL_TURN_REALM HUB_TURN_EXTERNAL_IP HUB_TURN_LISTENING_IP; do
|
||||
if [[ -z "$(env_get "$(instance_env_file)" "$k")" ]]; then
|
||||
_doctor_report 0 "$k required for calls profile"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [[ "$missing" == "0" ]] && validate_calls_network_boundary; then
|
||||
_doctor_report 1 "calls profile network boundary valid"
|
||||
else
|
||||
_doctor_report 0 "calls profile network boundary invalid"
|
||||
fi
|
||||
fi
|
||||
|
||||
if { [[ -d "$inst/backups" ]] && [[ -w "$inst/backups" ]]; } || [[ -w "$inst" ]]; then
|
||||
_doctor_report 1 "backup dir available"
|
||||
else
|
||||
_doctor_report 0 "backup dir not creatable: $inst/backups"
|
||||
fi
|
||||
|
||||
if compose_config_validate >/dev/null 2>&1; then
|
||||
_doctor_report 1 "compose config valid"
|
||||
else
|
||||
_doctor_report 0 "compose config invalid"
|
||||
fi
|
||||
|
||||
if [[ "$failures" != "0" ]]; then
|
||||
die "doctor: $failures check(s) failed" 1
|
||||
fi
|
||||
|
||||
if [[ "$CUSTOCRM_JSON" == "1" ]]; then
|
||||
printf '{"status":"ok","checks":"passed"}\n'
|
||||
else
|
||||
log_ok "doctor: all checks passed"
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# logs.sh — обёртка над `docker compose logs` с фильтром сервиса.
|
||||
|
||||
cmd_logs() {
|
||||
local svc=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-f|--follow) shift; ;;
|
||||
*) svc="$1"; shift; ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$svc" ]]; then
|
||||
run_compose logs --tail=200 "$svc" || die "logs: service not found or not running: $svc" 1
|
||||
else
|
||||
run_compose logs --tail=200 || die "logs: cannot fetch logs (not deployed yet?)" 1
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# status.sh — observability: версия, состояние сервисов, profiles, последняя
|
||||
# ошибка (SPEC-HUB-0019 §29). Installation-status (NEW/READY/…) — этап 2.
|
||||
|
||||
cmd_status() {
|
||||
local inst rel applied target
|
||||
inst="$(instance_dir)"
|
||||
rel="$(release_dir)"
|
||||
target="$(env_get "$(release_env_file)" CUSTOCRM_VERSION)"
|
||||
applied="$(env_get "$(_state_file)" applied_version)"
|
||||
|
||||
if [[ "$CUSTOCRM_JSON" == "1" ]]; then
|
||||
printf '{"status":"info","instance":%s,"release_dir":%s,"target_version":%s,"applied_version":%s}\n' \
|
||||
"$(json_escape "$inst")" "$(json_escape "$rel")" \
|
||||
"$(json_escape "${target:-unknown}")" "$(json_escape "${applied:-none}")"
|
||||
return 0
|
||||
fi
|
||||
|
||||
printf 'CustoCRM status\n'
|
||||
printf ' instance dir: %s\n' "$inst"
|
||||
printf ' release dir: %s\n' "$rel"
|
||||
printf ' target version: %s\n' "${target:-unknown}"
|
||||
printf ' applied version: %s\n' "${applied:-none}"
|
||||
printf ' profiles: %s\n' "$(env_get "$(instance_env_file)" COMPOSE_PROFILES || echo none)"
|
||||
|
||||
if [[ -f "$(_state_file)" ]]; then
|
||||
printf ' last applied at: %s\n' "$(env_get "$(_state_file)" applied_at)"
|
||||
else
|
||||
printf ' last applied at: (none)\n'
|
||||
fi
|
||||
|
||||
if docker info >/dev/null 2>&1; then
|
||||
printf '\nServices:\n'
|
||||
run_compose ps 2>/dev/null || log_warn "compose ps failed (not deployed yet?)"
|
||||
else
|
||||
printf '\nServices: (docker daemon not reachable)\n'
|
||||
fi
|
||||
}
|
||||
|
||||
_state_file() { printf '%s/applied_release' "$(state_dir)"; }
|
||||
@@ -14,12 +14,11 @@ COPY apps/internal-ui ./apps/internal-ui
|
||||
COPY apps/web-chat ./apps/web-chat
|
||||
COPY packages ./packages
|
||||
|
||||
# Build-time домен не привязывается (ADR-HUB-0028 §10): один образ работает на любом
|
||||
# домене; сниппет/WS/API выводятся от текущего origin в рантайме.
|
||||
ARG VITE_API_BASE_URL=
|
||||
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
|
||||
|
||||
ARG VITE_PUBLIC_HUB_URL=
|
||||
ENV VITE_PUBLIC_HUB_URL=${VITE_PUBLIC_HUB_URL}
|
||||
|
||||
RUN npm --workspace @edevs/internal-ui run build
|
||||
RUN npm --workspace @edevs/web-chat run build
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name hub.edevs.tech;
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/html;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name hub.edevs.tech;
|
||||
|
||||
# Set these paths after issuing the certificate with certbot.
|
||||
ssl_certificate /etc/letsencrypt/live/hub.edevs.tech/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/hub.edevs.tech/privkey.pem;
|
||||
|
||||
client_max_body_size 20m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ server {
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://internal-ui:5173;
|
||||
proxy_pass http://frontend:5173;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
Reference in new issue
Block a user