diff --git a/.env.example b/.env.example index 0e7f275..8001440 100644 --- a/.env.example +++ b/.env.example @@ -20,3 +20,6 @@ DOCKER_DATABASE_HOST_PORT=53853 DOCKER_REDIS_HOST_PORT=53852 DOCKER_INSTALL_XDEBUG=true DOCKER_HOST_USER_ID=1000 + +SOPS_AGE_BETA_SECRET_KEY= +SOPS_AGE_PROD_SECRET_KEY= diff --git a/.github/workflows/deploy-to-prod.yml b/.github/workflows/deploy-to-prod.yml index 91121c2..34fe24a 100644 --- a/.github/workflows/deploy-to-prod.yml +++ b/.github/workflows/deploy-to-prod.yml @@ -1,4 +1,4 @@ -name: Deploy to production init +name: Deploy to production Keating concurrency: group: deploy-prod @@ -6,18 +6,111 @@ concurrency: on: workflow_dispatch: + inputs: + appName: + description: 'For whom app should be deployed?' + required: true + type: choice + options: + - krewak + - eskrzypacz + - kzygadlo + - kpiech jobs: deploy: environment: production runs-on: ubuntu-22.04 - name: Deploy to production + name: Deploy to production - ${{ inputs.appName }} env: - DOCKER_REGISTRY: ghcr.io - DOCKER_REGISTRY_USER_NAME: blumilkbot - REPO_NAME: ${{ github.event.repository.name }} - REPO_OWNER: ${{ github.repository_owner }} + DOCKER_REGISTRY: registry.blumilk.pl + DOCKER_REGISTRY_USER_NAME: robot@blumilkbot-harbor + DOCKER_REGISTRY_PROJECT_NAME: internal-public + DOCKER_REGISTRY_REPO_NAME: keating TARGET_DIR_ON_SERVER: /blumilk/production + APP_NAME: ${{ inputs.appName }} steps: + - name: set branch name + run: echo "BRANCH_NAME=$GITHUB_REF_NAME" >> $GITHUB_ENV + - name: checkout - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + uses: actions/checkout@v4.1.1 + with: + fetch-depth: 0 + ref: ${{ env.BRANCH_NAME }} + + - name: sync with main branch + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + git merge --no-commit --no-ff origin/main + + - name: set deployment project version + run: echo "DEPLOYMENT_PROJECT_VERSION=$(bash ./environment/prod/deployment/scripts/version.sh --long)" >> $GITHUB_ENV + + - name: set up Docker Buildx + uses: docker/setup-buildx-action@v3.1.0 + + - name: login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ env.DOCKER_REGISTRY_USER_NAME }} + password: ${{ secrets.BLUMILKBOT_HARBOR_TOKEN }} + + - name: set docker image name + run: echo "DOCKER_IMAGE_NAME=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REGISTRY_PROJECT_NAME }}/${{ env.DOCKER_REGISTRY_REPO_NAME }}" >> $GITHUB_ENV + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5.5.1 + with: + images: ${{ env.DOCKER_IMAGE_NAME }} + tags: | + type=raw,value=prod + context: workflow + + - name: build and push image + uses: docker/build-push-action@v5.1.0 + with: + context: . + file: ./environment/prod/app/Dockerfile + build-args: | + DEPLOYMENT_PROJECT_VERSION_ARG=${{ env.DEPLOYMENT_PROJECT_VERSION }} + APP_ID_ARG=${{ env.APP_NAME }} + push: true + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha, ref=${{ env.DOCKER_IMAGE_NAME }}-prod-build-cache + cache-to: type=gha, ref=${{ env.DOCKER_IMAGE_NAME }}-prod-build-cache, mode=max + + - name: copy files via ssh + uses: appleboy/scp-action@v0.1.7 + with: + timeout: 10s + command_timeout: 10m + host: ${{ secrets.VPS_OVH_BF7EC892_HOST }} + port: ${{ secrets.VPS_OVH_BF7EC892_PORT }} + username: ${{ secrets.VPS_OVH_BF7EC892_USERNAME }} + key: ${{ secrets.VPS_OVH_BF7EC892_SSH_PRIVATE_KEY }} + passphrase: ${{ secrets.VPS_OVH_BF7EC892_SSH_PRIVATE_KEY_PASSPHRASE }} + source: "./environment/prod/deployment/prod/apps/${{ env.APP_NAME }}/*,./environment/prod/deployment/scripts/*, ./environment/prod/deployment/prod/Makefile, ./environment/prod/deployment/prod/docker-compose.dbprod.yml, ./environment/prod/deployment/prod/docker-compose.prod.yml, ./environment/prod/deployment/postgres/*" + target: ${{ env.TARGET_DIR_ON_SERVER }}/${{ env.DOCKER_REGISTRY_REPO_NAME }}/${{ env.APP_NAME }} + rm: true + + - uses: appleboy/ssh-action@v1.0.3 + with: + timeout: 10s + command_timeout: 10m + host: ${{ secrets.VPS_OVH_BF7EC892_HOST }} + port: ${{ secrets.VPS_OVH_BF7EC892_PORT }} + username: ${{ secrets.VPS_OVH_BF7EC892_USERNAME }} + key: ${{ secrets.VPS_OVH_BF7EC892_SSH_PRIVATE_KEY }} + passphrase: ${{ secrets.VPS_OVH_BF7EC892_SSH_PRIVATE_KEY_PASSPHRASE }} + script_stop: true + script: | + cd ${{ env.TARGET_DIR_ON_SERVER }}/${{ env.DOCKER_REGISTRY_REPO_NAME }}/${{ env.APP_NAME }}/environment/prod/deployment/prod/ + mv Makefile docker-compose.prod.yml docker-compose.dbprod.yml apps/${{ env.APP_NAME }}/ + cd apps/${{ env.APP_NAME }} + make prod-deploy SOPS_AGE_KEY=${{ secrets.SOPS_AGE_PROD_SECRET_KEY }} + docker images --filter dangling=true | grep "${{ env.DOCKER_IMAGE_NAME }}" | awk '{print $3}'| xargs --no-run-if-empty docker rmi 2>/dev/null || true diff --git a/.gitignore b/.gitignore index 6541a77..98fbf6e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ .env .env.backup .env.beta.secrets.decrypted +.env.prod.secrets.decrypted .phpunit.result.cache .php-cs-fixer.cache .appversion diff --git a/.sops.yaml b/.sops.yaml index a9a66f4..59a0e39 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -4,3 +4,8 @@ creation_rules: path_regex: \.env\.beta\.secrets.*$ age: >- age1vq7sw98g2xk9md2yg9f959k8xkaz8r32pds27jn3nsfcdue3757s0h7hd8 + + - name: prod + path_regex: \.env\.prod\.secrets.*$ + age: >- + age1h8wnpa5lx2vgn2h64lgpeanuct8q2tvzxvn5xmvms7exmwvmu9zq5str5w diff --git a/Makefile b/Makefile index 540f683..59f33b9 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,30 @@ encrypt-beta-secrets: decrypt-beta-secrets: @$(MAKE) decrypt-secrets SECRETS_ENV=beta AGE_SECRET_KEY=${SOPS_AGE_BETA_SECRET_KEY} +encrypt-krewak-prod-secrets: + @$(MAKE) encrypt-secrets-prod SECRETS_ENV=krewak + +decrypt-krewak-prod-secrets: + @$(MAKE) decrypt-secrets-prod SECRETS_ENV=krewak AGE_SECRET_KEY=${SOPS_AGE_PROD_SECRET_KEY} + +encrypt-eskrzypacz-prod-secrets: + @$(MAKE) encrypt-secrets-prod SECRETS_ENV=eskrzypacz + +decrypt-eskrzypacz-prod-secrets: + @$(MAKE) decrypt-secrets-prod SECRETS_ENV=eskrzypacz AGE_SECRET_KEY=${SOPS_AGE_PROD_SECRET_KEY} + +encrypt-kzygadlo-prod-secrets: + @$(MAKE) encrypt-secrets-prod SECRETS_ENV=kzygadlo + +decrypt-kzygadlo-prod-secrets: + @$(MAKE) decrypt-secrets-prod SECRETS_ENV=kzygadlo AGE_SECRET_KEY=${SOPS_AGE_PROD_SECRET_KEY} + +encrypt-kpiech-prod-secrets: + @$(MAKE) encrypt-secrets-prod SECRETS_ENV=kpiech + +decrypt-kpiech-prod-secrets: + @$(MAKE) decrypt-secrets-prod SECRETS_ENV=kpiech AGE_SECRET_KEY=${SOPS_AGE_PROD_SECRET_KEY} + decrypt-secrets: @docker compose exec --user "${CURRENT_USER_ID}:${CURRENT_USER_GROUP_ID}" --env SOPS_AGE_KEY=${AGE_SECRET_KEY} ${DOCKER_COMPOSE_APP_CONTAINER} \ bash -c "echo 'Decrypting ${SECRETS_ENV} secrets' \ @@ -79,4 +103,18 @@ encrypt-secrets: && sops --encrypt --input-type=dotenv --output-type=dotenv --output .env.${SECRETS_ENV}.secrets .env.${SECRETS_ENV}.secrets.decrypted \ && echo 'Done'" +decrypt-secrets-prod: + @docker compose exec --user "${CURRENT_USER_ID}:${CURRENT_USER_GROUP_ID}" --env SOPS_AGE_KEY=${AGE_SECRET_KEY} ${DOCKER_COMPOSE_APP_CONTAINER} \ + bash -c "echo 'Decrypting ${SECRETS_ENV} secrets' \ + && cd ./environment/prod/deployment/prod/apps/${SECRETS_ENV} \ + && sops --decrypt --input-type=dotenv --output-type=dotenv --output .env.prod.secrets.decrypted .env.prod.secrets \ + && echo 'Done'" + +encrypt-secrets-prod: + @docker compose exec --user "${CURRENT_USER_ID}:${CURRENT_USER_GROUP_ID}" ${DOCKER_COMPOSE_APP_CONTAINER} \ + bash -c "echo 'Encrypting ${SECRETS_ENV} secrets' \ + && cd ./environment/prod/deployment/prod/apps/${SECRETS_ENV} \ + && sops --encrypt --input-type=dotenv --output-type=dotenv --output .env.prod.secrets .env.prod.secrets.decrypted \ + && echo 'Done'" + .PHONY: init check-env-file build run stop restart shell shell-root test fix create-test-db queue encrypt-beta-secrets decrypt-beta-secrets diff --git a/environment/prod/deployment/prod/Makefile b/environment/prod/deployment/prod/Makefile new file mode 100644 index 0000000..e2eea78 --- /dev/null +++ b/environment/prod/deployment/prod/Makefile @@ -0,0 +1,46 @@ +export COMPOSE_DOCKER_CLI_BUILD = 1 +export DOCKER_BUILDKIT = 1 + +MAKEFLAGS += --no-print-directory + +SHELL := /bin/bash + +CURRENT_USER_ID = $(shell id --user) +CURRENT_USER_GROUP_ID = $(shell id --group) + +DOCKER_COMPOSE_FILENAME = docker-compose.prod.yml +DOCKER_COMPOSE_DB_FILENAME = docker-compose.dbprod.yml +DOCKER_COMPOSE_APP_SERVICE = keating-prod-app +PROJECT_NAME=keating-prod + +DOCKER_EXEC_SCRIPT = docker compose --file ${DOCKER_COMPOSE_FILENAME} exec --workdir /application/environment/prod/deployment/scripts ${DOCKER_COMPOSE_APP_SERVICE} bash + +CURRENT_DIR = $(shell pwd) + +prod-deploy: decrypt-secrets create-deployment-file + @docker compose --project-name ${PROJECT_NAME} --file ${DOCKER_COMPOSE_DB_FILENAME} pull && \ + docker compose --project-name ${PROJECT_NAME} --file ${DOCKER_COMPOSE_DB_FILENAME} up --detach && \ + docker compose --file ${DOCKER_COMPOSE_FILENAME} pull && \ + docker compose --file ${DOCKER_COMPOSE_FILENAME} up --detach && \ + sleep 5 && \ + echo "App post deploy actions" && \ + ${DOCKER_EXEC_SCRIPT} post-deploy-actions.sh + +SOPS_VERSION=3.8.1 + +decrypt-secrets: + @wget --output-document ./sops "https://github.com/getsops/sops/releases/download/v${SOPS_VERSION}/sops-v${SOPS_VERSION}.linux.amd64" \ + && chmod +x ./sops \ + && mv .env.prod .env \ + && echo "Decrypting secrets" \ + && ./sops --decrypt --input-type=dotenv --output-type=dotenv .env.prod.secrets >> .env \ + && echo "Done" + +DEPLOYMENT_DATETIME = $(shell TZ=Europe/Warsaw date --rfc-3339=seconds) + +create-deployment-file: + @echo "\ + DEPLOY_DATE='${DEPLOYMENT_DATETIME}'\ + " > .deployment + +.PHONY: prod-deploy decrypt-secrets create-deployment-file diff --git a/environment/prod/deployment/prod/apps/eskrzypacz/.env.prod b/environment/prod/deployment/prod/apps/eskrzypacz/.env.prod new file mode 100644 index 0000000..d2cf4f8 --- /dev/null +++ b/environment/prod/deployment/prod/apps/eskrzypacz/.env.prod @@ -0,0 +1,17 @@ +APP_NAME="Ewelina Skrzypacz Keating" +ENVIRONMENT=prod +APP_DEBUG=false + +COMPOSE_PROJECT_NAME=keating-prod-eskrzypacz +TRAEFIK_ENABLED=true +KEATING_HOST_NAME=ewelinaskrzypacz.collegiumwitelona.pl +APP_URL=https://${KEATING_HOST_NAME}/ +TRAEFIK_ROUTER_RULE="Host(`ewelinaskrzypacz.collegiumwitelona.pl`)" + +DB_CONNECTION=pgsql +DB_DATABASE=eskrzypacz + +CACHE_DRIVER=redis +QUEUE_CONNECTION=redis +SESSION_DRIVER=redis +MAIL_ENCRYPTION=tls diff --git a/environment/prod/deployment/prod/apps/eskrzypacz/.env.prod.secrets b/environment/prod/deployment/prod/apps/eskrzypacz/.env.prod.secrets new file mode 100644 index 0000000..355b757 --- /dev/null +++ b/environment/prod/deployment/prod/apps/eskrzypacz/.env.prod.secrets @@ -0,0 +1,15 @@ +APP_KEY=ENC[AES256_GCM,data:hb/ZfDIh+SN6eTqc+yNqw0SzrMVpghThfI+AYa0Sf0jSmux9WAOX7otEVzArO9s9pj+T,iv:cpJPkFCkwU0ZBQQ4OR+nznltASHTOZEzeN+NQvh/8rM=,tag:GrwQCdzWYvwLXYRpKPHVHw==,type:str] +DB_HOST=ENC[AES256_GCM,data:e24Z8QCKjc+gGChv+u3/iBY/+XLi,iv:CfExgC6gBjUQ9WYRBmmC+lM6scLB1ng0eogv547FHok=,tag:2vDGcuv+emxtbt4KIUWbzA==,type:str] +DB_PORT=ENC[AES256_GCM,data:wM4cEg==,iv:+B6x/3Vsw4ndsYQwbSSHHPYb3p0eZdwIWV63NNYQsto=,tag:obann9mKYrPqyWWKAu0gGQ==,type:str] +DB_ROOT_PASSWORD=ENC[AES256_GCM,data:q5b/s7d7v2/UmQlGETiuXKgFO8U=,iv:OyvSw6zLId1deJaUdfWA43NpdklDI1aW9tUvYcChqHw=,tag:WUIrBIVXFZH4s07T01cnjQ==,type:str] +DB_USERNAME=ENC[AES256_GCM,data:ZA7Z64IUW5gktw==,iv:VjOvYN/JzIe9a/We96SmXBT6LZ8hSoJuA4m79acb8i8=,tag:O94j0q1H4gAWFtZtn5po5Q==,type:str] +DB_PASSWORD=ENC[AES256_GCM,data:dGuekXE64EyL76b4U+e3235xIug=,iv:Cr7rPVpyQUFzQ781zrIKzVcHTwT5CuHVb/dZazokBII=,tag:MPwh2Og5M68yqP4UG4XPpA==,type:str] +REDIS_HOST=ENC[AES256_GCM,data:DiatVVsTzjDK7EsFN92XGvxH,iv:/UzAK8WsIzixtr40pmj4mD9SQfV/PyyYr9GWzE9UDkg=,tag:fNuWxdQbZef8pG3zZWMcSQ==,type:str] +REDIS_PORT=ENC[AES256_GCM,data:1tTosQ==,iv:hiBfC3BLC8R7CDkb90UHSc0X2NgG/H5hUVOfGAp2lTA=,tag:kaLAeCozYXDubGac34g7rg==,type:str] +SENTRY_LARAVEL_DSN=ENC[AES256_GCM,data:05sFU0f6qgxLMr2RZ8fGkqQf6ofN2I6mq0xcmBKLvd/sL4PaYXEnldXfpViJkyl7XD8RU5sMCEgJLrRZGhauXqkaq2deWPHXP2o2li7RMpYbhCs/gt5gPIp+pVIeZGc=,iv:wvMFYhWVFWb7zZ8Uw5Otj5K13ESYcymA+CPDZRPNnVY=,tag:5j9PdcVe5WtvoWiEHnHDhQ==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4L3Z3YjgzcTJYcDRUblE4\nSGxVQjNNVkdGdmdpT0pCY1lVNVZuSm9OaHlNCnQ2bVhDTURxdWUxY1F3TG1jK1du\nb0JVMWZQZHM5S3paV2M5M1B2QUZVdHMKLS0tIENsRHRTalU2dThzUXFtOTNFdkRh\nNFlnMmFheVVlazZ0L0I5SnJTbkJ2RmcKuqqcpH6cDsnwcfVCSm1LQdkdu2wHqHg9\ny/oLLgtrn3nQVrmPkIxR1kaF/FKtCAnz+nsMSjwaQBktuOFjp5S4Pw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1h8wnpa5lx2vgn2h64lgpeanuct8q2tvzxvn5xmvms7exmwvmu9zq5str5w +sops_lastmodified=2024-10-22T05:39:09Z +sops_mac=ENC[AES256_GCM,data:hJcyYVHA0LeHUYbsyAkCNo9Gc9ytMzWGpZD7xSqYQLHah5rKBvPLLGCqjaDt076ovqGfPgMUf7jlO9+gFJu0HzVWuzxFstlmBT4f7hOuzjwYQc4FuLkxUxfQFD+1PKR4JaVpvhIrCwGdaBGolCuWpRBauBp1tJs2iwnlgoBEsWA=,iv:+kmXtcQEDsX6jsnzH925vC/aN6Bd0dm4L9R72+l4Ghw=,tag:4Fdo5EHuGxqyCe3YswWnmQ==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.8.1 diff --git a/environment/prod/deployment/prod/apps/kpiech/.env.prod b/environment/prod/deployment/prod/apps/kpiech/.env.prod new file mode 100644 index 0000000..c3796a5 --- /dev/null +++ b/environment/prod/deployment/prod/apps/kpiech/.env.prod @@ -0,0 +1,17 @@ +APP_NAME="Kamil Piech Keating" +ENVIRONMENT=prod +APP_DEBUG=false + +COMPOSE_PROJECT_NAME=keating-prod-kpiech +TRAEFIK_ENABLED=true +KEATING_HOST_NAME=kamilpiech.collegiumwitelona.pl +APP_URL=https://${KEATING_HOST_NAME}/ +TRAEFIK_ROUTER_RULE="Host(`kamilpiech.collegiumwitelona.pl`)" + +DB_CONNECTION=pgsql +DB_DATABASE=kpiech + +CACHE_DRIVER=redis +QUEUE_CONNECTION=redis +SESSION_DRIVER=redis +MAIL_ENCRYPTION=tls diff --git a/environment/prod/deployment/prod/apps/kpiech/.env.prod.secrets b/environment/prod/deployment/prod/apps/kpiech/.env.prod.secrets new file mode 100644 index 0000000..b73d396 --- /dev/null +++ b/environment/prod/deployment/prod/apps/kpiech/.env.prod.secrets @@ -0,0 +1,15 @@ +APP_KEY=ENC[AES256_GCM,data:4kU6ha7/Rgg0V1gs9EFDPRoyae6xE4NKeFzd//3EZcepci5FLsuU5QRuYMp+b65pCoho,iv:dvFrvtfoKhy9aZnOSRauPDWjiojRiRJTppu2l33LKpk=,tag:PFh01v59pNhxOWVRx4bknQ==,type:str] +DB_HOST=ENC[AES256_GCM,data:0ENXGH5WKzSrnY68RLep6KHU+Yhu,iv:BI5DXCG4TK1IAvI0q0MkoLDKggoLrT9sBg4I+BoABLk=,tag:/JvNsI5T0Qq2xYshVWkL7g==,type:str] +DB_PORT=ENC[AES256_GCM,data:nVLZtA==,iv:EYK+LcaEvljpVa/XnO7yoxeu07NCMU1PILNU/qn6z/g=,tag:donSOud5/HvCT7xfcv3PDw==,type:str] +DB_ROOT_PASSWORD=ENC[AES256_GCM,data:vo0dCHsx40IbTSvlkktIS2Fp7fY=,iv:47A6WR/GpwyP48fu/4h7xLVAN86y9ZVzm7YC/osDWEY=,tag:Xmn5g1lpbdnRtyGO6nDeWA==,type:str] +DB_USERNAME=ENC[AES256_GCM,data:SYodtlR4,iv:IYbI1CJPZQnK0i3QG+HvYST8hHwJKXwGbsNIwtjuP/0=,tag:iQSGjoqd+EwGxujwWmK4ew==,type:str] +DB_PASSWORD=ENC[AES256_GCM,data:uFxMGVEfYbQ/Q9cB5Va/XEFmJhQ=,iv:uXMy/X6Ji9Cp3QdtqANFAHKdXQZMmGkCr16Zi90wwE8=,tag:Bn+6WwRgWu2IkSmQj6nLWQ==,type:str] +REDIS_HOST=ENC[AES256_GCM,data:kCcdqEWAhr+H8XcunEIriP3s,iv:mC0y9gIlJvt37O9AbSs4iUkt97hGXmGbMd9R+qZ4zbo=,tag:+OmrlYVzYMBnWHgKDYIzPw==,type:str] +REDIS_PORT=ENC[AES256_GCM,data:v/6hSg==,iv:KY1Duemhe+7FUzrVW/+qtscUTSmyRPGS5ja82jP7m4w=,tag:ma3YLU7/uKXuG6tiu3kpVQ==,type:str] +SENTRY_LARAVEL_DSN=ENC[AES256_GCM,data:3tnvpFtDFWMIzdeZ+UvqYfI+88YR+hZq6Nq3EYaKgaCRDNIiV0TXo34VQg5xj8pYvRqqqArGJJtNn0NP/yhYjJ5oMEpf0LLSFvtIH2pvMfCjWa6FxH4J+DSP3hb8mik=,iv:Yl/yV298ElPeXwO5gDcMs3ZqeG7GM5FcAOm31MjotEU=,tag:6J6KM8RFabkBkyDReuJMhQ==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLa0FPTHBIeEFtajc5Q0dw\nYzFONU1DaElRYVdVK29VbGU3ZEx1QTJZUzNBCm9pVnpyVno4T3JXM1lpR1RQYUNC\nYmhGTHJONTUzT0s5Uk9HekI1cE1rR2sKLS0tIGhTUzFRUUhMRlFNQWdRVWI2eWJn\nQWtZSGlSQm83c044NHhkNHRiL0N5Z3cK9fKHFNAyNohxwx9xCRcO65mWwZgadzIT\nJUdOJdrvkxgIP64tdNwlMjIIt/xxmU1Sl/xsbH27VUajmevta4SE9Q==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1h8wnpa5lx2vgn2h64lgpeanuct8q2tvzxvn5xmvms7exmwvmu9zq5str5w +sops_lastmodified=2024-10-22T05:38:55Z +sops_mac=ENC[AES256_GCM,data:mHmMzYQg4FUvjcD+FBOuxib6mTCnvqK0XG1sD7WszPB6/sO7qKKGC4+Dh1N949TkcbwCunwJgAVrapOrOQMJD4AMIu55nEcRQrH4OWDcEAZjCHYX/A5TBmdx1kN2ftIFy0lgjpuMJxb7Uzub2ZcYlnjPp1V/9hHg5ERSDkn2Ul0=,iv:tZ2XT7sLDbs0tV5rSUpMoPY3u8rqegtmo1D6ZwFoJZQ=,tag:KdRPI8gQYOwZTyEVHn31rw==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.8.1 diff --git a/environment/prod/deployment/prod/apps/krewak/.env.prod b/environment/prod/deployment/prod/apps/krewak/.env.prod new file mode 100644 index 0000000..57d927e --- /dev/null +++ b/environment/prod/deployment/prod/apps/krewak/.env.prod @@ -0,0 +1,18 @@ +APP_NAME="Krzysztof Rewak Keating" +ENVIRONMENT=prod +APP_DEBUG=false + +COMPOSE_PROJECT_NAME=keating-prod-krewak +TRAEFIK_ENABLED=true +KEATING_HOST_NAME=krzysztofrewak.collegiumwitelona.pl +APP_URL=https://${KEATING_HOST_NAME}/ +TRAEFIK_ROUTER_RULE="Host(`pwsz.rewak.pl`) || Host(`cwup.rewak.pl`)" + + +DB_CONNECTION=pgsql +DB_DATABASE=krewak + +CACHE_DRIVER=redis +QUEUE_CONNECTION=redis +SESSION_DRIVER=redis +MAIL_ENCRYPTION=tls diff --git a/environment/prod/deployment/prod/apps/krewak/.env.prod.secrets b/environment/prod/deployment/prod/apps/krewak/.env.prod.secrets new file mode 100644 index 0000000..e7472b8 --- /dev/null +++ b/environment/prod/deployment/prod/apps/krewak/.env.prod.secrets @@ -0,0 +1,15 @@ +APP_KEY=ENC[AES256_GCM,data:DFxaueDkLFZv3slgirmngLfk/68KfCHDlLFnoVWQrNHyBcae5m2Dt9x+zpKI1Ov6Izj2,iv:fMd3ryiPZvIyj6JU/teNl2u7jLvPKj2YzzcoPDutTKE=,tag:ZL1OPgCYBU6x4nf3SR3Xjg==,type:str] +DB_HOST=ENC[AES256_GCM,data:yQPRLse3h3yGfoaFkVbZQXGc83n8,iv:z9VmTXK5zdeuBvVE/U6PzjrkG0iGZzkP/Jl8WYZcu6w=,tag:wmyhx2plEa7vN/e51P1lRQ==,type:str] +DB_PORT=ENC[AES256_GCM,data:RM/hHA==,iv:sgNUKrA45gsBgsGjICjfE8Y/q4LG2ZXxijMBxqJbN24=,tag:gti36hW1If1fxvqpDDoa1g==,type:str] +DB_ROOT_PASSWORD=ENC[AES256_GCM,data:LctIyCoRaI42nBFhPP7f9Nmk48c=,iv:wUKBWYWkRQqJTQ1cStvZMwPoR+86XflOo9EJAOpqpVc=,tag:JvWCyei3fQQu7hdAeGJtmg==,type:str] +DB_USERNAME=ENC[AES256_GCM,data:LlF/lhGJ,iv:nr63/YZpNwIg0Wi6ie6lMzlo52f+yxZjO9f4Ju4HJlE=,tag:qv9s+xZ4Fue60GFQu7Ka5A==,type:str] +DB_PASSWORD=ENC[AES256_GCM,data:ixsdcCesCKwIpgvRiJkTpoRgnrk=,iv:7o7SCXl7iLia4M4EIqWAL6w3Y6yZAekVpUtITcKmfVs=,tag:WA85UPBzE+Bn+AEgOk/OKQ==,type:str] +REDIS_HOST=ENC[AES256_GCM,data:Lxddh0B2WthCwNq7l2M6Fdh7,iv:YpCSnT+ChemHBv8IcqBPn3+I+qNvodwdMhYxR/i7DHk=,tag:P4tjgUgLsmWAEx27j8BNjw==,type:str] +REDIS_PORT=ENC[AES256_GCM,data:JkRyxw==,iv:dliVaKQhAAZMci6EZ6/dgIt0fuEzauf7Kz27pAXCqrM=,tag:8WYigRnykJBnKJECVKsGQw==,type:str] +SENTRY_LARAVEL_DSN=ENC[AES256_GCM,data:gGH8TQAxCnOgrjoU0ze6+N45WpPWPInHSorauZAHVPFDD+xd5VxIpSw1wywXQOO8uW7h5RIZLGcAVLwEWXThamVHdBRTasnsuBj91hvm+/bMjm7QoVL09zflYCRD/1Y=,iv:E2S1IRSQQGxfvgjFBwCl0YLvo/WBlY79TPuhocCUo4A=,tag:iB8E38/ebvOYRzb1+H848g==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzTXB6QWU4SWhkTVlEYmh2\nSGgwQ0drSkJYeGxvc3ZBYXV6QlE4a1liTmlZCmFrK3hLdjVrNkNIVWljMWZzZWkx\nK0hIM3VrYTlIeWdMZDE1bHVVK1N5a2MKLS0tIHp0MXR0SStSQVZEQU9ObElSWHF6\nbW1MWW5LYmMvSXhxV0NNY0hsNmRCVDgKtpWX6heJmPvapZm93wRuDzc9FHFgdi8C\nI8bO5eJDA0+DWCz2TahXHjv1L3lqvcMrq673SNwQ3WcE9kzWcI6paw==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1h8wnpa5lx2vgn2h64lgpeanuct8q2tvzxvn5xmvms7exmwvmu9zq5str5w +sops_lastmodified=2024-10-22T05:38:51Z +sops_mac=ENC[AES256_GCM,data:XoKFFW90PVP8qMT5J28+ramFnmYavUcSL3jq+2gJwGHH2q7PImhBOUbF3FvETXEAniu7lr6361xNIfvmoU9++hhbodaAuDlXdeOnuJZN35ueTZdZgyz1GMDhDg5ql8KGDYPzibsvlftLxpCJC0rIip4FAsooydv3uX4rMwTXFl0=,iv:Tj66LEA56ut8raiQJiW1wUMtG21tm8Es8xx4R7QQm+8=,tag:UJjNVwSeb3B8Of4++OCXcg==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.8.1 diff --git a/environment/prod/deployment/prod/apps/kzygadlo/.env.prod b/environment/prod/deployment/prod/apps/kzygadlo/.env.prod new file mode 100644 index 0000000..d678981 --- /dev/null +++ b/environment/prod/deployment/prod/apps/kzygadlo/.env.prod @@ -0,0 +1,18 @@ +APP_NAME="Karol Zygadlo Keating" +ENVIRONMENT=prod +APP_DEBUG=false + +COMPOSE_PROJECT_NAME=keating-prod-kzygadlo +TRAEFIK_ENABLED=true +KEATING_HOST_NAME=karolzygadlo.collegiumwitelona.pl +APP_URL=https://${KEATING_HOST_NAME}/ +TRAEFIK_ROUTER_RULE="Host(`karolzygadlo.collegiumwitelona.pl`)" + +DB_CONNECTION=pgsql +DB_DATABASE=kzygadlo + +CACHE_DRIVER=redis +QUEUE_CONNECTION=redis +SESSION_DRIVER=redis +MAIL_ENCRYPTION=tls + diff --git a/environment/prod/deployment/prod/apps/kzygadlo/.env.prod.secrets b/environment/prod/deployment/prod/apps/kzygadlo/.env.prod.secrets new file mode 100644 index 0000000..a7ecd22 --- /dev/null +++ b/environment/prod/deployment/prod/apps/kzygadlo/.env.prod.secrets @@ -0,0 +1,15 @@ +APP_KEY=ENC[AES256_GCM,data:v/vJIPh7cr/17NmVwF2pmWwsA7ebEYdTxmfMIy50QKdT2FmPO+RJpA1Pxv5/zemnSorE,iv:l218RqcaoM2lf43w91Zun0Bdcs4MEJmCyuegkj3Jgdk=,tag:95/ERa+G7dl06r/FDjAUEA==,type:str] +DB_HOST=ENC[AES256_GCM,data:v6hjAaVH/sItSn2qtWuq+4ZmkN6S,iv:JsmpcLzh+Mz+84yiwMSJaFPpb9Og6jHqua7/rAwFjKw=,tag:YpO4GYkniddiwn/3uPOz/w==,type:str] +DB_PORT=ENC[AES256_GCM,data:G2oW0Q==,iv:pbItCJGZMKkl8dUFYGBq9FUL69STAjp8Gjt43UJrWRg=,tag:foyA58uFbgRF+n9/rDIukg==,type:str] +DB_ROOT_PASSWORD=ENC[AES256_GCM,data:iTFPEz5xz4xoO9t/MPatQhzw1LU=,iv:2cJnPsJVyrfVISqZcN1kcrXG/d5WsgBFD6oFfeoyv4k=,tag:VbsjkdRUsmNjJiywGLohgA==,type:str] +DB_USERNAME=ENC[AES256_GCM,data:6UveT80la/I=,iv:b4Mi80Tc53h43rUShPXU46uJ4a7f+85ViDRddCSXOtg=,tag:GLDInMDTD6PChoWxaCE05g==,type:str] +DB_PASSWORD=ENC[AES256_GCM,data:ZpJSdBKC+cfx0Dertdtfw4zU1qo=,iv:CAz383XJSjB564E0yg/zFLFnwWeEfrXFFsyrvD1Kc8o=,tag:ld4Y1xzQvvw8MlQLn9JnBg==,type:str] +REDIS_HOST=ENC[AES256_GCM,data:pGVvv1E1ru2HRh4ZCa2Lhsn5,iv:7/09+DHsXUa4oSFLoHAU//ZXTho7ubsBtmn6vGfjoX0=,tag:GWywLDwA3y4Y9ZLVjnfFhw==,type:str] +REDIS_PORT=ENC[AES256_GCM,data:52EnXQ==,iv:Mu+6E4+Zkooq0X3Rg2n3Sf3ouWqmlDg5gE+PbzftKIs=,tag:IfS8p+OG3eFusf+uQX9Pyw==,type:str] +SENTRY_LARAVEL_DSN=ENC[AES256_GCM,data:2a8loOHvgxSGhf40nmJ7fN6wqLCFcSJ80J+Q/SXmSePOL23pfrtNIzxIyXNB7N7e79MxramfAQjGk7baK24zs8TKlVaPTnxsgKuWZQsRaEK+sD/YZX+3TsGKIOMruns=,iv:Se3hRsj/Ah+iexgRZNgMmPnfqDQ1YGo8PtnvFeGEL94=,tag:JH6HQad3I1ivdyIUZxDfFQ==,type:str] +sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmOVI2Y2pTOTMrUnZGc2th\ncjUvdHJQR3FmTWRReTZsUXJkSENjWFc1RkZrCm5FVi8yd0ZoNHFleHl0STMzS1NR\ncUpUMklFV05uRjN0SzFYMlZBYktDVlkKLS0tIENQaWl3dGNqVGk1SDRmcGdKMnda\nZWROOUFSUU1uTHRQV1VRQWpGQ1FiSHcKaKj3mx5HSbrOub4UKVCcRttf/FfUcizO\nHhLvj+WLRXQC8oR/Quib/9QIo8NXcHK3JTFCi1lQ7AhniS7KGqeRDQ==\n-----END AGE ENCRYPTED FILE-----\n +sops_age__list_0__map_recipient=age1h8wnpa5lx2vgn2h64lgpeanuct8q2tvzxvn5xmvms7exmwvmu9zq5str5w +sops_lastmodified=2024-10-22T05:39:05Z +sops_mac=ENC[AES256_GCM,data:fzE10nL429L8hQHO9axP6bsFLlKLDs3XgvZuCrmwidEejdwhYDYJHJZrVAWcIgBxkWeHw638T9ehOywktfiJRjwRmqfjp6ltk9gvsZwNErrmYozXhCNiNhNtoPULO9Uk5yjL5enfBckX1N3Wl5Gnqq+S9Zu8ArZHfg92YWnbU1Y=,iv:8V7uhZWqC5S0isly9PYySF7t0/oEEjHD4Tr8RyKor64=,tag:7Y5VS2Flk8z/0W3W4Rk9UA==,type:str] +sops_unencrypted_suffix=_unencrypted +sops_version=3.8.1 diff --git a/environment/prod/deployment/prod/docker-compose.dbprod.yml b/environment/prod/deployment/prod/docker-compose.dbprod.yml new file mode 100644 index 0000000..49497c3 --- /dev/null +++ b/environment/prod/deployment/prod/docker-compose.dbprod.yml @@ -0,0 +1,45 @@ +networks: + traefik-proxy: + external: true + keating-prod: + driver: bridge + name: keating-prod + +volumes: + keating-prod-postgres-data: + name: keating-prod-postgres-data + keating-prod-redis-data: + name: keating-prod-redis-data + +services: + database: + image: postgres:15.5-alpine3.17@sha256:1961f9d61a86948fb3c02ef87a6616f74f3530d10a1cd299b84abba7ed6db791 + container_name: keating-prod-database + environment: + - POSTGRES_PASSWORD=${DB_ROOT_PASSWORD} + - PGDATA=/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready --dbname ${DB_DATABASE} --username ${DB_USERNAME}" ] + interval: 3s + timeout: 3s + retries: 5 + volumes: + - ../../../postgres/init-unaccent.sql:/docker-entrypoint-initdb.d/init-unaccent.sql + - keating-prod-postgres-data:/var/lib/postgresql/data + networks: + - keating-prod + restart: unless-stopped + + redis: + image: redis:7.0.11-alpine3.17@sha256:cbcf5bfbc3eaa232b1fa99e539459f46915a41334d46b54bf894f8837a7f071e + container_name: keating-prod-redis + healthcheck: + test: [ "CMD-SHELL", "redis-cli ping | grep PONG" ] + interval: 3s + timeout: 3s + retries: 5 + volumes: + - keating-prod-redis-data:/data + networks: + - keating-prod + restart: unless-stopped diff --git a/environment/prod/deployment/prod/docker-compose.prod.yml b/environment/prod/deployment/prod/docker-compose.prod.yml new file mode 100644 index 0000000..acd0f7a --- /dev/null +++ b/environment/prod/deployment/prod/docker-compose.prod.yml @@ -0,0 +1,38 @@ +networks: + traefik-proxy: + external: true + keating-prod: + external: true + +services: + keating-prod-app: + image: registry.blumilk.pl/internal-public/keating:prod + container_name: ${COMPOSE_PROJECT_NAME}-app-container + pull_policy: always + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "5" + deploy: + mode: replicated + replicas: 1 + resources: + limits: + memory: 512M + labels: + - "traefik.enable=${TRAEFIK_ENABLED}" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=${TRAEFIK_ROUTER_RULE}" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.entrypoints=websecure" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.tls=true" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.tls.certresolver=lets-encrypt-resolver" + - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.middlewares=response-gzip-compress@file,no-index-robots-response-header@file" + working_dir: /application + volumes: + - ./.env:/application/.env:ro + networks: + - keating-prod + - traefik-proxy + restart: unless-stopped + env_file: + - .deployment diff --git a/environment/prod/deployment/scripts/post-deploy-actions.sh b/environment/prod/deployment/scripts/post-deploy-actions.sh index ec4aa47..683c5a2 100644 --- a/environment/prod/deployment/scripts/post-deploy-actions.sh +++ b/environment/prod/deployment/scripts/post-deploy-actions.sh @@ -6,8 +6,6 @@ set -e ARTISAN_PATH="/application/artisan" php ${ARTISAN_PATH} migrate --force && \ -php ${ARTISAN_PATH} route:cache && \ -php ${ARTISAN_PATH} view:cache && \ -php ${ARTISAN_PATH} event:cache && \ -php ${ARTISAN_PATH} config:cache && \ -php ${ARTISAN_PATH} cache:title:flush +php ${ARTISAN_PATH} storage:link && \ +php ${ARTISAN_PATH} optimize && \ +php ${ARTISAN_PATH} cache:flush diff --git a/production_deployment.md b/production_deployment.md new file mode 100644 index 0000000..8aba783 --- /dev/null +++ b/production_deployment.md @@ -0,0 +1,24 @@ +## Keating +### Production deployment + +Implementing a new Keating app into production + +To add a new Keating app, several steps must be completed: + +1. First you need to create a new database for the new application instance in the existing production container `keating-prod-database`; + 1. `CREATE DATABASE database_name;` + 2. `CREATE USER user_name WITH ENCRYPTED PASSWORD 'password';` + 3. `GRANT ALL PRIVILEGES ON DATABASE database_name TO user_name;` + 4. `\c database_name` + 5. `GRANT CREATE ON SCHEMA public TO user_name;` + 6. `CREATE EXTENSION IF NOT EXISTS unaccent;` +2. Then follow a few steps to prepare the files for application deployment + * add a new option in your workflow to be able to trigger a workflow for a specific Keating app user deployment - (example: pnowak); + * create a new directory in the specified path - `environment/prod/deployment/prod/apps` - the directory must be named like AppName option from workflow (example: pnowak); + * please remember that `env.prod` and `.env.prod.secrets` must be created in the indicated folder (btw. the SOPS_AGE encryption and decryption key is located on Infisical); + * then, to implement a new keating app, trigger the workflow by selecting the app name option that is to be implemented - (example: keating-prod-pnowak-app-container). +3. After a successful first deployment,you can execute the command in the application container: + * ProductionSeeder to run for application installation: `php artisan db:seed --class=ProductionSeeder` + * command `php artisan cache:flush` to flush cached page title and external schedule link + +`btw. if the first deployment has not yet taken place on the target server and there is no container, especially a database one, the first implementation will give a negative result in GHA - after the first start of the deployment, a database container will be created, then we can proceed to the steps contained in the instructions.` diff --git a/readme.md b/readme.md index 164bb5f..6e3d18b 100644 --- a/readme.md +++ b/readme.md @@ -60,3 +60,6 @@ npm run lintf | app | keating-app-dev | [53851](http://localhost:53851) | | database | keating-db-dev | 53853 | | redis | keating-redis-dev | 53852 | + +### Further reading +* [Production deployment](./production_deployment.md)