Skip to content

Instantly share code, notes, and snippets.

@m3t
Last active December 13, 2018 04:37
Show Gist options
  • Save m3t/764be3cc514724a38980 to your computer and use it in GitHub Desktop.
Save m3t/764be3cc514724a38980 to your computer and use it in GitHub Desktop.
Continuous deployment over SSH with Tavis CI, e.g. gh-pages

On Mar 28, 2013 Dan Allen explained how to deploy to github-pages automatically. Many other GitHub users like Steve Klabnik and Domenic Denicola followed with the same approach:

Use an OAuth token with public_repo or repo permission to access the Github account over HTTPS inside a virtual build environment.

The same applies to Travis CI's built-in abilities, and there are more restrictions as deploying GitHub Releases works only for tags, not for branches.

Security concerns

The token grants write access for all of your (public) repositories and organizations!

This probably doesn't matter for dedicated accounts.

Potential solution

Use a deploy key specific to a particular repository:

https://github.com/GH_USER/GH_REPO/settings/keys

Method

To deploy to a custom or unsupported provider, use the after-success build stage or script provider.

Example

This work is inspired by Tommy Chen's article about Hexo in the first place.

By the way: There exist lots of different Static Site Generators, probably in your favoured programming language!

Custom data

placeholder description
GH_USER GitHub account to use
GH_REPO Repositories in GH_USER
GH_TOKEN Personal access token
GIT_NAME Identity used for deployed commits
GIT_EMAIL -
ssh_pass Passphrase for the encrypted SSH key

Initial situation

First of all set up Travis CI!

placeholder repo branch
GH_REPO_IN site master
GH_REPO_OUT GH_USER.github.io master

There's a Hexo installation (example) inside GH_REPO_IN. The following shell-commands will be executed in GH_REPO_IN's directory.

Other conceivable scenarios

placeholder repo branch
GH_REPO_IN GH_USER.github.io src2 3
GH_REPO_OUT GH_USER.github.io master
GH_REPO_IN blog master
GH_REPO_OUT blog gh-pages 1 2

If both IN and OUT are in the same repository, it's less secure since you're able to access all branches in it with the same deploy key.

  • 1: _config.yml: branch: gh-pages under deploy:
  • 2: git checkout --orphan (branch with its own history)
  • 3: .travis.yml: Modfiy branch under deploy: and branches

Travis CI: Login

GH_TOKEN

Make sure your GitHub token has the scopes required by Travis CI.

https://github.com/settings/tokens/new

travis — read:org, repo:status, repo_deployment, user:email, write:repo_hook

$ travis

gem install travis -v 1.8.0 --no-rdoc --no-ri

If you have a GitHub token, you can use the GitHub authentication endpoint to exchange it for an access token. The Travis API server will not store the token or use it for anything else then verifying the user account.

travis help login
travis login --org --user 'GH_USER' --github-token 'GH_TOKEN'
unset HISTFILE #Quit shell without saving history
travis settings maximum_number_of_builds --set 1
travis settings
# Settings for GH_USER/GH_REPO_IN:
# [+] builds_only_with_travis_yml    Only run builds with a .travis.yml
# [+] build_pushes                   Build pushes
# [-] build_pull_requests            Build pull requests
#  1 maximum_number_of_builds       Maximum number of concurrent builds

.travis/ssh_key

ssh-keygen -t rsa -b 4096 -C "GIT_EMAIL" -f "ssh_key" -q -N "ssh_pass"
unset HISTFILE

Make sure not to add the private ssh_key to the git repository. You should ignore it regarding the worst case.

printf "\n%s\n%s\n\n" ssh_key{,.pub} >> .gitignore

Attention: Do it after hexo init!

.travis/ssh_key.pub

Copy the contents of the ssh_key.pub file to your clipboard ..

xclip -sel clip < ssh_key.pub

.. and paste it into

https://github.com/GH_USER/GH_REPO_OUT/settings/keys

.travis/ssh_key.enc

travis encrypt-file ssh_key

The ouput contains a line which begins with openssl ... and must be added to .travis/deploy.sh (modify -in and -out options)

.travis/ssh_known_hosts

Save GitHub's public SSH key and verify with fingerprint

ssh-keyscan -t rsa github.com > ssh_known_hosts
wget -q -O - https://help.github.com/articles/what-are-github-s-ssh-key-fingerprints/ \
| { grep -q "$(ssh-keygen -l -f ssh_known_hosts | cut -d' ' -f2)" \
    && echo "Public key was successfully verified by fingerprint"; }

.travis/ssh_config

Host github.com
	User git
	StrictHostKeyChecking yes
	IdentityFile ~/.ssh/id_rsa
	IdentitiesOnly yes

${GIT_NAME}, ${GIT_EMAIL} and ${ssh_pass}

travis encrypt GIT_NAME=<Your input>
travis encrypt GIT_EMAIL=<Your input>
travis encrypt ssh_pass=<Your input>

The output has to be pasted into your .travis.yml under env: global:

.travis.yml

language: node_js

sudo: false

node_js:
  # Version depends on your Hexo
  - "0.12"

cache:
  apt: true
  directories:
    - node_modules

# Optional
addons:
  apt:
    packages:
      # used by deploy.sh
      - expect
      # used by gulpfile.js
      # - graphicsmagick

env:
  global:
  - secure: "<encrypted string for GIT_NAME>"
  - secure: "<encrypted string for GIT_EMAIL>"
  - secure: "<encrypted string for ssh_pass>"
  
branches:
  only:
    - master

install:
  # package.json
  - npm install --no-optional

before_script:
  # Clone the repository
  - git clone https://github.com/GH_USER/GH_REPO_OUT .deploy_git

script:
  # package.json
  - npm run build
  
before_deploy:
   # Make the deploy-script executable
   - chmod u+x .travis/deploy.sh

deploy:
  provider: script
  script: .travis/deploy.sh
  on:
  	branch: master
  # Prevents from automatically hard resetting to the latest git version
  skip_cleanup: true

.travis/deploy.sh

#!/bin/bash
# stop executing if any errors occur
# stop executing if an unset variable is encountered
set -o errexit -o nounset

# Decrypt the private key
openssl aes-256-cbc -K $encrypted_***_key -iv $encrypted_***_iv -in .travis/ssh_key.enc -out ~/.ssh/id_rsa -d
  
# Set the permission of the key
chmod 600 ~/.ssh/id_rsa
  
# Start SSH agent in the background
eval "$(ssh-agent -s)"
  
# Add the private key to the ssh-agent
# Enter passphrase automatically
expect >/dev/null 2>&1 << EOF
  set timeout 10
  spawn ssh-add "${HOME}/.ssh/id_rsa"
  expect {
    "Enter passphrase for" {
      send "$ssh_pass\r"
    }
  }
  expect {
    timeout { exit 1 }
    "denied" { exit 1 }
    eof { exit 0 }
  }
EOF

# Copy SSH config and known_hosts
cp .travis/ssh_config ~/.ssh/config
cp .travis/ssh_known_hosts ~/.ssh/known_hosts
  
# Set Git config
git config --global user.name "${GIT_NAME}"
git config --global user.email "${GIT_EMAIL}"

# Deploy with Hexo
# Hide any sensitive credential data that might otherwise be exposed
# In case of debugging you should remove those redirections to /dev/null
expect >/dev/null 2>&1 << EOF
  set timeout 600
  spawn hexo deploy
  expect {
    "Enter passphrase for" {
      send "$ssh_pass\r"
    }
  }
  expect {
    timeout { exit 1 }
    "denied" { exit 1 }
    eof { exit 0 }
  }
EOF

Extract of other files

package.json

{
  "name": "hexo-site",
  "version": "0.0.0",
  "private": true,
  "hexo": {
    "version": ""
  },
  "scripts": {
    "build": "hexo generate && gulp"
  },
  "dependencies": {
    "hexo": "^3.1.1",
    "hexo-deployer-git": "0.0.4",
    ...
  },
  "devDependencies": {
    "gulp": "^3.8.11",
     ...
  },
  "optionalDependencies": {
    ...
  }
}

_config.yml

...

# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
  type: git
  repo: git@github.com:GH_USER/GH_REPO_OUT
  branch: master

Creative Commons License
This work by m3t (96bd6c8bb869fe632b3650fb7156c797ef8c2a055d31dde634565f3edda485ba) <mlt [at] posteo [dot] de> is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment