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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Auto detect text files and perform LF normalization
* text=auto
/vendor/** linguist-vendored whitespace=-blank-at-eol,-blank-at-eof,-space-before-tab
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @ColinMario
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: monthly
groups:
go-dependencies:
patterns: ['*']
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
github-actions:
patterns: ['*']
90 changes: 90 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
GOFLAGS: -mod=vendor

jobs:
test:
name: Test (${{ matrix.os }}, Go ${{ matrix.go }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
go: [1.25.12, 1.26.5]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: ${{ matrix.go }}
cache: true
- run: go version
- run: go test -count=1 ./...
- run: go vet ./...
- name: Race detector
if: matrix.go == '1.26.5'
run: go test -race -count=1 ./...
- name: Coverage floor
if: matrix.os == 'ubuntu-latest' && matrix.go == '1.26.5'
run: |
go test -coverpkg=./... -coverprofile=coverage.out ./...
total="$(go tool cover -func=coverage.out | awk '/^total:/ {gsub(/%/, "", $3); print $3}')"
awk -v total="$total" 'BEGIN { if (total < 25.0) { printf "coverage %.1f%% is below 25.0%%\n", total; exit 1 } }'

quality:
name: Static and security checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: 1.26.5
cache: true
- run: go mod verify
- name: Verify generated module files
run: |
GOFLAGS=-mod=mod go mod tidy
GOFLAGS=-mod=mod go mod vendor
git diff --exit-code -- go.mod go.sum vendor
- run: test -z "$(gofmt -l cmd internal tools)"
- run: GOFLAGS=-mod=mod go run honnef.co/go/tools/cmd/staticcheck@v0.7.0 ./...
- run: GOFLAGS=-mod=mod go run github.com/securego/gosec/v2/cmd/gosec@v2.28.0 -quiet ./...
- run: GOFLAGS=-mod=mod go run golang.org/x/vuln/cmd/govulncheck@v1.6.0 ./...

cross-build:
name: Cross-build release targets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: 1.26.5
cache: true
- run: |
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 freebsd/amd64; do
GOOS="${target%/*}" GOARCH="${target#*/}" CGO_ENABLED=0 go build -trimpath -buildvcs=false -o /tmp/protondrive-${target//\//-} ./cmd/protondrive
done

packaging:
name: Packaging metadata
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- run: sudo apt-get update && sudo apt-get install -y appstream desktop-file-utils flatpak-builder libxml2-utils
- run: appstreamcli validate --no-net packaging/flatpak/io.github.colinmario.protondriveforlinux.metainfo.xml
- run: desktop-file-validate packaging/flatpak/io.github.colinmario.protondriveforlinux.desktop
- run: xmllint --noout packaging/flatpak/io.github.colinmario.protondriveforlinux.svg
- run: flatpak-builder --show-manifest packaging/flatpak/io.github.colinmario.protondriveforlinux.yml >/dev/null
60 changes: 60 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Release

on:
push:
tags: ['v*']

permissions:
contents: write
id-token: write
attestations: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: 1.26.5
cache: true
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.1.1
- uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
with:
syft-version: v1.46.0
- name: Verify release source
run: |
tag_pattern='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'
[[ "$GITHUB_REF_NAME" =~ $tag_pattern ]]
version="${GITHUB_REF_NAME#v}"
git fetch origin main
git merge-base --is-ancestor "$GITHUB_SHA" origin/main
grep -F "## [$version]" CHANGELOG.md
grep -F "tag: $GITHUB_REF_NAME" packaging/flatpak/io.github.colinmario.protondriveforlinux.yml
test -z "$(git status --porcelain)"
go version
go test -count=1 ./...
go vet ./...
- name: Build, sign, and publish
uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
with:
distribution: goreleaser
version: v2.17.0
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOFLAGS: -mod=vendor
- name: Attest release artifacts
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
with:
subject-path: |
dist/*.tar.gz
dist/*.deb
dist/*.rpm
dist/SHA256SUMS.txt
dist/*.spdx.json
dist/*.sigstore.json
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/dist/
/build/
/coverage.out
/coverage.html
/protondrive
/.flatpak-builder/
/packaging/flatpak/.flatpak-builder/
/packaging/flatpak/build-dir/
88 changes: 88 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
version: 2

project_name: protondrive

before:
hooks:
- go test ./...

builds:
- id: protondrive
main: ./cmd/protondrive
binary: protondrive
env:
- CGO_ENABLED=0
- GOFLAGS=-mod=vendor
goos: [linux, darwin]
goarch: [amd64, arm64]
flags: [-trimpath, -buildvcs=false]
mod_timestamp: '{{ .CommitTimestamp }}'
ldflags:
- >-
-s -w
-X main.version={{.Version}}
-X main.commit={{.FullCommit}}
-X main.buildDate={{.CommitDate}}

archives:
- id: archives
formats: [tar.gz]
name_template: 'protondrive_{{ .Version }}_{{ .Os }}_{{ .Arch }}'
files:
- README.md
- CHANGELOG.md
- LICENSE
- NOTICE.md

nfpms:
- id: native-packages
package_name: protondrive
ids: [protondrive]
vendor: ColinMario
homepage: https://github.com/ColinMario/Protondrive-for-Linux
maintainer: ColinMario
description: Unofficial command-line helper for safe Proton Drive transfers and mounts
license: GPL-3.0-only
formats: [deb, rpm]
bindir: /usr/bin
mtime: '{{ .CommitDate }}'
rpm:
buildhost: reproducible.invalid

checksum:
name_template: SHA256SUMS.txt

sboms:
- id: archive-sbom
artifacts: archive
documents:
- '${artifact}.spdx.json'
cmd: go
args:
- run
- ../tools/reproducible-sbom
- '${artifact}'
- '${document}'
- '{{ .CommitDate }}'

signs:
- id: checksum-signature
cmd: cosign
artifacts: checksum
signature: '${artifact}.sigstore.json'
args:
- sign-blob
- --yes
- --bundle=${signature}
- ${artifact}

changelog:
use: git
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'

release:
prerelease: auto
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,49 @@ All notable changes to this project are documented here. The format follows [Kee

## [Unreleased]

## [0.3.0] - 2026-07-14
### Added
- Added explicit safe `copy` and destructive `mirror` transfer operations with confirmation, deletion limits, source guards, and automatic destination backup directories.
- Added strict versioned sync-config validation, collision-resistant state filenames, JSON status output with stable exit codes, and build/version metadata.
- Added authenticated macOS WebDAV mounts with private logs and process identity tracking.
- Added Linux/macOS CI, security scanners, cross-builds, packaging validation, Dependabot, reproducible GoReleaser archives, SPDX SBOMs, Sigstore checksum signatures, and GitHub build attestations.
- Added security, contribution, notice, and full GPL-3.0 documentation.

### Changed
- Updated the supported Go baseline to 1.25 and refreshed all Go dependencies and vendored sources.
- Validated the private session bridge against Proton Drive CLI 0.5.0 while retaining 0.4.x compatibility and fail-closed handling for future format series.
- Changed rclone transfers to non-destructive copy semantics by default; exact destination mirroring now requires `--operation mirror --confirm-mirror`.
- Watch mode now retries failures with exponential backoff and remains available for later changes.
- Persistent systemd/OpenRC services now use atomic files, exact artifact/state rollback, update restarts, readiness checks, verified removal, and propagated errors.
- Flatpak release builds now use an immutable Git tag; local source builds use a separate development manifest. Flatpak host/home permissions are documented as a distribution tradeoff rather than a sandbox boundary.
- Flatpak release builds now embed the tagged application version and deterministic release metadata instead of reporting a development build.

### Fixed
- Fixed documented rclone passthrough by consuming the wrapper's standalone `--` delimiter.
- Fixed `status` returning success for missing remotes or failed authentication.
- Kept machine-readable status output single-shot by suppressing duplicate top-level error rendering.
- Prevented transient network errors from triggering destructive credential reconfiguration.
- Prevented one-time 2FA codes from being persisted or reused by staging them in a private temporary rclone config, and migrates legacy vaults when unlocked.
- Made rclone config, session, vault, and state writes atomic with cooperative locks, backups, and verification rollback.
- Refused transactional edits of encrypted rclone configs instead of risking plaintext replacement or corruption.
- Bounded ZIP extraction by bytes actually read instead of trusting archive metadata, and escaped systemd specifier and environment expansion in generated service paths.
- Blocked passthrough/config overrides of dry-run, deletion limits, backup handling, error handling, and other wrapper-owned mirror safeguards.
- Validated remote names before config rendering so colon ambiguity and INI section injection cannot redirect or corrupt rclone configuration.
- Rejected mirror backup directories nested inside the destination, including root-mirror recursion on the same remote.
- Removed the local vault passphrase from every child-process environment, including Flatpak host bridges.
- Enforced HTTPS-only dependency downloads and redirects, constrained Proton CLI assets to the official download path, and strictly validated rclone release versions.
- Made binaries, archives, native packages, and SPDX SBOMs reproducible from commit-derived timestamps and artifact digests; CI/release builds pin patched Go 1.25.12 and 1.26.5 toolchains, and tags fail unless they are SemVer, reachable from `main`, represented in the changelog, and matched by the immutable Flatpak source.
- Rejected conflicting credential sources, session-import modes, and unexpected positional arguments instead of silently ignoring them.
- Made empty-source checks ignore the sentinel itself, require regular-file sentinels, count actual remote files, and resolve symlink aliases before root/backup safety decisions.
- Expanded state/vault filename hashes to 64 bits and removes legacy unhashed files after a successful migration.
- Resolved sync, mount, unmount, backup, and persistent-service config paths to stable absolute locations.
- Prevented mount passthrough from overriding daemon/state, authentication, cache, read-only, config, and remote-control settings managed by the wrapper.
- Restricted recorded mount cleanup to generated direct-child bcrypt files so corrupted metadata cannot remove unrelated files.
- Removed the macOS Keychain fallback that exposed session JSON through process arguments.
- Prevented stale mount PIDs from signaling unrelated processes and verifies WebDAV shutdown after unmount.
- Removed the macOS WebDAV password from `mount_webdav` arguments by supplying it through a private pseudo-terminal conversation instead.
- Canonicalized mount-table paths so macOS `/var`/`/private/var` and other symlinked mount points are recognized correctly.

## [0.2.5] - 2026-06-29
### Fixed
- Prevented `auto` sync from silently falling back to `rclone sync` when Proton's official CLI is unavailable, avoiding unintended mirror deletion semantics.
Expand Down
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Contributing

Use Go 1.25 or 1.26 and keep `vendor/` synchronized with `go.mod` and `go.sum`.
Before opening a pull request, run:

```bash
go mod tidy
go mod vendor
gofmt -w cmd internal tools
go test -race ./...
go vet ./...
```

Changes to transfer semantics must include tests proving whether destination
deletions can occur. Changes to authentication, secret storage, mount helpers,
or service generation must include failure-path tests and must not put secrets
in process arguments or logs.

Release tags are created only from a clean, reviewed `main` commit. The release
workflow produces four archives plus native packages, signed checksums, SPDX
SBOMs, and GitHub build attestations.
Loading
Loading