mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-03-14 22:43:02 +03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58e0194aa6 | ||
|
|
7864c46279 | ||
|
|
e3970e53e1 |
6
.github/workflows/nix-build.yml
vendored
6
.github/workflows/nix-build.yml
vendored
@@ -13,10 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -34,4 +31,5 @@ jobs:
|
||||
run: nix flake show
|
||||
|
||||
- name: Build package
|
||||
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
|
||||
run: nix build --print-build-logs
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
# Nix Distribution - Ready to Use!
|
||||
|
||||
## Current Status: WORKS NOW
|
||||
|
||||
Your Nix package is **already distributable**! Users can run it directly from GitHub without any additional setup on your end.
|
||||
|
||||
## How Users Will Use It
|
||||
|
||||
### Simple: Just Run From GitHub
|
||||
|
||||
```bash
|
||||
nix run --extra-experimental-features 'nix-command flakes' github:RayLabsHQ/gitea-mirror
|
||||
```
|
||||
|
||||
That's it! No releases, no CI, no infrastructure needed. It works right now.
|
||||
|
||||
---
|
||||
|
||||
## What Happens When They Run This?
|
||||
|
||||
1. **Nix fetches** your repo from GitHub
|
||||
2. **Nix reads** `flake.nix` and `flake.lock`
|
||||
3. **Nix builds** the package on their machine
|
||||
4. **Nix runs** the application
|
||||
5. **Result cached** in `/nix/store` for reuse
|
||||
|
||||
---
|
||||
|
||||
## Do You Need CI or Releases?
|
||||
|
||||
### For Basic Usage: **NO**
|
||||
Users can already use it from GitHub. No CI or releases required.
|
||||
|
||||
### For CI Validation: **Already Set Up**
|
||||
GitHub Actions validates builds on every push with Magic Nix Cache (free, no setup).
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Optional)
|
||||
|
||||
### Option 1: Release Versioning (2 minutes)
|
||||
|
||||
**Why:** Users can pin to specific versions
|
||||
|
||||
**How:**
|
||||
```bash
|
||||
# When ready to release
|
||||
git tag v3.8.11
|
||||
git push origin v3.8.11
|
||||
|
||||
# Users can then pin to this version
|
||||
nix run github:RayLabsHQ/gitea-mirror/v3.8.11
|
||||
```
|
||||
|
||||
No additional CI needed - tags work automatically with flakes!
|
||||
|
||||
### Option 2: Submit to nixpkgs (Long Term)
|
||||
|
||||
**Why:** Maximum discoverability and trust
|
||||
|
||||
**When:** After package is stable and well-tested
|
||||
|
||||
**How:** Submit PR to https://github.com/NixOS/nixpkgs
|
||||
|
||||
---
|
||||
|
||||
## Files Created
|
||||
|
||||
### Essential (Already Working)
|
||||
- `flake.nix` - Package definition
|
||||
- `flake.lock` - Dependency lock file
|
||||
- `.envrc` - direnv integration
|
||||
|
||||
### Documentation
|
||||
- `NIX.md` - Quick reference for users
|
||||
- `docs/NIX_DEPLOYMENT.md` - Complete deployment guide
|
||||
- `docs/NIX_DISTRIBUTION.md` - Distribution guide for you (maintainer)
|
||||
- `README.md` - Updated with Nix instructions
|
||||
|
||||
### CI (Already Set Up)
|
||||
- `.github/workflows/nix-build.yml` - Builds and validates on Linux + macOS
|
||||
|
||||
### Updated
|
||||
- `.gitignore` - Added Nix artifacts
|
||||
|
||||
---
|
||||
|
||||
## Comparison: Your Distribution Options
|
||||
|
||||
| Setup | Time | User Experience | What You Need |
|
||||
|-------|------|----------------|---------------|
|
||||
| **Direct GitHub** | 0 min | Slow (build from source) | Nothing! Works now |
|
||||
| **+ Git Tags** | 2 min | Versionable | Just push tags |
|
||||
| **+ nixpkgs** | Hours | Official/Trusted | PR review process |
|
||||
|
||||
**Recommendation:** Direct GitHub works now. Add git tags for versioning. Consider nixpkgs submission once stable.
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Distribution
|
||||
|
||||
You can test it right now:
|
||||
|
||||
```bash
|
||||
# Test direct GitHub usage
|
||||
nix run --extra-experimental-features 'nix-command flakes' github:RayLabsHQ/gitea-mirror
|
||||
|
||||
# Test with specific commit
|
||||
nix run github:RayLabsHQ/gitea-mirror/$(git rev-parse HEAD)
|
||||
|
||||
# Validate flake
|
||||
nix flake check
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Documentation Locations
|
||||
|
||||
Users will find instructions in:
|
||||
1. **README.md** - Installation section (already updated)
|
||||
2. **NIX.md** - Quick reference
|
||||
3. **docs/NIX_DEPLOYMENT.md** - Detailed guide
|
||||
|
||||
All docs include the correct commands with experimental features flags.
|
||||
|
||||
---
|
||||
|
||||
## When to Release New Versions
|
||||
|
||||
### For Git Tag Releases:
|
||||
```bash
|
||||
# 1. Update version in package.json
|
||||
vim package.json
|
||||
|
||||
# 2. Update version in flake.nix (line 17)
|
||||
vim flake.nix # version = "3.8.12";
|
||||
|
||||
# 3. Commit and tag
|
||||
git add package.json flake.nix
|
||||
git commit -m "chore: bump version to v3.8.12"
|
||||
git tag v3.8.12
|
||||
git push origin main
|
||||
git push origin v3.8.12
|
||||
```
|
||||
|
||||
Users can then use: `nix run github:RayLabsHQ/gitea-mirror/v3.8.12`
|
||||
|
||||
### No Release Needed For:
|
||||
- Bug fixes
|
||||
- Small changes
|
||||
- Continuous updates
|
||||
|
||||
Users can always use latest from main: `nix run github:RayLabsHQ/gitea-mirror`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Ready to distribute RIGHT NOW**
|
||||
- Just commit and push your `flake.nix`
|
||||
- Users can run directly from GitHub
|
||||
- CI validates builds automatically
|
||||
|
||||
**Optional: Submit to nixpkgs**
|
||||
- Maximum discoverability
|
||||
- Official Nix repository
|
||||
- Do this once package is stable
|
||||
|
||||
See `docs/NIX_DISTRIBUTION.md` for complete details!
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gitea-mirror",
|
||||
"type": "module",
|
||||
"version": "3.9.6",
|
||||
"version": "3.10.1",
|
||||
"engines": {
|
||||
"bun": ">=1.2.9"
|
||||
},
|
||||
|
||||
115
src/lib/repo-backup.test.ts
Normal file
115
src/lib/repo-backup.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
import type { Config } from "@/types/config";
|
||||
import { resolveBackupPaths } from "@/lib/repo-backup";
|
||||
|
||||
describe("resolveBackupPaths", () => {
|
||||
let originalBackupDirEnv: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
originalBackupDirEnv = process.env.PRE_SYNC_BACKUP_DIR;
|
||||
delete process.env.PRE_SYNC_BACKUP_DIR;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalBackupDirEnv === undefined) {
|
||||
delete process.env.PRE_SYNC_BACKUP_DIR;
|
||||
} else {
|
||||
process.env.PRE_SYNC_BACKUP_DIR = originalBackupDirEnv;
|
||||
}
|
||||
});
|
||||
|
||||
test("returns absolute paths when backupDirectory is relative", () => {
|
||||
const config: Partial<Config> = {
|
||||
userId: "user-123",
|
||||
giteaConfig: {
|
||||
backupDirectory: "data/repo-backups",
|
||||
} as Config["giteaConfig"],
|
||||
};
|
||||
|
||||
const { backupRoot, repoBackupDir } = resolveBackupPaths({
|
||||
config,
|
||||
owner: "RayLabsHQ",
|
||||
repoName: "gitea-mirror",
|
||||
});
|
||||
|
||||
expect(path.isAbsolute(backupRoot)).toBe(true);
|
||||
expect(path.isAbsolute(repoBackupDir)).toBe(true);
|
||||
expect(repoBackupDir).toBe(
|
||||
path.join(backupRoot, "user-123", "RayLabsHQ", "gitea-mirror")
|
||||
);
|
||||
});
|
||||
|
||||
test("returns absolute paths when backupDirectory is already absolute", () => {
|
||||
const config: Partial<Config> = {
|
||||
userId: "user-123",
|
||||
giteaConfig: {
|
||||
backupDirectory: "/data/repo-backups",
|
||||
} as Config["giteaConfig"],
|
||||
};
|
||||
|
||||
const { backupRoot, repoBackupDir } = resolveBackupPaths({
|
||||
config,
|
||||
owner: "owner",
|
||||
repoName: "repo",
|
||||
});
|
||||
|
||||
expect(backupRoot).toBe("/data/repo-backups");
|
||||
expect(path.isAbsolute(repoBackupDir)).toBe(true);
|
||||
});
|
||||
|
||||
test("falls back to cwd-based path when no backupDirectory is set", () => {
|
||||
const config: Partial<Config> = {
|
||||
userId: "user-123",
|
||||
giteaConfig: {} as Config["giteaConfig"],
|
||||
};
|
||||
|
||||
const { backupRoot } = resolveBackupPaths({
|
||||
config,
|
||||
owner: "owner",
|
||||
repoName: "repo",
|
||||
});
|
||||
|
||||
expect(path.isAbsolute(backupRoot)).toBe(true);
|
||||
expect(backupRoot).toBe(
|
||||
path.resolve(process.cwd(), "data", "repo-backups")
|
||||
);
|
||||
});
|
||||
|
||||
test("uses PRE_SYNC_BACKUP_DIR env var when config has no backupDirectory", () => {
|
||||
process.env.PRE_SYNC_BACKUP_DIR = "custom/backup/path";
|
||||
|
||||
const config: Partial<Config> = {
|
||||
userId: "user-123",
|
||||
giteaConfig: {} as Config["giteaConfig"],
|
||||
};
|
||||
|
||||
const { backupRoot } = resolveBackupPaths({
|
||||
config,
|
||||
owner: "owner",
|
||||
repoName: "repo",
|
||||
});
|
||||
|
||||
expect(path.isAbsolute(backupRoot)).toBe(true);
|
||||
expect(backupRoot).toBe(path.resolve("custom/backup/path"));
|
||||
});
|
||||
|
||||
test("sanitizes owner and repoName in path segments", () => {
|
||||
const config: Partial<Config> = {
|
||||
userId: "user-123",
|
||||
giteaConfig: {
|
||||
backupDirectory: "/backups",
|
||||
} as Config["giteaConfig"],
|
||||
};
|
||||
|
||||
const { repoBackupDir } = resolveBackupPaths({
|
||||
config,
|
||||
owner: "org/with-slash",
|
||||
repoName: "repo name!",
|
||||
});
|
||||
|
||||
expect(repoBackupDir).toBe(
|
||||
path.join("/backups", "user-123", "org_with-slash", "repo_name_")
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -101,6 +101,36 @@ export function shouldBlockSyncOnBackupFailure(config: Partial<Config>): boolean
|
||||
return configSetting === undefined ? true : Boolean(configSetting);
|
||||
}
|
||||
|
||||
export function resolveBackupPaths({
|
||||
config,
|
||||
owner,
|
||||
repoName,
|
||||
}: {
|
||||
config: Partial<Config>;
|
||||
owner: string;
|
||||
repoName: string;
|
||||
}): { backupRoot: string; repoBackupDir: string } {
|
||||
let backupRoot =
|
||||
config.giteaConfig?.backupDirectory?.trim() ||
|
||||
process.env.PRE_SYNC_BACKUP_DIR?.trim() ||
|
||||
path.join(process.cwd(), "data", "repo-backups");
|
||||
|
||||
// Ensure backupRoot is absolute - relative paths break git bundle creation
|
||||
// because git runs with -C mirrorClonePath and interprets relative paths from there.
|
||||
// Always use path.resolve() which guarantees an absolute path, rather than a
|
||||
// conditional check that can miss edge cases (e.g., NixOS systemd services).
|
||||
backupRoot = path.resolve(backupRoot);
|
||||
|
||||
const repoBackupDir = path.join(
|
||||
backupRoot,
|
||||
sanitizePathSegment(config.userId || "unknown-user"),
|
||||
sanitizePathSegment(owner),
|
||||
sanitizePathSegment(repoName)
|
||||
);
|
||||
|
||||
return { backupRoot, repoBackupDir };
|
||||
}
|
||||
|
||||
export async function createPreSyncBundleBackup({
|
||||
config,
|
||||
owner,
|
||||
@@ -126,16 +156,7 @@ export async function createPreSyncBundleBackup({
|
||||
throw new Error("Decrypted Gitea token is required for pre-sync backup.");
|
||||
}
|
||||
|
||||
let backupRoot =
|
||||
config.giteaConfig?.backupDirectory?.trim() ||
|
||||
process.env.PRE_SYNC_BACKUP_DIR?.trim() ||
|
||||
path.join(process.cwd(), "data", "repo-backups");
|
||||
|
||||
// Ensure backupRoot is absolute - relative paths break git bundle creation
|
||||
// because git runs with -C mirrorClonePath and interprets relative paths from there
|
||||
if (!path.isAbsolute(backupRoot)) {
|
||||
backupRoot = path.resolve(process.cwd(), backupRoot);
|
||||
}
|
||||
const { repoBackupDir } = resolveBackupPaths({ config, owner, repoName });
|
||||
const retention = Math.max(
|
||||
1,
|
||||
Number.isFinite(config.giteaConfig?.backupRetentionCount)
|
||||
@@ -143,18 +164,13 @@ export async function createPreSyncBundleBackup({
|
||||
: parsePositiveInt(process.env.PRE_SYNC_BACKUP_KEEP_COUNT, 20)
|
||||
);
|
||||
|
||||
const repoBackupDir = path.join(
|
||||
backupRoot,
|
||||
sanitizePathSegment(config.userId || "unknown-user"),
|
||||
sanitizePathSegment(owner),
|
||||
sanitizePathSegment(repoName)
|
||||
);
|
||||
|
||||
await mkdir(repoBackupDir, { recursive: true });
|
||||
|
||||
const tmpDir = await mkdtemp(path.join(os.tmpdir(), "gitea-mirror-backup-"));
|
||||
const mirrorClonePath = path.join(tmpDir, "repo.git");
|
||||
const bundlePath = path.join(repoBackupDir, `${buildTimestamp()}.bundle`);
|
||||
// path.resolve guarantees an absolute path, critical because git -C changes
|
||||
// the working directory and would misinterpret a relative bundlePath
|
||||
const bundlePath = path.resolve(repoBackupDir, `${buildTimestamp()}.bundle`);
|
||||
|
||||
try {
|
||||
const authCloneUrl = buildAuthenticatedCloneUrl(cloneUrl, giteaToken);
|
||||
|
||||
Reference in New Issue
Block a user