From 02d191853bdabdb77c67578c78a85b4155bfc84b Mon Sep 17 00:00:00 2001 From: c-d-p Date: Tue, 22 Apr 2025 22:54:31 +0200 Subject: [PATCH] Dockerized everything and added CI/CD deployment --- .github/workflows/deploy.yml | 90 ++++++++++ backend/.env | 4 +- backend/Dockerfile | 19 +++ backend/core/__init__.py | 0 .../core/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 141 bytes .../__pycache__/celery_app.cpython-312.pyc | Bin 544 -> 467 bytes .../core/__pycache__/config.cpython-312.pyc | Bin 1083 -> 1889 bytes backend/core/celery_app.py | 16 +- backend/core/config.py | 30 ++-- backend/docker-compose.yml | 88 ++++++++-- .../admin/__pycache__/api.cpython-312.pyc | Bin 2506 -> 1691 bytes .../admin/__pycache__/tasks.cpython-312.pyc | Bin 0 -> 1742 bytes backend/modules/admin/api.py | 21 +-- backend/modules/admin/tasks.py | 35 ++++ backend/requirements.in | 17 ++ backend/requirements.txt | 161 ++++++++++++++++-- .../nativeapp/src/screens/AdminScreen.tsx | 24 ++- 17 files changed, 434 insertions(+), 71 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 backend/Dockerfile create mode 100644 backend/core/__init__.py create mode 100644 backend/core/__pycache__/__init__.cpython-312.pyc create mode 100644 backend/modules/admin/__pycache__/tasks.cpython-312.pyc create mode 100644 backend/modules/admin/tasks.py create mode 100644 backend/requirements.in diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..a27b975 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,90 @@ +# .github/workflows/deploy.yml + +name: Build and Deploy Backend +on: + # Triggers the workflow on push events but only for the main branch + push: + branches: [ main ] + paths: # Only run if backend code or Docker config changes + - 'backend/**' + - '.github/workflows/deploy.yml' + - 'backend/docker-compose.yml' + + # Allows running of this workflow manually from the Actions tab + workflow_dispatch: + + # Ensures the project will never be out of date by running a cron for this job + # Currently set to every Sunday at 3 AM UTC + schedule: + - cron: '0 3 * * 0' + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + # Checks out the repo under $GITHUB_WORKSPACE + - name: Checkout code + uses: actions/checkout@v4 + + # ------------------------------------------------------------------ + # Login to Container Registry (Using GHCR) + # ------------------------------------------------------------------ + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} # GitHub username + password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} # Uses the PAT stored in secrets + + # ------------------------------------------------------------------ + # Set up Docker Buildx for advanced build features + # ------------------------------------------------------------------ + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # ------------------------------------------------------------------ + # Build and Push Docker Image + # ------------------------------------------------------------------ + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ./backend + file: ./backend/Dockerfile # Explicit path to Dockerfile + push: true # Push the image after building + tags: | # Use SHA for version specific, latest for general + ghcr.io/${{ github.repository_owner }}/MAIA:${{ github.sha }} + ghcr.io/${{ github.repository_owner }}/MAIA:latest + # Pull latest base image updates when building (good for scheduled runs) + pull: true + cache-from: type=gha # Github Actions cache + cache-to: type=gha,mode=max + + # ------------------------------------------------------------------ + # Deploy to mara via SSH + # ------------------------------------------------------------------ + - name: Deploy to Server + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + set -e # Exit script on first error + cd ${{ secrets.DEPLOY_PATH }} + echo "Logged into server: $(pwd)" + + # Pull the specific image version built in this workflow + # Using the Git SHA ensures we deploy exactly what was just built + docker pull ghcr.io/${{ github.repository_owner }}/MAIA:${{ github.sha }} + + # Also pull latest for other services to keep up to date + docker-compose pull redis db + + # Uses sed to update the compose file with the new image tag + sed -i 's|image: ghcr.io/${{ github.repository_owner }}/MAIA:.*|image: ghcr.io/${{ github.repository_owner }}/MAIA:${{ github.sha }}|g' docker-compose.yml + echo "Updated docker-compose.yml image tag" + + # Restart the services using the new image(s) + echo "Bringing compose stack down and up with new image..." + docker-compose up -d --force-recreate --remove-orphans api worker db redis + echo "Deployment complete!" \ No newline at end of file diff --git a/backend/.env b/backend/.env index d0c81d1..6530144 100644 --- a/backend/.env +++ b/backend/.env @@ -1,3 +1,5 @@ +POSTGRES_USER = "maia" +POSTGRES_PASSWORD = "maia" PEPPER = "LsD7%" JWT_SECRET_KEY="1c8cf3ca6972b365f8108dad247e61abdcb6faff5a6c8ba00cb6fa17396702bf" -GOOGLE_API_KEY="AIzaSyBrte_mETZJce8qE6cRTSz_fHOjdjlShBk" \ No newline at end of file +GOOGLE_API_KEY="AIzaSyBrte_mETZJce8qE6cRTSz_fHOjdjlShBk" diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..84b4a3f --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,19 @@ +# backend/Dockerfile +FROM python:3.12-slim + +WORKDIR /app + +# Set environment variables to prevent buffering issues with logs +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Install dependencies +COPY ./requirements.txt /app/requirements.txt +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Copy application code (AFTER installing dependencies for better caching) +COPY . /app/ + +RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app +USER appuser \ No newline at end of file diff --git a/backend/core/__init__.py b/backend/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/core/__pycache__/__init__.cpython-312.pyc b/backend/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1415239e7a959e623073d16ecb06fa895f583407 GIT binary patch literal 141 zcmX@j%ge<81evGVGC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!($UYz&rQ`&PASk& z&QD3z_jUAi)K5xG&Q8rs0g4o*>c_`t=4F<|$LkeT{^GF7%}*)KNwq6t1!`sl;$jfv NBQql-V-Yiu1pv`_9~=Mx literal 0 HcmV?d00001 diff --git a/backend/core/__pycache__/celery_app.cpython-312.pyc b/backend/core/__pycache__/celery_app.cpython-312.pyc index 9e9d3f7d686304411ebeda974e02c1dd6585a2c0..8919cbeb61af57c6425b61dea27c34e6371d3c48 100644 GIT binary patch delta 271 zcmZ3$a+z8EG%qg~0}zBBWy@e-Vqka-;=lkql<_%XqPntJDsvVGOa?(uoF^uh!ji(8 z&a{SYH48*Mimj4elVjpJ87;Q*{G#mCqAH=>{FKt1)MCBF(vl3llEmWd;woXdSW0eY z-ee(06-M^S=8Ouag3hi!u0fIUPC@?Ou0ipkK|Z%cp+Z5f!J$4O@lKA;-mZQwx44p1 zb5e^c;}Z)CCg(AF+ZY4w|G>=1$oP~&@-BnILtfz3g|Bm~UgTE2!6R~=N9!Vw a)(u|83(6K3cr3qgvhguBaux9d^#K63JVa*z delta 328 zcmcc2ynsdhG%qg~0}vSg{+n*g$iVOz#DM`$DC6^@iR#KSDJ-eXSsXAq1U;J}g>_;Gc3 zs$$YN0Aj-`q1^nG(wx*{y~NUzjER?27}+L%R}kk4a&_?xj`#2n4!H$o2lxktO!i^) zPLBhc{(+g1k?|>mmL{!WAmfn6eqMzsNKqrfY5|SP-8aW0Jghp3TzQ0*Rusvdzb7w zl|WTQrBsU43!I2g#wVxZ(qsOFO1-SKJ)o&7QMEmwURse;PMukd{l_8W7igToKshr!Je6%E{k%xTu?Uk6q)EOsyPIa zT9%cs^9`)kkut4z@u!WJB?E~;K(Z|Znca3Zd@bE}XUm{rA_mz`$ZFaohwPMHh`CSf zHV7=@nuHFEz_v-SBS(|qmODue_BIJl3}<&&bC&lR_bZ}QIg*r?^eZh@aoif}$H zW^qR|;A%RP7P5uE2LQnS1i#1&Yj8$LFYtnuW_Td@Y_x@S+H_1e}8WY9=Z0a4DHu&hp}U zi0l#%3j70tntwB7HohjFJE`B1;WHUtzyNpA3?iQp_$*xD*KpTdIz6|@!+0i1$Ek2H zc0s7*N-~7l4Pi+|kBijyK=|l!uGsL{R2@6X-BQq01+$xSS+nx6dRfCh2y=>}mUHET zs^}1E^j^{EO^MlpQr75|qIsxzsrab)sq|3kqtZ|02XH|YypF`S)RG*_qn%h@MRIH@ zo{Y!7$mPG16+~t<;!>3_3!BlMUClukQ=uvK_ZR@a1E+!BqsU?8D0&z@9xy&wF$1gn z51u7VZuP8x;As1B`$#!dj@N(u*zBL)e|W%}-1O-ij&Za9?xo}I+0gZ>RFSG1l?~&= zX(Rr*IkawYeFwV9t)C9RTMyQP^-wKj+@3R%d2<*U+~D&ulS5~N*Q&uvuo|j_Uie}p2cpmP@K9b&z9-M{>FrnFmRNPY>R uzfa^JmSLE`K;&1@@f!&J4mzKDUvYkB`q?+H0O|g{1w6gqPk!sNNdE!XkCPDq delta 765 zcmYjP&1(}u9G%&n{b-WieDs4DY?`1=UD2WuQ;<@yR-2Z#DM^Yj9zt=K7|4h0rb0n% z1eJ=Hlsz=ZUZodLg7_zRFt8$Zp^zMUC|)e|Hu>a30|a*OPs?u#_vTXR>)$ zNavDwQ?9c7IA6HGvY5#(<|9` zyi98>j()Gd@@%E1Z$~jWu%lJe!yAG%qg~0}!|zW6R)WnaC%>s54PL(<_A|g)>JeS2#+Tk%5UJl{tkgg*%-Q z$YMzqT+Im5z`&5sxQ1snGlU%_!o=Xtkiy%-kiwVB4i^sfk4?Ns|vT zzE=lS$<53w zQUVGH0&y`fkZ53dz#}@L_5!!+1s2sJ*~x*->Y5X#HVrO!Sh(AL8+|9JTxAgld)ZG@>K0o` zWkF_MdJ)L9TkL6x#U+W8!#Vl_SxXX&vx^}vU@OWmElDi`hutma^wbh?DBNNx$S*Dd z+lLllzc_4i^HWN5QtgV2fyRR3qIl+HElvfty9`nfIfW+sbNcGBGfGUz`@jICR%mW; g-x+=&Y5CGHMeRWG{ z+Y@mfoCh$p2w)fm_Pq%dFBomaVGT8(14b6X*2|@dBtf4#jAM4=i6){1j5)(xe=NQz z?!$ZL62=kd6Yg{8ckVAeLBN=*2>Gl4QD1_V8d)5}{cOc~!e=vm)MSb|2guY@F*0G! zgr$5L>%2T7HN4 z1oJ{o$bwHV0x&PuL@3U@Njk+X2iv*V|BJ3B_7bvr$)-EQ1WGk=9el$dluoiv`l|2q-%OYTPr+v)+=!03|GA-N)IDZP?)eX)QC-Y^9Um@7*$*gIclct4q zj4Wj9Cq8`dV%G4aSqow7dNhlQ$j+Cibpk4)w{_z2@l!=)Vg!vaK0=5v-D*!MA=`um z;VBSTsyVY*^pt62=V(Arog$~LZX@QFrz)jV-u7h1Y z8PWuhA>&tRkk_bU;ZD+UD*HEAHcQBp+B`Dx>r5~^;0jAK2Pg3;S?LC#4y;GSS^#?y z5KgRQmNE<3M}g2EN_a(E(sq==rZRZ@{d*VgT(~>#Dudg~=&rw{Hx>Q%Zb$)E` z%tDk|uuy11g(OcZA(%&t{R8?B6k7VSixxLS1w)_uW+Q#cQ}3PKskT;fAMTv{%{kw9 z?#JQ&q9`&TYx+l__%jE<@AMF@SUWfxBf%Pg05T@9m|2Dah$)lo|1QX)Sb>GHuet5^ zuUT%1)A&E=Nn--pKqkUVb9$u`a77(MgE>LLlbA{#jj*8{16e9pBXW>|F_X1oH8zYn zle73*ER1c&U)%(s zCixOfEWQnZ%dARgbpJo>zC_!t$zC>6fk|I_1Rk>|yoi@ykNzHieYyO`@hOU;&W@{B zKjDD?a#s}Uiq^6GSXRk;*F-v2y&_Vf?&D{0(Pu+s*3IysN$(r7vXjt zCIe09Uup5qA$y@@BLXB{w5h8s7Eq6~vH^+#Osh8fLxHY8(eAvx$D(smEw0a#=2@>TS z)I0>Wu0VFbGhC?-rz&V6=k zCJ-&;>GW41LgMorv6egsq0M0|v@a;M{0_K9wB!bS1($3uh@(5msCp<6VCcD)!1VPA zn+ux>_1l6ibA#mVmbEjEp_>5<=W&MiBZvHRwr-(-Z)qh6PN%26UR=o+9Sh|RSjig> zMEQ>&7CoVcv3JmCQq4hgQkXd%C;m<|<1;JamtU(BY;Ji+e=274TOF;I(~j zc%K{C=Po=`de^UQ-q^UYqYST1KRuV-8vWtDpM>3WAFs?jmAb#}UzvOsPaKHJ`^r6K zPwa1q{ZFRrV*jo<+>AiAA*xRbby3|FFE>T`zI0DoO*B(I^;fUdJFnKotA_&UI=|NU zux~Ft*hmlVrH2~np`G-|U7^{lJ_o!seD}kH&YtzpcRB}}nb$Uz4P`Gg(#VW#%{DUE z9`L`Vy6c%sTfJM!`uWk_)LYF|*P8rLULV~Y-xz;%{nymcVSMF=W9mx4QOC&WI*u7y^&P+i1jiT1_Cg$QxqBUJ { const [isHardClear, setIsHardClear] = useState(false); const [isLoading, setIsLoading] = useState(false); const [snackbarVisible, setSnackbarVisible] = useState(false); const [snackbarMessage, setSnackbarMessage] = useState(''); + // const navigation = useNavigation(); // Remove if not used elsewhere + const { logout } = useAuth(); // Get the logout function from context const handleClearDb = async () => { setIsLoading(true); @@ -16,12 +21,25 @@ const AdminScreen = () => { const response = await clearDatabase(isHardClear); setSnackbarMessage(response.message || 'Database cleared successfully.'); setSnackbarVisible(true); + + // If hard clear was successful, trigger the logout process from AuthContext + if (isHardClear) { + console.log('Hard clear successful, calling logout...'); + await logout(); // Call the logout function from AuthContext + // The RootNavigator will automatically switch to the AuthFlow + // No need to manually navigate or set loading to false here + return; // Exit early + } + } catch (error: any) { console.error("Error clearing database:", error); setSnackbarMessage(error.response?.data?.detail || 'Failed to clear database.'); setSnackbarVisible(true); } finally { - setIsLoading(false); + // Only set loading to false if it wasn't a hard clear (as logout handles navigation) + if (!isHardClear) { + setIsLoading(false); + } } }; @@ -78,4 +96,4 @@ const styles = StyleSheet.create({ }, }); -export default AdminScreen; +export default AdminScreen; \ No newline at end of file