chore(project): initialize cargo workspace, CI workflows, and package config
This commit is contained in:
@@ -0,0 +1,49 @@
|
|||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
components: rustfmt, clippy
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
- name: Cache Cargo dependencies
|
||||||
|
uses: actions/cache@v6
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
- name: Cache npm dependencies
|
||||||
|
uses: actions/cache@v6
|
||||||
|
with:
|
||||||
|
path: app/node_modules
|
||||||
|
key: ${{ runner.os }}-npm-${{ hashFiles('app/package-lock.json') }}
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: npm ci
|
||||||
|
working-directory: app
|
||||||
|
- name: Run Rust tests
|
||||||
|
run: cargo test --workspace
|
||||||
|
- name: Run Clippy
|
||||||
|
run: cargo clippy --workspace -- -D warnings
|
||||||
|
- name: Run Rust fmt check
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
- name: Run frontend tests
|
||||||
|
run: npm test
|
||||||
|
working-directory: app
|
||||||
|
- name: Run ESLint
|
||||||
|
run: npm run lint
|
||||||
|
working-directory: app
|
||||||
|
- name: Run Prettier check
|
||||||
|
run: npm run format:check
|
||||||
|
working-directory: app
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
jobs:
|
||||||
|
build-server:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Cache Cargo dependencies
|
||||||
|
uses: actions/cache@v6
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
- name: Build server binary
|
||||||
|
run: cargo build --release -p clean-copy-server
|
||||||
|
- name: Create release artifact archive
|
||||||
|
run: |
|
||||||
|
tar -czf clean-copy-server-linux-x86_64.tar.gz -C target/release clean-copy-server
|
||||||
|
- name: Upload release artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: server-release
|
||||||
|
path: clean-copy-server-linux-x86_64.tar.gz
|
||||||
|
build-tauri:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: npm ci
|
||||||
|
working-directory: app
|
||||||
|
- name: Build Tauri app
|
||||||
|
run: npx tauri build
|
||||||
|
working-directory: app
|
||||||
|
- name: Upload release artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: tauri-release
|
||||||
|
path: |
|
||||||
|
app/src-tauri/target/release/clean-copy.exe
|
||||||
|
app/src-tauri/target/release/bundle/msi/*.msi
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
# data
|
||||||
|
server/releases/
|
||||||
|
server/data/
|
||||||
|
|
||||||
|
# env
|
||||||
|
*.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# Licensing Keys
|
||||||
|
licensing/
|
||||||
|
|
||||||
|
# Rust build artifacts
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Generated by Tauri
|
||||||
|
gen/schemas
|
||||||
Generated
+7673
File diff suppressed because it is too large
Load Diff
+24
@@ -0,0 +1,24 @@
|
|||||||
|
[workspace]
|
||||||
|
members = ["app/src-tauri", "shared", "server"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Stevan Freeborn"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
ed25519-dalek = "3.0.0"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
sha2 = "0.11.0"
|
||||||
|
thiserror = "2"
|
||||||
|
url = "2.5.8"
|
||||||
|
log = "0.4"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
strip = "symbols"
|
||||||
|
opt-level = "s"
|
||||||
|
panic = "abort"
|
||||||
@@ -1 +1,50 @@
|
|||||||
# CleanCopy
|
# CleanCopy
|
||||||
|
|
||||||
|
CleanCopy is a Windows utility that sits in the system tray, automatically sanitizing your clipboard text on trigger. Designed with a focus on privacy, ergonomics, and performance (it is written in Rust btw).
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
clean-copy/
|
||||||
|
├── app/ # Tauri desktop app (React frontend + Rust backend)
|
||||||
|
├── shared/ # Shared Rust crate (types, licensing, cleansing logic)
|
||||||
|
├── server/ # Axum web server (landing page, activation API, updater)
|
||||||
|
├── licensing/ # Ed25519 key files (gitignored)
|
||||||
|
├── licensing_profiles.json # Profile-based licensing config (compiled into binary)
|
||||||
|
├── Cargo.toml # Workspace root
|
||||||
|
└── package.json # Thin npm wrapper (delegates to app/)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Node.js** (v18+)
|
||||||
|
- **Rust & Cargo** (latest stable)
|
||||||
|
- **Windows C++ Build Tools** (for Tauri)
|
||||||
|
|
||||||
|
### Install & Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install # from repo root, installs app/ dependencies
|
||||||
|
npm run tauri dev # starts Vite dev server + Tauri tray app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test --workspace # Rust tests (shared + app)
|
||||||
|
cd app && npm test # Frontend tests (Vitest)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lint & Format
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd app && npm run lint # ESLint
|
||||||
|
cd app && npm run format:check # Prettier
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **[app/README.md](app/README.md)** — Desktop app: features, architecture, licensing system, CLI tools
|
||||||
|
- **[server/README.md](server/README.md)** — Web server: routes, local dev, Docker deployment
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"development": {
|
||||||
|
"activation_url": "http://localhost:3000/api/activate",
|
||||||
|
"public_key_hex": "5638e661da9d745dfe471c781ba91ee3ff25e56c566ff581b87255cf4c07f061",
|
||||||
|
"deprecated_public_keys": []
|
||||||
|
},
|
||||||
|
"testing": {
|
||||||
|
"activation_url": "http://localhost:3000/api/activate",
|
||||||
|
"public_key_hex": "b1dd4c2c035656de9cf1628567f3e81bdb72d4328359313c35196dc3c60641f0",
|
||||||
|
"deprecated_public_keys": []
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"activation_url": "https://cleancopy.stevanfreeborn.com/api/activate",
|
||||||
|
"public_key_hex": "dc72e7db51e5408fe3b7cb771c74e5633c48e30cf0950f16170e1789a067de4b",
|
||||||
|
"deprecated_public_keys": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "clean-copy",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "cd app && npm run dev",
|
||||||
|
"build": "cd app && npm run build",
|
||||||
|
"tauri": "cd app && npx tauri",
|
||||||
|
"test": "cd app && npm test",
|
||||||
|
"test:rust": "cargo test --workspace",
|
||||||
|
"lint": "cd app && npm run lint",
|
||||||
|
"lint:fix": "cd app && npm run lint:fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check ."
|
||||||
|
}
|
||||||
|
}
|
||||||
+233
@@ -0,0 +1,233 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$Version
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
$VpsHost = "bit_bastion"
|
||||||
|
$VpsDeployDir = "/home/stevan/cleancopy.stevanfreeborn.com"
|
||||||
|
$ContainerName = "clean-copy"
|
||||||
|
$ImageName = "clean-copy-server"
|
||||||
|
|
||||||
|
if ($Version -notmatch '^\d+\.\d+\.\d+$') {
|
||||||
|
Write-Error "Invalid version format '$Version'. Expected semver (e.g. 0.2.0)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "=== CleanCopy Release v$Version ===" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
try {
|
||||||
|
docker info 2>&1 | Out-Null
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Error "Docker Desktop is not running. Start it and try again."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[1/11] Checking SSH connectivity..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
try {
|
||||||
|
ssh $VpsHost "echo ok" 2>&1 | Out-Null
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Error "Cannot SSH into $VpsHost. Check your SSH config."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " OK" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[2/11] Running tests..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
cargo test --workspace
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Rust tests failed";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location app
|
||||||
|
|
||||||
|
npm test
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) { Pop-Location; Write-Error "Frontend tests failed"; exit 1 }
|
||||||
|
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
Write-Host " All tests passed" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[3/11] Bumping versions to $Version..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$cargoFiles = @(
|
||||||
|
"app/src-tauri/Cargo.toml",
|
||||||
|
"shared/Cargo.toml",
|
||||||
|
"server/Cargo.toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($file in $cargoFiles) {
|
||||||
|
$content = Get-Content $file -Raw
|
||||||
|
$content = $content -replace '(?m)^version = ".*"', "version = `"$Version`""
|
||||||
|
Set-Content $file $content -NoNewline
|
||||||
|
}
|
||||||
|
|
||||||
|
$pkg = Get-Content "app/package.json" -Raw
|
||||||
|
$pkg = $pkg -replace '"version":\s*"[^"]*"', "`"version`": `"$Version`""
|
||||||
|
Set-Content "app/package.json" $pkg -NoNewline
|
||||||
|
|
||||||
|
$tauri = Get-Content "app/src-tauri/tauri.conf.json" -Raw
|
||||||
|
$tauri = $tauri -replace '"version":\s*"[^"]*"', "`"version`": `"$Version`""
|
||||||
|
Set-Content "app/src-tauri/tauri.conf.json" $tauri -NoNewline
|
||||||
|
|
||||||
|
Write-Host " Updated: Cargo.toml (x3), package.json, tauri.conf.json" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[4/11] Creating git commit and tag..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
git add -A
|
||||||
|
git commit -m "release: v$Version"
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Git commit failed";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
git tag "v$Version"
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Git tag failed";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Tagged v$Version" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[5/11] Building Tauri app..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
Push-Location app
|
||||||
|
|
||||||
|
npx tauri build
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Pop-Location; Write-Error "Tauri build failed";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
Write-Host " Built clean-copy.exe + MSI" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[6/11] Organizing release artifacts..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$releaseDir = "server/releases/$Version/x86_64"
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null
|
||||||
|
|
||||||
|
$exeSrc = "app/src-tauri/target/release/clean-copy.exe"
|
||||||
|
$msiSrc = Get-ChildItem "app/src-tauri/target/release/bundle/msi/*.msi" | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not (Test-Path $exeSrc)) {
|
||||||
|
Write-Error "Expected $exeSrc not found"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Copy-Item $exeSrc "$releaseDir/clean-copy-$Version.exe"
|
||||||
|
|
||||||
|
Write-Host " Copied clean-copy-$Version.exe" -ForegroundColor Green
|
||||||
|
|
||||||
|
if ($msiSrc) {
|
||||||
|
Copy-Item $msiSrc.FullName "$releaseDir/$($msiSrc.Name)"
|
||||||
|
Write-Host " Copied $($msiSrc.Name)" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host " Warning: No MSI found" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[7/11] Building Docker image for linux/arm64..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
docker buildx build --platform linux/arm64 -t "${ImageName}:${Version}" -t "${ImageName}:latest" -f server/Dockerfile . --load
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Docker build failed";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Built ${ImageName}:${Version}" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[8/11] Saving Docker image to tarball..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$tarball = "${ImageName}-${Version}.tar"
|
||||||
|
docker save "${ImageName}:${Version}" -o $tarball
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Docker save failed";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$tarSize = [math]::Round((Get-Item $tarball).Length / 1MB, 1)
|
||||||
|
|
||||||
|
Write-Host " Saved $tarball ($tarSize MB)" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[9/11] Transferring files to VPS..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
scp $tarball "${VpsHost}:${VpsDeployDir}/"
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Failed to transfer tarball"; exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Transferred $tarball" -ForegroundColor Green
|
||||||
|
|
||||||
|
ssh $VpsHost "mkdir -p ${VpsDeployDir}/data/releases/${Version}/x86_64"
|
||||||
|
scp -r "${releaseDir}/*" "${VpsHost}:${VpsDeployDir}/data/releases/${Version}/x86_64/"
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Failed to transfer release artifacts";
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Transferred release artifacts" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[10/11] Deploying on VPS..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
$deployScript = @"
|
||||||
|
cd ${VpsDeployDir} && \
|
||||||
|
docker load -i ${ImageName}-${Version}.tar && \
|
||||||
|
docker stop ${ContainerName} 2>/dev/null; docker rm ${ContainerName} 2>/dev/null; \
|
||||||
|
docker run -d \
|
||||||
|
--name ${ContainerName} \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-v ${VpsDeployDir}/data:/data \
|
||||||
|
--env-file ${VpsDeployDir}/.env \
|
||||||
|
--restart unless-stopped \
|
||||||
|
${ImageName}:${Version} && \
|
||||||
|
rm ${ImageName}-${Version}.tar && \
|
||||||
|
echo "DEPLOY_OK"
|
||||||
|
"@
|
||||||
|
|
||||||
|
$result = ssh $VpsHost $deployScript
|
||||||
|
|
||||||
|
if ($result -notmatch "DEPLOY_OK") {
|
||||||
|
Write-Error "Deployment failed. Output: $result"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Container $ContainerName started" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host "[11/11] Cleaning up local tarball..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
Remove-Item $tarball -Force
|
||||||
|
|
||||||
|
Write-Host " Removed $tarball" -ForegroundColor Green
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "=== Release Complete ===" -ForegroundColor Cyan
|
||||||
|
Write-Host " Version: v$Version"
|
||||||
|
Write-Host " Image: ${ImageName}:${Version}"
|
||||||
|
Write-Host " Container: $ContainerName (running)"
|
||||||
|
Write-Host " Deploy dir: ${VpsDeployDir}"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Next: git push && git push --tags" -ForegroundColor Yellow
|
||||||
Reference in New Issue
Block a user