-
Notifications
You must be signed in to change notification settings - Fork 25
Create Makefile & release pipeline, make git-bundle-server web-server
more resilient
#12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
db1c776
374758a
02282c4
8c6a468
c82b8e0
7660c16
be8f1ea
7d869a7
bd85f79
9c8dae4
3259e5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
name: Release | ||
|
||
on: | ||
push: | ||
tags: | ||
- 'v[0-9]*.[0-9]*.[0-9]*' # matches "v<number...>.<number...>.<number>..." | ||
|
||
jobs: | ||
# Check prerequisites for the workflow | ||
prereqs: | ||
name: Prerequisites | ||
runs-on: ubuntu-latest | ||
outputs: | ||
tag_name: ${{ steps.tag.outputs.name }} # The full name of the tag, e.g. v1.0.0 | ||
tag_version: ${{ steps.tag.outputs.version }} # The version number (without preceding "v"), e.g. 1.0.0 | ||
steps: | ||
- name: Determine tag to build | ||
run: | | ||
echo "name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT | ||
echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT | ||
id: tag | ||
|
||
package: | ||
needs: prereqs | ||
name: ${{matrix.jobs.jobname}} | ||
strategy: | ||
fail-fast: false | ||
matrix: | ||
jobs: | ||
- jobname: Create MacOS .pkg (x86_64) | ||
goarch: amd64 | ||
pool: macos-latest | ||
artifact: _dist/*.pkg | ||
- jobname: Create MacOS .pkg (ARM64) | ||
goarch: arm64 | ||
pool: macos-latest | ||
artifact: _dist/*.pkg | ||
- jobname: Create binary Debian package (x86_64) | ||
goarch: amd64 | ||
pool: ubuntu-latest | ||
artifact: _dist/*.deb | ||
env: | ||
GOARCH: ${{matrix.jobs.goarch}} | ||
runs-on: ${{matrix.jobs.pool}} | ||
steps: | ||
- name: Setup Go | ||
uses: actions/setup-go@v3 | ||
with: | ||
go-version: '1.19.0' | ||
- name: Clone repository | ||
uses: actions/checkout@v3 | ||
- name: Build the release artifact | ||
run: make package VERSION=${{ needs.prereqs.outputs.tag_version }} | ||
- name: Get the release artifact | ||
shell: bash | ||
run: | | ||
artifacts=(${{matrix.jobs.artifact}}) | ||
|
||
# Get path to, and name of, artifact | ||
artifactPath="${artifacts[0]}" | ||
artifactName=$(basename "$artifactPath") | ||
|
||
# Export variables to environment | ||
echo "artifactPath=$artifactPath" >> $GITHUB_ENV | ||
echo "artifactName=$artifactName" >> $GITHUB_ENV | ||
- name: Upload release artifact | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: ${{env.artifactName}} | ||
path: ${{github.workspace}}/${{env.artifactPath}} | ||
if-no-files-found: error | ||
|
||
create-github-release: | ||
needs: [prereqs, package] | ||
name: Create release with artifacts | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Download packages | ||
uses: actions/download-artifact@v3 | ||
with: | ||
path: artifacts-raw | ||
- name: Consolidate artifact directory | ||
shell: bash | ||
run: | | ||
# This step is needed to extract the artifacts from their wrapper | ||
# parent directories. For more details, see | ||
# https://github.com/actions/download-artifact#download-all-artifacts | ||
mkdir artifacts | ||
mv artifacts-raw/*/* artifacts/ | ||
- name: Create release & attach artifacts | ||
uses: actions/github-script@v6 | ||
with: | ||
script: | | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
|
||
var releaseMetadata = { | ||
owner: context.repo.owner, | ||
repo: context.repo.repo | ||
}; | ||
|
||
// Create the release | ||
var tagName = "${{ needs.prereqs.outputs.tag_name }}"; | ||
var createdRelease = await github.rest.repos.createRelease({ | ||
...releaseMetadata, | ||
draft: true, | ||
tag_name: tagName, | ||
name: tagName, | ||
generate_release_notes: true | ||
}); | ||
releaseMetadata.release_id = createdRelease.data.id; | ||
|
||
// Upload contents of directory to the release created above | ||
async function uploadDirectoryToRelease(directory, includeExtensions=[]) { | ||
return fs.promises.readdir(directory) | ||
.then(async(files) => Promise.all( | ||
files.filter(file => { | ||
return includeExtensions.length==0 || includeExtensions.includes(path.extname(file).toLowerCase()); | ||
}) | ||
.map(async (file) => { | ||
var filePath = path.join(directory, file); | ||
return github.rest.repos.uploadReleaseAsset({ | ||
...releaseMetadata, | ||
name: file, | ||
headers: { | ||
"content-length": (await fs.promises.stat(filePath)).size | ||
}, | ||
data: fs.createReadStream(filePath) | ||
}); | ||
})) | ||
); | ||
} | ||
|
||
await Promise.all([ | ||
// Upload all artifacts | ||
uploadDirectoryToRelease('artifacts', ['.pkg', '.deb']) | ||
]); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
/git-bundle-server | ||
/git-bundle-web-server | ||
/bin/ | ||
vdye marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/_dist/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,25 @@ | |
"editor.wordWrap": "off", | ||
"files.trimTrailingWhitespace": true, | ||
}, | ||
"[makefile]": { | ||
"editor.detectIndentation": false, | ||
"editor.insertSpaces": false, | ||
"editor.tabSize": 8, | ||
"editor.wordWrap": "off", | ||
"files.trimTrailingWhitespace": true, | ||
}, | ||
"[shellscript]": { | ||
"editor.detectIndentation": false, | ||
"editor.insertSpaces": false, | ||
"editor.tabSize": 8, | ||
"editor.wordWrap": "off", | ||
"files.trimTrailingWhitespace": true, | ||
}, | ||
Comment on lines
+9
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good guardrails |
||
"files.associations": { | ||
"*.md": "markdown" | ||
"*.md": "markdown", | ||
"*.sh": "shellscript", | ||
"prerm": "shellscript", | ||
"postinstall": "shellscript", | ||
"Makefile": "makefile" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# Default target | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! This Makefile is really simple and lovely. |
||
build: | ||
|
||
# Project metadata (note: to package, VERSION *must* be set by the caller) | ||
NAME := git-bundle-server | ||
VERSION := | ||
PACKAGE_REVISION := 1 | ||
|
||
# Helpful paths | ||
BINDIR := $(CURDIR)/bin | ||
DISTDIR := $(CURDIR)/_dist | ||
|
||
# Platform information | ||
GOOS := $(shell go env GOOS) | ||
GOARCH := $(shell go env GOARCH) | ||
Comment on lines
+13
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are set from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revisiting this: I was incorrect. You are not manually overriding these, but using them to decide which kind of installer is necessary. I was expecting this to need to change to build macOS ARM binaries on intel machines, but I don't see that changing in those workflows. |
||
|
||
# Packaging information | ||
SUPPORTED_PACKAGE_GOARCHES := amd64 arm64 | ||
PACKAGE_ARCH := $(GOARCH) | ||
|
||
# Build targets | ||
.PHONY: build | ||
build: | ||
$(RM) -r $(BINDIR) | ||
@mkdir -p $(BINDIR) | ||
GOOS="$(GOOS)" GOARCH="$(GOARCH)" go build -o $(BINDIR) ./... | ||
|
||
# Packaging targets | ||
.PHONY: check-arch | ||
check-arch: | ||
$(if $(filter $(GOARCH),$(SUPPORTED_PACKAGE_GOARCHES)), , \ | ||
$(error cannot create package for GOARCH "$(GOARCH)"; \ | ||
supported architectures are: $(SUPPORTED_PACKAGE_GOARCHES))) | ||
|
||
.PHONY: check-version | ||
check-version: | ||
$(if $(VERSION), , $(error version is undefined)) | ||
|
||
ifeq ($(GOOS),linux) | ||
# Linux binary .deb file | ||
# Steps: | ||
# 1. Layout files in _dist/deb/root/ as they'll be installed (unlike MacOS | ||
# .pkg packages, symlinks created in the payload are preserved, so we | ||
# create them here to avoid doing so in a post-install step). | ||
# 2. Create the binary deb package in _dist/deb/. | ||
|
||
# Platform-specific variables | ||
DEBDIR := $(DISTDIR)/deb | ||
DEB_FILENAME := $(DISTDIR)/$(NAME)_$(VERSION)-$(PACKAGE_REVISION)_$(PACKAGE_ARCH).deb | ||
|
||
# Targets | ||
$(DEBDIR)/root: check-arch build | ||
@echo | ||
@echo "======== Formatting package contents ========" | ||
@build/package/layout-unix.sh --bindir="$(BINDIR)" \ | ||
--include-symlinks \ | ||
--output="$(DEBDIR)/root" | ||
|
||
$(DEB_FILENAME): check-version $(DEBDIR)/root | ||
@echo | ||
@echo "======== Creating binary Debian package ========" | ||
@build/package/deb/pack.sh --payload="$(DEBDIR)/root" \ | ||
--scripts="$(CURDIR)/build/package/deb/scripts" \ | ||
--arch="$(PACKAGE_ARCH)" \ | ||
--version="$(VERSION)" \ | ||
--output="$(DEB_FILENAME)" | ||
|
||
.PHONY: package | ||
package: $(DEB_FILENAME) | ||
|
||
else ifeq ($(GOOS),darwin) | ||
# MacOS .pkg file | ||
# Steps: | ||
# 1. Layout files in _dist/pkg/payload/ as they'll be installed (including | ||
# uninstall.sh script). | ||
# 2. Create the product archive in _dist/. | ||
|
||
# Platform-specific variables | ||
PKGDIR := $(DISTDIR)/pkg | ||
PKG_FILENAME := $(DISTDIR)/$(NAME)_$(VERSION)-$(PACKAGE_REVISION)_$(PACKAGE_ARCH).pkg | ||
|
||
# Targets | ||
$(PKGDIR)/payload: check-arch build | ||
@echo | ||
@echo "======== Formatting package contents ========" | ||
@build/package/layout-unix.sh --bindir="$(BINDIR)" \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just noticing that you're able to share a lot in this script between Linux and macOS. |
||
--uninstaller="$(CURDIR)/build/package/pkg/uninstall.sh" \ | ||
--output="$(PKGDIR)/payload" | ||
|
||
$(PKG_FILENAME): check-version $(PKGDIR)/payload | ||
@echo | ||
@echo "======== Creating product archive package ========" | ||
@build/package/pkg/pack.sh --version="$(VERSION)" \ | ||
--payload="$(PKGDIR)/payload" \ | ||
--output="$(PKG_FILENAME)" | ||
|
||
.PHONY: package | ||
package: $(PKG_FILENAME) | ||
|
||
else | ||
# Packaging not supported for platform, exit with error. | ||
.PHONY: package | ||
package: | ||
$(error cannot create package for GOOS "$(GOOS)") | ||
|
||
endif | ||
|
||
# Cleanup targets | ||
.PHONY: clean | ||
clean: | ||
go clean ./... | ||
$(RM) -r $(BINDIR) | ||
$(RM) -r $(DISTDIR) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
#!/bin/bash | ||
die () { | ||
echo "$*" >&2 | ||
exit 1 | ||
} | ||
|
||
# Directories | ||
THISDIR="$( cd "$(dirname "$0")" ; pwd -P )" | ||
|
||
# Local paths | ||
UNINSTALLER="$THISDIR/uninstall.sh" | ||
|
||
# Parse script arguments | ||
for i in "$@" | ||
do | ||
case "$i" in | ||
--payload=*) | ||
DEBROOT="${i#*=}" | ||
shift # past argument=value | ||
;; | ||
--scripts=*) | ||
SCRIPT_DIR="${i#*=}" | ||
shift # past argument=value | ||
;; | ||
--arch=*) | ||
ARCH="${i#*=}" | ||
shift # past argument=value | ||
;; | ||
--version=*) | ||
VERSION="${i#*=}" | ||
shift # past argument=value | ||
;; | ||
--output=*) | ||
DEBOUT="${i#*=}" | ||
shift # past argument=value | ||
;; | ||
*) | ||
die "unknown option '$i'" | ||
;; | ||
esac | ||
done | ||
|
||
# Perform pre-execution checks | ||
if [ -z "$DEBROOT" ]; then | ||
die "--payload was not set" | ||
elif [ ! -d "$DEBROOT" ]; then | ||
die "Could not find '$DEBROOT'. Did you run layout-unix.sh first?" | ||
fi | ||
if [ -z "$ARCH" ]; then | ||
die "--arch was not set" | ||
fi | ||
if [ -z "$VERSION" ]; then | ||
die "--version was not set" | ||
fi | ||
if [ -z "$DEBOUT" ]; then | ||
die "--output was not set" | ||
fi | ||
|
||
# Exit as soon as any line fails | ||
set -e | ||
|
||
# Cleanup old package | ||
if [ -e "$DEBOUT" ]; then | ||
echo "Deleting old package '$DEBOUT'..." | ||
rm -f "$DEBOUT" | ||
fi | ||
|
||
CONTROLDIR="$DEBROOT/DEBIAN" | ||
|
||
# Ensure the parent directory for the .deb exists | ||
mkdir -p "$(dirname "$DEBOUT")" | ||
|
||
# Build .deb | ||
mkdir -m 755 -p "$CONTROLDIR" | ||
|
||
# Create the debian control file | ||
cat >"$CONTROLDIR/control" <<EOF | ||
Package: git-bundle-server | ||
Version: $VERSION | ||
Section: vcs | ||
Priority: optional | ||
Architecture: $ARCH | ||
Depends: | ||
Maintainer: Git Bundle Server <[email protected]> | ||
Description: A self-hostable Git bundle server. | ||
EOF | ||
|
||
# Copy the maintainer scripts, if they exist | ||
if [ -d "$SCRIPT_DIR" ]; then | ||
cp -R "$SCRIPT_DIR/." "$CONTROLDIR" | ||
fi | ||
|
||
dpkg-deb -Zxz --build "$DEBROOT" "$DEBOUT" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#!/bin/bash | ||
set -e | ||
|
||
# Stop & cleanup the web server as the logged-in user | ||
# The XDG_RUNTIME_DIR is required for 'systemctl' to work, so we manually set it | ||
# to that of the logged-in user. | ||
LOGGED_IN_USER="${SUDO_USER:-${USER}}" | ||
LOGGED_IN_UID="$(sudo -u $LOGGED_IN_USER id -u)" | ||
sudo -u $LOGGED_IN_USER XDG_RUNTIME_DIR=/run/user/$LOGGED_IN_UID \ | ||
/usr/local/git-bundle-server/bin/git-bundle-server web-server stop --remove | ||
|
||
exit 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spiffy!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking forward to using the generated release notes in a new project. Having all of the releases match this generated format should help, as opposed to other places where an established release note pattern doesn't match the generated notes.