| -rw-r--r-- | .github/workflows/build-and-deploy-prod.yml | 103 | ||||
| -rw-r--r-- | .github/workflows/build-and-deploy-stage.yml | 104 | ||||
| -rw-r--r-- | Dockerfile | 6 | ||||
| -rw-r--r-- | Jenkinsfile | 281 | ||||
| -rw-r--r-- | deploy/playbook.yml | 8 |
5 files changed, 239 insertions, 263 deletions
diff --git a/.github/workflows/build-and-deploy-prod.yml b/.github/workflows/build-and-deploy-prod.yml new file mode 100644 index 0000000..fc44571 --- /dev/null +++ b/.github/workflows/build-and-deploy-prod.yml @@ -0,0 +1,103 @@ +name: Docker image building and publishing onto Production +on: + push: + tags: + - "*" +jobs: + build-and-push: + if: github.repository == 'SayaAndy/saya-today-web' + runs-on: ubuntu-latest + environment: Production + permissions: + packages: write + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Generate output.css with tailwindcss + uses: ZoeyVid/tailwindcss-update@main + with: + input: static/input.css + output: static/output.css + params: "--minify" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GHCR_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v7 + with: + context: . + file: ./Dockerfile + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + tags: | + ghcr.io/sayaandy/saya-today-web:latest + ghcr.io/sayaandy/saya-today-web:stable + ghcr.io/sayaandy/saya-today-web:${{ github.ref_name }} + + deploy: + if: github.repository == 'SayaAndy/saya-today-web' + runs-on: ubuntu-latest + environment: Production + needs: [build-and-push] + container: + image: ghcr.io/ansible/community-ansible-dev-tools:v26.4.6 + options: --user root + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install community.docker collection + run: ansible-galaxy collection install community.docker + + - name: Set up SSH connection for uz.saya.casa + run: | + mkdir -p ~/.ssh + (cat <<EOF + ${{ secrets.SVC_GITHUB_PK }} + EOF + ) > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + sed -i 's/\r$//' ~/.ssh/id_ed25519 + ssh-keyscan -H uz.saya.casa >> ~/.ssh/known_hosts + + - name: Test SSH connection + run: | + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 svc_github@uz.saya.casa "echo 'SSH connection successful'" + + - name: Run Ansible playbook + env: + ANSIBLE_HOST_KEY_CHECKING: "False" + working-directory: ./deploy + run: | + ansible-playbook \ + playbook.yml \ + -i inventory.yml \ + -l prod \ + --private-key ~/.ssh/id_ed25519 \ + -u svc_github \ + -e saya_today_web_auth_salt="${{ secrets.AUTH_SALT }}" \ + -e saya_today_web_s3_access_key_id="${{ secrets.S3_ACCESS_KEY_ID }}" \ + -e saya_today_web_s3_secret_access_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" \ + -e saya_today_web_environment=prod \ + -e saya_today_web_tag=${{ github.ref_name }} \ + -e saya_today_web_mail_salt="${{ secrets.MAIL_SALT }}" \ + -e saya_today_web_mail_host="${{ secrets.MAIL_HOST }}" \ + -e saya_today_web_mail_address="${{ secrets.MAIL_ADDRESS }}" \ + -e saya_today_web_mail_username="${{ secrets.MAIL_USERNAME }}" \ + -e saya_today_web_mail_password="${{ secrets.MAIL_PASSWORD }}" \ + -e saya_today_google_site_verification="${{ secrets.GOOGLE_SITE_VERIFICATION }}" \ + -e saya_today_yandex_verification="${{ secrets.YANDEX_VERIFICATION }}" \ + -e saya_today_bing_verification="${{ secrets.BING_VERIFICATION }}" diff --git a/.github/workflows/build-and-deploy-stage.yml b/.github/workflows/build-and-deploy-stage.yml new file mode 100644 index 0000000..ce4a1fb --- /dev/null +++ b/.github/workflows/build-and-deploy-stage.yml @@ -0,0 +1,104 @@ +name: Docker image building and publishing onto Stage +on: + push: + branches: [stage] +jobs: + build-and-push: + if: github.repository == 'SayaAndy/saya-today-web' + runs-on: ubuntu-latest + environment: Stage + permissions: + packages: write + outputs: + sha_short: ${{ steps.ghss_vars.outputs.sha_short }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Generate output.css with tailwindcss + uses: ZoeyVid/tailwindcss-update@main + with: + input: static/input.css + output: static/output.css + params: "--minify" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GHCR_TOKEN }} + + - name: Set github short sha + id: ghss_vars + run: echo "sha_short=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v7 + with: + context: . + file: ./Dockerfile + push: true + cache-from: type=gha + cache-to: type=gha,mode=max + tags: | + ghcr.io/sayaandy/saya-today-web:latest + ghcr.io/sayaandy/saya-today-web:commit-${{ steps.ghss_vars.outputs.sha_short }} + + deploy: + if: github.repository == 'SayaAndy/saya-today-web' + runs-on: ubuntu-latest + environment: Stage + needs: [build-and-push] + container: + image: ghcr.io/ansible/community-ansible-dev-tools:v26.4.6 + options: --user root + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install community.docker collection + run: ansible-galaxy collection install community.docker + + - name: Set up SSH connection for uz.saya.casa + run: | + mkdir -p ~/.ssh + (cat <<EOF + ${{ secrets.SVC_GITHUB_PK }} + EOF + ) > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + sed -i 's/\r$//' ~/.ssh/id_ed25519 + ssh-keyscan -H uz.saya.casa >> ~/.ssh/known_hosts + + - name: Test SSH connection + run: | + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ~/.ssh/id_ed25519 svc_github@uz.saya.casa "echo 'SSH connection successful'" + + - name: Run Ansible playbook + env: + ANSIBLE_HOST_KEY_CHECKING: "False" + working-directory: ./deploy + run: | + ansible-playbook \ + playbook.yml \ + -i inventory.yml \ + -l stage \ + --private-key ~/.ssh/id_ed25519 \ + -u svc_github \ + -e saya_today_web_auth_salt="${{ secrets.AUTH_SALT }}" \ + -e saya_today_web_s3_access_key_id="${{ secrets.S3_ACCESS_KEY_ID }}" \ + -e saya_today_web_s3_secret_access_key="${{ secrets.S3_SECRET_ACCESS_KEY }}" \ + -e saya_today_web_environment=stage \ + -e saya_today_web_tag=commit-${{ needs.build-and-push.outputs.sha_short }} \ + -e saya_today_web_mail_salt="${{ secrets.MAIL_SALT }}" \ + -e saya_today_web_mail_host="${{ secrets.MAIL_HOST }}" \ + -e saya_today_web_mail_address="${{ secrets.MAIL_ADDRESS }}" \ + -e saya_today_web_mail_username="${{ secrets.MAIL_USERNAME }}" \ + -e saya_today_web_mail_password="${{ secrets.MAIL_PASSWORD }}" @@ -1,9 +1,7 @@ FROM golang:1.26.4-alpine3.24 AS build-stage -ARG TARGETOS=linux -ARG TARGETARCH=amd64 -ENV GOOS=${TARGETOS} -ENV GOARCH=${TARGETARCH} +ENV GOOS=linux +ENV GOARCH=amd64 RUN apk add --no-cache sqlite-dev musl-dev gcc diff --git a/Jenkinsfile b/Jenkinsfile index 8633145..516b8c0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,13 +13,13 @@ pipeline { checkout scm sh ''' npm install tailwindcss @tailwindcss/cli @tailwindcss/forms - npx tailwindcss -i static/input.css -o static/output.css --minify + npx tailwindcss -i static/input.css -o static/output.css --watch ''' stash name: 'tailwindcss-output', includes: 'static/output.css' } } - stage('Matrix Build') { + stage('Build') { matrix { axes { axis { @@ -31,92 +31,23 @@ pipeline { values 'arm64', 'amd64' } } - stages { - stage('Build') { - agent { - kubernetes { - defaultContainer 'kaniko' - yaml """ -apiVersion: v1 -kind: Pod -metadata: - namespace: jenkins -spec: - nodeSelector: - kubernetes.io/arch: ${GOARCH} - containers: - - name: kaniko - image: gcr.io/kaniko-project/executor:v1.24.0-debug - imagePullPolicy: Always - command: [ /busybox/cat ] - tty: true - resources: - requests: - cpu: "1" - memory: 2Gi - ephemeral-storage: 4Gi - limits: - memory: 4Gi - ephemeral-storage: 8Gi -""" - } - } - environment { - IMAGE_PUSH_DESTINATION="registry.sayag.it/sayauz/web" - } - steps { - checkout scm - unstash 'tailwindcss-output' - container(name: 'kaniko', shell: '/busybox/sh') { - withCredentials([string(credentialsId: 'registry-sayagit-jenkins-password', variable: 'REGISTRY_SAYAGIT_JENKINS_PASSWORD')]) { - withEnv(['PATH+EXTRA=/busybox']) { - script { - env.GIT_COMMIT_SHORT = env.GIT_COMMIT ? env.GIT_COMMIT.take(7) : "unknown" - try { - sh '''#!/busybox/sh - mkdir -p /kaniko/.docker - printf '{"auths":{"registry.sayag.it":{"username":"jenkins","password":"%s"}}}' "$REGISTRY_SAYAGIT_JENKINS_PASSWORD" > /kaniko/.docker/config.json - /kaniko/executor --context `pwd` \ - --custom-platform=linux/${GOARCH} \ - --build-arg TARGETOS=linux \ - --build-arg TARGETARCH=${GOARCH} \ - --destination $IMAGE_PUSH_DESTINATION:commit-$GIT_COMMIT_SHORT-$GOARCH - ''' - } catch (Exception e) { - echo "Caught exception: ${e.getMessage()}" - currentBuild.result = 'FAILURE' - throw e - } - } - } - } - } - } - } - } } - } - stage('Stitch Manifest') { agent { kubernetes { - defaultContainer 'manifest-tool' + defaultContainer 'kaniko' yaml """ apiVersion: v1 kind: Pod metadata: + name: kaniko-sayauz-web namespace: jenkins spec: containers: - - name: manifest-tool - image: mplatform/manifest-tool:alpine-v2.2.2 + - name: kaniko + image: gcr.io/kaniko-project/executor:v1.24.0-debug imagePullPolicy: Always - command: [ 'cat' ] + command: [ /busybox/cat ] tty: true - resources: - requests: - cpu: "1" - memory: 2Gi - ephemeral-storage: 4Gi """ } } @@ -124,186 +55,26 @@ spec: IMAGE_PUSH_DESTINATION="registry.sayag.it/sayauz/web" } steps { - script { - env.GIT_COMMIT_SHORT = env.GIT_COMMIT ? env.GIT_COMMIT.take(7) : "unknown" - } - withCredentials([string(credentialsId: 'registry-sayagit-jenkins-password', variable: 'REGISTRY_SAYAGIT_JENKINS_PASSWORD')]) { - sh '''mkdir -p /root/.docker - printf '{"auths":{"registry.sayag.it":{"username":"jenkins","password":"%s"}}}' "$REGISTRY_SAYAGIT_JENKINS_PASSWORD" > /root/.docker/config.json - - manifest-tool push from-args \ - --platforms linux/amd64,linux/arm64 \ - --template $IMAGE_PUSH_DESTINATION:commit-$GIT_COMMIT_SHORT-ARCH \ - --target $IMAGE_PUSH_DESTINATION:commit-$GIT_COMMIT_SHORT - manifest-tool push from-args \ - --platforms linux/amd64,linux/arm64 \ - --template $IMAGE_PUSH_DESTINATION:commit-$GIT_COMMIT_SHORT-ARCH \ - --target $IMAGE_PUSH_DESTINATION:latest - if [ "$TAG_NAME" != "" ]; then - manifest-tool push from-args \ - --platforms linux/amd64,linux/arm64 \ - --template $IMAGE_PUSH_DESTINATION:commit-$GIT_COMMIT_SHORT-ARCH \ - --target $IMAGE_PUSH_DESTINATION:stable - manifest-tool push from-args \ - --platforms linux/amd64,linux/arm64 \ - --template $IMAGE_PUSH_DESTINATION:commit-$GIT_COMMIT_SHORT-ARCH \ - --target $IMAGE_PUSH_DESTINATION:$TAG_NAME - fi - ''' - } - } - } - stage('Deploy (Stage)') { - when { branch 'stage' } - agent { - kubernetes { - defaultContainer 'ansible' - yaml """ -apiVersion: v1 -kind: Pod -metadata: - namespace: jenkins -spec: - containers: - - name: ansible - image: ghcr.io/ansible/community-ansible-dev-tools:v26.7.2 - command: [ 'cat' ] - tty: true - resources: - requests: - cpu: "500m" - memory: 1Gi -""" - } - } - steps { - checkout scm - container('ansible') { - script { - env.GIT_COMMIT_SHORT = env.GIT_COMMIT ? env.GIT_COMMIT.take(7) : "unknown" - - def secretVars = [ - 'sayauz-web-stage-auth-salt' : 'AUTH_SALT', - 'sayauz-web-stage-s3-access-key-id' : 'S3_ACCESS_KEY_ID', - 'sayauz-web-stage-s3-secret-access-key' : 'S3_SECRET_ACCESS_KEY', - 'sayauz-web-stage-mail-salt' : 'MAIL_SALT', - 'sayauz-web-stage-mail-host' : 'MAIL_HOST', - 'sayauz-web-stage-mail-address' : 'MAIL_ADDRESS', - 'sayauz-web-stage-mail-username' : 'MAIL_USERNAME', - 'sayauz-web-stage-mail-password' : 'MAIL_PASSWORD' - ] - def bindings = secretVars.collect { id, varName -> string(credentialsId: id, variable: varName) } - bindings << sshUserPrivateKey(credentialsId: 'sayauz-svc-github', keyFileVariable: 'SSH_KEY_FILE', usernameVariable: 'SSH_USER') - - withCredentials(bindings) { - sh ''' - ansible-galaxy collection install community.docker - - mkdir -p ~/.ssh - cp "$SSH_KEY_FILE" ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -H uz.saya.casa >> ~/.ssh/known_hosts - - ssh -o ConnectTimeout=10 -i ~/.ssh/id_ed25519 "$SSH_USER@uz.saya.casa" "echo 'SSH connection successful'" - - cd deploy - ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook \ - playbook.yml \ - -i inventory.yml \ - -l stage \ - --private-key ~/.ssh/id_ed25519 \ - -u "$SSH_USER" \ - -e saya_today_web_auth_salt="$AUTH_SALT" \ - -e saya_today_web_s3_access_key_id="$S3_ACCESS_KEY_ID" \ - -e saya_today_web_s3_secret_access_key="$S3_SECRET_ACCESS_KEY" \ - -e saya_today_web_environment=stage \ - -e saya_today_web_tag=commit-$GIT_COMMIT_SHORT \ - -e saya_today_web_mail_salt="$MAIL_SALT" \ - -e saya_today_web_mail_host="$MAIL_HOST" \ - -e saya_today_web_mail_address="$MAIL_ADDRESS" \ - -e saya_today_web_mail_username="$MAIL_USERNAME" \ - -e saya_today_web_mail_password="$MAIL_PASSWORD" - ''' - } - } - } - } - } - stage('Deploy (Prod)') { - when { buildingTag() } - agent { - kubernetes { - defaultContainer 'ansible' - yaml """ -apiVersion: v1 -kind: Pod -metadata: - namespace: jenkins -spec: - containers: - - name: ansible - image: ghcr.io/ansible/community-ansible-dev-tools:v26.7.2 - command: [ 'cat' ] - tty: true - resources: - requests: - cpu: "500m" - memory: 1Gi -""" - } - } - steps { checkout scm - container('ansible') { - script { - def secretVars = [ - 'sayauz-web-prod-auth-salt' : 'AUTH_SALT', - 'sayauz-web-prod-s3-access-key-id' : 'S3_ACCESS_KEY_ID', - 'sayauz-web-prod-s3-secret-access-key' : 'S3_SECRET_ACCESS_KEY', - 'sayauz-web-prod-mail-salt' : 'MAIL_SALT', - 'sayauz-web-prod-mail-host' : 'MAIL_HOST', - 'sayauz-web-prod-mail-address' : 'MAIL_ADDRESS', - 'sayauz-web-prod-mail-username' : 'MAIL_USERNAME', - 'sayauz-web-prod-mail-password' : 'MAIL_PASSWORD', - 'sayauz-web-prod-verification-google' : 'GOOGLE_VERIFICATION', - 'sayauz-web-prod-verification-yandex' : 'YANDEX_VERIFICATION', - 'sayauz-web-prod-verification-bing' : 'BING_VERIFICATION' - ] - def bindings = secretVars.collect { id, varName -> string(credentialsId: id, variable: varName) } - bindings << sshUserPrivateKey(credentialsId: 'sayauz-svc-github', keyFileVariable: 'SSH_KEY_FILE', usernameVariable: 'SSH_USER') - - withCredentials(bindings) { - sh ''' - ansible-galaxy collection install community.docker - - mkdir -p ~/.ssh - cp "$SSH_KEY_FILE" ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan -H uz.saya.casa >> ~/.ssh/known_hosts - - ssh -o ConnectTimeout=10 -i ~/.ssh/id_ed25519 "$SSH_USER@uz.saya.casa" "echo 'SSH connection successful'" - - cd deploy - ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook \ - playbook.yml \ - -i inventory.yml \ - -l prod \ - --private-key ~/.ssh/id_ed25519 \ - -u "$SSH_USER" \ - -e saya_today_web_auth_salt="$AUTH_SALT" \ - -e saya_today_web_s3_access_key_id="$S3_ACCESS_KEY_ID" \ - -e saya_today_web_s3_secret_access_key="$S3_SECRET_ACCESS_KEY" \ - -e saya_today_web_environment=prod \ - -e saya_today_web_tag=$TAG_NAME \ - -e saya_today_web_mail_salt="$MAIL_SALT" \ - -e saya_today_web_mail_host="$MAIL_HOST" \ - -e saya_today_web_mail_address="$MAIL_ADDRESS" \ - -e saya_today_web_mail_username="$MAIL_USERNAME" \ - -e saya_today_web_mail_password="$MAIL_PASSWORD" \ - -e saya_today_google_verification="$GOOGLE_VERIFICATION" \ - -e saya_today_yandex_verification="$YANDEX_VERIFICATION" \ - -e saya_today_bing_verification="$BING_VERIFICATION" - ''' + unstash 'tailwindcss-output' + container(name: 'kaniko', shell: '/busybox/sh') { + withCredentials([file(credentialsId: 'registry-sayagit-jenkins-password', variable: 'REGISTRY_SAYAGIT_JENKINS_PASSWORD')]) { + withEnv(['PATH+EXTRA=/busybox']) { + try { + sh '''#!/busybox/sh + mkdir -p /kaniko/.docker + echo "{\"auths\":{\"https://registry.sayag.it\":{\"username\":\"jenkins\",\"password\":\"$REGISTRY_SAYAGIT_JENKINS_PASSWORD\"}}}" > /kaniko/.docker/config.json + pushToTags="$IMAGE_PUSH_DESTINATION:latest $IMAGE_PUSH_DESTINATION:commit-`git rev-parse --short HEAD`" + if [ "$TAG_NAME" != "" ]; then + pushToTags="$pushToTags $IMAGE_PUSH_DESTINATION:stable $IMAGE_PUSH_DESTINATION:$TAG_NAME" + fi + /kaniko/executor --context `pwd` --custom-platform=${GOOS}/${GOARCH} --destination $pushToTags + ''' + } catch (Exception e) { + echo "Caught exception: ${e.getMessage()}" + currentBuild.result = 'FAILURE' + throw e + } } } } diff --git a/deploy/playbook.yml b/deploy/playbook.yml index 057cc19..f725335 100644 --- a/deploy/playbook.yml +++ b/deploy/playbook.yml @@ -6,7 +6,7 @@ vars: docker_volumes: - "{{ saya_today_web_name }}-volume:/data:rw" - + tasks: - name: Initialize Unix socket volume when: >- @@ -26,11 +26,11 @@ community.docker.docker_volume: name: "{{ saya_today_web_name }}-volume" - - name: Deploy sayauz/web Docker Container + - name: Deploy saya-today-web Docker Container community.docker.docker_container: name: "{{ saya_today_web_name }}" hostname: "{{ saya_today_web_hostname }}" - image: "registry.sayag.it/sayauz/web:{{ saya_today_web_tag }}" + image: "ghcr.io/sayaandy/saya-today-web:{{ saya_today_web_tag }}" env: S3_ACCESS_KEY_ID: "{{ saya_today_web_s3_access_key_id }}" S3_SECRET_ACCESS_KEY: "{{ saya_today_web_s3_secret_access_key }}" @@ -42,7 +42,7 @@ MAIL_PASSWORD: "{{ saya_today_web_mail_password }}" MAIL_SALT: "{{ saya_today_web_mail_salt }}" FQDN: "{{ saya_today_web_listen_address }}" - GOOGLE_VERIFICATION: "{{ saya_today_google_verification | default('') }}" + GOOGLE_SITE_VERIFICATION: "{{ saya_today_google_site_verification | default('') }}" YANDEX_VERIFICATION: "{{ saya_today_yandex_verification | default('') }}" BING_VERIFICATION: "{{ saya_today_bing_verification | default('') }}" network_mode: caddy |