Compare commits
5 commits
23442bb6ea
...
bef415b591
| Author | SHA1 | Date | |
|---|---|---|---|
| bef415b591 | |||
| 6d25a6074b | |||
| 5c6b4f18eb | |||
| 67e81d39f3 | |||
| 1f5e5b9a37 |
23 changed files with 315 additions and 1531 deletions
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
server = [
|
||||
"/epicenter.works/10.50.60.1"
|
||||
"/epicenter.intra/10.50.60.1"
|
||||
"/akvorrat.at/10.50.60.1"
|
||||
"9.9.9.9"
|
||||
"149.112.112.11"
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
endpoint = "5.9.131.17:51821";
|
||||
publicKey = "T7jPGSapSudtKyWwi2nu+2hjjse96I4U3lccRHZWd2s=";
|
||||
presharedKeyFile = config.sops.secrets.wg_epicenter_works_psk.path;
|
||||
allowedIPs = [ "10.14.1.0/24" "10.14.2.0/24" "10.14.11.0/24" "10.14.40.0/24" "10.25.0.0/24" "10.50.60.0/24" "10.60.60.0/24" ];
|
||||
allowedIPs = [ "10.14.1.0/24" "10.14.2.0/24" "10.14.11.0/24" "10.14.40.0/24" "10.14.50.0/24" "10.25.0.0/24" "10.50.60.0/24" "10.60.60.0/24" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
./modules/openldap.nix
|
||||
./modules/dovecot.nix
|
||||
./modules/postfix.nix
|
||||
./modules/dkim-fueltide.nix
|
||||
|
||||
./utils/modules/borgbackup.nix
|
||||
./utils/modules/promtail
|
||||
|
|
|
|||
28
hosts/mail/modules/dkim-fueltide.nix
Normal file
28
hosts/mail/modules/dkim-fueltide.nix
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
sops.secrets.rspamd-dkim-fueltide-io-key = {
|
||||
owner = "rspamd";
|
||||
group = "rspamd";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
# rspamd's dkim_signing module in rspamd.nix picks up per-domain keys from
|
||||
# /var/lib/rspamd/dkim/$domain.$selector.key. This one-shot drops the
|
||||
# fueltide.io key into place before rspamd starts.
|
||||
systemd.services.rspamd-dkim-fueltide-setup = {
|
||||
description = "Install fueltide.io DKIM key into rspamd";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "rspamd.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
install -d -o rspamd -g rspamd -m 0750 /var/lib/rspamd/dkim
|
||||
install -o rspamd -g rspamd -m 0400 \
|
||||
${config.sops.secrets.rspamd-dkim-fueltide-io-key.path} \
|
||||
/var/lib/rspamd/dkim/fueltide.io.default.key
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
@ -1,47 +1,48 @@
|
|||
borg-passphrase: ENC[AES256_GCM,data:D6+ZedxUQ7m/m0YkM5m/B4kFsNySJjFyh8Gmhn3Mpe+mqEzzMRjAbwmGzx9i9Lnr1dTjRElUOgevnnvW5J2KRA==,iv:cG4w1KsEm1SOTni9bsbSW1+ypzjjs2Q42I+4xvcCAu0=,tag:WkkNVa27Uy5nFpmXaIH6ww==,type:str]
|
||||
borg-ssh-key: ENC[AES256_GCM,data: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,iv:yA1CkRMapP1S3zMwu6Tj0/0/HHpwD1yRAm/qrZx/kPs=,tag:SYg2IoXeD9fMYb35J/AJ1Q==,type:str]
|
||||
netdata-claim-token: ENC[AES256_GCM,data:ECx8zLnU/dj08vfA76oVbVzL3JG9MLBoFmxSjtjiFbSiFtdaHtG/8u5FEuyQ1bQMQntV91xj7x1kY8fAp7VNbWyC13pOEOrt6rvJYch14eM3bqNvfGeqgJsHmAaRbY6mBrxJBkiRJBLYVil4e1oDNZVnzFQ4ditXZbMGtAV2063K1MRI/48p,iv:viE84mOp5KSdj8vdK5XxR0W9A54oPxQO5ahnpPLeAdE=,tag:WjzKjGXRRAc7vlzreFHbng==,type:str]
|
||||
openldap-rootpw: ENC[AES256_GCM,data:W0em1Dffg+IUoynwwPD4NjFksR38ZO4mhWFI83ALvYcwYIplxw/gDRLGCqbSt6TR5C65CKr1sOUiU+4Xq3UWmw==,iv:BHQhISTIYuwSM3KiSb0mEEo3BMNo6FXEDXoIvI3SZrU=,tag:tX8gfnk1JYnaNionk/jrLg==,type:str]
|
||||
dovecot-ldap-password: ENC[AES256_GCM,data:JYAt8/WggwclNEPO9CaWfQsvQBA8DDJCU2km93HpowoVwIdvQ/0lQHeXndPYe1EmJGJ3vLErie+Zn2kDINIMqQ==,iv:HR0QJ0GgQks3NzhfXwjHupCKcPOekkiTcp5Jxbz7CxI=,tag:19m7F6TjGUPOuHQJuUq2pw==,type:str]
|
||||
borg-passphrase: ENC[AES256_GCM,data:BPfGmuF0wI6LAge/wWObEHhUxfyNHYmFHJW3kkFxxHQDjQqQtORfGiQGUYnzw6BhJa7FGpvHHiagLbSZcpXvWw==,iv:jzm3toujgf2rCwDokbR3/YEs6BBwt5DNUyzoLQiBlSE=,tag:/X/7tG1bG/wqNhshMfUkSg==,type:str]
|
||||
borg-ssh-key: ENC[AES256_GCM,data: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,iv:8qdeLajGkVgn5xw44BJNUbUZQH2cMq5mBnZByvktsuI=,tag:YjNLIl0mw7h+6wfI5hYnQQ==,type:str]
|
||||
netdata-claim-token: ENC[AES256_GCM,data:XB+OXsHtohopphWDWbW7dAI/UXbntsHRIOt4OiWI4QPy1pamL7f9x4QPTMUM2TfVqxrRYGdvDXh0fnUTIK8OqoksrrjdOiy2fQ6k4W7y11+/Un2bEXTMrS3GT3BcVYN9ppc/VUhgX/JDmIm9EptLyASOV0VyQCHOkTVLuyYfQva7tetVgX+W,iv:8cpwuMQi3IAAYSGOzKPTsr+SrUW95UB+YCZBO0sDdEw=,tag:WBcvCoknTgkxgbWRAKWwLA==,type:str]
|
||||
openldap-rootpw: ENC[AES256_GCM,data:GtR9nwx1f5zx8D8p6cmvCyM1lKyKXDdcum6mCvU87Jm/C868qRiatLDBbP6qUsDzzyFG+9hyVPetik88kGhvrw==,iv:j5JYdAbUga5eUFmIUNrPNZ0G6Sx1zYtb68nNVAClpXs=,tag:WpcrFPRuqTpRZmcrr6T/Vg==,type:str]
|
||||
dovecot-ldap-password: ENC[AES256_GCM,data:86vTpWKCKINNrkD+a1UJeJkECW+vmIwXrtD4KPyNBmmPN6xi+LutzEDuwIGKQrC1ISTcmjo3SePsR1KTDSqJ3A==,iv:kqyT1bEyCWHvs8o6wwSC+08jtuOc/gA77yFCkv75gQg=,tag:hLt7Vw5WltVI1L83adcepA==,type:str]
|
||||
rspamd-dkim-fueltide-io-key: ENC[AES256_GCM,data:Rhiuu+vc6XyYnY/Syjs1qvrZeIW3HEPcK/GNUEN+FDQ/vBMHwSnw5XTmS8DjyvMMNB6ga+YrRAOPFu1P4Py23w3HEA9YL/+5g1QLOveBwS159peRSBsGB029JHiFLrZ4cpiaBSn36gr+/ygSpQRM0JPWhJkh8t456bnc9rtFSn8MBgT/AnZh5SYGfhNyrj7HfRV09espFSqRMQyccxykiQi84ojasQiq/lCLv9TLz07bZAQRbu/cEAe2yQKfPrgCCY3m27wJf/Hd/iCWv4/uWXcqlD8lCnK0Auat0s5Qq36oKUF6GMmVME2jRSLjwo9Uo6rT16iUA6lV/TrsYPAcxBtkXroiZn8sp6aXRvOHaWuLwClF3ajfZ06PO4WB2Wi7up6s3WASGF7GX4zyPmi8FEfo/QnCTeqcLGZdBrwqkAkPZ7S7IB0pYCHWLfp47TX2IB7T4AB6RJZ6LQ1nwqLgChjEZXF7eylVZyNPPLpSV3WegdkvqmLA+o5WyzGI76hkKNou91Quizd2BhbWKfvlbcqLvu3jWnsp/3xKmFS+7CdhB5P+pkNYnvQN7r33UrVhm3jSbDF5NJqMiVmgX5H8/4m9bNS7KRDbGQdQo/egploOc5PKUVrxlXFTpEofpoGBy/XW+69y0GoPYPAB4nFT0fIGox/N6bxgP232MMZKn3wBqiI3l8UVQRU/OT71bta/gQo+Y+PljSn18hF41k77j421FXG41rL1Psuc5SSKKI4vxxogHE3RdbhEvE/mSawM+kgWGOBfK0VejxGkBjPRKO+bP5HnN0J4L35D+3y0zpYkHTJJP99T+14GECmogk1GrAj8EQFRCSgE3bq58VlUKV9ZmBDS+/gbpxZ/bcXWpg7OQZ3OvZFKh3MgmphYQhiqy2kamLOisqU3ueQNgA9h4QEX48cPxBo69hTGsfn+IMI1lJYjZsm7hykvGWxfHN3uH971ZYh8Ct3xaFX8Z3pNBmvuWm11aLzglwOVTQJXFBBRbIB3v1mHexGa13QjEXJ2smZyaW5cdpLBVEYmWCR+sHoOq+7V6F67bpEOyfVKl4pyRXgg+u7t049MsLqyZ5i0yTVmSEvqGdwhkYQZi3y3RiMOFvorb63ePuSSfxXrs0GXdIfQhtWkUMGXDk+r+UC+l0jh31VJ3gwNBiDit8WKmLpKEMvaTi4Z+by6oZY3s5G/f5j0HXpOqnJi944bxF/5eXa6YQ==,iv:CC1jJ0YBTUwiwX8fPXub1+yG+eeDIUBorv7mgTRWGLw=,tag:M7L0763goCdaM5o8UZ9QTQ==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0NWZPWXltTVNXNGxPd0hZ
|
||||
R0U4VzN5WlI0WWZrRVVFMmpnckpMMkREaTBvCm54eTZtZlZzRVpwRmg4Ulp0VG5w
|
||||
VnJkc29nN0VBRFR1U1J6L0RQeWlLNlkKLS0tIDJ3eTdiUWJzbURvSk1neEhyakJS
|
||||
Z2MzZi8ybW1PMngyRGk4NHhIMzZsem8KZuy1TWwvkFGsAVMIEk2+bwDcsmYziUjj
|
||||
Wd4wMK1XuLnJyFYPt6CwzBAPG+1LQzmYWdC9mNI00YZM6XneU3OisQ==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1dWxFbG1McEYzWlN2WmZ6
|
||||
enU5bnRjblI4ZHhvVHhIMGdBdzR5VFBrL1E0CkljRmpqTko3NDdXTS9RWDVXaDZl
|
||||
bVVjbGJwalZuT3VMdUErUUg3N2JiL1UKLS0tIEcrYTNGSFYvd0VLRnJ2V0syNGNz
|
||||
UlNlWURkNmk0dXBRQ212U0dWaXpxM0UKS+6vyPlzyhlgbj+1OHdv07I8CKK3dLKN
|
||||
8jY30HiMPoBWS6Rk8mItRcLi56aTEGUsbdg85fxy8TUvdEdxgxLA0g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBZEhsK0x1QkczeFIvL0JI
|
||||
UWY5R252WkZvR0s2SStlWVBMQk9ENFpaRHpRClg3VjhpYW5UbzJkODRFYWF2aGpr
|
||||
ajE3aUFhZStYY0NJYlg1QTZqVHJsODAKLS0tIGsyRHlXSVQyV2RXVCswRVlsbktV
|
||||
c0Z5ZXhtb0wrT0Q3WU1ONjFiNk1WOVkKHxnDqJkGfiqrlAyzJHYVbJlR1/jluFU+
|
||||
hM/wENwqtlZ7RCSdG68AssgP9zukO94sV9mAtbfOdeVwXa1LU66Ncw==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhS2s4bWlZN2pjRUticDJY
|
||||
eGtIVEZEVkM3c2RKVmVKQnF3Z1cxbnBzZEh3CmRFN2c0T2FjV0UyMUxKREJsUnhl
|
||||
YWZ3WGJOZWptd1c2SG5pTy82djBmVXMKLS0tIC9YamwwNHV3RjNtZ25mY2NPVTRQ
|
||||
a1NSUlY4cWFWYzVYdVFxVFdNQm5DZzAKKmUA1AbqsFOhpczeHtiPnOcVMVp92m//
|
||||
fB+AfPQUdb2/4p87PpzE/2xUMUTgY5Eng2KaHyJHq0gh+5XKhsDi3Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5TG9wT2JHN2pOVjRueUF2
|
||||
UGJkM2d5VFpLT0hKVmIwV2Qva25ubk1lK0ZBCkJiNWpuZ3grQ0lkSDlCMDBwYjRR
|
||||
cDlPVHhtWlpnaVFYMFJqWWY2ZVFGNncKLS0tIFZQVVRSQXVOZnNDOHVwTHBraUx3
|
||||
MVRVRlRQMFcyelNvL3FaNjc3U3VYbmsKZ+rJ/EFb3KNyyJ5hqO/wV4AtO1FJCeB/
|
||||
oazkDDoFBE+uhiLmdCy41eYkqW8Owt/zrO29nITeJ5EtGAXTbACcgg==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOUk80d2VXVHp6UU1sYVBz
|
||||
WGY4ZmFINHVzV0lRbVdxczl4MjVWbWRMR21JCkJSVVV1b3RPZnBnUlF5N0RsRkZO
|
||||
cDRqYTFPRm5lUkhRUnVTQ0hCVXRVancKLS0tIFB5SWw1L1Q5NWROZk1ucE5nZjRt
|
||||
QUdNcjB4OHNNcENpWnJXTEw5K0ZqcFEKlO7SN3jy8KUCjcO1vYLo4INsNlLi9s7H
|
||||
mMUbt+4kwruhY8gN3UB0ATDAD2MpcxprdfZEq7swxtxsWOLA+IpcXQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDZlJYSG51NEE3emlTVDM0
|
||||
WEE4LzFqazdZQkRZSUlqQ0dzYURkbWc5RWxnCnJobm5LVnkxZkFIeTNWWUJvOUFU
|
||||
SlZhZDBsdHhDRzFVQjhsN3F1dE9SVDAKLS0tIFBlOEwxallncjBxWDZCSkhZdlJN
|
||||
b21icTBmeFM1cnVkaXAySHFzam1hYmcKULP2EuMGhspSusYPZs/DTksaZb0Asfel
|
||||
mVn9Unqe2b9tT5cchGrxLiDJ+2YvfTA0s/JpDtLN+MpiRQQl0vJikg==
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2LzBJVk5KcjdVWng3azU5
|
||||
N0dNQzRWcmlQMnRzWXk0MmZrK2ltbnBDMkJrCnpmenBlUExLOEtaM1gzdUg0RW9T
|
||||
Z3dDcVRqVmU1WXg1eWVDaGlLdjRSRGsKLS0tIE5hYVNkWHVKNWlmdGIzTDhuSStS
|
||||
aTJueXRDNDlvUEZHajVHZEpyVnlVVGMKK7gUYs3D1BUeD8pH81iy7Hoc0VjCCYCq
|
||||
PAnweggfzOVvZj8YHUBZ6/kfAODdjQi/16B9yBR6A0K499/+FGeazg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2024-07-08T11:20:50Z"
|
||||
mac: ENC[AES256_GCM,data:GPUwpSAz6fj7mRxX1ebEb2sLAMLkQLuKPXk+B3+zZmA6+D7gAKrrBGUWHqYA9DMMY0r32OZSccGRmeKqdA7sWmzdIJTcBu8EyER1nJqVFJiXcOOdTkCLdOM4xW969YE0lBKpIAQ40E7YXYYwkI1JINneIBTuXkvIBmSQ3Bt2+ak=,iv:VEPNQxDLzxyTxkn8dI6xNDe9ESk2RojSNYYEwT+Ggas=,tag:cfUEKU3arSJl+lEOa+4iRA==,type:str]
|
||||
lastmodified: "2026-04-22T20:20:18Z"
|
||||
mac: ENC[AES256_GCM,data:lmtkTa+zts+gA9HPRrfCCzlj3TvDL7ROf6+OmPIPHx+e7yIeLXuvDDGlEATkVLc3CfetdFpd0cMOb5UYixqqE75ivNxZHwh+g3qwHAdmNP2NtjWTkTi1fSPjuuwSWG6e1lHCmX5SS/bmnnT/bfCRCDruyVtm766d7iWicLuGq1M=,iv:jBTDksnZRJrV0jJ8QccK8Ov5lAPf+dfSQ6D88icUMXQ=,tag:zlfequv/RHz1Y21uMvwseQ==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.8.1
|
||||
version: 3.12.1
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@
|
|||
./modules/blackbox-exporter.nix
|
||||
./modules/updns.nix
|
||||
./modules/atticd.nix
|
||||
./modules/supabase
|
||||
|
||||
./utils/modules/autoupgrade.nix
|
||||
./utils/modules/promtail
|
||||
|
|
@ -43,6 +42,7 @@
|
|||
./modules/scana11y.nix
|
||||
|
||||
./modules/wireguard.nix
|
||||
./modules/fueltide-backup
|
||||
];
|
||||
|
||||
nixpkgs.overlays = [
|
||||
|
|
|
|||
129
hosts/web-arm/modules/fueltide-backup/RESTORATION.md
Normal file
129
hosts/web-arm/modules/fueltide-backup/RESTORATION.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Fueltide Supabase Restoration Runbook
|
||||
|
||||
Use this when the upstream Supabase project at `majxbigjafpzayzboxsf.supabase.co` is gone, broken, or you want to move to a new project.
|
||||
|
||||
## What this backup covers
|
||||
|
||||
The nightly `fueltide-backup.service` on `web-arm` produces three SQL files per run under `/var/backup/fueltide-supabase/<timestamp>/`:
|
||||
|
||||
- `roles.sql` — cluster roles (via `pg_dumpall --roles-only --no-role-passwords`)
|
||||
- `schema.sql` — DDL: tables, functions, triggers, RLS policies, views, extensions, types (via `pg_dump --schema-only`)
|
||||
- `data.sql` — all row data, including `auth.users`, `auth.identities`, `storage.objects` metadata (via `pg_dump --data-only`)
|
||||
- `sha256.txt` — checksums for verification
|
||||
|
||||
These files are included in the nightly borgbackup run (03:00 UTC) and shipped to the Hetzner Storage Box at `u149513-sub8`.
|
||||
|
||||
## What this backup does **not** cover
|
||||
|
||||
- **Supabase Edge Functions** — lives in the `fueltide` app repo, deployed via `supabase functions deploy`. No action needed beyond redeploying from source.
|
||||
- **Storage bucket files** — not in use for this project (only DB-backed data).
|
||||
- **Control-plane settings** — auth providers, SMTP, email templates, API keys. These live in Supabase's dashboard, not the database. Must be reapplied manually (steps below).
|
||||
|
||||
---
|
||||
|
||||
## Restoration steps
|
||||
|
||||
### 1. Provision a fresh Supabase project
|
||||
|
||||
Dashboard → New project. Use the same region (`eu-west-1`). Record:
|
||||
- New **project ref** (20-char subdomain)
|
||||
- New **database password**
|
||||
- New **session pooler hostname** (Project Settings → Database → Connection string → Session pooler) — the cluster prefix (`aws-1-`, `aws-0-`, etc.) may differ from the old project.
|
||||
|
||||
### 2. Fetch the latest dump from borg
|
||||
|
||||
From `web-arm.cloonar.com`:
|
||||
|
||||
```bash
|
||||
borg-list # find newest archive, e.g. web-arm-2026-04-24
|
||||
mkdir -p /mnt/borg
|
||||
borg-mount web-arm-2026-04-24 /mnt/borg
|
||||
ls /mnt/borg/var/backup/fueltide-supabase/ # pick newest timestamped directory
|
||||
cp -r /mnt/borg/var/backup/fueltide-supabase/<ts> /tmp/restore
|
||||
borg umount /mnt/borg
|
||||
|
||||
cd /tmp/restore
|
||||
sha256sum -c sha256.txt # verify integrity
|
||||
```
|
||||
|
||||
If `web-arm` itself is lost, fetch from any machine with the borg SSH key + passphrase (secrets are in sops under `borg-ssh-key` / `borg-passphrase`).
|
||||
|
||||
### 3. Restore the database
|
||||
|
||||
```bash
|
||||
export NEW_URL="postgres://postgres.<new-ref>:<new-pw>@<new-pooler-host>:5432/postgres"
|
||||
|
||||
# roles (some will error because Supabase-managed roles already exist — safe to ignore)
|
||||
psql "$NEW_URL" -f /tmp/restore/roles.sql || true
|
||||
|
||||
# schema
|
||||
psql "$NEW_URL" -f /tmp/restore/schema.sql
|
||||
|
||||
# data
|
||||
psql "$NEW_URL" -f /tmp/restore/data.sql
|
||||
```
|
||||
|
||||
Expected noise that is safe to ignore:
|
||||
- `role "supabase_admin" already exists`, same for `authenticator`, `service_role`, `anon`, `authenticated`, `dashboard_user`
|
||||
- `extension "pg_graphql" already exists` (if schema uses `CREATE EXTENSION` without `IF NOT EXISTS` for any extension not pre-installed — rare)
|
||||
- `schema "auth" already exists`
|
||||
|
||||
Stop and investigate if you see errors like `permission denied`, `syntax error`, or `duplicate key value`.
|
||||
|
||||
### 4. Redeploy Edge Functions from the app repo
|
||||
|
||||
From a checkout of the fueltide app repo:
|
||||
|
||||
```bash
|
||||
supabase link --project-ref <new-ref>
|
||||
supabase functions deploy # deploys all functions in supabase/functions/
|
||||
```
|
||||
|
||||
If specific function secrets are configured (via `supabase secrets set`), re-set them from the app repo's documented env values.
|
||||
|
||||
### 5. Reapply dashboard-only settings
|
||||
|
||||
These live in Supabase's control plane and are **not** in any dump:
|
||||
|
||||
| Setting | Location | Notes |
|
||||
|---|---|---|
|
||||
| Google OAuth provider | Authentication → Providers → Google | Client ID + secret from SOPS (commit `67e81d3` added these) |
|
||||
| Apple OAuth provider | Authentication → Providers → Apple | Services ID + Team ID + Key ID + P8 key from SOPS |
|
||||
| SMTP settings | Authentication → SMTP Settings | Sender `noreply@fueltide.io`, use the mail host's SMTP creds |
|
||||
| Email templates | Authentication → Email Templates | Fueltide-branded magic link, confirm, recovery — bodies in commit `67e81d3` |
|
||||
| API keys | Project Settings → API | A **new** `anon` and `service_role` are generated per project — copy them |
|
||||
|
||||
### 6. Update app clients
|
||||
|
||||
Update the iOS app (and any server-side callers) with:
|
||||
|
||||
- `SUPABASE_URL = https://<new-ref>.supabase.co`
|
||||
- `SUPABASE_ANON_KEY = <new anon key>`
|
||||
- `SUPABASE_SERVICE_ROLE_KEY = <new service role key>` (server-side only)
|
||||
|
||||
Update CSP in `hosts/web-arm/sites/fueltide.io.nix` (currently commented out, references `*.supabase.co`) if you reinstate it.
|
||||
|
||||
### 7. Smoke test
|
||||
|
||||
- Sign up + sign in via email magic link (confirms SMTP + email templates)
|
||||
- Sign in via Google (confirms OAuth provider)
|
||||
- Sign in via Apple (confirms OAuth provider)
|
||||
- Read a known row from the largest app table (confirms data restored, RLS intact)
|
||||
- Insert + read back a new row (confirms writes work)
|
||||
- Call an edge function (confirms functions redeployed)
|
||||
|
||||
### 8. Update this backup service to point at the new project
|
||||
|
||||
Edit `hosts/web-arm/modules/fueltide-backup/default.nix`:
|
||||
|
||||
- Set `project = "<new-ref>"`
|
||||
- Set `poolerHost = "<new-pooler-host>"` (the region + cluster may differ)
|
||||
- If the new project is on a different Postgres major version, update `pg = pkgs.postgresql_XX`
|
||||
|
||||
Rotate the `fueltide-supabase-db-password` secret in `hosts/web-arm/secrets.yaml` via:
|
||||
|
||||
```bash
|
||||
nix-shell -p sops --run 'sops hosts/web-arm/secrets.yaml'
|
||||
```
|
||||
|
||||
Deploy, then run `systemctl start fueltide-backup.service` manually on `web-arm` and verify a new dump lands under `/var/backup/fueltide-supabase/`.
|
||||
64
hosts/web-arm/modules/fueltide-backup/default.nix
Normal file
64
hosts/web-arm/modules/fueltide-backup/default.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
let
|
||||
project = "majxbigjafpzayzboxsf";
|
||||
poolerHost = "aws-1-eu-west-1.pooler.supabase.com";
|
||||
outDir = "/var/backup/fueltide-supabase";
|
||||
# retain local dumps for this many days; borg handles offsite retention
|
||||
retainDays = 1;
|
||||
# match the upstream Supabase Postgres major version
|
||||
pg = pkgs.postgresql_17;
|
||||
in {
|
||||
sops.secrets.fueltide-supabase-db-password = { };
|
||||
|
||||
systemd.tmpfiles.rules = [ "d ${outDir} 0700 root root -" ];
|
||||
|
||||
systemd.services.fueltide-backup = {
|
||||
description = "Dump upstream Supabase database for ${project}";
|
||||
path = [ pg pkgs.coreutils pkgs.findutils ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "root";
|
||||
LoadCredential = "db-password:${config.sops.secrets.fueltide-supabase-db-password.path}";
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
export PGPASSWORD
|
||||
PGPASSWORD=$(cat "$CREDENTIALS_DIRECTORY/db-password")
|
||||
export PGHOST="${poolerHost}"
|
||||
export PGPORT=5432
|
||||
export PGUSER="postgres.${project}"
|
||||
export PGDATABASE=postgres
|
||||
|
||||
TS=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
OUT="${outDir}/$TS"
|
||||
mkdir -p "$OUT"
|
||||
chmod 700 "$OUT"
|
||||
|
||||
# cluster roles (Supabase-managed roles already exist on a fresh project;
|
||||
# restore errors for those are expected and benign)
|
||||
pg_dumpall --roles-only --no-role-passwords > "$OUT/roles.sql"
|
||||
|
||||
# schema: tables, functions, triggers, RLS policies, views, extensions
|
||||
pg_dump --schema-only --no-owner --no-privileges > "$OUT/schema.sql"
|
||||
|
||||
# data: all rows (includes auth.users, storage.objects metadata, etc.)
|
||||
pg_dump --data-only --no-owner > "$OUT/data.sql"
|
||||
|
||||
( cd "$OUT" && sha256sum *.sql > sha256.txt )
|
||||
|
||||
find "${outDir}" -mindepth 1 -maxdepth 1 -type d \
|
||||
-mtime +${toString retainDays} -exec rm -rf {} +
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.fueltide-backup = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 02:30:00";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "10m";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,452 +0,0 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
kongEntrypoint = pkgs.writeTextFile {
|
||||
name = "kong-entrypoint.sh";
|
||||
executable = true;
|
||||
text = builtins.readFile ./kong-entrypoint.sh;
|
||||
};
|
||||
|
||||
envGenerateScript = pkgs.writeShellScript "supabase-env-generate"
|
||||
(builtins.readFile ./env-generate.sh);
|
||||
|
||||
# Common extra options for all containers to join the supabase network
|
||||
supabaseNet = [ "--network=supabase-net" ];
|
||||
|
||||
in
|
||||
{
|
||||
# --- SOPS secret ---
|
||||
sops.secrets.supabase-env = { };
|
||||
|
||||
# --- Persistent data directories ---
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/lib/supabase/db/data 0700 root root -"
|
||||
"d /var/lib/supabase/storage 0755 root root -"
|
||||
"d /var/lib/supabase/functions 0755 root root -"
|
||||
"d /var/lib/supabase/snippets 0755 root root -"
|
||||
];
|
||||
|
||||
|
||||
# --- Systemd services: network, env generation, and container ordering ---
|
||||
systemd.services =
|
||||
let
|
||||
containerNames = [
|
||||
"supabase-db"
|
||||
"supabase-analytics"
|
||||
"supabase-auth"
|
||||
"supabase-rest"
|
||||
"supabase-realtime"
|
||||
"supabase-storage"
|
||||
"supabase-imgproxy"
|
||||
"supabase-meta"
|
||||
"supabase-studio"
|
||||
"supabase-kong"
|
||||
"supabase-vector"
|
||||
"supabase-pooler"
|
||||
"supabase-functions"
|
||||
];
|
||||
mkContainerDeps = name: {
|
||||
"podman-${name}" = {
|
||||
after = [ "init-supabase-network.service" "supabase-env-generate.service" ];
|
||||
requires = [ "init-supabase-network.service" "supabase-env-generate.service" ];
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.mkMerge (map mkContainerDeps containerNames ++ [
|
||||
{
|
||||
init-supabase-network = {
|
||||
description = "Create supabase-net Podman network";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
# '-' prefix tells systemd to ignore non-zero exit (network may already exist)
|
||||
ExecStart = "-${pkgs.podman}/bin/podman network create supabase-net";
|
||||
};
|
||||
};
|
||||
supabase-env-generate = {
|
||||
description = "Generate Supabase per-container env files from SOPS secrets";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.jq ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${envGenerateScript} ${config.sops.secrets.supabase-env.path}";
|
||||
};
|
||||
};
|
||||
# Seed the edge-runtime's bootstrap `main` function. The container's
|
||||
# entrypoint requires `/home/deno/functions/main/index.ts` to exist;
|
||||
# without it edge-runtime fails with "could not find an appropriate
|
||||
# entrypoint". Re-seed on every activation so updates to the bootstrap
|
||||
# are picked up, while leaving user-authored functions untouched.
|
||||
supabase-functions-seed = {
|
||||
description = "Seed Supabase edge-functions main bootstrap";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
install -d -m 0755 /var/lib/supabase/functions/main
|
||||
install -m 0644 ${./functions/main/index.ts} /var/lib/supabase/functions/main/index.ts
|
||||
'';
|
||||
};
|
||||
podman-supabase-functions = {
|
||||
after = [ "supabase-functions-seed.service" ];
|
||||
requires = [ "supabase-functions-seed.service" ];
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
# --- Containers ---
|
||||
virtualisation.oci-containers.containers = {
|
||||
|
||||
# 1. PostgreSQL
|
||||
supabase-db = {
|
||||
image = "supabase/postgres:15.8.1.085";
|
||||
environment = {
|
||||
POSTGRES_HOST = "/var/run/postgresql";
|
||||
PGPORT = "5432";
|
||||
POSTGRES_PORT = "5432";
|
||||
PGDATABASE = "postgres";
|
||||
POSTGRES_DB = "postgres";
|
||||
JWT_EXP = "3600";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/db.env" ];
|
||||
volumes = [
|
||||
"/var/lib/supabase/db/data:/var/lib/postgresql/data"
|
||||
"${./sql/_supabase.sql}:/docker-entrypoint-initdb.d/migrations/97-_supabase.sql:ro"
|
||||
"${./sql/realtime.sql}:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:ro"
|
||||
"${./sql/logs.sql}:/docker-entrypoint-initdb.d/migrations/99-logs.sql:ro"
|
||||
"${./sql/pooler.sql}:/docker-entrypoint-initdb.d/migrations/99-pooler.sql:ro"
|
||||
"${./sql/webhooks.sql}:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:ro"
|
||||
"${./sql/roles.sql}:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:ro"
|
||||
"${./sql/jwt.sql}:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:ro"
|
||||
"supabase-db-config:/etc/postgresql-custom"
|
||||
];
|
||||
cmd = [
|
||||
"postgres"
|
||||
"-c" "config_file=/etc/postgresql/postgresql.conf"
|
||||
"-c" "log_min_messages=fatal"
|
||||
];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=db"
|
||||
"--shm-size=2g"
|
||||
];
|
||||
};
|
||||
|
||||
# 2. Analytics (Logflare)
|
||||
supabase-analytics = {
|
||||
image = "supabase/logflare:1.31.2";
|
||||
dependsOn = [ "supabase-db" ];
|
||||
environment = {
|
||||
LOGFLARE_NODE_HOST = "127.0.0.1";
|
||||
DB_USERNAME = "supabase_admin";
|
||||
DB_DATABASE = "_supabase";
|
||||
DB_HOSTNAME = "db";
|
||||
DB_PORT = "5432";
|
||||
DB_SCHEMA = "_analytics";
|
||||
LOGFLARE_SINGLE_TENANT = "true";
|
||||
LOGFLARE_SUPABASE_MODE = "true";
|
||||
POSTGRES_BACKEND_SCHEMA = "_analytics";
|
||||
LOGFLARE_FEATURE_FLAG_OVERRIDE = "multibackend=true";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/analytics.env" ];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=analytics"
|
||||
];
|
||||
};
|
||||
|
||||
# 3. Auth (GoTrue)
|
||||
supabase-auth = {
|
||||
image = "supabase/gotrue:v2.186.0";
|
||||
dependsOn = [ "supabase-db" "supabase-analytics" ];
|
||||
environment = {
|
||||
GOTRUE_API_HOST = "0.0.0.0";
|
||||
GOTRUE_API_PORT = "9999";
|
||||
API_EXTERNAL_URL = "https://supabase.cloonar.com";
|
||||
GOTRUE_DB_DRIVER = "postgres";
|
||||
GOTRUE_SITE_URL = "https://supabase.cloonar.com";
|
||||
GOTRUE_URI_ALLOW_LIST = "";
|
||||
GOTRUE_DISABLE_SIGNUP = "false";
|
||||
GOTRUE_JWT_ADMIN_ROLES = "service_role";
|
||||
GOTRUE_JWT_AUD = "authenticated";
|
||||
GOTRUE_JWT_DEFAULT_GROUP_NAME = "authenticated";
|
||||
GOTRUE_JWT_EXP = "3600";
|
||||
GOTRUE_EXTERNAL_EMAIL_ENABLED = "true";
|
||||
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED = "false";
|
||||
GOTRUE_MAILER_AUTOCONFIRM = "true";
|
||||
GOTRUE_SMTP_ADMIN_EMAIL = "admin@cloonar.com";
|
||||
GOTRUE_SMTP_HOST = "supabase-mail";
|
||||
GOTRUE_SMTP_PORT = "2500";
|
||||
GOTRUE_SMTP_USER = "";
|
||||
GOTRUE_SMTP_PASS = "";
|
||||
GOTRUE_SMTP_SENDER_NAME = "Supabase";
|
||||
GOTRUE_MAILER_URLPATHS_INVITE = "/auth/v1/verify";
|
||||
GOTRUE_MAILER_URLPATHS_CONFIRMATION = "/auth/v1/verify";
|
||||
GOTRUE_MAILER_URLPATHS_RECOVERY = "/auth/v1/verify";
|
||||
GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE = "/auth/v1/verify";
|
||||
GOTRUE_EXTERNAL_PHONE_ENABLED = "false";
|
||||
GOTRUE_SMS_AUTOCONFIRM = "false";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/auth.env" ];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=auth"
|
||||
];
|
||||
};
|
||||
|
||||
# 4. REST (PostgREST)
|
||||
supabase-rest = {
|
||||
image = "postgrest/postgrest:v14.6";
|
||||
dependsOn = [ "supabase-db" ];
|
||||
environment = {
|
||||
PGRST_DB_SCHEMAS = "public,storage,graphql_public";
|
||||
PGRST_DB_MAX_ROWS = "1000";
|
||||
PGRST_DB_EXTRA_SEARCH_PATH = "public";
|
||||
PGRST_DB_ANON_ROLE = "anon";
|
||||
PGRST_DB_USE_LEGACY_GUCS = "false";
|
||||
PGRST_APP_SETTINGS_JWT_EXP = "3600";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/rest.env" ];
|
||||
cmd = [ "postgrest" ];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=rest"
|
||||
];
|
||||
};
|
||||
|
||||
# 5. Realtime
|
||||
supabase-realtime = {
|
||||
image = "supabase/realtime:v2.76.5";
|
||||
dependsOn = [ "supabase-db" ];
|
||||
environment = {
|
||||
PORT = "4000";
|
||||
DB_HOST = "db";
|
||||
DB_PORT = "5432";
|
||||
DB_USER = "supabase_admin";
|
||||
DB_NAME = "postgres";
|
||||
DB_AFTER_CONNECT_QUERY = "SET search_path TO _realtime";
|
||||
DB_ENC_KEY = "supabaserealtime";
|
||||
ERL_AFLAGS = "-proto_dist inet_tcp";
|
||||
DNS_NODES = "''";
|
||||
RLIMIT_NOFILE = "10000";
|
||||
APP_NAME = "realtime";
|
||||
SEED_SELF_HOST = "true";
|
||||
RUN_JANITOR = "true";
|
||||
DISABLE_HEALTHCHECK_LOGGING = "true";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/realtime.env" ];
|
||||
extraOptions = supabaseNet ++ [
|
||||
# Hostname must be realtime-dev.supabase-realtime for tenant ID parsing
|
||||
"--hostname=realtime-dev.supabase-realtime"
|
||||
"--network-alias=realtime-dev.supabase-realtime"
|
||||
];
|
||||
};
|
||||
|
||||
# 6. Storage
|
||||
supabase-storage = {
|
||||
image = "supabase/storage-api:v1.44.2";
|
||||
dependsOn = [ "supabase-db" "supabase-rest" "supabase-imgproxy" ];
|
||||
environment = {
|
||||
POSTGREST_URL = "http://rest:3000";
|
||||
STORAGE_PUBLIC_URL = "https://supabase.cloonar.com";
|
||||
REQUEST_ALLOW_X_FORWARDED_PATH = "true";
|
||||
FILE_SIZE_LIMIT = "52428800";
|
||||
STORAGE_BACKEND = "file";
|
||||
GLOBAL_S3_BUCKET = "stub";
|
||||
FILE_STORAGE_BACKEND_PATH = "/var/lib/storage";
|
||||
TENANT_ID = "stub";
|
||||
REGION = "stub";
|
||||
ENABLE_IMAGE_TRANSFORMATION = "true";
|
||||
IMGPROXY_URL = "http://imgproxy:5001";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/storage.env" ];
|
||||
volumes = [
|
||||
"/var/lib/supabase/storage:/var/lib/storage"
|
||||
];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=storage"
|
||||
];
|
||||
};
|
||||
|
||||
# 7. Imgproxy
|
||||
supabase-imgproxy = {
|
||||
image = "darthsim/imgproxy:v3.30.1";
|
||||
environment = {
|
||||
IMGPROXY_BIND = ":5001";
|
||||
IMGPROXY_LOCAL_FILESYSTEM_ROOT = "/";
|
||||
IMGPROXY_USE_ETAG = "true";
|
||||
IMGPROXY_AUTO_WEBP = "true";
|
||||
IMGPROXY_MAX_SRC_RESOLUTION = "16.8";
|
||||
};
|
||||
volumes = [
|
||||
"/var/lib/supabase/storage:/var/lib/storage"
|
||||
];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=imgproxy"
|
||||
];
|
||||
};
|
||||
|
||||
# 8. Meta (pg-meta)
|
||||
supabase-meta = {
|
||||
image = "supabase/postgres-meta:v0.95.2";
|
||||
dependsOn = [ "supabase-db" ];
|
||||
environment = {
|
||||
PG_META_PORT = "8080";
|
||||
PG_META_DB_HOST = "db";
|
||||
PG_META_DB_PORT = "5432";
|
||||
PG_META_DB_NAME = "postgres";
|
||||
PG_META_DB_USER = "supabase_admin";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/meta.env" ];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=meta"
|
||||
];
|
||||
};
|
||||
|
||||
# 9. Studio
|
||||
supabase-studio = {
|
||||
image = "supabase/studio:2026.03.16-sha-5528817";
|
||||
dependsOn = [ "supabase-analytics" ];
|
||||
environment = {
|
||||
HOSTNAME = "::";
|
||||
STUDIO_PG_META_URL = "http://meta:8080";
|
||||
POSTGRES_PORT = "5432";
|
||||
POSTGRES_HOST = "db";
|
||||
POSTGRES_DB = "postgres";
|
||||
PGRST_DB_SCHEMAS = "public,storage,graphql_public";
|
||||
PGRST_DB_MAX_ROWS = "1000";
|
||||
PGRST_DB_EXTRA_SEARCH_PATH = "public";
|
||||
DEFAULT_ORGANIZATION_NAME = "Default Organization";
|
||||
DEFAULT_PROJECT_NAME = "Default Project";
|
||||
SUPABASE_URL = "http://kong:8000";
|
||||
SUPABASE_PUBLIC_URL = "https://supabase.cloonar.com";
|
||||
NEXT_PUBLIC_ENABLE_LOGS = "true";
|
||||
NEXT_ANALYTICS_BACKEND_PROVIDER = "postgres";
|
||||
LOGFLARE_URL = "http://analytics:4000";
|
||||
SNIPPETS_MANAGEMENT_FOLDER = "/app/snippets";
|
||||
EDGE_FUNCTIONS_MANAGEMENT_FOLDER = "/app/edge-functions";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/studio.env" ];
|
||||
volumes = [
|
||||
"/var/lib/supabase/snippets:/app/snippets"
|
||||
"/var/lib/supabase/functions:/app/edge-functions"
|
||||
];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=studio"
|
||||
];
|
||||
};
|
||||
|
||||
# 10. Kong (API Gateway)
|
||||
supabase-kong = {
|
||||
image = "kong/kong:3.9.1";
|
||||
dependsOn = [ "supabase-studio" ];
|
||||
environment = {
|
||||
KONG_DATABASE = "off";
|
||||
KONG_DECLARATIVE_CONFIG = "/usr/local/kong/kong.yml";
|
||||
KONG_DNS_ORDER = "LAST,A,CNAME";
|
||||
KONG_DNS_NOT_FOUND_TTL = "1";
|
||||
KONG_PLUGINS = "request-transformer,cors,key-auth,acl,basic-auth,request-termination,ip-restriction,post-function";
|
||||
KONG_NGINX_PROXY_PROXY_BUFFER_SIZE = "160k";
|
||||
KONG_NGINX_PROXY_PROXY_BUFFERS = "64 160k";
|
||||
KONG_PROXY_ACCESS_LOG = "/dev/stdout combined";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/kong.env" ];
|
||||
ports = [
|
||||
"127.0.0.1:8000:8000"
|
||||
"127.0.0.1:8443:8443"
|
||||
];
|
||||
volumes = [
|
||||
"${./kong.yml}:/home/kong/temp.yml:ro"
|
||||
"${kongEntrypoint}:/home/kong/kong-entrypoint.sh:ro"
|
||||
];
|
||||
entrypoint = "/home/kong/kong-entrypoint.sh";
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=kong"
|
||||
];
|
||||
};
|
||||
|
||||
# 11. Vector (log collection)
|
||||
supabase-vector = {
|
||||
image = "timberio/vector:0.53.0-alpine";
|
||||
environment = { };
|
||||
environmentFiles = [ "/run/supabase/vector.env" ];
|
||||
volumes = [
|
||||
"${./vector.yml}:/etc/vector/vector.yml:ro"
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||
];
|
||||
cmd = [ "--config" "/etc/vector/vector.yml" ];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=vector"
|
||||
"--security-opt=label=disable"
|
||||
];
|
||||
};
|
||||
|
||||
# 12. Pooler (Supavisor)
|
||||
supabase-pooler = {
|
||||
image = "supabase/supavisor:2.7.4";
|
||||
dependsOn = [ "supabase-db" ];
|
||||
environment = {
|
||||
PORT = "4000";
|
||||
CLUSTER_POSTGRES = "true";
|
||||
REGION = "local";
|
||||
ERL_AFLAGS = "-proto_dist inet_tcp";
|
||||
POOLER_POOL_MODE = "transaction";
|
||||
POSTGRES_PORT = "5432";
|
||||
POSTGRES_DB = "postgres";
|
||||
POOLER_TENANT_ID = "default-tenant";
|
||||
POOLER_DEFAULT_POOL_SIZE = "20";
|
||||
POOLER_MAX_CLIENT_CONN = "100";
|
||||
DB_POOL_SIZE = "10";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/pooler.env" ];
|
||||
volumes = [
|
||||
"${./pooler.exs}:/etc/pooler/pooler.exs:ro"
|
||||
];
|
||||
cmd = [
|
||||
"/bin/sh" "-c"
|
||||
"/app/bin/migrate && /app/bin/supavisor eval \"$(cat /etc/pooler/pooler.exs)\" && /app/bin/server"
|
||||
];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=pooler"
|
||||
];
|
||||
};
|
||||
|
||||
# 13. Edge Functions
|
||||
supabase-functions = {
|
||||
image = "supabase/edge-runtime:v1.71.2";
|
||||
dependsOn = [ "supabase-kong" ];
|
||||
environment = {
|
||||
SUPABASE_URL = "http://kong:8000";
|
||||
SUPABASE_PUBLIC_URL = "https://supabase.cloonar.com";
|
||||
VERIFY_JWT = "false";
|
||||
};
|
||||
environmentFiles = [ "/run/supabase/functions.env" ];
|
||||
volumes = [
|
||||
"/var/lib/supabase/functions:/home/deno/functions"
|
||||
"supabase-deno-cache:/root/.cache/deno"
|
||||
];
|
||||
cmd = [ "start" "--main-service" "/home/deno/functions/main" ];
|
||||
extraOptions = supabaseNet ++ [
|
||||
"--network-alias=functions"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# --- Nginx reverse proxy ---
|
||||
services.nginx.virtualHosts."supabase.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8000";
|
||||
proxyWebsockets = true;
|
||||
extraConfig = ''
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_send_timeout 86400s;
|
||||
client_max_body_size 50M;
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
set -euo pipefail
|
||||
umask 077
|
||||
mkdir -p /run/supabase
|
||||
|
||||
set -a
|
||||
source "$1"
|
||||
set +a
|
||||
|
||||
# URL-encode password for use in connection strings
|
||||
PG_PASS_ENCODED=$(printf '%s' "$POSTGRES_PASSWORD" | jq -sRr @uri)
|
||||
|
||||
cat > /run/supabase/db.env <<EOF
|
||||
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||
PGPASSWORD=$POSTGRES_PASSWORD
|
||||
JWT_SECRET=$JWT_SECRET
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/analytics.env <<EOF
|
||||
DB_PASSWORD=$POSTGRES_PASSWORD
|
||||
LOGFLARE_PUBLIC_ACCESS_TOKEN=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
||||
LOGFLARE_PRIVATE_ACCESS_TOKEN=$LOGFLARE_PRIVATE_ACCESS_TOKEN
|
||||
POSTGRES_BACKEND_URL=postgresql://supabase_admin:$PG_PASS_ENCODED@db:5432/_supabase
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/auth.env <<EOF
|
||||
GOTRUE_JWT_SECRET=$JWT_SECRET
|
||||
GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:$PG_PASS_ENCODED@db:5432/postgres
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/rest.env <<EOF
|
||||
PGRST_JWT_SECRET=$JWT_SECRET
|
||||
PGRST_APP_SETTINGS_JWT_SECRET=$JWT_SECRET
|
||||
PGRST_DB_URI=postgres://authenticator:$PG_PASS_ENCODED@db:5432/postgres
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/realtime.env <<EOF
|
||||
DB_PASSWORD=$POSTGRES_PASSWORD
|
||||
API_JWT_SECRET=$JWT_SECRET
|
||||
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||
METRICS_JWT_SECRET=$JWT_SECRET
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/storage.env <<EOF
|
||||
ANON_KEY=$ANON_KEY
|
||||
SERVICE_KEY=$SERVICE_ROLE_KEY
|
||||
AUTH_JWT_SECRET=$JWT_SECRET
|
||||
DATABASE_URL=postgres://supabase_storage_admin:$PG_PASS_ENCODED@db:5432/postgres
|
||||
S3_PROTOCOL_ACCESS_KEY_ID=$S3_PROTOCOL_ACCESS_KEY_ID
|
||||
S3_PROTOCOL_ACCESS_KEY_SECRET=$S3_PROTOCOL_ACCESS_KEY_SECRET
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/meta.env <<EOF
|
||||
PG_META_DB_PASSWORD=$POSTGRES_PASSWORD
|
||||
CRYPTO_KEY=$PG_META_CRYPTO_KEY
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/studio.env <<EOF
|
||||
POSTGRES_PASSWORD=$PG_PASS_ENCODED
|
||||
PG_META_CRYPTO_KEY=$PG_META_CRYPTO_KEY
|
||||
SUPABASE_ANON_KEY=$ANON_KEY
|
||||
SUPABASE_SERVICE_KEY=$SERVICE_ROLE_KEY
|
||||
AUTH_JWT_SECRET=$JWT_SECRET
|
||||
LOGFLARE_API_KEY=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
||||
LOGFLARE_PUBLIC_ACCESS_TOKEN=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
||||
LOGFLARE_PRIVATE_ACCESS_TOKEN=$LOGFLARE_PRIVATE_ACCESS_TOKEN
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/kong.env <<EOF
|
||||
SUPABASE_ANON_KEY=$ANON_KEY
|
||||
SUPABASE_SERVICE_KEY=$SERVICE_ROLE_KEY
|
||||
DASHBOARD_USERNAME=supabase
|
||||
DASHBOARD_PASSWORD=$DASHBOARD_PASSWORD
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/vector.env <<EOF
|
||||
LOGFLARE_PUBLIC_ACCESS_TOKEN=$LOGFLARE_PUBLIC_ACCESS_TOKEN
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/pooler.env <<EOF
|
||||
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
|
||||
DATABASE_URL=ecto://supabase_admin:$PG_PASS_ENCODED@db:5432/_supabase
|
||||
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||
VAULT_ENC_KEY=$VAULT_ENC_KEY
|
||||
API_JWT_SECRET=$JWT_SECRET
|
||||
METRICS_JWT_SECRET=$JWT_SECRET
|
||||
EOF
|
||||
|
||||
cat > /run/supabase/functions.env <<EOF
|
||||
JWT_SECRET=$JWT_SECRET
|
||||
SUPABASE_ANON_KEY=$ANON_KEY
|
||||
SUPABASE_SERVICE_ROLE_KEY=$SERVICE_ROLE_KEY
|
||||
EOF
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
import * as jose from 'https://deno.land/x/jose@v4.14.4/index.ts'
|
||||
|
||||
console.log('main function started')
|
||||
|
||||
const JWT_SECRET = Deno.env.get('JWT_SECRET')
|
||||
const SUPABASE_URL = Deno.env.get('SUPABASE_URL')
|
||||
const VERIFY_JWT = Deno.env.get('VERIFY_JWT') === 'true'
|
||||
|
||||
// Create JWKS for ES256/RS256 tokens (newer tokens)
|
||||
let SUPABASE_JWT_KEYS: ReturnType<typeof jose.createRemoteJWKSet> | null = null
|
||||
if (SUPABASE_URL) {
|
||||
try {
|
||||
SUPABASE_JWT_KEYS = jose.createRemoteJWKSet(
|
||||
new URL('/auth/v1/.well-known/jwks.json', SUPABASE_URL)
|
||||
)
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch JWKS from SUPABASE_URL:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function getAuthToken(req: Request) {
|
||||
const authHeader = req.headers.get('authorization')
|
||||
if (!authHeader) {
|
||||
throw new Error('Missing authorization header')
|
||||
}
|
||||
const [bearer, token] = authHeader.split(' ')
|
||||
if (bearer !== 'Bearer') {
|
||||
throw new Error(`Auth header is not 'Bearer {token}'`)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
async function isValidLegacyJWT(jwt: string): Promise<boolean> {
|
||||
if (!JWT_SECRET) {
|
||||
console.error('JWT_SECRET not available for HS256 token verification')
|
||||
return false
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const secretKey = encoder.encode(JWT_SECRET)
|
||||
|
||||
try {
|
||||
await jose.jwtVerify(jwt, secretKey);
|
||||
} catch (e) {
|
||||
console.error('Symmetric Legacy JWT verification error', e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function isValidJWT(jwt: string): Promise<boolean> {
|
||||
if (!SUPABASE_JWT_KEYS) {
|
||||
console.error('JWKS not available for ES256/RS256 token verification')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await jose.jwtVerify(jwt, SUPABASE_JWT_KEYS)
|
||||
} catch (e) {
|
||||
console.error('Asymmetric JWT verification error', e);
|
||||
return false
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function isValidHybridJWT(jwt: string): Promise<boolean> {
|
||||
const { alg: jwtAlgorithm } = jose.decodeProtectedHeader(jwt)
|
||||
|
||||
if (jwtAlgorithm === 'HS256') {
|
||||
console.log(`Legacy token type detected, attempting ${jwtAlgorithm} verification.`)
|
||||
|
||||
return await isValidLegacyJWT(jwt)
|
||||
}
|
||||
|
||||
if (jwtAlgorithm === 'ES256' || jwtAlgorithm === 'RS256') {
|
||||
return await isValidJWT(jwt)
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Deno.serve(async (req: Request) => {
|
||||
if (req.method !== 'OPTIONS' && VERIFY_JWT) {
|
||||
try {
|
||||
const token = getAuthToken(req)
|
||||
const isValidJWT = await isValidHybridJWT(token);
|
||||
|
||||
if (!isValidJWT) {
|
||||
return new Response(JSON.stringify({ msg: 'Invalid JWT' }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return new Response(JSON.stringify({ msg: e.toString() }), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const url = new URL(req.url)
|
||||
const { pathname } = url
|
||||
const path_parts = pathname.split('/')
|
||||
const service_name = path_parts[1]
|
||||
|
||||
if (!service_name || service_name === '') {
|
||||
const error = { msg: 'missing function name in request' }
|
||||
return new Response(JSON.stringify(error), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
|
||||
const servicePath = `/home/deno/functions/${service_name}`
|
||||
console.error(`serving the request with ${servicePath}`)
|
||||
|
||||
const memoryLimitMb = 150
|
||||
const workerTimeoutMs = 1 * 60 * 1000
|
||||
const noModuleCache = false
|
||||
const importMapPath = null
|
||||
const envVarsObj = Deno.env.toObject()
|
||||
const envVars = Object.keys(envVarsObj).map((k) => [k, envVarsObj[k]])
|
||||
|
||||
try {
|
||||
const worker = await EdgeRuntime.userWorkers.create({
|
||||
servicePath,
|
||||
memoryLimitMb,
|
||||
workerTimeoutMs,
|
||||
noModuleCache,
|
||||
importMapPath,
|
||||
envVars,
|
||||
})
|
||||
return await worker.fetch(req)
|
||||
} catch (e) {
|
||||
const error = { msg: e.toString() }
|
||||
return new Response(JSON.stringify(error), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Legacy API keys, not sb_ API keys -> pass apikey through unchanged
|
||||
export LUA_AUTH_EXPR="\$((headers.authorization ~= nil and headers.authorization:sub(1, 10) ~= 'Bearer sb_' and headers.authorization) or headers.apikey)"
|
||||
export LUA_RT_WS_EXPR="\$(query_params.apikey)"
|
||||
|
||||
# Substitute environment variables in the Kong declarative config
|
||||
awk '{
|
||||
result = ""
|
||||
rest = $0
|
||||
while (match(rest, /\$[A-Za-z_][A-Za-z_0-9]*/)) {
|
||||
varname = substr(rest, RSTART + 1, RLENGTH - 1)
|
||||
if (varname in ENVIRON) {
|
||||
result = result substr(rest, 1, RSTART - 1) ENVIRON[varname]
|
||||
} else {
|
||||
result = result substr(rest, 1, RSTART + RLENGTH - 1)
|
||||
}
|
||||
rest = substr(rest, RSTART + RLENGTH)
|
||||
}
|
||||
print result rest
|
||||
}' /home/kong/temp.yml > "$KONG_DECLARATIVE_CONFIG"
|
||||
|
||||
# Remove empty key-auth credentials (unconfigured opaque keys)
|
||||
sed -i '/^[[:space:]]*- key:[[:space:]]*$/d' "$KONG_DECLARATIVE_CONFIG"
|
||||
|
||||
exec /entrypoint.sh kong docker-start
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
_format_version: '2.1'
|
||||
_transform: true
|
||||
|
||||
consumers:
|
||||
- username: DASHBOARD
|
||||
- username: anon
|
||||
keyauth_credentials:
|
||||
- key: $SUPABASE_ANON_KEY
|
||||
- username: service_role
|
||||
keyauth_credentials:
|
||||
- key: $SUPABASE_SERVICE_KEY
|
||||
|
||||
acls:
|
||||
- consumer: anon
|
||||
group: anon
|
||||
- consumer: service_role
|
||||
group: admin
|
||||
|
||||
basicauth_credentials:
|
||||
- consumer: DASHBOARD
|
||||
username: '$DASHBOARD_USERNAME'
|
||||
password: '$DASHBOARD_PASSWORD'
|
||||
|
||||
services:
|
||||
- name: auth-v1-open
|
||||
url: http://auth:9999/verify
|
||||
routes:
|
||||
- name: auth-v1-open
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/verify
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: auth-v1-open-callback
|
||||
url: http://auth:9999/callback
|
||||
routes:
|
||||
- name: auth-v1-open-callback
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/callback
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: auth-v1-open-authorize
|
||||
url: http://auth:9999/authorize
|
||||
routes:
|
||||
- name: auth-v1-open-authorize
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/authorize
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: auth-v1-open-jwks
|
||||
url: http://auth:9999/.well-known/jwks.json
|
||||
routes:
|
||||
- name: auth-v1-open-jwks
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/.well-known/jwks.json
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: auth-v1
|
||||
url: http://auth:9999/
|
||||
routes:
|
||||
- name: auth-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: request-transformer
|
||||
config:
|
||||
add:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
replace:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
- name: rest-v1
|
||||
url: http://rest:3000/
|
||||
routes:
|
||||
- name: rest-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /rest/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: request-transformer
|
||||
config:
|
||||
add:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
replace:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
- name: graphql-v1
|
||||
url: http://rest:3000/rpc/graphql
|
||||
routes:
|
||||
- name: graphql-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /graphql/v1
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: request-transformer
|
||||
config:
|
||||
add:
|
||||
headers:
|
||||
- "Content-Profile: graphql_public"
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
replace:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
- name: realtime-v1-ws
|
||||
url: http://realtime-dev.supabase-realtime:4000/socket
|
||||
protocol: ws
|
||||
routes:
|
||||
- name: realtime-v1-ws
|
||||
strip_path: true
|
||||
paths:
|
||||
- /realtime/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: request-transformer
|
||||
config:
|
||||
add:
|
||||
headers:
|
||||
- "x-api-key:$LUA_RT_WS_EXPR"
|
||||
replace:
|
||||
querystring:
|
||||
- "apikey:$LUA_RT_WS_EXPR"
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
- name: realtime-v1-rest
|
||||
url: http://realtime-dev.supabase-realtime:4000/api
|
||||
protocol: http
|
||||
routes:
|
||||
- name: realtime-v1-rest
|
||||
strip_path: true
|
||||
paths:
|
||||
- /realtime/v1/api
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: request-transformer
|
||||
config:
|
||||
add:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
replace:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
- name: storage-v1
|
||||
url: http://storage:5000/
|
||||
routes:
|
||||
- name: storage-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /storage/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: request-transformer
|
||||
config:
|
||||
add:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
replace:
|
||||
headers:
|
||||
- "Authorization: $LUA_AUTH_EXPR"
|
||||
- name: post-function
|
||||
config:
|
||||
access:
|
||||
- |
|
||||
local auth = kong.request.get_header("authorization")
|
||||
if auth == nil or auth == "" or auth:find("^%s*$") then
|
||||
kong.service.request.clear_header("authorization")
|
||||
end
|
||||
- name: functions-v1
|
||||
url: http://functions:9000/
|
||||
read_timeout: 150000
|
||||
routes:
|
||||
- name: functions-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /functions/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: well-known-oauth
|
||||
url: http://auth:9999/.well-known/oauth-authorization-server
|
||||
routes:
|
||||
- name: well-known-oauth
|
||||
strip_path: true
|
||||
paths:
|
||||
- /.well-known/oauth-authorization-server
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: meta
|
||||
url: http://meta:8080/
|
||||
routes:
|
||||
- name: meta-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /pg/
|
||||
plugins:
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- name: dashboard
|
||||
url: http://studio:3000/
|
||||
routes:
|
||||
- name: dashboard-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: basic-auth
|
||||
config:
|
||||
hide_credentials: true
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
{:ok, _} = Application.ensure_all_started(:supavisor)
|
||||
|
||||
{:ok, version} =
|
||||
case Supavisor.Repo.query!("select version()") do
|
||||
%{rows: [[ver]]} -> Supavisor.Helpers.parse_pg_version(ver)
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
params = %{
|
||||
"external_id" => System.get_env("POOLER_TENANT_ID"),
|
||||
"db_host" => "db",
|
||||
"db_port" => System.get_env("POSTGRES_PORT"),
|
||||
"db_database" => System.get_env("POSTGRES_DB"),
|
||||
"require_user" => false,
|
||||
"auth_query" => "SELECT * FROM pgbouncer.get_auth($1)",
|
||||
"default_max_clients" => System.get_env("POOLER_MAX_CLIENT_CONN"),
|
||||
"default_pool_size" => System.get_env("POOLER_DEFAULT_POOL_SIZE"),
|
||||
"default_parameter_status" => %{"server_version" => version},
|
||||
"users" => [%{
|
||||
"db_user" => "pgbouncer",
|
||||
"db_password" => System.get_env("POSTGRES_PASSWORD"),
|
||||
"mode_type" => System.get_env("POOLER_POOL_MODE"),
|
||||
"pool_size" => System.get_env("POOLER_DEFAULT_POOL_SIZE"),
|
||||
"is_manager" => true
|
||||
}]
|
||||
}
|
||||
|
||||
if !Supavisor.Tenants.get_tenant_by_external_id(params["external_id"]) do
|
||||
{:ok, _} = Supavisor.Tenants.create_tenant(params)
|
||||
end
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
\set pguser `echo "$POSTGRES_USER"`
|
||||
CREATE DATABASE _supabase WITH OWNER :pguser;
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
\set jwt_secret `echo "$JWT_SECRET"`
|
||||
\set jwt_exp `echo "$JWT_EXP"`
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
\set pguser `echo "$POSTGRES_USER"`
|
||||
\c _supabase
|
||||
create schema if not exists _analytics;
|
||||
alter schema _analytics owner to :pguser;
|
||||
\c postgres
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
\set pguser `echo "$POSTGRES_USER"`
|
||||
\c _supabase
|
||||
create schema if not exists _supavisor;
|
||||
alter schema _supavisor owner to :pguser;
|
||||
\c postgres
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
\set pguser `echo "$POSTGRES_USER"`
|
||||
create schema if not exists _realtime;
|
||||
alter schema _realtime owner to :pguser;
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
\set pgpass `echo "$POSTGRES_PASSWORD"`
|
||||
ALTER USER authenticator WITH PASSWORD :'pgpass';
|
||||
ALTER USER pgbouncer WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_functions_admin WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
BEGIN;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;
|
||||
CREATE SCHEMA supabase_functions AUTHORIZATION supabase_admin;
|
||||
GRANT USAGE ON SCHEMA supabase_functions TO postgres, anon, authenticated, service_role;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON TABLES TO postgres, anon, authenticated, service_role;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON FUNCTIONS TO postgres, anon, authenticated, service_role;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA supabase_functions GRANT ALL ON SEQUENCES TO postgres, anon, authenticated, service_role;
|
||||
CREATE TABLE supabase_functions.migrations (
|
||||
version text PRIMARY KEY,
|
||||
inserted_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
INSERT INTO supabase_functions.migrations (version) VALUES ('initial');
|
||||
CREATE TABLE supabase_functions.hooks (
|
||||
id bigserial PRIMARY KEY,
|
||||
hook_table_id integer NOT NULL,
|
||||
hook_name text NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
request_id bigint
|
||||
);
|
||||
CREATE INDEX supabase_functions_hooks_request_id_idx ON supabase_functions.hooks USING btree (request_id);
|
||||
CREATE INDEX supabase_functions_hooks_h_table_id_h_name_idx ON supabase_functions.hooks USING btree (hook_table_id, hook_name);
|
||||
COMMENT ON TABLE supabase_functions.hooks IS 'Supabase Functions Hooks: Audit trail for triggered hooks.';
|
||||
CREATE FUNCTION supabase_functions.http_request()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
request_id bigint;
|
||||
payload jsonb;
|
||||
url text := TG_ARGV[0]::text;
|
||||
method text := TG_ARGV[1]::text;
|
||||
headers jsonb DEFAULT '{}'::jsonb;
|
||||
params jsonb DEFAULT '{}'::jsonb;
|
||||
timeout_ms integer DEFAULT 1000;
|
||||
BEGIN
|
||||
IF url IS NULL OR url = 'null' THEN
|
||||
RAISE EXCEPTION 'url argument is missing';
|
||||
END IF;
|
||||
IF method IS NULL OR method = 'null' THEN
|
||||
RAISE EXCEPTION 'method argument is missing';
|
||||
END IF;
|
||||
IF TG_ARGV[2] IS NULL OR TG_ARGV[2] = 'null' THEN
|
||||
headers = '{"Content-Type": "application/json"}'::jsonb;
|
||||
ELSE
|
||||
headers = TG_ARGV[2]::jsonb;
|
||||
END IF;
|
||||
IF TG_ARGV[3] IS NULL OR TG_ARGV[3] = 'null' THEN
|
||||
params = '{}'::jsonb;
|
||||
ELSE
|
||||
params = TG_ARGV[3]::jsonb;
|
||||
END IF;
|
||||
IF TG_ARGV[4] IS NULL OR TG_ARGV[4] = 'null' THEN
|
||||
timeout_ms = 1000;
|
||||
ELSE
|
||||
timeout_ms = TG_ARGV[4]::integer;
|
||||
END IF;
|
||||
CASE
|
||||
WHEN method = 'GET' THEN
|
||||
SELECT http_get INTO request_id FROM net.http_get(url, params, headers, timeout_ms);
|
||||
WHEN method = 'POST' THEN
|
||||
payload = jsonb_build_object(
|
||||
'old_record', OLD, 'record', NEW, 'type', TG_OP,
|
||||
'table', TG_TABLE_NAME, 'schema', TG_TABLE_SCHEMA
|
||||
);
|
||||
SELECT http_post INTO request_id FROM net.http_post(url, payload, params, headers, timeout_ms);
|
||||
ELSE
|
||||
RAISE EXCEPTION 'method argument % is invalid', method;
|
||||
END CASE;
|
||||
INSERT INTO supabase_functions.hooks (hook_table_id, hook_name, request_id)
|
||||
VALUES (TG_RELID, TG_NAME, request_id);
|
||||
RETURN NEW;
|
||||
END
|
||||
$function$;
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_functions_admin') THEN
|
||||
CREATE USER supabase_functions_admin NOINHERIT CREATEROLE LOGIN NOREPLICATION;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
GRANT ALL PRIVILEGES ON SCHEMA supabase_functions TO supabase_functions_admin;
|
||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA supabase_functions TO supabase_functions_admin;
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA supabase_functions TO supabase_functions_admin;
|
||||
ALTER USER supabase_functions_admin SET search_path = "supabase_functions";
|
||||
ALTER table "supabase_functions".migrations OWNER TO supabase_functions_admin;
|
||||
ALTER table "supabase_functions".hooks OWNER TO supabase_functions_admin;
|
||||
ALTER function "supabase_functions".http_request() OWNER TO supabase_functions_admin;
|
||||
GRANT supabase_functions_admin TO postgres;
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'supabase_pg_net_admin') THEN
|
||||
REASSIGN OWNED BY supabase_pg_net_admin TO supabase_admin;
|
||||
DROP OWNED BY supabase_pg_net_admin;
|
||||
DROP ROLE supabase_pg_net_admin;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net') THEN
|
||||
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
CREATE OR REPLACE FUNCTION extensions.grant_pg_net_access()
|
||||
RETURNS event_trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM pg_event_trigger_ddl_commands() AS ev
|
||||
JOIN pg_extension AS ext ON ev.objid = ext.oid
|
||||
WHERE ext.extname = 'pg_net'
|
||||
) THEN
|
||||
GRANT USAGE ON SCHEMA net TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SECURITY DEFINER;
|
||||
ALTER function net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
ALTER function net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) SET search_path = net;
|
||||
REVOKE ALL ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
REVOKE ALL ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION net.http_get(url text, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
GRANT EXECUTE ON FUNCTION net.http_post(url text, body jsonb, params jsonb, headers jsonb, timeout_milliseconds integer) TO supabase_functions_admin, postgres, anon, authenticated, service_role;
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
COMMENT ON FUNCTION extensions.grant_pg_net_access IS 'Grants access to pg_net';
|
||||
DO
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_event_trigger WHERE evtname = 'issue_pg_net_access') THEN
|
||||
CREATE EVENT TRIGGER issue_pg_net_access ON ddl_command_end WHEN TAG IN ('CREATE EXTENSION')
|
||||
EXECUTE PROCEDURE extensions.grant_pg_net_access();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
INSERT INTO supabase_functions.migrations (version) VALUES ('20210809183423_update_grants');
|
||||
ALTER function supabase_functions.http_request() SECURITY DEFINER;
|
||||
ALTER function supabase_functions.http_request() SET search_path = supabase_functions;
|
||||
REVOKE ALL ON FUNCTION supabase_functions.http_request() FROM PUBLIC;
|
||||
GRANT EXECUTE ON FUNCTION supabase_functions.http_request() TO postgres, anon, authenticated, service_role;
|
||||
COMMIT;
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
api:
|
||||
enabled: true
|
||||
address: 0.0.0.0:9001
|
||||
|
||||
sources:
|
||||
docker_host:
|
||||
type: docker_logs
|
||||
exclude_containers:
|
||||
- supabase-vector
|
||||
|
||||
transforms:
|
||||
project_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- docker_host
|
||||
source: |-
|
||||
.project = "default"
|
||||
.event_message = del(.message)
|
||||
.appname = del(.container_name)
|
||||
del(.container_created_at)
|
||||
del(.container_id)
|
||||
del(.source_type)
|
||||
del(.stream)
|
||||
del(.label)
|
||||
del(.image)
|
||||
del(.host)
|
||||
del(.stream)
|
||||
router:
|
||||
type: route
|
||||
inputs:
|
||||
- project_logs
|
||||
route:
|
||||
kong: '.appname == "supabase-kong"'
|
||||
auth: '.appname == "supabase-auth"'
|
||||
rest: '.appname == "supabase-rest"'
|
||||
realtime: '.appname == "realtime-dev.supabase-realtime"'
|
||||
storage: '.appname == "supabase-storage"'
|
||||
functions: '.appname == "supabase-edge-functions"'
|
||||
db: '.appname == "supabase-db"'
|
||||
kong_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.kong
|
||||
source: |-
|
||||
req, err = parse_nginx_log(.event_message, "combined")
|
||||
if err == null {
|
||||
.timestamp = req.timestamp
|
||||
.metadata.request.headers.referer = req.referer
|
||||
.metadata.request.headers.user_agent = req.agent
|
||||
.metadata.request.headers.cf_connecting_ip = req.client
|
||||
.metadata.response.status_code = req.status
|
||||
url, split_err = split(req.request, " ")
|
||||
if split_err == null {
|
||||
.metadata.request.method = url[0]
|
||||
.metadata.request.path = url[1]
|
||||
.metadata.request.protocol = url[2]
|
||||
}
|
||||
}
|
||||
if err != null {
|
||||
abort
|
||||
}
|
||||
kong_err:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.kong
|
||||
source: |-
|
||||
.metadata.request.method = "GET"
|
||||
.metadata.response.status_code = 200
|
||||
parsed, err = parse_nginx_log(.event_message, "error")
|
||||
if err == null {
|
||||
.timestamp = parsed.timestamp
|
||||
.severity = parsed.severity
|
||||
.metadata.request.host = parsed.host
|
||||
.metadata.request.headers.cf_connecting_ip = parsed.client
|
||||
url, err = split(parsed.request, " ")
|
||||
if err == null {
|
||||
.metadata.request.method = url[0]
|
||||
.metadata.request.path = url[1]
|
||||
.metadata.request.protocol = url[2]
|
||||
}
|
||||
}
|
||||
if err != null {
|
||||
abort
|
||||
}
|
||||
auth_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.auth
|
||||
source: |-
|
||||
parsed, err = parse_json(.event_message)
|
||||
if err == null {
|
||||
.metadata.timestamp = parsed.time
|
||||
.metadata = merge!(.metadata, parsed)
|
||||
}
|
||||
rest_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.rest
|
||||
source: |-
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>.*): (?P<msg>.*)$')
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.timestamp = parse_timestamp!(value: parsed.time,format: "%d/%b/%Y:%H:%M:%S %z")
|
||||
.metadata.host = .project
|
||||
}
|
||||
realtime_logs_filtered:
|
||||
type: filter
|
||||
inputs:
|
||||
- router.realtime
|
||||
condition: '!contains(string!(.event_message), "/health")'
|
||||
realtime_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- realtime_logs_filtered
|
||||
source: |-
|
||||
.metadata.project = del(.project)
|
||||
.metadata.external_id = .metadata.project
|
||||
parsed, err = parse_regex(.event_message, r'^(?P<time>\d+:\d+:\d+\.\d+) \[(?P<level>\w+)\] (?P<msg>.*)$')
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.metadata.level = parsed.level
|
||||
}
|
||||
functions_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.functions
|
||||
source: |-
|
||||
.metadata.project_ref = del(.project)
|
||||
storage_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.storage
|
||||
source: |-
|
||||
.metadata.project = del(.project)
|
||||
.metadata.tenantId = .metadata.project
|
||||
parsed, err = parse_json(.event_message)
|
||||
if err == null {
|
||||
.event_message = parsed.msg
|
||||
.metadata.level = parsed.level
|
||||
.metadata.timestamp = parsed.time
|
||||
.metadata.context[0].host = parsed.hostname
|
||||
.metadata.context[0].pid = parsed.pid
|
||||
}
|
||||
db_logs:
|
||||
type: remap
|
||||
inputs:
|
||||
- router.db
|
||||
source: |-
|
||||
.metadata.host = "db-default"
|
||||
.metadata.parsed.timestamp = .timestamp
|
||||
parsed, err = parse_regex(.event_message, r'.*(?P<level>INFO|NOTICE|WARNING|ERROR|LOG|FATAL|PANIC?):.*', numeric_groups: true)
|
||||
if err != null || parsed == null {
|
||||
.metadata.parsed.error_severity = "info"
|
||||
}
|
||||
if parsed.level != null {
|
||||
.metadata.parsed.error_severity = parsed.level
|
||||
}
|
||||
if .metadata.parsed.error_severity == "info" {
|
||||
.metadata.parsed.error_severity = "log"
|
||||
}
|
||||
.metadata.parsed.error_severity = upcase!(.metadata.parsed.error_severity)
|
||||
|
||||
sinks:
|
||||
logflare_auth:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- auth_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 30
|
||||
retry_initial_backoff_secs: 1
|
||||
headers:
|
||||
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
|
||||
uri: 'http://analytics:4000/api/logs?source_name=gotrue.logs.prod'
|
||||
logflare_realtime:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- realtime_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 30
|
||||
retry_initial_backoff_secs: 1
|
||||
headers:
|
||||
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
|
||||
uri: 'http://analytics:4000/api/logs?source_name=realtime.logs.prod'
|
||||
logflare_rest:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- rest_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 30
|
||||
retry_initial_backoff_secs: 1
|
||||
headers:
|
||||
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
|
||||
uri: 'http://analytics:4000/api/logs?source_name=postgREST.logs.prod'
|
||||
logflare_db:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- db_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 30
|
||||
retry_initial_backoff_secs: 1
|
||||
headers:
|
||||
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
|
||||
uri: 'http://analytics:4000/api/logs?source_name=postgres.logs'
|
||||
logflare_functions:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- functions_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 30
|
||||
retry_initial_backoff_secs: 1
|
||||
headers:
|
||||
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
|
||||
uri: 'http://analytics:4000/api/logs?source_name=deno-relay-logs'
|
||||
logflare_storage:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- storage_logs
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 30
|
||||
retry_initial_backoff_secs: 1
|
||||
headers:
|
||||
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
|
||||
uri: 'http://analytics:4000/api/logs?source_name=storage.logs.prod.2'
|
||||
logflare_kong:
|
||||
type: 'http'
|
||||
inputs:
|
||||
- kong_logs
|
||||
- kong_err
|
||||
encoding:
|
||||
codec: 'json'
|
||||
method: 'post'
|
||||
request:
|
||||
retry_max_duration_secs: 30
|
||||
retry_initial_backoff_secs: 1
|
||||
headers:
|
||||
x-api-key: ${LOGFLARE_PUBLIC_ACCESS_TOKEN}
|
||||
uri: 'http://analytics:4000/api/logs?source_name=cloudflare.logs.prod'
|
||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue