Skip to content

Commit ed8463d

Browse files
authored
Merge pull request #12 from github/vdye/release-pipeline
Create Makefile & release pipeline, make `git-bundle-server web-server` more resilient
2 parents d70a1b1 + 3259e5f commit ed8463d

File tree

20 files changed

+978
-46
lines changed

20 files changed

+978
-46
lines changed

Diff for: .github/workflows/release.yml

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v[0-9]*.[0-9]*.[0-9]*' # matches "v<number...>.<number...>.<number>..."
7+
8+
jobs:
9+
# Check prerequisites for the workflow
10+
prereqs:
11+
name: Prerequisites
12+
runs-on: ubuntu-latest
13+
outputs:
14+
tag_name: ${{ steps.tag.outputs.name }} # The full name of the tag, e.g. v1.0.0
15+
tag_version: ${{ steps.tag.outputs.version }} # The version number (without preceding "v"), e.g. 1.0.0
16+
steps:
17+
- name: Determine tag to build
18+
run: |
19+
echo "name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
20+
echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
21+
id: tag
22+
23+
package:
24+
needs: prereqs
25+
name: ${{matrix.jobs.jobname}}
26+
strategy:
27+
fail-fast: false
28+
matrix:
29+
jobs:
30+
- jobname: Create MacOS .pkg (x86_64)
31+
goarch: amd64
32+
pool: macos-latest
33+
artifact: _dist/*.pkg
34+
- jobname: Create MacOS .pkg (ARM64)
35+
goarch: arm64
36+
pool: macos-latest
37+
artifact: _dist/*.pkg
38+
- jobname: Create binary Debian package (x86_64)
39+
goarch: amd64
40+
pool: ubuntu-latest
41+
artifact: _dist/*.deb
42+
env:
43+
GOARCH: ${{matrix.jobs.goarch}}
44+
runs-on: ${{matrix.jobs.pool}}
45+
steps:
46+
- name: Setup Go
47+
uses: actions/setup-go@v3
48+
with:
49+
go-version: '1.19.0'
50+
- name: Clone repository
51+
uses: actions/checkout@v3
52+
- name: Build the release artifact
53+
run: make package VERSION=${{ needs.prereqs.outputs.tag_version }}
54+
- name: Get the release artifact
55+
shell: bash
56+
run: |
57+
artifacts=(${{matrix.jobs.artifact}})
58+
59+
# Get path to, and name of, artifact
60+
artifactPath="${artifacts[0]}"
61+
artifactName=$(basename "$artifactPath")
62+
63+
# Export variables to environment
64+
echo "artifactPath=$artifactPath" >> $GITHUB_ENV
65+
echo "artifactName=$artifactName" >> $GITHUB_ENV
66+
- name: Upload release artifact
67+
uses: actions/upload-artifact@v3
68+
with:
69+
name: ${{env.artifactName}}
70+
path: ${{github.workspace}}/${{env.artifactPath}}
71+
if-no-files-found: error
72+
73+
create-github-release:
74+
needs: [prereqs, package]
75+
name: Create release with artifacts
76+
runs-on: ubuntu-latest
77+
steps:
78+
- name: Download packages
79+
uses: actions/download-artifact@v3
80+
with:
81+
path: artifacts-raw
82+
- name: Consolidate artifact directory
83+
shell: bash
84+
run: |
85+
# This step is needed to extract the artifacts from their wrapper
86+
# parent directories. For more details, see
87+
# https://github.com/actions/download-artifact#download-all-artifacts
88+
mkdir artifacts
89+
mv artifacts-raw/*/* artifacts/
90+
- name: Create release & attach artifacts
91+
uses: actions/github-script@v6
92+
with:
93+
script: |
94+
const fs = require('fs');
95+
const path = require('path');
96+
97+
var releaseMetadata = {
98+
owner: context.repo.owner,
99+
repo: context.repo.repo
100+
};
101+
102+
// Create the release
103+
var tagName = "${{ needs.prereqs.outputs.tag_name }}";
104+
var createdRelease = await github.rest.repos.createRelease({
105+
...releaseMetadata,
106+
draft: true,
107+
tag_name: tagName,
108+
name: tagName,
109+
generate_release_notes: true
110+
});
111+
releaseMetadata.release_id = createdRelease.data.id;
112+
113+
// Upload contents of directory to the release created above
114+
async function uploadDirectoryToRelease(directory, includeExtensions=[]) {
115+
return fs.promises.readdir(directory)
116+
.then(async(files) => Promise.all(
117+
files.filter(file => {
118+
return includeExtensions.length==0 || includeExtensions.includes(path.extname(file).toLowerCase());
119+
})
120+
.map(async (file) => {
121+
var filePath = path.join(directory, file);
122+
return github.rest.repos.uploadReleaseAsset({
123+
...releaseMetadata,
124+
name: file,
125+
headers: {
126+
"content-length": (await fs.promises.stat(filePath)).size
127+
},
128+
data: fs.createReadStream(filePath)
129+
});
130+
}))
131+
);
132+
}
133+
134+
await Promise.all([
135+
// Upload all artifacts
136+
uploadDirectoryToRelease('artifacts', ['.pkg', '.deb'])
137+
]);
138+

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/git-bundle-server
22
/git-bundle-web-server
3+
/bin/
4+
/_dist/

Diff for: .vscode/settings.json

+19-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,25 @@
66
"editor.wordWrap": "off",
77
"files.trimTrailingWhitespace": true,
88
},
9+
"[makefile]": {
10+
"editor.detectIndentation": false,
11+
"editor.insertSpaces": false,
12+
"editor.tabSize": 8,
13+
"editor.wordWrap": "off",
14+
"files.trimTrailingWhitespace": true,
15+
},
16+
"[shellscript]": {
17+
"editor.detectIndentation": false,
18+
"editor.insertSpaces": false,
19+
"editor.tabSize": 8,
20+
"editor.wordWrap": "off",
21+
"files.trimTrailingWhitespace": true,
22+
},
923
"files.associations": {
10-
"*.md": "markdown"
24+
"*.md": "markdown",
25+
"*.sh": "shellscript",
26+
"prerm": "shellscript",
27+
"postinstall": "shellscript",
28+
"Makefile": "makefile"
1129
}
1230
}

Diff for: Makefile

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Default target
2+
build:
3+
4+
# Project metadata (note: to package, VERSION *must* be set by the caller)
5+
NAME := git-bundle-server
6+
VERSION :=
7+
PACKAGE_REVISION := 1
8+
9+
# Helpful paths
10+
BINDIR := $(CURDIR)/bin
11+
DISTDIR := $(CURDIR)/_dist
12+
13+
# Platform information
14+
GOOS := $(shell go env GOOS)
15+
GOARCH := $(shell go env GOARCH)
16+
17+
# Packaging information
18+
SUPPORTED_PACKAGE_GOARCHES := amd64 arm64
19+
PACKAGE_ARCH := $(GOARCH)
20+
21+
# Build targets
22+
.PHONY: build
23+
build:
24+
$(RM) -r $(BINDIR)
25+
@mkdir -p $(BINDIR)
26+
GOOS="$(GOOS)" GOARCH="$(GOARCH)" go build -o $(BINDIR) ./...
27+
28+
# Packaging targets
29+
.PHONY: check-arch
30+
check-arch:
31+
$(if $(filter $(GOARCH),$(SUPPORTED_PACKAGE_GOARCHES)), , \
32+
$(error cannot create package for GOARCH "$(GOARCH)"; \
33+
supported architectures are: $(SUPPORTED_PACKAGE_GOARCHES)))
34+
35+
.PHONY: check-version
36+
check-version:
37+
$(if $(VERSION), , $(error version is undefined))
38+
39+
ifeq ($(GOOS),linux)
40+
# Linux binary .deb file
41+
# Steps:
42+
# 1. Layout files in _dist/deb/root/ as they'll be installed (unlike MacOS
43+
# .pkg packages, symlinks created in the payload are preserved, so we
44+
# create them here to avoid doing so in a post-install step).
45+
# 2. Create the binary deb package in _dist/deb/.
46+
47+
# Platform-specific variables
48+
DEBDIR := $(DISTDIR)/deb
49+
DEB_FILENAME := $(DISTDIR)/$(NAME)_$(VERSION)-$(PACKAGE_REVISION)_$(PACKAGE_ARCH).deb
50+
51+
# Targets
52+
$(DEBDIR)/root: check-arch build
53+
@echo
54+
@echo "======== Formatting package contents ========"
55+
@build/package/layout-unix.sh --bindir="$(BINDIR)" \
56+
--include-symlinks \
57+
--output="$(DEBDIR)/root"
58+
59+
$(DEB_FILENAME): check-version $(DEBDIR)/root
60+
@echo
61+
@echo "======== Creating binary Debian package ========"
62+
@build/package/deb/pack.sh --payload="$(DEBDIR)/root" \
63+
--scripts="$(CURDIR)/build/package/deb/scripts" \
64+
--arch="$(PACKAGE_ARCH)" \
65+
--version="$(VERSION)" \
66+
--output="$(DEB_FILENAME)"
67+
68+
.PHONY: package
69+
package: $(DEB_FILENAME)
70+
71+
else ifeq ($(GOOS),darwin)
72+
# MacOS .pkg file
73+
# Steps:
74+
# 1. Layout files in _dist/pkg/payload/ as they'll be installed (including
75+
# uninstall.sh script).
76+
# 2. Create the product archive in _dist/.
77+
78+
# Platform-specific variables
79+
PKGDIR := $(DISTDIR)/pkg
80+
PKG_FILENAME := $(DISTDIR)/$(NAME)_$(VERSION)-$(PACKAGE_REVISION)_$(PACKAGE_ARCH).pkg
81+
82+
# Targets
83+
$(PKGDIR)/payload: check-arch build
84+
@echo
85+
@echo "======== Formatting package contents ========"
86+
@build/package/layout-unix.sh --bindir="$(BINDIR)" \
87+
--uninstaller="$(CURDIR)/build/package/pkg/uninstall.sh" \
88+
--output="$(PKGDIR)/payload"
89+
90+
$(PKG_FILENAME): check-version $(PKGDIR)/payload
91+
@echo
92+
@echo "======== Creating product archive package ========"
93+
@build/package/pkg/pack.sh --version="$(VERSION)" \
94+
--payload="$(PKGDIR)/payload" \
95+
--output="$(PKG_FILENAME)"
96+
97+
.PHONY: package
98+
package: $(PKG_FILENAME)
99+
100+
else
101+
# Packaging not supported for platform, exit with error.
102+
.PHONY: package
103+
package:
104+
$(error cannot create package for GOOS "$(GOOS)")
105+
106+
endif
107+
108+
# Cleanup targets
109+
.PHONY: clean
110+
clean:
111+
go clean ./...
112+
$(RM) -r $(BINDIR)
113+
$(RM) -r $(DISTDIR)

Diff for: build/package/deb/pack.sh

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
#!/bin/bash
2+
die () {
3+
echo "$*" >&2
4+
exit 1
5+
}
6+
7+
# Directories
8+
THISDIR="$( cd "$(dirname "$0")" ; pwd -P )"
9+
10+
# Local paths
11+
UNINSTALLER="$THISDIR/uninstall.sh"
12+
13+
# Parse script arguments
14+
for i in "$@"
15+
do
16+
case "$i" in
17+
--payload=*)
18+
DEBROOT="${i#*=}"
19+
shift # past argument=value
20+
;;
21+
--scripts=*)
22+
SCRIPT_DIR="${i#*=}"
23+
shift # past argument=value
24+
;;
25+
--arch=*)
26+
ARCH="${i#*=}"
27+
shift # past argument=value
28+
;;
29+
--version=*)
30+
VERSION="${i#*=}"
31+
shift # past argument=value
32+
;;
33+
--output=*)
34+
DEBOUT="${i#*=}"
35+
shift # past argument=value
36+
;;
37+
*)
38+
die "unknown option '$i'"
39+
;;
40+
esac
41+
done
42+
43+
# Perform pre-execution checks
44+
if [ -z "$DEBROOT" ]; then
45+
die "--payload was not set"
46+
elif [ ! -d "$DEBROOT" ]; then
47+
die "Could not find '$DEBROOT'. Did you run layout-unix.sh first?"
48+
fi
49+
if [ -z "$ARCH" ]; then
50+
die "--arch was not set"
51+
fi
52+
if [ -z "$VERSION" ]; then
53+
die "--version was not set"
54+
fi
55+
if [ -z "$DEBOUT" ]; then
56+
die "--output was not set"
57+
fi
58+
59+
# Exit as soon as any line fails
60+
set -e
61+
62+
# Cleanup old package
63+
if [ -e "$DEBOUT" ]; then
64+
echo "Deleting old package '$DEBOUT'..."
65+
rm -f "$DEBOUT"
66+
fi
67+
68+
CONTROLDIR="$DEBROOT/DEBIAN"
69+
70+
# Ensure the parent directory for the .deb exists
71+
mkdir -p "$(dirname "$DEBOUT")"
72+
73+
# Build .deb
74+
mkdir -m 755 -p "$CONTROLDIR"
75+
76+
# Create the debian control file
77+
cat >"$CONTROLDIR/control" <<EOF
78+
Package: git-bundle-server
79+
Version: $VERSION
80+
Section: vcs
81+
Priority: optional
82+
Architecture: $ARCH
83+
Depends:
84+
Maintainer: Git Bundle Server <[email protected]>
85+
Description: A self-hostable Git bundle server.
86+
EOF
87+
88+
# Copy the maintainer scripts, if they exist
89+
if [ -d "$SCRIPT_DIR" ]; then
90+
cp -R "$SCRIPT_DIR/." "$CONTROLDIR"
91+
fi
92+
93+
dpkg-deb -Zxz --build "$DEBROOT" "$DEBOUT"

Diff for: build/package/deb/scripts/prerm

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Stop & cleanup the web server as the logged-in user
5+
# The XDG_RUNTIME_DIR is required for 'systemctl' to work, so we manually set it
6+
# to that of the logged-in user.
7+
LOGGED_IN_USER="${SUDO_USER:-${USER}}"
8+
LOGGED_IN_UID="$(sudo -u $LOGGED_IN_USER id -u)"
9+
sudo -u $LOGGED_IN_USER XDG_RUNTIME_DIR=/run/user/$LOGGED_IN_UID \
10+
/usr/local/git-bundle-server/bin/git-bundle-server web-server stop --remove
11+
12+
exit 0

0 commit comments

Comments
 (0)