Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ env:

jobs:
test:
runs-on: macos-latest
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

Expand Down
65 changes: 57 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ env:
SIGNING_IDENTITY: "Developer ID Application: Paddo Tech PTY LTD (D9Q57P9D3L)"

jobs:
build:
build-macos:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -77,14 +77,62 @@ jobs:

- name: Package binaries
run: |
mkdir -p dist

cd target/aarch64-apple-darwin/release
tar -czf tether-aarch64-apple-darwin.tar.gz tether
shasum -a 256 tether-aarch64-apple-darwin.tar.gz > tether-aarch64-apple-darwin.tar.gz.sha256
cp tether-aarch64-apple-darwin.tar.gz tether-aarch64-apple-darwin.tar.gz.sha256 ../../../dist/
cd -

cd target/x86_64-apple-darwin/release
tar -czf tether-x86_64-apple-darwin.tar.gz tether
shasum -a 256 tether-x86_64-apple-darwin.tar.gz > tether-x86_64-apple-darwin.tar.gz.sha256
cp tether-x86_64-apple-darwin.tar.gz tether-x86_64-apple-darwin.tar.gz.sha256 ../../../dist/

- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos-binaries
path: dist/

build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

- name: Build release
run: cargo build --release

- name: Package binary
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist
cd target/release
Compress-Archive -Path tether.exe -DestinationPath tether-x86_64-pc-windows-msvc.zip
$hash = (Get-FileHash tether-x86_64-pc-windows-msvc.zip -Algorithm SHA256).Hash.ToLower()
"$hash tether-x86_64-pc-windows-msvc.zip" | Out-File -NoNewline -Encoding ascii tether-x86_64-pc-windows-msvc.zip.sha256
Copy-Item tether-x86_64-pc-windows-msvc.zip, tether-x86_64-pc-windows-msvc.zip.sha256 ../../dist/

- name: Upload Windows artifact
uses: actions/upload-artifact@v4
with:
name: windows-binary
path: dist/

release:
needs: [build-macos, build-windows]
runs-on: self-hosted
steps:
- uses: actions/checkout@v4

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts

- name: Get version from Cargo.toml
id: version
Expand Down Expand Up @@ -129,10 +177,12 @@ jobs:
prerelease: ${{ steps.version.outputs.prerelease == 'true' }}
generate_release_notes: true
files: |
target/aarch64-apple-darwin/release/tether-aarch64-apple-darwin.tar.gz
target/aarch64-apple-darwin/release/tether-aarch64-apple-darwin.tar.gz.sha256
target/x86_64-apple-darwin/release/tether-x86_64-apple-darwin.tar.gz
target/x86_64-apple-darwin/release/tether-x86_64-apple-darwin.tar.gz.sha256
artifacts/macos-binaries/tether-aarch64-apple-darwin.tar.gz
artifacts/macos-binaries/tether-aarch64-apple-darwin.tar.gz.sha256
artifacts/macos-binaries/tether-x86_64-apple-darwin.tar.gz
artifacts/macos-binaries/tether-x86_64-apple-darwin.tar.gz.sha256
artifacts/windows-binary/tether-x86_64-pc-windows-msvc.zip
artifacts/windows-binary/tether-x86_64-pc-windows-msvc.zip.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand All @@ -143,8 +193,8 @@ jobs:
VERSION: ${{ steps.version.outputs.version }}
IS_PRERELEASE: ${{ steps.version.outputs.prerelease }}
run: |
SHA_ARM=$(cat target/aarch64-apple-darwin/release/tether-aarch64-apple-darwin.tar.gz.sha256 | awk '{print $1}')
SHA_X86=$(cat target/x86_64-apple-darwin/release/tether-x86_64-apple-darwin.tar.gz.sha256 | awk '{print $1}')
SHA_ARM=$(cat artifacts/macos-binaries/tether-aarch64-apple-darwin.tar.gz.sha256 | awk '{print $1}')
SHA_X86=$(cat artifacts/macos-binaries/tether-x86_64-apple-darwin.tar.gz.sha256 | awk '{print $1}')

# Clone the tap repo
rm -rf /tmp/homebrew-tap
Expand Down Expand Up @@ -207,4 +257,3 @@ jobs:
git add "$FORMULA_FILE"
git commit -m "tether-cli ${VERSION}"
git push

12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Windows platform support with winget package manager
- Cross-platform package mapping for dotfile sync across macOS/Linux/Windows
- Windows daemon scheduling via Task Scheduler
- Windows secrets file permissions via `icacls`
- Symlink fallback to file copy when Windows privileges unavailable
- Cross-platform path normalization (forward slashes in sync state)
- Windows CI test matrix and release builds (`.zip` + SHA256)

## [1.11.8] - 2026-03-09

### Fixed
Expand Down
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ env_logger = "0.11"
# IPC for daemon
interprocess = "2.3"


# Async trait
async-trait = "0.1"

Expand All @@ -82,8 +83,7 @@ glob = "0.3"
# Text diffing
similar = "2.6"

# System signals
libc = "0.2"
# Temp files
tempfile = "3.8"

# File locking
Expand All @@ -93,6 +93,10 @@ fs2 = "0.4"
ratatui = "0.30"
crossterm = "0.29"

# System signals (Unix only)
[target.'cfg(unix)'.dependencies]
libc = "0.2"

[dev-dependencies]
tempfile = "3.8"
assert_cmd = "2.0"
Expand Down
40 changes: 40 additions & 0 deletions WINDOWS_SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Windows Support

## What works

- **Dotfile sync** — Push/pull encrypted dotfiles, `.gitconfig` syncs via `~/.gitconfig` (`%USERPROFILE%\.gitconfig`)
- **Package management** — WinGet integration: list, install, uninstall, upgrade, export/import manifests
- **Daemon** — Background sync via Task Scheduler (`schtasks`), RestartOnFailure for auto-restart
- **Project configs** — Sync project-level configs with symlink support (falls back to copy without Developer Mode)
- **Team sync** — Symlinks for team configs, copy fallback on Windows
- **Notifications** — PowerShell toast notifications for sync conflicts
- **CI** — `ubuntu-latest` + `macos-latest` + `windows-latest` matrix
- **Release** — Signed macOS binaries + Windows `.zip` with SHA256

## Platform behavior differences

| Feature | macOS | Windows |
|---------|-------|---------|
| Daemon install | launchd (KeepAlive) | Task Scheduler (RestartOnFailure) |
| Symlinks | Native | Requires Developer Mode; copies as fallback |
| Notifications | AppleScript | PowerShell toast |
| Default merge tool | `opendiff` | `code` (VS Code) |
| Default editor | `nano` | `notepad` |
| File permissions | `0o600` for secrets | ACL restricted via `icacls` |
| Process management | `kill`/signals (graceful SIGTERM) | `tasklist`/`taskkill /F` (no graceful signal for detached processes) |
| Package manager | Homebrew | WinGet |

## Architecture notes

- Package managers check `is_available()` at runtime — brew returns false on Windows, winget returns false on macOS
- Default dotfiles (`.zshrc`, `.bashrc`) have `create_if_missing: false` so they won't be created on Windows
- Config dir is `~/.tether/` → `C:\Users\<name>\.tether\` via the `home` crate
- State keys use forward slashes on all platforms (normalized with `.replace('\\', "/")`)
- Path validation rejects `..`, `/`, `\`, and drive letters (`C:\`) for traversal safety
- `create_symlink()` in `sync/mod.rs` handles the Developer Mode fallback centrally

## Known limitations

- No ARM64 Windows build (x86_64 runs under emulation)
- `winget list` parser uses fixed-width column positions — handles CJK double-width characters but may break with non-English locales (different header text)
- No WinGet manifest in release pipeline (users install from GitHub releases)
2 changes: 2 additions & 0 deletions src/cli/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ pub async fn edit() -> Result<()> {
let editor = std::env::var("EDITOR").unwrap_or_else(|_| {
if cfg!(target_os = "macos") {
"nano".to_string()
} else if cfg!(target_os = "windows") {
"notepad".to_string()
} else {
"vi".to_string()
}
Expand Down
Loading
Loading