Repository navigation
Expand file tree
/
Copy pathentrypoint.sh
More file actions
154 lines (137 loc) · 6.13 KB
/
Copy pathentrypoint.sh
File metadata and controls
154 lines (137 loc) · 6.13 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
#!/bin/sh
set -e
# Activate custom git hooks (harmless if .git doesn't exist in container)
if [ -d ".git" ]; then
git config core.hooksPath .githooks 2>/dev/null || true
fi
# Verify FIELD_ENCRYPTION_KEY is set before migrations touch encrypted fields
if [ -z "${FIELD_ENCRYPTION_KEY:-}" ]; then
if [ "${KONOTE_MODE:-production}" = "production" ]; then
echo "ERROR: FIELD_ENCRYPTION_KEY is not set. Cannot run migrations safely."
exit 1
fi
echo "WARNING: FIELD_ENCRYPTION_KEY not set — encrypted field migrations will be skipped."
fi
echo "Running migrations..."
# migrate_default uses Django's real migrate (bypassing django_tenants' migrate_schemas)
# so the PUBLIC schema is kept in sync with all pending migrations, including
# tenant-app migrations that migrate_schemas would skip for the public schema.
python manage.py migrate_default --noinput
echo "Migrations complete."
echo "Running tenant schema migrations..."
# migrate (overridden by django_tenants as migrate_schemas) now handles
# per-tenant schema migrations for any existing agencies.
python manage.py migrate --noinput
echo "Tenant schema migrations complete."
# Bootstrap single-tenant deployments: register the domain in the agency_domains
# table so TenantMainMiddleware can route requests. Safe to re-run (idempotent).
# Uses ALLOWED_HOSTS env var for the domain; does nothing if already registered.
echo ""
echo "Registering tenant domain..."
if ! python manage.py setup_public_tenant 2>&1; then
if [ "${KONOTE_MODE:-production}" = "production" ]; then
echo "ERROR: setup_public_tenant failed in production mode. Refusing to start."
exit 1
fi
echo "WARNING: setup_public_tenant failed (see above). Site may not be accessible."
fi
echo "Tenant domain registration complete."
echo "Running audit migrations..."
# migrate_audit is a custom command that bypasses the django-tenants migrate_schemas
# override; the audit DB uses the standard PostgreSQL backend which does not have
# the set_schema() method that migrate_schemas requires.
python manage.py migrate_audit --noinput
echo "Audit migrations complete."
echo "Locking down audit database permissions..."
python manage.py lockdown_audit_db 2>&1 || echo "WARNING: Audit lockdown failed (see error above). Audit logs may not be write-protected."
# Seed runs all sub-commands in the right order:
# metrics, features, settings, event types, note templates, intake fields,
# demo data (if DEMO_MODE), and demo client field values.
#
# For blank production instances (for example, a new agency deployment that
# should start without templates or demo content yet), set
# KONOTE_SKIP_SEED=true to bypass this step.
echo ""
case "$(printf '%s' "${KONOTE_SKIP_SEED:-}" | tr '[:upper:]' '[:lower:]')" in
1|true|yes|on)
echo "Skipping seed step because KONOTE_SKIP_SEED is enabled."
;;
*)
echo "Seeding data..."
python manage.py seed 2>&1 || echo "WARNING: Seed failed (see error above). App will start but may be missing data."
;;
esac
# Merge any duplicate suggestion themes (non-blocking — safe to run repeatedly)
# TODO: Remove this merge step once migration 0016_unique_theme_per_program
# has been deployed and verified. The unique constraint and this merge step
# should be removed together.
echo ""
echo "Merging duplicate suggestion themes..."
python manage.py merge_duplicate_themes 2>&1 || echo "WARNING: Theme merge failed (see error above). App will start normally."
# Translation check (non-blocking — logs issues but never prevents startup)
echo ""
echo "Checking translations..."
python manage.py check_translations 2>&1 || echo "WARNING: Translation issues detected (non-blocking — app will start)"
# Security check before starting the application
# Set KONOTE_MODE=demo to allow startup with security warnings (for evaluation)
# Set KONOTE_MODE=production (default) to block startup if security checks fail
echo ""
echo "Running security checks..."
python manage.py startup_check
# If startup_check exits non-zero, the script stops here (set -e)
# Verify encrypted fields are readable (catches FERNET_KEY mismatches after migration)
echo ""
echo "Verifying encrypted field integrity..."
python manage.py shell -c "
import sys
bad_total = 0
checked_total = 0
# Check SurveyResponse.respondent_name
from apps.surveys.models import SurveyResponse
bad = 0
checked = 0
for r in SurveyResponse.objects.exclude(_respondent_name_encrypted=b'').iterator():
checked += 1
if r.respondent_name_display == '[DECRYPTION ERROR]':
bad += 1
if bad:
print(f'FAIL: {bad}/{checked} survey respondent names cannot be decrypted.')
else:
print(f'OK: {checked} encrypted respondent name(s) verified.' if checked else 'OK: No encrypted respondent names to verify.')
bad_total += bad
checked_total += checked
# Check ClientFile.first_name and last_name
from apps.clients.models import ClientFile
bad = 0
checked = 0
for c in ClientFile.objects.exclude(_first_name_encrypted=b'').exclude(_first_name_encrypted__isnull=True).iterator():
checked += 1
if c.first_name == '[DECRYPTION ERROR]' or c.last_name == '[DECRYPTION ERROR]':
bad += 1
if bad:
print(f'FAIL: {bad}/{checked} client names cannot be decrypted.')
else:
print(f'OK: {checked} encrypted client name(s) verified.' if checked else 'OK: No encrypted client names to verify.')
bad_total += bad
checked_total += checked
if bad_total:
print(f'TOTAL: {bad_total}/{checked_total} encrypted fields failed decryption.')
print('The FIELD_ENCRYPTION_KEY may not match the key used during migration.')
sys.exit(1)
print(f'All {checked_total} encrypted field(s) verified successfully.' if checked_total else 'No encrypted fields to verify.')
" 2>&1 || {
if [ "${KONOTE_MODE:-production}" = "production" ]; then
echo "ERROR: Encryption verification failed. Refusing to start."
exit 1
fi
echo "WARNING: Encryption verification failed (non-production — continuing)."
}
PORT=${PORT:-8000}
echo "Starting gunicorn on port $PORT"
exec gunicorn konote.wsgi:application \
--bind 0.0.0.0:$PORT \
--workers 2 \
--timeout 120 \
--log-level info \
--error-logfile - \
--access-logfile -