<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Philo Hermans</title>
    <description>The latest articles on Forem by Philo Hermans (@philo01).</description>
    <link>https://forem.com/philo01</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F544786%2F22a27b9f-7d75-47d8-b973-283dfd6f28f1.jpg</url>
      <title>Forem: Philo Hermans</title>
      <link>https://forem.com/philo01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/philo01"/>
    <language>en</language>
    <item>
      <title>How to use GitHub Actions build matrix to deploy artifacts to multiple servers</title>
      <dc:creator>Philo Hermans</dc:creator>
      <pubDate>Mon, 25 Jan 2021 13:30:00 +0000</pubDate>
      <link>https://forem.com/philo01/how-to-use-github-actions-build-matrix-to-deploy-artifacts-to-multiple-servers-5eeg</link>
      <guid>https://forem.com/philo01/how-to-use-github-actions-build-matrix-to-deploy-artifacts-to-multiple-servers-5eeg</guid>
      <description>&lt;p&gt;In the past, I've always used multiple third-party services to, for example, host and deploy my code to production. I like the idea of minimizing the number of third-party services as they provide overhead, potential security risks, and often additional costs. So I decided to give Github Actions a try to see if I can replicate the famous zero-downtime deployments across multiple servers. GitHub introduced GitHub Actions in 2019, a workflow automation tool that allows GitHub users to use Actions to build their continuous delivery pipelines.&lt;/p&gt;

&lt;p&gt;As the article implies, I was able to do so 😄 Before I walk you through every step, I recommend taking a look at the free &lt;a href="https://lab.github.com/githubtraining/github-actions:-hello-world"&gt;GitHub Actions: Hello World&lt;/a&gt; course if you haven't used GitHub Actions before.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/2zduPKmszmI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  GitHub Action Workflow jobs overview
&lt;/h1&gt;

&lt;p&gt;Each GitHub Action workflow consists of one or multiple jobs which contain one or multiple steps. To deploy our application, we need to create the following jobs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create GitHub Action build artifacts for deployment&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I want to create one archive containing all the code ready to be deployed on production. You could run commands like &lt;code&gt;npm run production&lt;/code&gt; on your production server, but I like to keep my production server mean and clean. This reduces server maintenance overhead, like updating NodeJS across multiple servers.&lt;/p&gt;

&lt;p&gt;We want to achieve the following for our Laravel application artifacts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install NPM dependencies.&lt;/li&gt;
&lt;li&gt;Compile CSS and Javascript assets.&lt;/li&gt;
&lt;li&gt;Install Composer dependencies.&lt;/li&gt;
&lt;li&gt;Archive our build and remove unnecessary data (e.g., node_modules).&lt;/li&gt;
&lt;li&gt;Store our archive so we can deploy it to our servers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Prepare release on all our servers&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We want to make sure our deployments are stable and reliable, meaning we don't want one server to be updated while the second server failed. If the preparation fails on one server, we want the deployment sequence to stop.&lt;/p&gt;

&lt;p&gt;We want the release preparation job to do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ensure we have a directory that holds every release.&lt;/li&gt;
&lt;li&gt;Ensure we have a storage directory that shares data between releases.&lt;/li&gt;
&lt;li&gt;Ensure we have a current directory that links to the active release.&lt;/li&gt;
&lt;li&gt;Extract our build files into our releases directory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Run optional before hooks&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This is an optional feature, but I want to execute specific commands before the release is activated (e.g., chmod directories). So there needs to be a way to configure these so-called before hooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Activate the release&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Now we are ready to activate our new release without any downtime. We can do this by changing symbolic links; this basically swaps the underlying release, which is linked to our &lt;code&gt;current&lt;/code&gt; directory, to a new release inside our &lt;code&gt;releases&lt;/code&gt; directory. I'm running PHP FPM on my servers, so I also want to reload PHP FPM to detect the changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run optional after hooks&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This is an optional feature as well, but I want to execute specific commands after the release is activated to send a notification that my deployment completed for example. So there needs to be a way to configure these so-called after hooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cleaning up&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Given that we are uploading and extracting new releases, we take up more disk spaces after each release. To make sure we don't end up with thousands of releases and a full disk, we need to limit the number of release artifacts living on every server.&lt;/p&gt;
&lt;h3&gt;
  
  
  Project scaffolding
&lt;/h3&gt;

&lt;p&gt;To get started, you need to create a GitHub repository as testing your workflow requires you to commit and push your workflow yml file. It doesn't matter if your chosen repository is public or private. Feel free to try and implement this with one of your existing projects. I recommend you create a separate branch for this, so your repository stays clean, and you don't have a bunch of angry colleagues looking at you ;)&lt;/p&gt;

&lt;p&gt;I'm using a clean Laravel 8.* installation to deploy in this demonstration, which you can download &lt;a href="https://laravel.com/docs/8.x/installation"&gt;here&lt;/a&gt;. Be sure to verify that everything works before you continue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TyoU45JR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/1-laravel-clean-install.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TyoU45JR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/1-laravel-clean-install.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, create a new workflow file; feel free to give it any name you would like and place it in the &lt;code&gt;.github/workflows&lt;/code&gt; directory inside your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy Application

on:
  push:
    branches: [master]

jobs:
  # Magic

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I mentioned earlier, you will probably end up committing and pushing to GitHub several times. So if you are working along with one of your existing projects, make sure you choose a different branch if you don't want to clutter your commit history.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our first job: Create deployment Artifacts
&lt;/h3&gt;

&lt;p&gt;Before we have something to deploy, we need to start the build our Laravel application as we would typically do. So let's kick off by checking out our project by using the predefined &lt;a href="https://github.com/actions/checkout"&gt;checkout action by GitHub&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# // code from earlier is ommited for clearity

jobs:
  create-deployment-artifacts:
    name: Create deployment artifacts
    runs-on: ubuntu-latest

  steps:
  - uses: actions/checkout@v2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub will checkout the code from our repository in the container; no further steps necessary. Now that we have our code, we can continue with compiling our assets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
  steps:
  - uses: actions/checkout@v2

  - name: Compile CSS and Javascript
    run: |
      npm install
      npm run prod

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip: If you use a CDN to deliver your static files, be sure to implement your own solution. The front-end assets are now shared amongst all servers individually, which is not ideal. It may impact your website speed since assets could be loaded from different servers on every request depending on your load-balancer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We can now continue with our back-end code. Before we can install any composer packages, we need to make sure PHP is installed first. We will use the &lt;code&gt;setup-php&lt;/code&gt; action by &lt;a href="https://github.com/shivammathur/setup-php"&gt;Shivam Mathur&lt;/a&gt;, which makes this a breeze.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
  - name: Compile CSS and Javascript
    run: |
      npm ci
      npm run prod

  - name: Configure PHP 8.0
    uses: shivammathur/setup-php@master
    with:
      php-version: 8.0
      extensions: mbstring, ctype, fileinfo, openssl, PDO, bcmath, json, tokenizer, xml

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will configure PHP 8.0 (I've started with PHP 7.4 and updated the article to PHP 8.0) and install the required extensions. If your application requires additional extensions, be sure to add them to the list. We can continue by installing our Composer dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
  - name: Configure PHP 8.0
     uses: shivammathur/setup-php@master
     with:
      php-version: 8.0
      extensions: mbstring, ctype, fileinfo, openssl, PDO, bcmath, json, tokenizer, xml
  - name: Composer install
    run: |
    composer install --no-dev --no-interaction --prefer-dist

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we are preparing to deploy to production, I've added the &lt;code&gt;--no-dev&lt;/code&gt; flag. These additional packages are not required for production. In case you want to run PHPUnit before your deployment, you could either install the dependencies, run PHPUnit, and remove the dependencies or simply create a new workflow. I recommend the latter since it keeps testing and deployment logic separated.&lt;/p&gt;

&lt;p&gt;Time to test out what we've got so far! Commit your code changes and push them to GitHub. Once you've done so, visit the Actions page (github.com///actions)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SZnCfjFE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/2-first-workflow-execution.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SZnCfjFE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/2-first-workflow-execution.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can click each job to see the execution output. If a job fails, it will show a red cross instead of a checkmark. The execution output will often provide you with the information you need to resolve the issue.&lt;/p&gt;

&lt;p&gt;We've successfully compiled our front-end assets and installed our Composer dependencies. Now we need to store the results. GitHub provides an &lt;a href="https://github.com/actions/upload-artifact"&gt;Upload-Artifact&lt;/a&gt; helper. This will help us to share the Github Actions artifacts between jobs.&lt;/p&gt;

&lt;p&gt;You can upload single files, multiple files, and directories. Since we just want all our files deployed, I prefer to create a TAR archive, so we have a single file to work with. You could also create a ZIP archive, but this will require installing additional software in the build container as Ubuntu doesn't ship with the required libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
  - name: Composer install
    run: |
     composer install --no-dev --no-interaction --prefer-dist

  - name: Create deployment artifact
    run: tar -czf app.tar.gz *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new tar archive called &lt;code&gt;app.tar.gz&lt;/code&gt; containing all the files, including the additional build artifacts we've made in the previous steps.&lt;/p&gt;

&lt;p&gt;This works just fine, but the archive now contains files we don't need, like the &lt;code&gt;node_modules&lt;/code&gt; directory. We only required these to run the &lt;code&gt;npm run production command&lt;/code&gt;. Let's fix this by excluding directories from our archive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
- name: Create deployment artifact
  run: tar -czf app.tar.gz --exclude=*.git --exclude=node_modules --exclude=tests *

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our archive will now skip the &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;node_modules&lt;/code&gt;, and &lt;code&gt;tests&lt;/code&gt; directories from our archive. If you have additional files that are not required to be on your production server, exclude them now. By making the archive smaller, your deployment will be quicker.&lt;/p&gt;

&lt;p&gt;I want to change our archive's filename so it's easier to identify which commit our archive contains. GitHub has some global variables you can use in your .yml file, so let's change the name to the commit hash.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
- name: Create deployment artifact
  env:
    GITHUB_SHA: ${{ github.sha }}
  run: tar -czf "${GITHUB_SHA}".tar.gz --exclude=*.git --exclude=node_modules *

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We use the &lt;code&gt;env&lt;/code&gt; option to pass environment variables down into the container. In this case, we define an environment variable called &lt;code&gt;GITHUB_SHA&lt;/code&gt; and assign it the commit hash from the current job. If you want to know more about context and expression syntax for GitHub Actions, click &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We're now ready to upload our build artifacts with the &lt;a href="https://github.com/actions/upload-artifact"&gt;Upload-Artifact&lt;/a&gt; helper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
- name: Create deployment artifact
  env:
    GITHUB_SHA: ${{ github.sha }}
  run: tar -czf "${GITHUB_SHA}".tar.gz --exclude=*.git --exclude=node_modules *

- name: Store artifact for distribution
  uses: actions/upload-artifact@v2
  with:
    name: app-build
    path: ${{ github.sha }}.tar.gz

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We only need to provide this step with the path to our file. We again use the GitHub Action expression to get the commit hash. Finally, we provide our artifact with a name, which we can use for later reference to download the GitHub Actions artifacts in our deployment job.&lt;/p&gt;

&lt;p&gt;Before we can continue with any of the follow-up jobs, we need to prepare something called a GitHub Actions strategy matrix. A strategy creates a build matrix for your jobs. You can define different variations to run each job in.&lt;/p&gt;

&lt;p&gt;You can define a matrix of different job configurations. A matrix allows you to create multiple jobs by performing variable substitution in a single job definition.&lt;/p&gt;

&lt;p&gt;In our case, servers are the variable in our jobs. We we will use a JSON file inside our repository, so we don't have to switch back and forth between the GitHub UI.&lt;/p&gt;

&lt;p&gt;Create a new file called &lt;code&gt;deployment-config.json&lt;/code&gt; in the root of your project and add the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "name": "server-1",
    "ip": "123.456.78.90",
    "username": "web",
    "port": "22",
    "beforeHooks": "",
    "afterHooks": "",
    "path": ""
  }
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file contains a list of all our servers we wish to deploy to. We can define the before and after hook, and our Nginx directory path, which will serve our application for each server. For authentication we will use an SSH key which you can store inside a &lt;a href="https://docs.github.com/en/actions/reference/encrypted-secrets"&gt;repository secret&lt;/a&gt;. In this example I will name the secret &lt;code&gt;SSH_KEY&lt;/code&gt; and reference this secret inside our workflow to authenticate the commands we want to execute on our remote server .&lt;/p&gt;

&lt;p&gt;You could even host this file somewhere to dynamically populate this file, so if you add more instances to your infrastructure, they will be automatically included in the deployment cycle.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: A job matrix can generate a maximum of 256 jobs per workflow run. If you want to deploy to hundreds of servers in a single workflow, you need an alternative. I would recommend going serverless with a solution like&lt;/em&gt; &lt;a href="https://vapor.laravel.com/"&gt;&lt;em&gt;Laravel Vapor&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To make this configuration available inside our GitHub Actions workflow, we need to export this data. Using a special syntax, GitHub can identify which data to assign to which variable to reference later on our workflow matrix.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
- name: Store artifact for distribution
  uses: actions/upload-artifact@v2
  with:
    name: app-build
    path: ${{ github.sha }}.tar.gz

- name: Export deployment matrix
  id: export-deployment-matrix
  run: |
    JSON="$(cat ./deployment-config.json)"
    JSON="${JSON//'%'/'%25'}"
    JSON="${JSON//$'\n'/'%0A'}"
    JSON="${JSON//$'\r'/'%0D'}"
    echo "::set-output name=deployment-matrix::$JSON"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we need to get the JSON from our &lt;code&gt;deployment-config.json&lt;/code&gt; by using a simple &lt;code&gt;cat&lt;/code&gt; command. Next, we will escape the JSON (only required if your JSON is multiple lines). Finally, we use the &lt;code&gt;::set-output&lt;/code&gt; syntax to tell GitHub which value we want to associate with the &lt;code&gt;deployment-matrix&lt;/code&gt; key, ::set-output name= &lt;strong&gt;deployment-matrix&lt;/strong&gt; ::$JSON.&lt;/p&gt;

&lt;p&gt;If we want our other jobs to get the output from this job, we need to specify the output reference in our &lt;code&gt;create-deployment-artifacts&lt;/code&gt; job.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
jobs:
  create-deployment-artifacts:
    name: Create deployment artifacts
    runs-on: ubuntu-latest
    outputs:
      deployment-matrix: ${{ steps.export-deployment-matrix.outputs.deployment-matrix }}
    steps:
      - uses: actions/checkout@v2

# //

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now reference &lt;code&gt;deployment-matrix&lt;/code&gt; in other jobs and get all the required information for each of our servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our second job: Prepare the release on all the servers
&lt;/h3&gt;

&lt;p&gt;Next up, we can continue to prepare our release on every server. Thanks to the deployment matrix configuration we've created, we can cycle through each server to repeat all the steps on every server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
prepare-release-on-servers:
  name: "${{ matrix.server.name }}: Prepare release"
  runs-on: ubuntu-latest
  needs: create-deployment-artifacts
  strategy:
    matrix:
      server: ${{ fromJson(needs.create-deployment-artifacts.outputs.deployment-matrix) }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there are a couple of new parameters. The first new parameter, &lt;code&gt;needs&lt;/code&gt;, allows us to make sure a specific step has finished before this job can start. In our case, we want to prepare the release on the servers once the &lt;code&gt;create-deployment-artifacts&lt;/code&gt;. If you have multiple step dependencies, you can also pass an array.&lt;/p&gt;

&lt;p&gt;Next, we got our &lt;code&gt;strategy&lt;/code&gt; parameter, which allows us to define our &lt;code&gt;matrix&lt;/code&gt;. In this case, we define a matrix variable named &lt;code&gt;server&lt;/code&gt; and assign it to our &lt;code&gt;deployment-matrix&lt;/code&gt; we've created in our previous job. By default, the &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix"&gt;build matrix&lt;/a&gt; expects an array and not a JSON string.&lt;/p&gt;

&lt;p&gt;As mentioned earlier, GitHub Actions &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions"&gt;support context and expression syntax&lt;/a&gt; to access context information but also to evaluate expressions. This includes a couple of &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#functions"&gt;functions&lt;/a&gt; to, for example, cast values. To read our server configuration, we need to change our JSON string into a real JSON object using the &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#fromjson"&gt;fromJSON&lt;/a&gt; function.&lt;/p&gt;

&lt;p&gt;We can retrieve our JSON object through the &lt;code&gt;matrix&lt;/code&gt; context. This enables access to the matrix parameters we've configured for the current job. For example, in the code above, you can see we define a variable job name: &lt;code&gt;name: "${{ matrix.server.name }}: Prepare release"&lt;/code&gt;. In the GitHub UI, this will resolve to "server-1: Prepare release".&lt;/p&gt;

&lt;p&gt;We are now ready to continue with the steps of our job, downloading our artifact to our build container, uploading our artifact to the server, extracting our archive, and set up required directories on our server if they don't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
prepare-release-on-servers:
  name: "${{ matrix.server.name }}: Prepare release"
  runs-on: ubuntu-latest
  needs: create-deployment-artifacts
  strategy:
    matrix:
      server: ${{ fromJson(needs.create-deployment-artifacts.outputs.deployment-matrix) }}
  steps:
    - uses: actions/download-artifact@v2
      with:
        name: app-build
    - name: Upload
      uses: appleboy/scp-action@master
      with:
        host: ${{ matrix.server.ip }}
        username: ${{ matrix.server.username }}
        key: ${{ secrets.SSH_KEY }}
        port: ${{ matrix.server.port }}
        source: ${{ github.sha }}.tar.gz
        target: ${{ matrix.server.path }}/artifacts

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by downloading our GitHub Actions build artifact, which is quite simple as GitHub provides a simple action out of the box called "&lt;a href="https://github.com/actions/download-artifact"&gt;Download Artifact&lt;/a&gt;s." We reference the name we've used for uploading our artifact. This will download the artifact into the build container.&lt;/p&gt;

&lt;p&gt;Next, we upload the artifact to the server using a third-party &lt;a href="https://github.com/appleboy/scp-action"&gt;SCP&lt;/a&gt; action in the GitHub Actions marketplace. This action will copy our file via SSH based on our configuration. Make sure to check out the &lt;a href="https://github.com/appleboy/scp-action"&gt;repository&lt;/a&gt; for all available input variables for this action if you, for example, want to use SSH keys for authentication.&lt;/p&gt;

&lt;p&gt;The SSH credentials are self-explanatory; we simply reference our JSON object to get the server's connection details and the credentials. The SCP action requires a &lt;code&gt;source&lt;/code&gt; input variable; this is the file we want to upload. We use the commit hash context object again to generate the filename &lt;code&gt;${{ github.sha }}.tar.gz&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Besides the &lt;code&gt;source&lt;/code&gt; input variable, we also need to provide the &lt;code&gt;target&lt;/code&gt; input variable. I want to keep all the uploaded artifacts in a separate directory. I reference the server path and append the path with the directory name &lt;code&gt;artifacts&lt;/code&gt; to achieve this. The final path will be &lt;code&gt;/var/www/html/artifacts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's take a look and make sure everything is working so far. Commit all your changes and push them to GitHub. Visit the actions page of your repository again, and you should see a running action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---zlTl5F1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/3-upload-artifact.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---zlTl5F1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/3-upload-artifact.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;GitHub Actions Job Results and Artifacts&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;server-1: Prepare release&lt;/strong&gt; job so you can see the output of all the steps. Click on the &lt;strong&gt;Upload&lt;/strong&gt; job to expand the output. Seems to be looking good so far, our GitHub Actions release artifacts are now uploaded to our remote server.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iUXZfrHX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/4-upload-output.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iUXZfrHX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/4-upload-output.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;GitHub Actions Job Output&lt;/p&gt;

&lt;p&gt;Our final step for this job is to extract our uploaded archive and create a couple of directories if they don't exist. We will use SSH commands to set this up. We will again use a third-party action in the GitHub Actions marketplace called &lt;a href="https://github.com/appleboy/ssh-action"&gt;SSH Action&lt;/a&gt;. By now, you will be familiar with most of the syntax. The input variables are similar to the previous upload step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
- name: Extract archive and create directories
  uses: appleboy/ssh-action@master
  env:
    GITHUB_SHA: ${{ github.sha }}
  with:
    host: ${{ matrix.server.ip }}
    username: ${{ matrix.server.username }}
    key: ${{ secrets.SSH_KEY }}
    port: ${{ matrix.server.port }}
    envs: GITHUB_SHA
    script: |
      mkdir -p "${{ matrix.server.path }}/releases/${GITHUB_SHA}"
      tar xzf ${{ matrix.server.path }}/artifacts/${GITHUB_SHA}.tar.gz -C "${{ matrix.server.path }}/releases/${GITHUB_SHA}"
      rm -rf ${{ matrix.server.path }}/releases/${GITHUB_SHA}/storage

      mkdir -p ${{ matrix.server.path }}/storage/{app,public,framework,logs}
      mkdir -p ${{ matrix.server.path }}/storage/framework/{cache,sessions,testing,views}
      chmod -R 0777 ${{ matrix.server.path }}/storage

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use the &lt;code&gt;|&lt;/code&gt; character in your script, you can define multiple commands split across multiple lines you want to execute on your server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create a new directory with the commit hash as the directory name
mkdir -p "${{ matrix.server.path }}/releases/${GITHUB_SHA}"

# Extract the tar file into our release directory
tar xzf ${{ matrix.server.path }}/artifacts/${GITHUB_SHA}.tar.gz -C "${{ matrix.server.path }}/releases/${GITHUB_SHA}"

# Create Laravel storage directories and set permissions
mkdir -p ${{ matrix.server.path }}/storage/{app,public,framework,logs}
mkdir -p ${{ matrix.server.path }}/storage/framework/{cache,sessions,testing,views}
chmod -R 0777 ${{ matrix.server.path }}/storage

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want, you can SSH into one of your servers and verify that the directories exist and the archive is unarchived.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MmkP69R9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/5-ssh-verification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MmkP69R9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/5-ssh-verification.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;GitHub Actions job result manual verification via SSH&lt;/p&gt;

&lt;p&gt;So far, so good! We've made our build, uploaded the results to our server, extracted the archive, and made sure the required directories exist. We are almost there :)&lt;/p&gt;

&lt;h3&gt;
  
  
  Our third job: Run before hooks
&lt;/h3&gt;

&lt;p&gt;This step is optional, but it's quite useful if you want to execute specific commands before you activate your release. In this example, we are using Laravel, so you might want to run database migrations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
run-before-hooks:
  name: "${{ matrix.server.name }}: Before hook"
  runs-on: ubuntu-latest
  needs: [create-deployment-artifacts, prepare-release-on-servers]
  strategy:
    matrix:
      server: ${{ fromJson(needs.create-deployment-artifacts.outputs.deployment-matrix) }}
  steps:
  - name: Run before hooks
    uses: appleboy/ssh-action@master
    env:
      GITHUB_SHA: ${{ github.sha }}
      RELEASE_PATH: ${{ matrix.server.path }}/releases/${{ github.sha }}
      ACTIVE_RELEASE_PATH: ${{ matrix.server.path }}/current
      STORAGE_PATH: ${{ matrix.server.path }}/storage
      BASE_PATH: ${{ matrix.server.path }}
    with:
      host: ${{ matrix.server.ip }}
      username: ${{ matrix.server.username }}
      key: ${{ secrets.SSH_KEY }}
      port: ${{ matrix.server.port }}
      envs: envs: GITHUB_SHA,RELEASE_PATH,ACTIVE_RELEASE_PATH,STORAGE_PATH,BASE_PATH
      script: |
        ${{ matrix.server.beforeHooks }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, similar job but with a couple of changes. First, we want to make sure the &lt;code&gt;create-deployment-artifacts&lt;/code&gt; and the &lt;code&gt;prepare-release-on-servers&lt;/code&gt; have been completed by passing an array to the &lt;code&gt;need&lt;/code&gt; property.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;needs: [create-deployment-artifacts, prepare-release-on-servers]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make things a bit easier when defining before hooks, I want to use specific environment variables to simplify things. Let's say I want to execute set permissions on the storage directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "name": "server-1",
    "beforeHooks": "chmod -R 0777 ${RELEASE_PATH}/storage",
    "afterHooks": "",
    "path": "/var/www/html"
  }
]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make these environment variables available, you need to explicitly define which variables you want to pass via the &lt;code&gt;envs&lt;/code&gt; input variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our fourth job: Activating the release
&lt;/h3&gt;

&lt;p&gt;Time for the most exciting part, if I say so myself, activating our release. We will re-use a big chunk of our previous step with a couple of changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
activate-release:
  name: "${{ matrix.server.name }}: Activate release"
  runs-on: ubuntu-latest
  needs: [create-deployment-artifacts, prepare-release-on-servers, run-before-hooks]
  strategy:
    matrix:
      server: ${{ fromJson(needs.create-deployment-artifacts.outputs.deployment-matrix) }}
  steps:
    - name: Activate release
      uses: appleboy/ssh-action@master
      env:
        GITHUB_SHA: ${{ github.sha }}
        RELEASE_PATH: ${{ matrix.server.path }}/releases/${{ github.sha }}
        ACTIVE_RELEASE_PATH: ${{ matrix.server.path }}/current
        STORAGE_PATH: ${{ matrix.server.path }}/storage
        BASE_PATH: ${{ matrix.server.path }}
        LARAVEL_ENV: ${{ secrets.LARAVEL_ENV }}
      with:
        host: ${{ matrix.server.ip }}
        username: ${{ matrix.server.username }}
        key: ${{ secrets.SSH_KEY }}
        port: ${{ matrix.server.port }}
        envs: GITHUB_SHA,RELEASE_PATH,ACTIVE_RELEASE_PATH,STORAGE_PATH,BASE_PATH,ENV_PATH,LARAVEL_ENV
        script: |
          printf "%s" "$LARAVEL_ENV" &amp;gt; "${BASE_PATH}/.env"
          ln -s -f ${BASE_PATH}/.env $RELEASE_PATH
          ln -s -f $STORAGE_PATH $RELEASE_PATH
          ln -s -n -f $RELEASE_PATH $ACTIVE_RELEASE_PATH
          service php8.0-fpm reload

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we update the &lt;code&gt;need&lt;/code&gt; input variable to include all previous steps before running the &lt;code&gt;activate-release&lt;/code&gt; job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;needs: [create-deployment-artifacts, prepare-release-on-servers, run-before-hooks]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've added a new Laravel environment variable, &lt;code&gt;LARAVEL_ENV&lt;/code&gt;, which will contain the environment variable for our Laravel application. This variable doesn't contain any data just yet. So let's do this first, you can define key-value secrets per repository via the GitHub UI (repository-&amp;gt;settings -&amp;gt;secrets)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YsMTK5Mi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/6-repository-env-secret.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YsMTK5Mi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/6-repository-env-secret.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;GitHub Repository Secrets&lt;/p&gt;

&lt;p&gt;Let's take a closer look at our bash script line by line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Store the environment data to the /var/www/html/.env file 
printf "%s" "$LARAVEL_ENV" &amp;gt; "${BASE_PATH}/.env"

# Link /var/www/html/.env file to /var/www/html/releases/633be605b03169ef96c2cee1f756852e1ceb2688/.env
ln -s -f ${BASE_PATH}/.env $RELEASE_PATH

# Link /var/www/html/storage directory to /var/www/html/releases/633be605b03169ef96c2cee1f756852e1ceb2688/storage
ln -s -f $STORAGE_PATH $RELEASE_PATH

# Link the release path to the active release path, /var/www/html/current -&amp;gt; /var/www/html/releases/633be605b03169ef96c2cee1f756852e1ceb2688
ln -s -n -f $RELEASE_PATH $ACTIVE_RELEASE_PATH

# Reload php8.0 to detect file changes
service php8.0-fpm reload

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Tip: There is a minor delay between the activation and the after hook job. So make sure to include critical commands in the activation job. You could add an extra release hook configuration if you want to define this per server.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I6UZzCVT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/7-activation-overview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I6UZzCVT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/7-activation-overview.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Taking a quick look at all the directories via SSH verifies shows that everything is working correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--40AXWhYT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/8-ssh-current-verification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--40AXWhYT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/8-ssh-current-verification.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;current&lt;/code&gt; directory is pointing to &lt;code&gt;/var/www/html/releases/5b62b9a13...&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bfAhlC7G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/8-ssh-storage-env-verification.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bfAhlC7G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/8-ssh-storage-env-verification.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The current release environment file &lt;code&gt;/var/www/html/current/.env&lt;/code&gt; is pointing to &lt;code&gt;/var/www/html/.env&lt;/code&gt; and &lt;code&gt;/var/www/html/current/storage&lt;/code&gt; is pointing to &lt;code&gt;/var/www/html/storage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nQeEbpq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/9-laravel-live.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nQeEbpq4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/9-laravel-live.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our Laravel application is up and running with zero downtime! Hurray!&lt;/p&gt;

&lt;h3&gt;
  
  
  Our fifth job: After hook
&lt;/h3&gt;

&lt;p&gt;Our after hook is exactly the same as our before hook besides a few naming changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# //
run-after-hooks:
  name: "${{ matrix.server.name }}: After hook"
  runs-on: ubuntu-latest
  needs: [create-deployment-artifacts, prepare-release-on-servers, run-before-hooks, activate-release]
  strategy:
    matrix:
      server: ${{ fromJson(needs.create-deployment-artifacts.outputs.deployment-matrix) }}
  steps:
    - name: Run after hooks
      uses: appleboy/ssh-action@master
      env:
        GITHUB_SHA: ${{ github.sha }}
        RELEASE_PATH: ${{ matrix.server.path }}/releases/${{ github.sha }}
        ACTIVE_RELEASE_PATH: ${{ matrix.server.path }}/current
        STORAGE_PATH: ${{ matrix.server.path }}/storage
        BASE_PATH: ${{ matrix.server.path }}
      with:
        host: ${{ matrix.server.ip }}
        username: ${{ matrix.server.username }}
        key: ${{ secrets.SSH_KEY }}
        port: ${{ matrix.server.port }}
        envs: GITHUB_SHA,RELEASE_PATH,ACTIVE_RELEASE_PATH,STORAGE_PATH,BASE_PATH
        script: |
          ${{ matrix.server.afterHooks }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again the &lt;code&gt;needs&lt;/code&gt; input variable is updated to make sure all previous steps have been completed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our sixth job: Cleaning up
&lt;/h3&gt;

&lt;p&gt;Every release we upload and extract on our production servers take up space. You don't want to end up having servers meltdown because of their hard drives being full. So let's clean up after each deployment and keep 5 releases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clean-up:
  name: "${{ matrix.server.name }}: Clean up"
  runs-on: ubuntu-latest
  needs: [create-deployment-artifacts, prepare-release-on-servers, run-before-hooks, activate-release, run-after-hooks]
  strategy:
    matrix:
      server: ${{ fromJson(needs.create-deployment-artifacts.outputs.deployment-matrix) }}
  steps:
    - name: Run after hooks
      uses: appleboy/ssh-action@master
      env:
        RELEASES_PATH: ${{ matrix.server.path }}/releases
        ARTIFACTS_PATH: ${{ matrix.server.path }}/artifacts
      with:
        host: ${{ matrix.server.ip }}
        username: ${{ matrix.server.username }}
        key: ${{ secrets.SSH_KEY }}
        port: ${{ matrix.server.port }}
        envs: RELEASES_PATH
        script: |
          cd $RELEASES_PATH &amp;amp;&amp;amp; ls -t -1 | tail -n +6 | xargs rm -rf
          cd $ARTIFACTS_PATH &amp;amp;&amp;amp; ls -t -1 | tail -n +6 | xargs rm -rf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's take closer look at the commands we are executing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd $RELEASES_PATH &amp;amp;&amp;amp; ls -t -1 | tail -n +6 | xargs rm -rf
cd $ARTIFACTS_PATH &amp;amp;&amp;amp; ls -t -1 | tail -n +6 | xargs rm -rf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will &lt;code&gt;cd&lt;/code&gt; into our release and artifacts directory, list all files in given directory, order the list by timestamp, return the results with the offset of 5 entries, and finally remove given files or folders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-server deployment
&lt;/h3&gt;

&lt;p&gt;To deploy to multiple servers, you only need to update your &lt;code&gt;deployment-config.json&lt;/code&gt; to include the additional servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  {
    "name": "server-1",
    "ip": "123.456.78.90",
    "username": "web",
    "port": "22",
    "beforeHooks": "",
    "afterHooks": "",
    "path": "/var/www/html"
  },
  {
    "name": "server-2",
    "ip": "901.234.56.78",
    "username": "web",
    "port": "22",
    "beforeHooks": "",
    "afterHooks": "",
    "path": "/var/www/html"
  }  
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
Multi-server configuration





&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cEdx9uaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/multi-server-action.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cEdx9uaM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://philo.dev/content/images/2021/01/multi-server-action.png" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;Release activation on multiple servers&lt;/p&gt;

&lt;p&gt;That's a wrap! You've successfully made a release artifact from your application build and deployed it to multiple servers without any downtime!&lt;/p&gt;

&lt;p&gt;I'd love to hear if you use this workflow to deploy your application, if you are happy with the results, which steps you've implemented or skipped, and the modifications/improvements you've made. Drop me a tweet &lt;a href="https://twitter.com/philo01"&gt;@Philo01&lt;/a&gt; and talk! 🙌🏼&lt;/p&gt;




&lt;p&gt;&lt;a href="https://philo.dev/#/portal/signup"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--viTUpawx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://d.pr/i/HLzE3V%2B" alt="How to use GitHub Actions build matrix to deploy artifacts to multiple servers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm thinking about releasing a premium video course in the near future where I will show you how to build a real-world application from start to finish using Laravel, Livewire, and Tailwind CSS. I will be covering everything, from registering a domain, setting up a server, writing tests, you name it. &lt;a href="https://philo.dev/#/portal/signup"&gt;&lt;strong&gt;Subscribe&lt;/strong&gt;&lt;/a&gt; if you are interested and want to be notified, and you might also get access for free as I will be doing a give away when the course launches.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>devops</category>
      <category>github</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Electron auto update introduction</title>
      <dc:creator>Philo Hermans</dc:creator>
      <pubDate>Sat, 16 Jan 2021 15:16:10 +0000</pubDate>
      <link>https://forem.com/philo01/electron-auto-update-introduction-m7i</link>
      <guid>https://forem.com/philo01/electron-auto-update-introduction-m7i</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RQ94ePQO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/1%2AvjfT0uxExYqwBfHjbJVajw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RQ94ePQO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/4000/1%2AvjfT0uxExYqwBfHjbJVajw.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ve just completed building your Electron application; everything is working so far; it’s &lt;a href="https://philo.dev/notarizing-your-electron-application/"&gt;notarized&lt;/a&gt; to work on macOS, and you’ve tested your application on Windows and Linux as well. You shared your application with the world and got some great responses from your community. Eventually, more and more messages appear in your inbox about your app crashing. You discovered a bug in your application that was causing the crashes and quickly fixed it. But how do you get this new version of your application out to your users?&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Electron Auto Update
&lt;/h2&gt;

&lt;p&gt;Electron ships with an auto-update feature so you can quickly ship updates of your product to your users. When I implemented the auto update feature in my &lt;a href="https://philo.dev/introducing-artisan-remote/"&gt;first Electron application&lt;/a&gt;, I went down the rabbit hole to figure out how the Electron auto-update feature works because it wasn’t that straightforward, in my opinion.&lt;/p&gt;

&lt;p&gt;A few things that you should know about Electron auto update:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It only supports macOS and Windows (no support for Linux).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Both the macOS and Windows updater use &lt;a href="https://github.com/Squirrel"&gt;Squirrel&lt;/a&gt; behind the scenes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Windows version of Squirrel is looking for maintainers to “reboot” the project and has over 300 issues, meaning you might expect some problems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You must sign your application on macOS for the auto-updater to work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You must &lt;a href="https://www.electronjs.org/docs/api/app#appmovetoapplicationsfolderoptions-macos"&gt;move your application&lt;/a&gt; to the &lt;code&gt;Applications&lt;/code&gt; directory on macOS for the auto-updater to work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On Windows, make sure you &lt;a href="https://github.com/electron/windows-installer#handling-squirrel-events"&gt;don’t update your application&lt;/a&gt; on its first run, or your app will throw a very user &lt;a href="https://github.com/electron/electron/issues/7155"&gt;unfriendly error&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuring Electron Auto Update
&lt;/h2&gt;

&lt;p&gt;Implementing Electron Auto Update is relatively easy; it only requires a few lines of code to integrate with your deployment server.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { app, autoUpdater } = require('electron')
autoUpdater.setFeedURL('[https://dist.unlock.sh/v1/electron/my-app'](https://dist.unlock.sh/v1/electron/my-app'))
autoUpdater.checkForUpdates()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In case you want to check for updates on a regular interval (the code above only executes on startup), you can use setInterval to check every 30 minutes for example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;setInterval(() =&amp;gt; {
  autoUpdater.checkForUpdates()
}, 30000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The update will be downloaded in the background and installed automatically when the application is restarted (most of the time, see troubleshooting for some exceptions).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YTNFMlkt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ALeQWR2X0FPhFGwlGAp5Ufw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YTNFMlkt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2ALeQWR2X0FPhFGwlGAp5Ufw.png" alt="Native update notification"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to make your users aware that a new update has been downloaded and is available to be installed, you can also use autoUpdater.checkForUpdatesAndNotify() instead. The notification will be native to your user’s operating system.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do you want to learn step by step how to release updates for your public repository? Be sure to check out the section below about Electron auto update for public repositories. I will publish an article in the near future about using a different server for private repositories.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing your Electron Auto Update notification
&lt;/h2&gt;

&lt;p&gt;If you want to use your own in-app update notification instead, you can do this by listening to the update-downloaded event emitted by the auto-updater.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) =&amp;gt; {
  //
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;releaseName is only available on Windows.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you want to force the auto-updater to install your update immediately after the update has been downloaded, you can use autoUpdater.quitAndInstall():&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) =&amp;gt; {
   autoUpdater.quitAndInstall()
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Please note that your users might not appreciate this, as work could be lost when your app just quits while they were just filling out a form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron auto update for public repositories
&lt;/h2&gt;

&lt;p&gt;If your code repository is publicly available on GitHub, you can use a free service by Electron to ship your updates. It’s an easy process. Let’s quickly scaffold an application to test this out. I’m using Electron Forge’s starting template; if you want to follow along, execute the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Yarn
yarn create electron-app auto-update-example

// NPM
npx create-electron-app auto-update-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Qwakg19---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2320/0%2AdHSyJupdaPurLuDv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Qwakg19---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2320/0%2AdHSyJupdaPurLuDv.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To use the public auto-updater, we need to download the NPM dependency, so be sure to install this dependency:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install update-electron-app --save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now just run yarn start or npm start, and your Electron application will build and execute.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9k2Mm_V5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2A0U8VZ7i6ZxRo7xUL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9k2Mm_V5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/0%2A0U8VZ7i6ZxRo7xUL.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need a public GitHub repository, so head over to &lt;a href="https://github.com/new"&gt;github.com/new&lt;/a&gt; and create a repository we can use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_hGzXiJr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2498/0%2AUgOjZQ8bo5n2-RUw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_hGzXiJr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2498/0%2AUgOjZQ8bo5n2-RUw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To inform the auto-updater about our repository, we need to make sure we define it in our package.json:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "auto-update-example",
  "productName": "auto-update-example",
  "version": "1.0.0",
  "description": "My Electron application description",
  "repository": "[https://github.com/PhiloNL/electron-hello-world](https://github.com/PhiloNL/electron-hello-world)",
  "main": "src/index.js",
  //
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s open src/index.js and call the updater to check for updates every hour and notify the user when an update is available.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app.on('ready', () =&amp;gt; {
    updateApp = require('update-electron-app');

updateApp({
        // repo: 'PhiloNL/electron-hello-world', // defaults to package.json
        updateInterval: '1 hour',
        notifyUser: true
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we need to publish our app to GitHub. Electron Forge ships with a couple of build-in publishers, including one for GitHub. To install the publisher run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install [@electron](http://twitter.com/electron)-forge/publisher-github
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can define the configuration for different publishers in your package.json file. So let's add our GitHub configuration:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  //...
  "main": "src/index.js",
  "config": {
    "forge": {
      "packagerConfig": {},
      "publishers": [
        {
          "name": "[@electron](http://twitter.com/electron)-forge/publisher-github",
          "config": {
            "repository": {
              "owner": "PhiloNL",
              "name": "electron-hello-world"
            }
          }
        }
      ],
      //...
    }
  },
  //...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let’s publish our application to GitHub by running the publish command. The publish command requires that you set your GitHub personal access token so it can access your account. You can create a personal access token &lt;a href="https://github.com/settings/tokens"&gt;here&lt;/a&gt;. Make sure you keep this token secure and don’t share it with anyone.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please note, from this point forward, you are required to have your application signed and notarized. To learn more about signing and notarizing your application, visit &lt;a href="https://philo.dev/notarizing-your-electron-application/"&gt;this&lt;/a&gt; article.&lt;/em&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export GITHUB_TOKEN=&amp;lt;your-token&amp;gt;
yarn run publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oDBFlDH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2824/1%2ALZ50OGJoGQ1aqlF2pV9iCw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oDBFlDH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2824/1%2ALZ50OGJoGQ1aqlF2pV9iCw.png" alt="First release published"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, you’ve just pushed version 1.0.0 to GitHub. By default, your release is set to ‘Draft’, awaiting your final approval. So head over to your repository releases and publish your release (github.com/username/repository/releases).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qIgyaaHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2298/1%2A8m197r5qJKq-nY9lYoe_lw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qIgyaaHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2298/1%2A8m197r5qJKq-nY9lYoe_lw.png" alt="Published release on GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s test if the updater works by publishing a new release. Open src/index.html and make a couple of changes so you can see that the application has been updated.&lt;/p&gt;

&lt;p&gt;Next, increment the version number of your application by opening package.json and changing the version number:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "auto-update-example",
  "productName": "auto-update-example",
  "version": "1.0.1",
  // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run yarn run publish again and head over to GitHub to publish v1.0.1 of your application. Start v1.0.0 of your application and wait for the notification :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--59Iu-x67--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AOnbppMMmeqSyw5KYSZNRdQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--59Iu-x67--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2AOnbppMMmeqSyw5KYSZNRdQ.png" alt="Update available notification"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click restart, and you will see the new version of your application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kTNsY0V7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Aq8PL7uJlA5xjvVyZQAV-6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kTNsY0V7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/2000/1%2Aq8PL7uJlA5xjvVyZQAV-6g.png" alt="Application is updated to v1.0.1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, this will work on both macOS and should also work on Windows if you &lt;a href="https://github.com/electron/windows-installer#handling-squirrel-events"&gt;handle the Squirrel events correctly&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Since auto-updating happens behind the scenes, you have no idea what is happening, which can be quite frustrating when your application doesn’t update.&lt;/p&gt;

&lt;p&gt;To debug what is happening in the background, you can enable the logger by passing it in the update-electron-app constructor.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require('update-electron-app')({
  logger: require('electron-log')
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You will be able to find the log files in the following locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Linux: ~/.config/{app name}/logs/{process type}.log&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;macOS: /Library/Logs/{app name}/{process type}.log&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Windows: %USERPROFILE%\AppData\Roaming{app name}\logs{process type}.log&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    [info] Checking for update
    [info] Found version v1.0.1 (url: [https://github.com/PhiloNL/electron-hello-world/releases/download/v1.0.0/auto-update-example-darwin-x64-1.0.0.zip](https://github.com/PhiloNL/electron-hello-world/releases/download/v1.0.0/auto-update-example-darwin-x64-1.0.0.zip))
    [info] Downloading update from [https://github.com/PhiloNL/electron-hello-world/releases/download/v1.0.0/auto-update-example-darwin-x64-1.0.0.zip](https://github.com/PhiloNL/electron-hello-world/releases/download/v1.0.0/auto-update-example-darwin-x64-1.0.0.zip)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Race condition on macOS with Squirrel updater
&lt;/h2&gt;

&lt;p&gt;In some cases, your application might require multiple restarts for the update to work if your user starts the application to quickly after quitting. This can also be the case when using autoUpdater.quitAndInstall(). I've experienced this with Electron Builder, so I'm not sure if this is also the case with Electron Forge. Still, I am assuming that since they all use the Squirrel updater, it affects any application that uses the built-in Electron updater.&lt;/p&gt;

&lt;p&gt;After a long search, I finally found this &lt;a href="https://github.com/electron-userland/electron-builder/issues/2317"&gt;issue&lt;/a&gt; and this &lt;a href="https://github.com/electron-userland/electron-builder/issues/2317#issuecomment-462808392"&gt;comment&lt;/a&gt; with a possible solution. It’s not ideal, but it fixes the problem.&lt;/p&gt;

&lt;p&gt;When you start your application, and the Squirrel updater has found a new update for your application, it will spawn an idle process called ShipIt. This process remains idle until you quit your application. Once your application is closed, the ShipIt process will start extracting your update and replace your application with the latest version. Depending on the size of your application and the user's machine's speed, this may take a moment.&lt;/p&gt;

&lt;p&gt;If your application starts too quickly after quitting, meaning before the updater has completed, a new ShipIt instance will replace the process, and the update process restarts. In the meantime, the user might be confused because the app is still running on the same version.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://gist.github.com/dsabanin/aadfbe508a627a3e95e3f024d2e5207d"&gt;Gist&lt;/a&gt; from the issue above resolves this problem by ensuring the ShipIt process has ended. Let’s break the code down step by step.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const shipItProcesses = await findProcess('name', 'ShipIt');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Look for an active process named ShipIt.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (shipItProcesses.some(f =&amp;gt; f.cmd.includes('com.org.my-app'))) {
  shouldRestartBeforeLaunch = true;
  console.debug('Waiting for auto update to finish');
  setTimeout(makeSureAutoUpdateFinished, 1500);
} else {
 // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Since a user could be running multiple Electron apps, we want to make sure the ShipIt process belongs to our app com.org.my-app. If this process exists, we wait for the application to start, so the auto-updater has a chance to finish. This check will repeat recursively until the process is gone.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        if (shouldRestartBeforeLaunch) {
          try {
            const Electron = require('electron');
            Electron.app.relaunch();
            Electron.app.exit(0);
          } catch (error) {
            console.error('Failed to restart the app through electron', error);
            process.exit(1);
          }
        } else {
          require('./main');
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, it will restart the existing app to complete the update process. These multiple restarts will cause your app to bounce a couple of times in the macOS dock, but at least you are sure your user is using the latest version of your application. Finally, it will execute the main code of your Electron application.&lt;/p&gt;

&lt;p&gt;That’s it! You’ve successfully used Electron Auto Update together with GitHub to distribute a new version of your application to your user.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Do you want to know more about how to publish updates from private repositories and to license your products? Be sure to subscribe for future articles or give me a follow on &lt;a href="https://twitter.com/philo01"&gt;**Twitter&lt;/a&gt;&lt;/em&gt;&lt;em&gt;. I appreciate the support!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://philo.dev/electron-auto-update-explained/"&gt;https://philo.dev&lt;/a&gt; on January 13, 2021.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>electron</category>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Notarizing your Electron application with Electron Builder</title>
      <dc:creator>Philo Hermans</dc:creator>
      <pubDate>Mon, 28 Dec 2020 21:11:19 +0000</pubDate>
      <link>https://forem.com/philo01/notarizing-your-electron-application-with-electron-builder-27p</link>
      <guid>https://forem.com/philo01/notarizing-your-electron-application-with-electron-builder-27p</guid>
      <description>&lt;p&gt;Recently I released my first &lt;a href="https://philo.dev/introducing-artisan-remote/"&gt;Electron application&lt;/a&gt;. When my application was ready to be released, it was required to have it notarized by Apple, or else nobody could use my your application. Although there are quite a few articles on this matter, it still took some trial and error because I ran into a couple of problems that were not mentioned. To save others some time figuring these things out, I've created this walkthrough to get you started.&lt;/p&gt;

&lt;h3&gt;
  
  
  Electron Builder
&lt;/h3&gt;

&lt;p&gt;I've used &lt;a href="https://github.com/electron-userland/electron-builder"&gt;Electron Builder&lt;/a&gt; together with the &lt;a href="https://github.com/nklayman/vue-cli-plugin-electron-builder"&gt;Electro Builder Vue CLI&lt;/a&gt; to build my first application. According to the official Electron website, it's described as a "complete solution to package and build a ready-for-distribution Electron app that focuses on an integrated experience."&lt;/p&gt;

&lt;p&gt;From my experience, Electron Builder works excellent during development, but it seems a bit buggy in regards to making your application available to the general public. I've experienced some issues with notarizing and publishing. So I might give &lt;a href="https://www.electronforge.io/"&gt;Electron Forge&lt;/a&gt; (maintained by Electron) a try for my next application to see how that works compared to Electron Builder (maintained by a third-party).&lt;/p&gt;

&lt;h3&gt;
  
  
  Apple Developer Program
&lt;/h3&gt;

&lt;p&gt;In order to distribute your Electron app on macOS, you need to participate in Apple's Developer Program, which costs $99 per year. You can sign up at &lt;a href="https://developer.apple.com"&gt;https://developer.apple.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Tdgbj3dQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ejjl8otwij6tup040px.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Tdgbj3dQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/4ejjl8otwij6tup040px.png" alt="apple-developer-portal"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head over to your Developer Portal and click on "Certificates, IDs &amp;amp; Profiles."&lt;/p&gt;

&lt;p&gt;Next, head over to "Certificates" and click the blue plus icon to create a new certificate. Depending on your distribution wishes, you need to select a certificate type. In this example, we will go ahead and select "Developer ID Application," which reads, "This certificate is used to code sign your app for distribution outside of the Mac App Store."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JaNOzV25--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6vue9ptmi2lqmnfjgbl3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JaNOzV25--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/6vue9ptmi2lqmnfjgbl3.png" alt="apple-developer-certificate-type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s2LTCmBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/esxzs3tsouaxhx40xghw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s2LTCmBY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/esxzs3tsouaxhx40xghw.png" alt="apple-developer-csr"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next up, we need to upload a "Certificate Signing Request." You can create this with the Keychain Tool on your Mac. You can find this application in &lt;code&gt;/Applications/Utilities&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Next, choose Keychain Access &amp;gt; Certificate Assistant &amp;gt; Request a Certificate from a Certificate Authority. Fill out the form:  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;User Email Address: your email address&lt;br&gt;
Common Name: anything&lt;br&gt;
CA Email Address: leave empty&lt;br&gt;
Request is: Saved to disk&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--47EneORV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bnss14u051dqnyz9t8kv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--47EneORV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/bnss14u051dqnyz9t8kv.png" alt="apple-certificate-assistant"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will create a new file "CertificateSigningRequest.certSigningRequest". Head back to Apple's Developer Portal again and upload the certificate signing request.&lt;/p&gt;

&lt;p&gt;Now download your certificate to your Mac, then double click the .cer file to install in Keychain Access. If you don't do this, you will get some strange errors from Apple when notarizing your application, which doesn't tell you anything useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Notarizing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;nl.philo.artisan-remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;./artisan-remote-app/dist_electron/mac/Artisan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Remote.app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Error:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Apple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;notarize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;application,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;info&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Code:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Message:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Invalid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Logs:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;https://osxapps-ssl.itunes.apple.com/itunes-assets/...&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logFormatVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cabb3b08-744a-4b94-853c-62fb7908bd25"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invalid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusSummary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Archive contains critical validation errors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"archiveFilename"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Artisan_Remote.zip"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"uploadDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-12-22T22:45:50Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3199b031a2d8819bec4ea52973a363657a8a221d81cc764c3169115cf1dda893"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ticketContents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issues"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Artisan_Remote.zip/Artisan Remote.app/Contents/MacOS/Artisan Remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The binary is not signed."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"docUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x86_64"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Artisan_Remote.zip/Artisan Remote.app/Contents/MacOS/Artisan Remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The signature does not include a secure timestamp."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"docUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x86_64"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Artisan_Remote.zip/Artisan Remote.app/Contents/MacOS/Artisan Remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The executable does not have the hardened runtime enabled."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"docUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x86_64"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure after sign hook
&lt;/h3&gt;

&lt;p&gt;Electron Builder also uses &lt;a href="https://github.com/electron/electron-notarize"&gt;Electron Notarize&lt;/a&gt; behind the scenes. For example, this is the &lt;code&gt;afterSignHook.js&lt;/code&gt; I'm using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "afterSign": "./afterSignHook.js",&lt;/span&gt;

&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;electron_notarize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;electron-notarize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;darwin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;afterSign hook triggered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;appId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nl.philo.artisan-remote&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;appPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appOutDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;packager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productFilename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.app`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appPath&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;skip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Notarizing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; found at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;electron_notarize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notarize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;appBundleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;appPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;appPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;appleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPLE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;appleIdPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APPLE_ID_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Done notarizing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you adjust the &lt;code&gt;appId&lt;/code&gt; to your app's name; the convention is &lt;code&gt;country.company.appname&lt;/code&gt;. Finally, you need to set your Apple ID and password. It's recommended not to include your credentials in your code, so please use environment variables when possible. You can &lt;a href="https://support.apple.com/en-au/HT204397"&gt;generate app specific passwords&lt;/a&gt;, so you don't need to write your personal password. Apple will provide you with something like &lt;code&gt;edqv-akmn-hasr-tyui&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That should do it. You should now be able to successfully notarize your application with the &lt;code&gt;npm run osx:build command&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At the time of writing Electron Builder still contains a &lt;a href="https://github.com/electron-userland/electron-builder/issues/4299"&gt;bug&lt;/a&gt; that causes the &lt;code&gt;your-app-mac.zip&lt;/code&gt; file to have an unsigned copy of your application (the &lt;code&gt;your-app.dmg&lt;/code&gt; will work just fine).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QEp3fsdZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/970bxx4i3sceem11eeqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QEp3fsdZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/970bxx4i3sceem11eeqx.png" alt="electron-builder-bug"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily &lt;a href="https://gist.github.com/harshitsilly/a1bd5a405f93966aad20358ae6c4cec5"&gt;someone wrote&lt;/a&gt; a workaround (I've made some adjustments for it to work with my Vue boilerplate). Place this file &lt;code&gt;fixMacDistributionArchive.js&lt;/code&gt; in the root of your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yaml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;js-yaml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;appBuilderPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app-builder-bin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Verification if MacOS build is present.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;macBuild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platformToTargets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mac&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;macBuild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zip&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;packager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No MacOS build is present in platform targets.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mac OS build found, creating new archive.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`ditto -c -k --sequesterRsrc --keepParent --zlibCompressionLevel 9 "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mac/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productFilename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.app" "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productFilename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buildVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-mac.zip"`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mac OS build archive has been created.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_GENERATED_BINARY_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productFilename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buildVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-mac.zip`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appBuilderPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; blockmap --input="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;APP_GENERATED_BINARY_PATH&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" --output="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productFilename&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;macBuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buildVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-mac.zip.blockmap" --compression=gzip`&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sha512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ymlPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;latest-mac.yml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ymlData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;safeLoad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ymlPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="nx"&gt;ymlData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha512&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sha512&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;ymlData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;sha512&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sha512&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;ymlData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;yamlStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;safeDump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ymlData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ymlPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;yamlStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Successfully updated YAML file and configurations with blockmap.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error in updating YAML file and configurations with blockmap.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Electron Builder has a couple of hooks you can use to run the &lt;code&gt;fixMacDistributionArchive.js&lt;/code&gt; file. Open your &lt;code&gt;vue.config.js&lt;/code&gt; file if you are using the Vue CLI and add the &lt;code&gt;afterAllArtifactBuild&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;module.exports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;configureWebpack:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;devtool:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'source-map'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;resolve:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;alias:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;'vue$':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'vue/dist/vue.esm.js'&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;pluginOptions:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;electronBuilder:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;nodeIntegration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;builderOptions:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;mac:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="err"&gt;hardenedRuntime:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="err"&gt;entitlements:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./build/entitlements.mac.inherit.plist"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;linux:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="err"&gt;target:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"AppImage"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;publish:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'github'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;appId:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'nl.codebite.artisan-remote'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;afterSign:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'./afterSignHook.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;afterAllArtifactBuild:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'./fixMacDistributionArchive.js'&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All done! You should now be able to share your awesome app with the rest of the world. I'm working on a solution to make it easier for you to distribute your applications. If you are interested make sure you subscribe and be notified at &lt;a href="https://unlock.sh"&gt;Unlock&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article was originally published on &lt;a href="https://philo.dev"&gt;philo.dev&lt;/a&gt;. Head over there if you like this post and want to read others like it.&lt;/p&gt;

</description>
      <category>electron</category>
      <category>notarize</category>
      <category>macos</category>
      <category>node</category>
    </item>
    <item>
      <title>Introducing Artisan Remote</title>
      <dc:creator>Philo Hermans</dc:creator>
      <pubDate>Tue, 22 Dec 2020 17:20:45 +0000</pubDate>
      <link>https://forem.com/philo01/introducing-artisan-remote-28il</link>
      <guid>https://forem.com/philo01/introducing-artisan-remote-28il</guid>
      <description>&lt;p&gt;Over the years, I've SSH'ed into dozens of servers to execute Laravel Artisan commands. Creating some bash shortcuts made things a bit easier, luckily. On some occasions, though, clients did not support SSH, and in other cases, they did, but it was a terrible process of back-and-forth emails between clients and hosting providers to get things sorted.&lt;/p&gt;

&lt;p&gt;I started to play around with Electron (build cross-platform desktop apps with JavaScript, HTML, and CSS) and had the idea to create something that would fix this issue as my first Electron project. So without further ado, I would like to introduce you to Artisan Remote, the perfect companion for your Laravel applications.&lt;/p&gt;

&lt;p&gt;Artisan Remote is a desktop application that allows you to run artisan commands with a simple click and view the output of earlier executed commands across multiple Laravel applications.&lt;/p&gt;

&lt;p&gt;Behind the scenes, the application utilizes HTTP endpoints to send and retrieve information from your Laravel applications. To allow Laravel artisan commands to execute commands via HTTP endpoints instead of the command line, I've created a simple package that exposes your artisan commands to the web.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;Open your terminal, switch to your favorite Laravel application, and install the Artisan Remote composer package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;composer require philo/artisan-remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you will need to publish the associated configuration file with the &lt;code&gt;vendor:publish&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan vendor:publish --provider="Philo\ArtisanRemote\ArtisanRemoteServiceProvider"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will place a configuration file in your Laravel application's config directory &lt;code&gt;config/artisan-remote.php&lt;/code&gt;. Inside the configuration file, you define which commands are available via the HTTP endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'commands'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;\Illuminate\Foundation\Console\UpCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Illuminate\Foundation\Console\DownCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Illuminate\Cache\Console\ClearCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given the nature and possible power of commands, you can't execute any without authentication. So Artisan Remote requires you to define at least one API authentication token. You can choose to restrict API tokens only to perform specific commands. For example, you might want to expose the up and down command to your client and give yourself access to execute the queue:restart command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'commands'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;\Illuminate\Foundation\Console\UpCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Illuminate\Foundation\Console\DownCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;\Illuminate\Cache\Console\ClearCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'auth'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'c557510d-7f2d-4e69-b600-d1050e5dc896'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;\Illuminate\Foundation\Console\UpCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;\Illuminate\Foundation\Console\DownCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;\Illuminate\Cache\Console\ClearCommand&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'240d4017-d7e3-4b30-8d74-6cb60816caf2'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Best practices recommend using environment variables for sensitive information. So be sure to adjust your config file accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;        &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'MY_ARTISAN_REMOTE_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For those who would like to be able to bring their application back online after running the down command ;) be sure to allow the desktop application to interact with your application when in maintenance mode by listing the API endpoint as an exception.&lt;/p&gt;

&lt;p&gt;You can add this exception in the CheckForMaintenanceMode.php middleware in Laravel 7 or PreventRequestsDuringMaintenance.php in Laravel 8.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;    &lt;span class="cd"&gt;/**
     * The URIs that should be reachable while maintenance mode is enabled.
     *
     * @var array
     */&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$except&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'artisan-remote/*'&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Download and configure the desktop application
&lt;/h3&gt;

&lt;p&gt;Now it's time for the exciting part. Click &lt;a href="https://unlock.sh/download/a428d391-79a8-4300-b7f7-ace69518525b" rel="noopener noreferrer"&gt;here&lt;/a&gt; to download the desktop application for your operating system and launch it!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F04eul1cnohv73fb1bfzg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F04eul1cnohv73fb1bfzg.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get started, click "Add new Laravel application." and enter your application URL and the API token you've set in the &lt;code&gt;artisan-remote.php&lt;/code&gt; configuration file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdt8zgaparqztfh4tgdk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fdt8zgaparqztfh4tgdk2.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! You've just added your first Laravel application to the Artisan Remote desktop application. To execute your first command, click on your application's name; this will present you with an overview of the commands available per your configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fitfzras4jyab24oqk14m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fitfzras4jyab24oqk14m.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the blue play icon on the right side of the command you would like to execute. Clicking the icon will display the available options for the command you've chosen. In case your command has no options, click the "Execute Command" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2y4k56qijlr74nyal2d9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F2y4k56qijlr74nyal2d9.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the command execution finishes, a new window will pop-up with your command's output. If you want, you can see the history of commands you've run earlier by clicking "Execution history" located in the cog menu.&lt;/p&gt;

&lt;p&gt;The Artisan Remote composer package and the desktop application are available for free. The desktop application source code is not available for now as I used Tailwind UI's commercial components. Depending on the amount of interest from people, I might update the design and remove the Tailwind UI components so I can open-source the project.&lt;/p&gt;

&lt;p&gt;Feedback is always welcome. If you want to report a bug, submit an idea, go to the &lt;a href="https://github.com/PhiloNL/artisan-remote" rel="noopener noreferrer"&gt;repository&lt;/a&gt;, and create an issue.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;a href="https://unlock.sh/download/a428d391-79a8-4300-b7f7-ace69518525b" rel="noopener noreferrer"&gt;Download for Mac, Windows or Linux.&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;This article was originally published on &lt;a href="https://philo.dev" rel="noopener noreferrer"&gt;philo.dev&lt;/a&gt;. Head over there if you like this post and want to read others like it.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>electron</category>
    </item>
  </channel>
</rss>
