release 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. #!/usr/bin/env bash
  2. # release <owner/repo> <commit-or-tag>
  3. #
  4. # Builds and deploys one exact commit of a repo listed in sites.json, then
  5. # tags it deploy/<target>/<UTC time> in Gogs and appends a line to the
  6. # deploy log. Runs as the deploy user, from deployd or by hand for a
  7. # rollback. Unknown repos are refused.
  8. #
  9. # The RELEASE_* variables exist only so tests can point the script at a
  10. # sandbox; on the server they are never set.
  11. set -euo pipefail
  12. conf=${RELEASE_CONF:-/etc/deployd/sites.json}
  13. git_base=${RELEASE_GIT_BASE:-git@git.monoaf.com:}
  14. work_root=${RELEASE_WORK:-/var/lib/deploy/src}
  15. apps=${RELEASE_APPS:-/opt/apps}
  16. vhosts=${RELEASE_VHOSTS:-/srv/www/vhosts}
  17. logfile=${RELEASE_LOG:-/var/log/deploys.log}
  18. by=${DEPLOY_BY:-manual}
  19. [[ $# -eq 2 ]] || { echo "usage: release <owner/repo> <commit-or-tag>" >&2; exit 2; }
  20. repo=$1; ref=$2
  21. [[ $repo =~ ^[A-Za-z0-9_-][A-Za-z0-9_.-]*/[A-Za-z0-9_-][A-Za-z0-9_.-]*$ ]] || { echo "bad repo name: $repo" >&2; exit 2; }
  22. [[ $ref =~ ^[A-Za-z0-9_./-]+$ && $ref != -* ]] || { echo "bad ref: $ref" >&2; exit 2; }
  23. site=$(jq -ce --arg r "$repo" '.[$r]' "$conf") || { echo "unknown repo $repo" >&2; exit 1; }
  24. kind=$(jq -r .kind <<<"$site")
  25. # One release at a time, including manual ones.
  26. mkdir -p "$work_root"
  27. exec 9>"$work_root/.release.lock"
  28. flock 9
  29. work=$work_root/${repo//\//_}
  30. [[ -d $work ]] || git clone --quiet "$git_base$repo.git" "$work"
  31. cd "$work"
  32. git fetch --quiet --tags origin
  33. git -c advice.detachedHead=false checkout --quiet --detach "$ref"
  34. sha=$(git rev-parse HEAD)
  35. [[ -z $(git status --porcelain) ]] || { echo "working tree not clean" >&2; exit 1; }
  36. case $kind in
  37. go)
  38. name=$(jq -r .name <<<"$site")
  39. [[ $name =~ ^[a-z0-9][a-z0-9-]*$ ]] || { echo "bad app name: $name" >&2; exit 1; }
  40. CGO_ENABLED=0 go build -trimpath -o "$apps/$name/$name.new" .
  41. content=$(jq -r '.content // empty' <<<"$site")
  42. if [[ -n $content ]]; then
  43. # -rlt, not -a: let the folder's default ACLs set group and mode, so the
  44. # app (group apps) can always read what lands there.
  45. rsync -rlt --delete "$content/" "$apps/$name/content/"
  46. fi
  47. mv "$apps/$name/$name.new" "$apps/$name/$name"
  48. sudo -n systemctl restart "goapp@$name"
  49. sleep 2
  50. systemctl is-active --quiet "goapp@$name" || { echo "goapp@$name is not active" >&2; exit 1; }
  51. target=$name ;;
  52. static)
  53. host=$(jq -r .host <<<"$site")
  54. dir=$(jq -r '.dir // "public"' <<<"$site")
  55. rsync -rlt --delete "$dir/" "$vhosts/$host/"
  56. target=$host ;;
  57. *) echo "unknown kind $kind" >&2; exit 1 ;;
  58. esac
  59. tag="deploy/$target/$(date -u +%Y%m%dT%H%M%SZ)"
  60. git tag -a "$tag" -m "Deployed $sha to $target ($by)"
  61. git push --quiet origin "$tag"
  62. echo "$(date -Is) $target $sha $tag $by" >> "$logfile"
  63. echo "released $repo $sha as $tag"