Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Bug report
description: Something siphon did wrong — a crash, wrong output, or a failed operation.
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for the report. Please include enough to reproduce — siphon shells
out to native dump/restore tools, so the exact command and engine matter.
- type: textarea
id: what-happened
attributes:
label: What happened
description: What you ran and what went wrong. Paste the command and the error.
placeholder: |
$ siphon backup prod
✗ ...
validations:
required: true
- type: textarea
id: expected
attributes:
label: What you expected
validations:
required: true
- type: input
id: version
attributes:
label: siphon version
description: Output of `siphon --version`.
placeholder: "v1.0.0"
validations:
required: true
- type: dropdown
id: engine
attributes:
label: Database engine
options:
- PostgreSQL
- MySQL
- MariaDB
- Cross-engine (specify in details)
- Not engine-specific
validations:
required: true
- type: dropdown
id: os
attributes:
label: OS
options: [Linux, macOS, Windows]
validations:
required: true
- type: input
id: exit-code
attributes:
label: Exit code
description: siphon uses a POSIX exit-code taxonomy; the number helps classify the failure.
placeholder: "e.g. 1 (user), 70 (system), 65 (integrity)"
- type: textarea
id: details
attributes:
label: Anything else
description: Profile config (redact secrets), storage backend, logs, repro steps.
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Question or discussion
url: https://github.com/nixrajput/siphon/discussions
about: For usage questions and ideas, start a discussion instead of an issue.
- name: Security vulnerability
url: https://github.com/nixrajput/siphon/security/policy
about: Please report security issues privately, not as a public issue.
37 changes: 37 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Feature request
description: Suggest a capability or improvement.
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: The problem
description: What are you trying to do that siphon makes hard or impossible today?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution
description: What you'd like siphon to do. A command sketch helps.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
- type: dropdown
id: area
attributes:
label: Area
options:
- Backup / restore
- Sync / CDC / cross-engine
- Storage backends
- Retention
- Secrets / auth / audit
- TUI
- Drivers (new engine)
- Other
validations:
required: true
18 changes: 18 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## What & why

<!-- What this changes and the reason. Link any issue: Closes #123. -->

## How it was verified

<!-- Commands run and their outcome — evidence, not assertions. -->

- [ ] `make test` passes
- [ ] `make lint` passes (0 issues)
- [ ] `make test-integration` (if the change touches a live DB / storage path)
- [ ] Docs updated (README / `docs/*.md` / CHANGELOG) for any user-facing change
- [ ] `web/` builds (`npm run build`) — only if the site changed

## Notes for reviewers

<!-- Anything non-obvious: a deliberate scope decision, a deferred follow-up,
a tradeoff, or a place you'd especially like eyes. -->
90 changes: 90 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Release

# Auto-releases when a version tag (vX.Y.Z) is pushed. GoReleaser builds the
# cross-platform binaries, SHA-256 checksums, and cosign-keyless signatures,
# publishes a GitHub Release, and — when the tap repos + tokens exist — pushes
# the Homebrew formula and Scoop manifest.
#
# A validation gate (test + lint + config check) runs first, so a broken tag
# never publishes. workflow_dispatch runs a snapshot dry-run (no publish) to
# rehearse a release without tagging.
on:
push:
tags:
- "v*"
workflow_dispatch: {}

permissions:
contents: read

jobs:
# Gate: the same checks CI runs, plus goreleaser config validation. The
# release job depends on this, so a failing test/lint aborts before publish.
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false # don't leave the token in .git/config
- uses: actions/setup-go@v6
with:
go-version-file: go.mod # reproducible: pin the toolchain to go.mod
cache: true
Comment thread
nixrajput marked this conversation as resolved.
- name: Test
run: go test ./... github.com/nixrajput/siphon/internal/driver/_mysqlcommon
- name: Lint
uses: golangci/golangci-lint-action@v6
- name: GoReleaser check
uses: goreleaser/goreleaser-action@v6
Comment thread
nixrajput marked this conversation as resolved.
with:
version: "~> v2"
args: check

release:
name: Release
needs: validate
runs-on: ubuntu-latest
permissions:
contents: write # create the GitHub Release
id-token: write # cosign keyless signing via GitHub OIDC
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false # don't leave the token in .git/config # GoReleaser needs full history + tags for the changelog

- uses: actions/setup-go@v6
with:
go-version-file: go.mod # reproducible: pin the toolchain to go.mod
cache: true

- name: Install cosign
uses: sigstore/cosign-installer@v3

# On a tag push: full release. On manual dispatch: snapshot dry-run that
# builds everything locally and publishes nothing (rehearsal).
- name: Set release args
id: args
run: |
if [ "${{ github.ref_type }}" = "tag" ]; then
echo "args=release --clean" >> "$GITHUB_OUTPUT"
else
echo "args=release --clean --snapshot" >> "$GITHUB_OUTPUT"
fi

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: "~> v2"
args: ${{ steps.args.outputs.args }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Tap-push tokens. These secrets may be unset on early releases; a
# GitHub Actions secret expression yields "" when absent, so the
# GoReleaser token template still resolves and skip_upload: auto
# quietly skips the brew/scoop push instead of erroring. Provision
# these (PATs with repo scope on the tap repos) to enable publishing.
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
SCOOP_TAP_GITHUB_TOKEN: ${{ secrets.SCOOP_TAP_GITHUB_TOKEN }}
6 changes: 6 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ linters:
- pkg: "github.com/nixrajput/siphon/internal/app"
desc: "Drivers must not depend on Application — dependency flows down"
exclusions:
# web/ is the Next.js site; its node_modules can vendor stray .go files
# (e.g. flatted/golang) that are not siphon code. Keep the Go linters off
# the JS subtree. (node_modules is gitignored, so CI never sees it on a
# fresh checkout; this keeps local lint consistent with CI.)
paths:
- web
rules:
- path: _test\.go
linters:
Expand Down
109 changes: 109 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# GoReleaser v2 configuration — https://goreleaser.com
# Drives the release: cross-platform binaries, archives, SHA-256 checksums,
# cosign-keyless signatures, a GitHub Release, and (when the tap repos +
# token exist) a Homebrew formula and Scoop manifest.
#
# Run locally (no publish): goreleaser release --snapshot --clean
# Validate config: goreleaser check
version: 2

project_name: siphon

# No `go mod tidy` before-hook: the release path stays read-only so the
# published binary is built from exactly the committed dependency graph at the
# tagged revision (CI enforces tidiness separately).

builds:
- id: siphon
main: ./cmd/siphon
binary: siphon
env:
- CGO_ENABLED=0
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
# Match the version symbol the Makefile already injects, so a released
# binary's `siphon --version` reports the tag (not "0.0.1-dev").
ldflags:
- -s -w
- -X github.com/nixrajput/siphon/internal/cli.Version={{.Version}}

archives:
- id: siphon
formats: [tar.gz]
# Windows users expect .zip.
format_overrides:
- goos: windows
formats: [zip]
name_template: >-
{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}
files:
- LICENSE
- README.md

checksum:
name_template: "checksums.txt"
algorithm: sha256

# Cosign keyless signing of the checksums file. The release workflow grants
# id-token: write so cosign authenticates via GitHub's OIDC — no key to manage.
# Verify with: cosign verify-blob --certificate checksums.txt.pem \
# --signature checksums.txt.sig --certificate-identity-regexp ... checksums.txt
signs:
- cmd: cosign
artifacts: checksum
output: true
certificate: "${artifact}.pem"
args:
- sign-blob
- "--output-certificate=${certificate}"
- "--output-signature=${signature}"
- "${artifact}"
- "--yes"

changelog:
use: github
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
- "^chore:"
- "Merge pull request"

release:
github:
owner: nixrajput
name: siphon
prerelease: auto # tags like v1.0.0-rc1 → marked prerelease automatically

# Homebrew tap. Publishes to a SEPARATE repo (nixrajput/homebrew-siphon) using
# a PAT in HOMEBREW_TAP_GITHUB_TOKEN. skip_upload: auto means snapshots and
# prereleases do NOT push the formula, so the core release succeeds even before
# the tap repo / token exist — light up brew on a later stable release.
brews:
- name: siphon
repository:
owner: nixrajput
name: homebrew-siphon
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
homepage: "https://github.com/nixrajput/siphon"
description: "Sync any database, anywhere — backup, restore, and sync across engines."
license: "MIT"
skip_upload: auto
install: |
bin.install "siphon"
test: |
system "#{bin}/siphon", "--version"

# Scoop bucket (Windows). Publishes to nixrajput/scoop-siphon via
# SCOOP_TAP_GITHUB_TOKEN; same skip_upload: auto tolerance as brew.
scoops:
- name: siphon
repository:
owner: nixrajput
name: scoop-siphon
token: "{{ .Env.SCOOP_TAP_GITHUB_TOKEN }}"
homepage: "https://github.com/nixrajput/siphon"
description: "Sync any database, anywhere — backup, restore, and sync across engines."
license: "MIT"
skip_upload: auto
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Phase H (distribution) — release tooling + landing/docs site (publishing awaits the `v1.0.0` tag + owner provisioning):
- **GoReleaser** (`.goreleaser.yaml`, v2): cross-platform builds (linux/darwin/windows × amd64/arm64), tar.gz/zip archives, a SHA-256 `checksums.txt`, and **cosign-keyless** signing of the checksums (GitHub OIDC, no key to manage). The version ldflags match the symbol the Makefile injects, so a released binary's `--version` reports its tag.
- **Release workflow** (`.github/workflows/release.yml`): triggers on a `v*` tag, pins GoReleaser ~> v2, installs cosign, and grants `contents: write` + `id-token: write`. Tap-push tokens resolve to empty when unset so a first release without taps doesn't error (`skip_upload: auto`).
- **Homebrew tap + Scoop bucket** blocks publishing to `nixrajput/homebrew-siphon` and `nixrajput/scoop-siphon`.
- **Install script** (`scripts/install.sh`): POSIX `curl | sh` that detects OS/arch, resolves the latest release, and **verifies the archive's SHA-256 before installing** (refuses on mismatch).
- **Landing + docs site** (`web/`): a Next.js (App Router) + Tailwind app deployed on Vercel. The docs pages render the repository's own `docs/*.md` at build time (single source of truth). README install section, CHANGELOG, and `web/README.md` (with the owner provisioning checklist) updated.

- Phase G (ops) — **multi-backend secrets** (final G cycle): two new secret-ref backends on the existing resolver seam, so passwords can come from a credential store instead of config.
- **OS keychain** (`keychain://<account>` or `keychain://<service>/<account>`) via `go-keyring` — macOS Keychain, Windows Credential Manager, Linux Secret Service. No config, no network; short form looks up service `siphon`.
- **AWS Secrets Manager** (`awssm://<secret-id>`, or `awssm://<secret-id>#<json-key>` to pull one field of a JSON secret) via the AWS SDK, reusing the same credential chain as S3 storage. Off by default; enable with `secrets.awssm: true` (+ optional `awssm_region`).
Expand Down
Loading
Loading