Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 4 additions & 2 deletions .github/workflows/dectris-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g. 0.43.8-dc.1). A 'v' prefix will be added for the tag/release."
# Deliberately has no default: it must match GEESEFS_VERSION in
# core/cfg/flags.go, and a default here goes stale the moment that
# constant moves. Pass it explicitly at dispatch time.
description: "Release version, must match GEESEFS_VERSION in core/cfg/flags.go (e.g. 0.43.8-dc.2). A 'v' prefix is added for the tag/release."
required: true
type: string
default: '0.43.8-dc.1'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down
36 changes: 36 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,42 @@ cd docker && just test-multipart-boundary

**Virtual symlinks**: Symlinks stored in `.geesefs_symlinks` (no S3 object) are distinguished from S3-backed symlinks via `Inode.isVirtualSymlink` bool field. Always use this field for detection, not `userMetadata[SymlinkAttr] != nil`.

## Branches

**`dev` is the main branch.** All dectris development on geesefs lives here, every PR targets it, and releases are cut from it. Treat it as the trunk.

**`master` is a read-only mirror of `yandex-cloud/geesefs`.** It must always point at the exact upstream commit the fork is based on, and must never carry a dectris change. Verify with:

```bash
git fetch upstream
git rev-list --count upstream/master..origin/master # must be 0
```

Upstream syncs go `master` → `dev`: fast-forward `master` to the new upstream commit, then merge `master` into `dev` and resolve there.

Opening a PR against `master` is always wrong. Its content will be absent from `dev`, and a release cut afterwards silently ships without it, with nothing failing to warn you. If it happens, merge `master` into `dev` to recover the work, then reset `master` back to the upstream commit.

Older branches on the remote (`symlinks`, `fix/utf-8`, `ci/dev_releases`, `dectris-master`, `perf-fio-sync-master`, …) predate this layout and are superseded. Their content is already in `dev`; do not branch from them.

## Releasing

The version lives in one place: `GEESEFS_VERSION` in `core/cfg/flags.go`. Bump it, merge to `dev`, then dispatch:

```bash
gh workflow run dectris-release.yml -f version=<version> --ref dev
gh release edit v<version> --prerelease=false --latest
```

The version is a dispatch parameter, so do not edit the workflow to change it. It has no default on purpose: a default duplicates `GEESEFS_VERSION` and goes stale the moment that constant moves.

A dispatched run is marked prerelease, hence the `release edit`. Pushing a `v*` tag instead publishes a full release directly, but only the dispatch path is exercised regularly.

Tag scheme: `v<upstream-version>-dc.<n>`, e.g. `v0.43.8-dc.2`.

## Go Module

Module: `github.com/yandex-cloud/geesefs` (Go 1.25). Uses the stock `github.com/aws/aws-sdk-go` with no `replace` directive. Yandex-only S3 extensions (`PatchObject`, `ListObjectsV1Ext`) live in `core/ycs3ext/`, built on the SDK's `request.Request` rather than a forked SDK.

## Downstream

`compute-amis` pins the version in `terraform/infrastructure/main.tf` (`geesefs_version`) and its AMI recipe downloads the release asset. A new release needs a matching bump there.
2 changes: 1 addition & 1 deletion core/cfg/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"github.com/urfave/cli"
)

const GEESEFS_VERSION = "0.43.8-dc.1"
const GEESEFS_VERSION = "0.43.8-dc.2"

var flagCategories map[string]string

Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ func main() {
log.Println("File system has been successfully mounted.")
if !flags.Foreground {
daemonizer.NotifySuccess(true)
// Runtime panics write to fd 2, which the two Close calls
// below discard, so a crashed daemon leaves no trace anywhere.
// Route crash output to a file first (Go keeps a dup of it).
setupCrashLog(bucketName, flags.MountPoint)
os.Stderr.Close()
os.Stdout.Close()
}
Expand Down
30 changes: 30 additions & 0 deletions main_nowindows.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,46 @@ import (
"fmt"
"os"
"os/signal"
"runtime/debug"
"strings"
"sync"
"syscall"
"time"

daemon "github.com/sevlyar/go-daemon"

"github.com/yandex-cloud/geesefs/core"
"github.com/yandex-cloud/geesefs/core/cfg"
)

// Where Go runtime crash output goes once the daemon has closed its stderr.
// Overridable for non-root mounts via GEESEFS_CRASH_LOG.
const defaultCrashLogPath = "/var/log/geesefs-crash.log"

// setupCrashLog routes runtime crash output (panics, fatal errors, deadlock
// dumps) to a shared append-only file. These bypass the logger and write to
// fd 2, which the daemon closes right after mounting, so without this a
// crashed daemon leaves no trace anywhere. The header line ties the pid to
// its mount so a following dump can be attributed.
func setupCrashLog(bucketName string, mountPoint string) {
path := os.Getenv("GEESEFS_CRASH_LOG")
if path == "" {
path = defaultCrashLogPath
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
if err != nil {
log.Warnf("Cannot open crash log %v: %v; runtime panics will be lost", path, err)
return
}
fmt.Fprintf(f, "--- %s geesefs pid %d serving %s at %s: crash output armed ---\n",
time.Now().UTC().Format(time.RFC3339), os.Getpid(), bucketName, mountPoint)
if err = debug.SetCrashOutput(f, debug.CrashOptions{}); err != nil {
log.Warnf("Cannot set crash output to %v: %v", path, err)
}
// SetCrashOutput keeps its own duplicate of the descriptor.
f.Close()
}

var signalsToHandle = []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1}

func isSigUsr1(s os.Signal) bool {
Expand Down
5 changes: 5 additions & 0 deletions main_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ import (

var signalsToHandle = []os.Signal{os.Interrupt, syscall.SIGTERM}

// Unreachable on Windows: canDaemonize is false, so the daemon-only branch
// that calls this never runs. Present only to keep the build compiling.
func setupCrashLog(bucketName string, mountPoint string) {
}

func isSigUsr1(s os.Signal) bool {
return false
}
Expand Down
Loading