diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1cea58d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml new file mode 100644 index 0000000..b27e31f --- /dev/null +++ b/.github/workflows/ci-docker.yml @@ -0,0 +1,31 @@ +name: Docker CI + +on: + pull_request: + branches: ['main'] + paths: ['Dockerfile','cmd/**','docs/**','internal/**','go.*','.github/workflows/ci-docker.yml'] + +env: + GHCR_IMAGE_NAME: ghcr.io/blinklabs-io/node + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: qemu + uses: docker/setup-qemu-action@v3 + - uses: docker/setup-buildx-action@v3 + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.GHCR_IMAGE_NAME }} + - name: build + uses: docker/build-push-action@v5 + with: + context: . + push: false + ### TODO: test multiple platforms + # platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml new file mode 100644 index 0000000..9beef23 --- /dev/null +++ b/.github/workflows/conventional-commits.yml @@ -0,0 +1,16 @@ +# The below is pulled from upstream and slightly modified +# https://github.com/webiny/action-conventional-commits/blob/master/README.md#usage + +name: Conventional Commits + +on: + pull_request: + +jobs: + build: + name: Conventional Commits + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: webiny/action-conventional-commits@v1.3.0 diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml new file mode 100644 index 0000000..c5e7a02 --- /dev/null +++ b/.github/workflows/go-test.yml @@ -0,0 +1,25 @@ +name: go-test + +on: + push: + tags: + - v* + branches: + - main + pull_request: + +jobs: + go-test: + name: go-test + strategy: + matrix: + go-version: [1.21.x, 1.22.x] + platform: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + - name: go-test + run: go test ./... diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..e059eeb --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,33 @@ +# This file was copied from the following URL and modified: +# https://github.com/golangci/golangci-lint-action/blob/master/README.md#how-to-use + +name: golangci-lint +on: + push: + tags: + - v* + branches: + - main + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.21.x + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: v1.54 # current version at time of commit + args: --timeout=10m + # Only show new issues in a PR but show all issues for pushes + only-new-issues: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f154e95 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,141 @@ +name: publish + +on: + push: + branches: ['main'] + tags: + - 'v*.*.*' + +concurrency: ${{ github.ref }} + +jobs: + create-draft-release: + runs-on: ubuntu-latest + outputs: + RELEASE_ID: ${{ steps.create-release.outputs.result }} + steps: + - run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV" + - uses: actions/github-script@v7 + id: create-release + if: startsWith(github.ref, 'refs/tags/') + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + result-encoding: string + script: | + try { + const response = await github.rest.repos.createRelease({ + draft: true, + generate_release_notes: true, + name: process.env.RELEASE_TAG, + owner: context.repo.owner, + prerelease: false, + repo: context.repo.repo, + tag_name: process.env.RELEASE_TAG, + }); + + return response.data.id; + } catch (error) { + core.setFailed(error.message); + } + + build-binaries: + strategy: + matrix: + os: [linux, darwin] + arch: [amd64, arm64] + runs-on: ubuntu-latest + needs: [create-draft-release] + steps: + - run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV" + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: 1.21.x + - name: Build binary + run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} make build + - name: Upload release asset + if: startsWith(github.ref, 'refs/tags/') + run: | + _filename=node-${{ env.RELEASE_TAG }}-${{ matrix.os }}-${{ matrix.arch }} + mv node ${_filename} + curl \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @${_filename} \ + https://uploads.github.com/repos/${{ github.repository_owner }}/node/releases/${{ needs.create-draft-release.outputs.RELEASE_ID }}/assets?name=${_filename} + + build-images: + runs-on: ubuntu-latest + needs: [create-draft-release] + steps: + - run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV" + - uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: blinklabs + password: ${{ secrets.DOCKER_PASSWORD }} # uses token + - name: Login to GHCR + uses: docker/login-action@v3 + with: + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + registry: ghcr.io + - id: meta + uses: docker/metadata-action@v5 + with: + images: | + blinklabs/node + ghcr.io/${{ github.repository }} + tags: | + # Only version, no revision + type=match,pattern=v(.*)-(.*),group=1 + # branch + type=ref,event=branch + # semver + type=semver,pattern={{version}} + - name: Build images + uses: docker/build-push-action@v5 + with: + outputs: "type=registry,push=true" + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # Update Docker Hub from README + - name: Docker Hub Description + uses: peter-evans/dockerhub-description@v4 + with: + username: blinklabs + password: ${{ secrets.DOCKER_PASSWORD }} + repository: blinklabs/node + readme-filepath: ./README.md + short-description: "Cardano Blockchain Node" + + finalize-release: + runs-on: ubuntu-latest + needs: [create-draft-release, build-binaries, build-images] + steps: + - uses: actions/github-script@v7 + if: startsWith(github.ref, 'refs/tags/') + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + try { + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: ${{ needs.create-draft-release.outputs.RELEASE_ID }}, + draft: false, + }); + } catch (error) { + core.setFailed(error.message); + } + + # This updates the documentation on pkg.go.dev and the latest version available via the Go module proxy + - name: Pull new module version + if: startsWith(github.ref, 'refs/tags/') + uses: andrewslotin/go-proxy-pull-action@v1.1.0 diff --git a/.gitignore b/.gitignore index 3b735ec..685bf49 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ # Go workspace file go.work + +# Program binary +/node diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..1eea048 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# Blink Labs +# +* @agaffney @verbotenj @wolf31o2 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e0e430b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM ghcr.io/blinklabs-io/go:1.21.9-1 AS build + +WORKDIR /code +COPY . . +RUN make build + +FROM cgr.dev/chainguard/glibc-dynamic AS node +COPY --from=build /code/node /bin/ +ENTRYPOINT ["node"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e6ddf47 --- /dev/null +++ b/Makefile @@ -0,0 +1,50 @@ +# Determine root directory +ROOT_DIR=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +# Gather all .go files for use in dependencies below +GO_FILES=$(shell find $(ROOT_DIR) -name '*.go') + +# Gather list of expected binaries +BINARIES=$(shell cd $(ROOT_DIR)/cmd && ls -1 | grep -v ^common) + +# Extract Go module name from go.mod +GOMODULE=$(shell grep ^module $(ROOT_DIR)/go.mod | awk '{ print $$2 }') + +# Set version strings based on git tag and current ref +GO_LDFLAGS=-ldflags "-s -w -X '$(GOMODULE)/internal/version.Version=$(shell git describe --tags --exact-match 2>/dev/null)' -X '$(GOMODULE)/internal/version.CommitHash=$(shell git rev-parse --short HEAD)'" + +.PHONY: build mod-tidy clean format golines test + +# Alias for building program binary +build: $(BINARIES) + +# Builds and installs binary in ~/.local/bin +install: build + mv $(BINARIES) $(HOME)/.local/bin + +uninstall: + rm -f $(HOME)/.local/bin/$(BINARIES) +mod-tidy: + # Needed to fetch new dependencies and add them to go.mod + go mod tidy + +clean: + rm -f $(BINARIES) + +format: + go fmt ./... + +golines: + golines -w --ignore-generated --chain-split-dots --max-len=80 --reformat-tags . + +test: mod-tidy + go test -v -race ./... + +# Build our program binaries +# Depends on GO_FILES to determine when rebuild is needed +$(BINARIES): mod-tidy $(GO_FILES) + CGO_ENABLED=0 \ + go build \ + $(GO_LDFLAGS) \ + -o $(@) \ + ./cmd/$(@) diff --git a/cmd/node/main.go b/cmd/node/main.go new file mode 100644 index 0000000..a3627ff --- /dev/null +++ b/cmd/node/main.go @@ -0,0 +1,72 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "log/slog" + "os" + + "github.com/blinklabs-io/node/internal/node" + "github.com/blinklabs-io/node/internal/version" + "github.com/spf13/cobra" +) + +const ( + programName = "node" +) + +func main() { + globalFlags := struct { + version bool + debug bool + }{} + + rootCmd := &cobra.Command{ + Use: programName, + Run: func(cmd *cobra.Command, args []string) { + if globalFlags.version { + fmt.Printf("%s %s\n", programName, version.GetVersionString()) + os.Exit(0) + } + // Configure default logger + logLevel := slog.LevelInfo + if globalFlags.debug { + logLevel = slog.LevelDebug + } + logger := slog.New( + slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: logLevel, + }), + ) + slog.SetDefault(logger) + // Run node + if err := node.Run(); err != nil { + slog.Error(err.Error()) + os.Exit(1) + } + }, + } + + // Global flags + rootCmd.PersistentFlags().BoolVarP(&globalFlags.debug, "debug", "D", false, "enable debug logging") + rootCmd.PersistentFlags().BoolVarP(&globalFlags.version, "version", "", false, "show version and exit") + + // Execute cobra command + if err := rootCmd.Execute(); err != nil { + // NOTE: we purposely don't display the error, since cobra will have already displayed it + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..7837422 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/blinklabs-io/node + +go 1.21.5 + +require github.com/spf13/cobra v1.8.0 + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d0e8c2c --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/node/node.go b/internal/node/node.go new file mode 100644 index 0000000..7f3ad9e --- /dev/null +++ b/internal/node/node.go @@ -0,0 +1,34 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + "log/slog" + + "github.com/blinklabs-io/node" +) + +func Run() error { + // TODO + slog.Info("running node") + n, err := node.New() + if err != nil { + return err + } + if err := n.Run(); err != nil { + return err + } + return nil +} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..6aaab6d --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,31 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package version + +import ( + "fmt" +) + +// These are populated at build time +var Version string +var CommitHash string + +func GetVersionString() string { + if Version != "" { + return fmt.Sprintf("%s (commit %s)", Version, CommitHash) + } else { + return fmt.Sprintf("devel (commit %s)", CommitHash) + } +} diff --git a/node.go b/node.go new file mode 100644 index 0000000..4df8330 --- /dev/null +++ b/node.go @@ -0,0 +1,29 @@ +// Copyright 2024 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +type Node struct { + // TODO +} + +func New() (*Node, error) { + // TODO + return &Node{}, nil +} + +func (n *Node) Run() error { + // TODO + return nil +}