diff --git a/.github/workflows/job-deploy.yml b/.github/workflows/job-deploy.yml index b8f436c..8719216 100644 --- a/.github/workflows/job-deploy.yml +++ b/.github/workflows/job-deploy.yml @@ -7,8 +7,10 @@ on: required: true SSH_CONFIG: required: true + COMPOSER_AUTH: + required: true inputs: - SSH_ALIAS: + SSH_HOST_ALIAS: required: true type: string DEPLOY_PATH_DESTINATION: @@ -29,21 +31,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up SSH key - run: | - mkdir -p ~/.ssh/ - echo '${{ secrets.SSH_KEY }}' > ~/.ssh/id_rsa - chmod 400 ~/.ssh/id_rsa - echo '${{ secrets.SSH_CONFIG }}' > ~/.ssh/config - - name: Prepare .env run: | bash ./sh/env/secret-gen.sh + echo "COMPOSER_AUTH=${{ secrets.COMPOSER_AUTH }}" >> ./config/environment/.env.secret bash ./sh/env/init.sh ${{ inputs.ENVIRONMENT_TYPE }} - name: Install Composer and Node Dependencies run: | - source ./.env export CURRENT_UID=$(id -u) export CURRENT_GID=$(id -g) bash ./sh/install.sh yes @@ -76,7 +71,7 @@ jobs: - name: Deploy via SSH run: | echo "Deploying to ${{ inputs.DEPLOY_PATH_DESTINATION }}" - ssh ${{ inputs.SSH_ALIAS }} mkdir -p ${{ inputs.DEPLOY_PATH_DESTINATION }} + ssh ${{ inputs.SSH_HOST_ALIAS }} mkdir -p ${{ inputs.DEPLOY_PATH_DESTINATION }} rsync -og \ --chmod=Dug=rwx,Fug=rw \ --checksum \ @@ -94,12 +89,12 @@ jobs: --exclude ".env" \ --exclude ".env.*override" \ --exclude ".env.*secret" \ - --exclude "config/ssl/*.pem" \ - --exclude "web/wp-content/cache" \ + --exclude "config/ssl/*/" \ --exclude "web/wp-content/languages" \ --exclude "web/wp-content/uploads" \ - ./ ${{ inputs.SSH_ALIAS }}:${{ inputs.DEPLOY_PATH_DESTINATION }} - ssh ${{ inputs.SSH_ALIAS }} " \ + --include "web/wp-content/cache" \ + ./ ${{ inputs.SSH_HOST_ALIAS }}:${{ inputs.DEPLOY_PATH_DESTINATION }} + ssh ${{ inputs.SSH_HOST_ALIAS }} " \ cd ${{ inputs.DEPLOY_PATH_DESTINATION }} && \ make secret && \ make recreate ${{ inputs.ENVIRONMENT_TYPE }}" diff --git a/.github/workflows/workflow-develop-deploy.yml b/.github/workflows/workflow-deploy-develop.yml similarity index 67% rename from .github/workflows/workflow-develop-deploy.yml rename to .github/workflows/workflow-deploy-develop.yml index c9ae2f3..60c91d1 100644 --- a/.github/workflows/workflow-develop-deploy.yml +++ b/.github/workflows/workflow-deploy-develop.yml @@ -1,4 +1,4 @@ -name: Develop Deploy +name: Deploy to Develop on: push: @@ -11,9 +11,10 @@ jobs: uses: ./.github/workflows/job-deploy.yml secrets: SSH_KEY: ${{ secrets.SSH_KEY }} - SSH_CONFIG: ${{ secrets.SSH_CONFIG_DEV }} + SSH_CONFIG: ${{ secrets.SSH_CONFIG }} + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} with: - SSH_ALIAS: ssh_alias + SSH_HOST_ALIAS: develop.starter-kit.io DEPLOY_PATH_DESTINATION: /srv/develop.starter-kit.io DEPLOYMENT_NAME: "StarterKit push to develop" ENVIRONMENT_TYPE: dev diff --git a/.github/workflows/workflow-production-deploy.yml b/.github/workflows/workflow-deploy-production.yml similarity index 65% rename from .github/workflows/workflow-production-deploy.yml rename to .github/workflows/workflow-deploy-production.yml index e1ffa89..b09a0f4 100644 --- a/.github/workflows/workflow-production-deploy.yml +++ b/.github/workflows/workflow-deploy-production.yml @@ -1,4 +1,4 @@ -name: Production Deploy +name: Deploy to Production on: workflow_dispatch: {} @@ -8,9 +8,10 @@ jobs: uses: ./.github/workflows/job-deploy.yml secrets: SSH_KEY: ${{ secrets.SSH_KEY }} - SSH_CONFIG: ${{ secrets.SSH_CONFIG_PROD }} + SSH_CONFIG: ${{ secrets.SSH_CONFIG }} + COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} with: - SSH_ALIAS: ssh_alias + SSH_HOST_ALIAS: starter-kit.io DEPLOY_PATH_DESTINATION: /srv/starter-kit.io DEPLOYMENT_NAME: "StarterKit push to production" ENVIRONMENT_TYPE: prod diff --git a/README.MD b/README.MD index 4999c4e..ce1ff3f 100644 --- a/README.MD +++ b/README.MD @@ -26,7 +26,7 @@ ## Creating new project -1. Check your GitHub SSH connection `ssh -T git@github.com` +1. Check your GitHub SSH connection `ssh -T git@github.com` or use [Personal Access Token](#cicd-deployments) in `.env.secret` `COMPOSER_AUTH` 2. Create a new project directory and clone this repository into the project folder. @@ -315,24 +315,39 @@ Use GitHub Actions, GitLab CI/CD or other pipelines. 3. Add secrets variables to repo options: - `SSH_KEY` - Private key from deploy pair that used for servers access -- `SSH_CONFIG_DEV` - SSH config for develop server with address, port, user, etc. See the example -- `SSH_CONFIG_STAGE` - Same for staging -- `SSH_CONFIG_PROD` - SSH config for production server +- `SSH_CONFIG` - SSH config for servers with address, port, user, etc. See the example +- `COMPOSER_AUTH` - [Composer authentication](https://getcomposer.org/doc/articles/authentication-for-private-packages.md) JSON object with Personal Access Token, see [Managing your personal access tokens on GitHub](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) +and [Personal access tokens on GitLab](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html). For local usage in `.env.secret` file use a serialized **unescaped** JSON object without spaces, for GitHub secrets use **escaped** JSON object without spaces. SSH config example: ```conf +# SSH_CONFIG Host * IdentitiesOnly yes StrictHostKeyChecking no -Host ssh_alias - HostName starter-kit.io +# Develop server ssh alias +Host develop.starter-kit.io + HostName 00.00.00.00 User serverusername Port 22 -Host github.com - HostName github.com - User git +# Prod server ssh alias +Host starter-kit.io + HostName 00.00.00.00 + User serverusername + Port 22 + +``` + +COMPOSER_AUTH example for GitHub secrets: +```bash +{\"github-oauth\":{\"github.com\":\"ACCESS_TOKEN_GITHUB\"}} +``` + +COMPOSER_AUTH example for local usage: +```bash +{"github-oauth":{"github.com":"ACCESS_TOKEN_GITHUB"}} ``` 4. Check CI/CD jobs config file, use `./.github` for GitHub Actions diff --git a/dockerfiles/composer/Dockerfile b/dockerfiles/composer/Dockerfile index 503cee8..4988af2 100644 --- a/dockerfiles/composer/Dockerfile +++ b/dockerfiles/composer/Dockerfile @@ -26,4 +26,10 @@ RUN set -eux ; \ $([ "$(apk --print-arch)" != "x86" ] && echo mercurial) \ $([ "$(apk --print-arch)" != "armhf" ] && echo p7zip) +################################## +# Prepare entrypoint # +################################## +COPY ./docker-entrypoint.d/* /docker-entrypoint.d +RUN chmod +x /docker-entrypoint.d/*.sh + CMD ["composer"] diff --git a/dockerfiles/composer/docker-entrypoint.d/30-composer-config.sh b/dockerfiles/composer/docker-entrypoint.d/30-composer-config.sh new file mode 100644 index 0000000..c40303c --- /dev/null +++ b/dockerfiles/composer/docker-entrypoint.d/30-composer-config.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +# Current file +ME=$(basename "$0") + +entrypoint_log() { + if [ -z "${PHP_ENTRYPOINT_QUIET_LOGS:-}" ]; then + echo "$@" + fi +} + +# Using COMPOSER_AUTH JSON object for Composer authentication +if [ ! -z "${COMPOSER_AUTH:-}" ]; then + entrypoint_log "$ME: Used COMPOSER_AUTH JSON object for Composer authentication" +fi diff --git a/dockerfiles/php/Dockerfile b/dockerfiles/php/Dockerfile index 3c975a1..6c56ae3 100644 --- a/dockerfiles/php/Dockerfile +++ b/dockerfiles/php/Dockerfile @@ -97,8 +97,13 @@ RUN mkdir -p /var/log/wordpress ################################## # Prepare and run entrypoint # ################################## -COPY docker-entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/docker-entrypoint.sh +RUN mkdir /docker-entrypoint.d -ENTRYPOINT ["docker-entrypoint.sh"] +COPY docker-entrypoint.sh / +COPY ./docker-entrypoint.d/* /docker-entrypoint.d + +RUN chmod +x /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.d/*.sh + +ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["php-fpm"] diff --git a/dockerfiles/php/docker-entrypoint.d/10-update-user.sh b/dockerfiles/php/docker-entrypoint.d/10-update-user.sh new file mode 100644 index 0000000..fc26df0 --- /dev/null +++ b/dockerfiles/php/docker-entrypoint.d/10-update-user.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +# Current file +ME=$(basename "$0") + +entrypoint_log() { + if [ -z "${PHP_ENTRYPOINT_QUIET_LOGS:-}" ]; then + echo "$@" + fi +} + +# Recreate www-data user +# Fix www-data UID from 82 to ${CURRENT_UID} (Permission denied error) +# Deleting default user (with group) +deluser www-data +# 82 is the standard uid/gid for "www-data" in Alpine +# https://git.alpinelinux.org/aports/tree/main/apache2/apache2.pre-install?h=3.14-stable +# https://git.alpinelinux.org/aports/tree/main/lighttpd/lighttpd.pre-install?h=3.14-stable +# https://git.alpinelinux.org/aports/tree/main/nginx/nginx.pre-install?h=3.14-stable + +addgroup -g "${CURRENT_GID}" "${DEFAULT_USER}" +adduser -u "${CURRENT_UID}" -D -G "${DEFAULT_USER}" "${DEFAULT_USER}" +chown "${DEFAULT_USER}":"${DEFAULT_USER}" /var/log/wordpress + +echo "${DEFAULT_USER} user UID=${CURRENT_UID} updated" diff --git a/dockerfiles/php/docker-entrypoint.d/20-prepare-configs.sh b/dockerfiles/php/docker-entrypoint.d/20-prepare-configs.sh new file mode 100644 index 0000000..a6e300a --- /dev/null +++ b/dockerfiles/php/docker-entrypoint.d/20-prepare-configs.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +# Current file +ME=$(basename "$0") + +entrypoint_log() { + if [ -z "${PHP_ENTRYPOINT_QUIET_LOGS:-}" ]; then + echo "$@" + fi +} + +replace_env_vars() { + local template_dir="$1" + local output_dir="$2" + local suffix="${PHP_ENVSUBST_TEMPLATE_SUFFIX:-.template}" + local filter="${PHP_ENVSUBST_FILTER:-}" + + local template defined_envs relative_path output_path subdir + defined_envs=$(printf '${%s} ' $(awk "END { for (name in ENVIRON) { print ( name ~ /${filter}/ ) ? name : \"\" } }" < /dev/null )) + [ -d "$template_dir" ] || return 0 + if [ ! -w "$output_dir" ]; then + entrypoint_log "$ME: ERROR: $template_dir exists, but $output_dir is not writable" + return 0 + fi + find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do + relative_path="${template#"$template_dir/"}" + output_path="$output_dir/${relative_path%"$suffix"}" + subdir=$(dirname "$relative_path") + # create a subdirectory where the template file exists + mkdir -p "$output_dir/$subdir" + entrypoint_log "$ME: Running envsubst on $template to $output_path" + envsubst "$defined_envs" < "$template" > "$output_path" + done +} + +# Replace env variables with values in sSMTP config using gettext app +replace_env_vars "/etc/ssmtp/templates" "/etc/ssmtp" diff --git a/dockerfiles/php/docker-entrypoint.sh b/dockerfiles/php/docker-entrypoint.sh index 0a6f1c6..e84adaf 100644 --- a/dockerfiles/php/docker-entrypoint.sh +++ b/dockerfiles/php/docker-entrypoint.sh @@ -2,6 +2,7 @@ set -Eeuo pipefail +# Current file ME=$(basename "$0") entrypoint_log() { @@ -10,48 +11,38 @@ entrypoint_log() { fi } -replace_env_vars() { - local template_dir="$1" - local output_dir="$2" - local suffix="${PHP_ENVSUBST_TEMPLATE_SUFFIX:-.template}" - local filter="${PHP_ENVSUBST_FILTER:-}" - - local template defined_envs relative_path output_path subdir - defined_envs=$(printf '${%s} ' $(awk "END { for (name in ENVIRON) { print ( name ~ /${filter}/ ) ? name : \"\" } }" < /dev/null )) - [ -d "$template_dir" ] || return 0 - if [ ! -w "$output_dir" ]; then - entrypoint_log "$ME: ERROR: $template_dir exists, but $output_dir is not writable" - return 0 - fi - find "$template_dir" -follow -type f -name "*$suffix" -print | while read -r template; do - relative_path="${template#"$template_dir/"}" - output_path="$output_dir/${relative_path%"$suffix"}" - subdir=$(dirname "$relative_path") - # create a subdirectory where the template file exists - mkdir -p "$output_dir/$subdir" - entrypoint_log "$ME: Running envsubst on $template to $output_path" - envsubst "$defined_envs" < "$template" > "$output_path" - done -} - -# Recreate www-data user -# Fix www-data UID from 82 to ${CURRENT_UID} (Permission denied error) -# Deleting default user (with group) -deluser www-data -# 82 is the standard uid/gid for "www-data" in Alpine -# https://git.alpinelinux.org/aports/tree/main/apache2/apache2.pre-install?h=3.14-stable -# https://git.alpinelinux.org/aports/tree/main/lighttpd/lighttpd.pre-install?h=3.14-stable -# https://git.alpinelinux.org/aports/tree/main/nginx/nginx.pre-install?h=3.14-stable - -addgroup -g "${CURRENT_GID}" "${DEFAULT_USER}" -adduser -u "${CURRENT_UID}" -D -G "${DEFAULT_USER}" "${DEFAULT_USER}" -chown "${DEFAULT_USER}":"${DEFAULT_USER}" /var/log/wordpress - -echo "${DEFAULT_USER} user UID=${CURRENT_UID} updated" - -# Replace env variables with values in sSMTP config using gettext app -replace_env_vars "/etc/ssmtp/templates" "/etc/ssmtp" - +if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then + entrypoint_log "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration" + + entrypoint_log "$0: Looking for shell scripts in /docker-entrypoint.d/" + find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do + case "$f" in + *.envsh) + if [ -x "$f" ]; then + entrypoint_log "$0: Sourcing $f"; + . "$f" + else + # warn on shell scripts without exec bit + entrypoint_log "$0: Ignoring $f, not executable"; + fi + ;; + *.sh) + if [ -x "$f" ]; then + entrypoint_log "$0: Launching $f"; + "$f" + else + # warn on shell scripts without exec bit + entrypoint_log "$0: Ignoring $f, not executable"; + fi + ;; + *) entrypoint_log "$0: Ignoring $f";; + esac + done + + entrypoint_log "$0: Configuration complete; ready for start up" +else + entrypoint_log "$0: No files found in /docker-entrypoint.d/, skipping configuration" +fi ## exec php-fpm (added as parameter in Dockerfile CMD ["php-fpm"]) exec "$@" diff --git a/iac/terraform/instances.tf b/iac/terraform/instances.tf index fc7ce97..edd6bcc 100644 --- a/iac/terraform/instances.tf +++ b/iac/terraform/instances.tf @@ -59,3 +59,11 @@ output "develop_ip_addr" { /*output "prod_ip_addr" { value = aws_instance.production-server.public_ip }*/ + +/** + * If IP address was renew, follow this steps: + * 1. Update DNS for selected domains + * 2. Update SSH config in git deploy variables + * 3. Update local SSH config + * 4. Update Ansible inventory if needed +**/ diff --git a/iac/terraform/provider.tf b/iac/terraform/provider.tf index 21e6ef2..b2f017e 100644 --- a/iac/terraform/provider.tf +++ b/iac/terraform/provider.tf @@ -23,6 +23,6 @@ provider "aws" { resource "aws_key_pair" "deploy" { provider = aws.frankfurt # Refer to the aliased provider key_name = "deploy-key" - public_key = file("./public_keys/id_rsa.pub") + public_key = file("./public_keys/id_rsa_starter_kit_deploy.pub") # make terraform import aws_key_pair.deploy deploy-key } diff --git a/sh/env/.env.secret.template b/sh/env/.env.secret.template index eb6a557..5ad5d91 100644 --- a/sh/env/.env.secret.template +++ b/sh/env/.env.secret.template @@ -64,3 +64,11 @@ SSMTP_REVALIASES=root:your_login@your.domain:mailhub.your.domain[:port] APP_BA_USER=control_area APP_BA_PASSWORD=generate_this_pass +# Set GitHub or GitLab auth token to securely use packages. +# Use a serialized JSON object without spaces +# For GitHub secrets use escaped JSON object without spaces +# https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens +# https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html +# For GitHub Secrets +#COMPOSER_AUTH={\"github-oauth\":{\"github.com\":\"ACCESS_TOKEN_GITHUB\"}} +#COMPOSER_AUTH={"github-oauth":{"github.com":"ACCESS_TOKEN_GITHUB"}}