@@ -5,73 +5,118 @@ metadata:
55 name : {{ .Release.Name }}-nginx-configmap
66data :
77 wger-app.conf : |
8- # custom access log configuration
9- log_format custom '$remote_addr - $remote_user [$time_local] "$request" '
10- '$status $body_bytes_sent "$http_referer" '
11- '"$http_user_agent" "$http_x_forwarded_for"';
12-
13- # proxy
148 upstream app_server {
159 # fail_timeout=0 means we always retry an upstream even if it failed
1610 # to return a good HTTP response <- according to gunicorn doc
1711 server 127.0.0.1:8000 fail_timeout=0;
1812 zone upstreams 64K;
1913 keepalive 2;
2014 }
15+ # Used by the /ps/ proxy below.
16+ upstream powersync {
17+ server powersync:8080;
18+ }
2119
22- # if no Host match, close the connection to prevent host spoofing
23- server {
24- listen 80 default_server;
25- return 444;
20+ # custom access log configuration
21+ log_format custom '$remote_addr - $remote_user [$time_local] "$request" '
22+ '$status $body_bytes_sent "$http_referer" '
23+ '"$http_user_agent" "$http_x_forwarded_for"';
24+
25+ # JSON access log format compatible with the Loki/Alloy parser
26+ # (same field names as the Caddy parser, so the same dashboard works).
27+ log_format json_access escape=json
28+ '{'
29+ '"ts":"$time_iso8601",'
30+ '"client_ip":"$remote_addr",'
31+ '"request_method":"$request_method",'
32+ '"request_uri":"$request_uri",'
33+ '"request_proto":"$server_protocol",'
34+ '"request_host":"$host",'
35+ '"status":$status,'
36+ '"size":$body_bytes_sent,'
37+ '"duration":$request_time,'
38+ '"user_agent":"$http_user_agent",'
39+ '"referer":"$http_referer"'
40+ '}';
41+
42+ # Universal X-Forwarded-Proto handling for all deployment scenarios
43+ # - Uses upstream value when behind reverse proxy (Traefik, Caddy, etc.)
44+ # - Falls back to nginx's scheme for direct connections and port forwarding
45+ map $http_x_forwarded_proto $final_forwarded_proto {
46+ default $http_x_forwarded_proto;
47+ '' $scheme;
2648 }
2749
2850 # webserver
2951 server {
30- listen 8080 deferred;
31- client_max_body_size 4G;
52+ listen 80;
3253
3354 # set the correct host(s) for your site
3455 server_name {{ join " " .Values.ingress.url }};
3556
36- access_log /var/log/nginx/access.log custom;
37- error_log /var/log/nginx/error.log warn;
38-
39- # path for static files (only needed for serving "local" static and media files)
40- root /var/www/html/;
57+ access_log /var/log/nginx/access.log custom json_access;
4158
4259 location / {
43- # checks for static file, if not found proxy to app
44- try_files $uri @proxy_to_app;
45- }
60+ proxy_pass http://app_server;
4661
47- location @proxy_to_app {
62+ # Standard proxy headers
63+ proxy_set_header Host $http_host;
64+ proxy_set_header X-Real-IP $remote_addr;
65+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
66+ proxy_set_header X-Forwarded-Proto $final_forwarded_proto;
67+ proxy_set_header X-Forwarded-Host $http_host;
68+
69+ # WebSocket support
70+ proxy_set_header Upgrade $http_upgrade;
71+ proxy_set_header Connection "upgrade";
72+
73+ proxy_redirect off;
4874 proxy_http_version 1.1;
4975
50- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
51- {{- if .Values.ingress.tls }}
52- proxy_set_header X-Forwarded-Proto https;
53- {{- else }}
54- proxy_set_header X-Forwarded-Proto $scheme;
55- {{- end }}
76+ # Timeouts for WebSocket connections
77+ proxy_read_timeout 86400s;
78+ proxy_send_timeout 86400s;
79+ }
80+
81+ # Reverse proxy for the PowerSync service, used by the mobile app for offline mode
82+ location /ps/ {
83+ proxy_pass http://powersync/;
84+
5685 proxy_set_header Host $http_host;
86+ proxy_set_header X-Real-IP $remote_addr;
87+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
88+ proxy_set_header X-Forwarded-Proto $final_forwarded_proto;
5789
58- # keep alive
59- proxy_set_header "Connection" "";
90+ # WebSocket upgrade (used by the diagnostics app and the Web SDK)
91+ proxy_http_version 1.1;
92+ proxy_set_header Upgrade $http_upgrade;
93+ proxy_set_header Connection "upgrade";
6094
61- # https://www.getpagespeed.com/server-setup/nginx/tuning-proxy_buffer_size-in-nginx
62- proxy_buffer_size 32k;
63- proxy_busy_buffers_size 40k; # proxy_buffer_size + 2 small buffers of 4k
64- proxy_buffers 64 4k;
65- proxy_max_temp_file_size 0;
95+ # Sync streams are long-lived (HTTP/2 streaming POST) and must not
96+ # be buffered or cut off by short timeouts.
97+ proxy_buffering off;
98+ proxy_read_timeout 1d;
99+ proxy_send_timeout 1d;
100+ }
66101
67- # give gunicorn time to process
68- proxy_read_timeout 1800 ;
102+ location /static/ {
103+ alias /wger/static/ ;
69104
70- proxy_redirect off;
71- proxy_pass http://app_server;
105+ # Files in the static folder are preprocessed by collectstatic and have a hash
106+ # in their names, so we can cache longer
107+ add_header Cache-Control "public, max-age=31536000, immutable" always;
108+ add_header Vary "Accept-Encoding" always;
72109 }
110+
111+ location /media/ {
112+ alias /wger/media/;
113+ }
114+
115+ # Increase max body size to allow for video uploads
116+ client_max_body_size 100M;
73117 }
74118{{- end }}
119+
75120---
76121kind : ConfigMap
77122apiVersion : v1
@@ -80,3 +125,216 @@ metadata:
80125data :
81126 40-grantSuperuser.sql : |
82127 ALTER USER {{ .Values.postgres.userDatabase.user.value }} WITH SUPERUSER;
128+
129+ ---
130+ kind : ConfigMap
131+ apiVersion : v1
132+ metadata :
133+ name : {{ .Release.Name }}-powersync-configmap
134+ data :
135+ powersync.yaml : |
136+ # yaml-language-server: $schema=../schema/schema.json
137+
138+ # Note that this example uses YAML custom tags for environment variable substitution.
139+ # Using `!env [variable name]` will substitute the value of the environment variable named
140+ # [variable name].
141+ #
142+ # Only environment variables with names starting with `PS_` can be substituted.
143+ #
144+ # e.g. With the environment variable `export PS_STORAGE_MONGO_URI=mongodb://localhost:27017`
145+ # and YAML code:
146+ # uri: !env PS_STORAGE_MONGO_URI
147+ # The YAML will resolve to:
148+ # uri: mongodb://localhost:27017
149+ #
150+ # If using VS Code see the `.vscode/settings.json` definitions which define custom tags.
151+
152+ # migrations:
153+ # # Migrations run automatically by default.
154+ # # Setting this to true will skip automatic migrations.
155+ # # Migrations can be triggered externally by altering the container `command`.
156+ # disable_auto_migration: true
157+
158+ # Settings for telemetry reporting
159+ # See https://docs.powersync.com/self-hosting/telemetry
160+ telemetry:
161+ disable_telemetry_sharing: true
162+
163+ # Expose prometheus metrics on this port
164+ prometheus_port: 9090
165+
166+ # Settings for source database replication
167+ replication:
168+ # Specify database connection details
169+ # Note only 1 connection is currently supported
170+ # Multiple connection support is on the roadmap
171+ connections:
172+ - type: postgresql
173+ uri: !env PS_DATABASE_URI
174+
175+ # Or use individual params
176+ # hostname: db # From the Docker Compose service name
177+ # port: 5432
178+ # database: postgres
179+ # username: postgres
180+ # password: mypassword
181+
182+ # SSL settings
183+ sslmode: disable # 'verify-full' (default) or 'verify-ca' or 'disable'
184+ # 'disable' is OK for local/private networks, not for public networks
185+
186+
187+ # Connection settings for sync bucket storage
188+ storage:
189+ type: postgresql
190+ uri: !env PS_STORAGE_PG_URI
191+ sslmode: disable
192+
193+ # The port which the PowerSync API server will listen on
194+ port: !env PS_PORT
195+
196+ # Specify sync rules
197+ sync_rules:
198+ path: sync_rules.yaml
199+
200+ # Client (application end user) authentication settings
201+ client_auth:
202+ allow_local_jwks: true
203+ jwks_uri: !env PS_JWKS_URL
204+
205+ # JWKS audience
206+ audience: ["powersync"]
207+
208+ sync_rules.yaml : |
209+ # Note that changes to this file are not watched.
210+ # The service needs to be restarted for changes to take effect.
211+
212+ # Warning: a user may have at most 1000 buckets, i.e. parameter-query results
213+ # summed across all streams. This counts the *parameter* rows, not the data
214+ # rows inside a bucket (a single bucket can hold any number of rows). For a
215+ # stream with a `with:` CTE the count is the number of rows the CTE returns,
216+ # so for `user_ingredients` below that is the number of distinct ingredients
217+ # a user has ever referenced. Exceeding the limit is a hard error
218+ # (PSYNC_S2305 "Too many parameter query results").
219+ # See https://docs.powersync.com/sync/rules/parameter-queries
220+ #
221+ # Streams are split by update frequency (cold / medium / hot) so that
222+ # bucket compaction can collapse the head of hot buckets without being
223+ # blocked by long-lived rows from cold tables.
224+ #
225+ # For details, see the documentation:
226+ # https://docs.powersync.com/sync/streams/overview
227+ # https://docs.powersync.com/maintenance-ops/compacting-buckets
228+
229+ config:
230+ edition: 3
231+
232+ streams:
233+ # Global reference data, shared by all users, changes rarely enough
234+ core:
235+ auto_subscribe: true
236+ queries:
237+ - SELECT * FROM core_language
238+ - SELECT * FROM core_license
239+ - SELECT * FROM core_repetitionunit
240+ - SELECT * FROM core_weightunit
241+ - SELECT * FROM exercises_exercise
242+ - SELECT * FROM exercises_translation
243+ - SELECT * FROM exercises_alias
244+ - SELECT * FROM exercises_exercisecomment
245+ - SELECT * FROM exercises_muscle
246+ - SELECT * FROM exercises_exercise_muscles
247+ - SELECT * FROM exercises_exercise_muscles_secondary
248+ - SELECT * FROM exercises_equipment
249+ - SELECT * FROM exercises_exercise_equipment
250+ - SELECT * FROM exercises_exercisecategory
251+ - SELECT * FROM exercises_exerciseimage
252+ - SELECT * FROM exercises_exercisevideo
253+
254+ # COLD, per-user data that almost never changes after creation.
255+ user_profile:
256+ auto_subscribe: true
257+ queries:
258+ - SELECT * FROM core_userprofile WHERE CAST(user_id AS TEXT) = auth.user_id()
259+ - SELECT * FROM gallery_image WHERE CAST(user_id AS TEXT) = auth.user_id()
260+
261+ # COLD but potentially large, only the per-user *filter set* changes when the user logs new foods
262+ user_ingredients:
263+ auto_subscribe: true
264+ with:
265+ user_ingredients: |
266+ SELECT DISTINCT nutrition_synced_ingredient.id
267+ FROM nutrition_synced_ingredient
268+ WHERE nutrition_synced_ingredient.id IN (
269+ SELECT nutrition_logitem.ingredient_id FROM nutrition_logitem
270+ JOIN nutrition_nutritionplan ON nutrition_logitem.plan_id = nutrition_nutritionplan.id
271+ WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id())
272+ OR nutrition_synced_ingredient.id IN (
273+ SELECT nutrition_mealitem.ingredient_id FROM nutrition_mealitem
274+ JOIN nutrition_meal ON nutrition_mealitem.meal_id = nutrition_meal.id
275+ JOIN nutrition_nutritionplan ON nutrition_meal.plan_id = nutrition_nutritionplan.id
276+ WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id())
277+ queries:
278+ - SELECT * FROM nutrition_synced_ingredient AS nutrition_ingredient WHERE id IN user_ingredients
279+ - |
280+ SELECT nutrition_image.* FROM nutrition_image
281+ WHERE nutrition_image.ingredient_id IN user_ingredients
282+ - |
283+ SELECT nutrition_ingredientweightunit.* FROM nutrition_ingredientweightunit
284+ WHERE nutrition_ingredientweightunit.ingredient_id IN user_ingredients
285+
286+ # MEDIUM. Edited e.g. when the user builds or edits their routine or nutrition plan,
287+ # but not on every workout.
288+ user_planning:
289+ auto_subscribe: true
290+ queries:
291+ # Routines (templates excluded)
292+ - SELECT * FROM manager_routine WHERE CAST(user_id AS TEXT) = auth.user_id() AND is_template = FALSE
293+
294+ # Measurements
295+ - SELECT * FROM measurements_category WHERE CAST(user_id AS TEXT) = auth.user_id()
296+ - |
297+ SELECT measurements_measurement.*
298+ FROM measurements_measurement
299+ INNER JOIN measurements_category
300+ ON measurements_measurement.category_id = measurements_category.id
301+ WHERE CAST(measurements_category.user_id AS TEXT) = auth.user_id()
302+
303+ # Nutrition plan structure (not the log items)
304+ - SELECT * FROM nutrition_nutritionplan WHERE CAST(user_id AS TEXT) = auth.user_id()
305+ - |
306+ SELECT nutrition_meal.*
307+ FROM nutrition_meal
308+ JOIN nutrition_nutritionplan
309+ ON nutrition_meal.plan_id = nutrition_nutritionplan.id
310+ WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id()
311+ - |
312+ SELECT nutrition_mealitem.*
313+ FROM nutrition_mealitem
314+ JOIN nutrition_meal
315+ ON nutrition_mealitem.meal_id = nutrition_meal.id
316+ JOIN nutrition_nutritionplan
317+ ON nutrition_meal.plan_id = nutrition_nutritionplan.id
318+ WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id()
319+
320+
321+ # HOT. Generates one or more new rows per workout / meal. Compaction has the
322+ # biggest impact here, so it must stay isolated from the other streams above
323+ user_activity:
324+ auto_subscribe: true
325+ queries:
326+ # Weight tracking
327+ - SELECT uuid AS id, weight, date, user_id FROM weight_weightentry WHERE CAST(user_id AS TEXT) = auth.user_id()
328+
329+ # Workout sessions and per-set logs
330+ - SELECT * FROM manager_workoutsession WHERE CAST(user_id AS TEXT) = auth.user_id()
331+ - SELECT * FROM manager_workoutlog WHERE CAST(user_id AS TEXT) = auth.user_id()
332+
333+ # Nutrition log entries
334+ - |
335+ SELECT nutrition_logitem.*
336+ FROM nutrition_logitem
337+ JOIN nutrition_nutritionplan
338+ ON nutrition_logitem.plan_id = nutrition_nutritionplan.id
339+ WHERE CAST(nutrition_nutritionplan.user_id AS TEXT) = auth.user_id()
340+
0 commit comments