default scanned repo to private
MREADME.md
@@ -80,6 +80,7 @@ All settings are environment variables:
| `MAX_USER_UPLOAD_BYTES` | `2097152` | Max upload size for non-admin users (2 MB) |
| `INLINE_MAX_BYTES` | `524288` | Max file size rendered inline in the code view (512 KB) |
| `SSH_DISABLED` | `0` | Set to `1` to disable the embedded SSH server |
| `SCANNED_REPO_PRIVATE` | `1` | Set to `0` to make auto-scanned repos public by default |
| `TRUSTED_PROXY` | `0` | Trust `X-Forwarded-For` headers \*\* |
| `RATE_LIMIT_DISABLED` | `0` | Set to `1` to disable rate limiting |
| `HIGHLIGHT_WORKERS` | `4` | Syntax highlighting worker threads \* |
Mcompose.yml
@@ -15,6 +15,7 @@ services:
BASE_URL: "http://localhost:3000"
# ADMIN_PASSWORD: change-me
# SSH_DISABLED: 0
# SCANNED_REPO_PRIVATE: 1
# ARCHIVE_ZST_ENABLED: 0
# TRUSTED_PROXY: 1
# REGISTRATION_TYPE: enabled
Msrc/config.ts
@@ -19,6 +19,9 @@ const config = {
TRUSTED_PROXY: !!env.TRUSTED_PROXY,
RATE_LIMIT_DISABLED: !!env.RATE_LIMIT_DISABLED,
SSH_DISABLED: !!env.SSH_DISABLED,
SCANNED_REPO_PRIVATE:
env.SCANNED_REPO_PRIVATE !== "0" &&
env.SCANNED_REPO_PRIVATE !== "false",
PORT: parseInt(env.PORT ?? "", 10) || 3000,
SSH_PORT: parseInt(env.SSH_PORT ?? "", 10) || 2222,
REGISTRATION_TYPE: (env.REGISTRATION_TYPE ?? "enabled") as
Msrc/index.tsx
@@ -10,6 +10,6 @@ for (const cmd of ["git", "ssh-keygen"]) {
}
}
if (!config.SSH_DISABLED) await startSshServer();
await createApp(config.PORT);
if (!config.SSH_DISABLED) await startSshServer();
console.log(`Hearthforge running at http://localhost:${config.PORT}`);
Msrc/services/repoSync.ts
@@ -103,7 +103,7 @@ export async function ensureRepoRecord(name: string): Promise<RepositoryRow> {
.values({
name,
description: null,
is_private: 0,
is_private: config.SCANNED_REPO_PRIVATE ? 1 : 0,
default_branch: branch,
created_at: now,
})
Mtests/e2e.releases.test.ts
@@ -351,6 +351,12 @@ describe('releases', () => {
});
test('source archive tar.zst appears in downloads', async () => {
const zstd = Bun.spawnSync({ cmd: ['which', 'zstd'] });
if (zstd.exitCode !== 0) {
console.log('zstd not available, skipping tar.zst test');
return;
}
const page = await adminCtx.newPage();
try {
await page.goto(srcReleaseUrl);
Mtests/e2e.repos.test.ts
@@ -1,9 +1,14 @@
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { rmSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { chromium } from 'playwright';
import type { Browser, BrowserContext } from 'playwright';
import config from '../src/config.ts';
import { db } from '../src/db/index.ts';
import {
BASE,
ADMIN_PASS,
DATA_DIR,
setupTestEnv,
spawnServer,
killServer,
@@ -340,4 +345,27 @@ describe('repos', () => {
expect(resp.status()).toBe(200);
expect(await resp.text()).toContain('Invalid repository name');
});
test('auto-scanned repo is private by default', async () => {
const name = 'auto-private-repo';
const dir = `${process.cwd()}/${DATA_DIR}/repos/${name}.git`;
rmSync(dir, { recursive: true, force: true });
const tmp = `/tmp/hf-scan-${Date.now()}`;
try {
spawnSync('git', ['init', '--bare', dir], { stdio: 'ignore' });
spawnSync('git', ['clone', dir, tmp], { stdio: 'ignore' });
spawnSync('git', ['-C', tmp, 'commit', '--allow-empty', '-m', 'init'], { stdio: 'ignore' });
spawnSync('git', ['-C', tmp, 'push', 'origin', 'HEAD:main'], { stdio: 'ignore' });
} finally {
rmSync(tmp, { recursive: true, force: true });
}
const { ensureRepoRecord } = await import('../src/services/repoSync.ts');
const repo = await ensureRepoRecord(name);
expect(repo.is_private).toBe(1);
await db.deleteFrom('repositories').where('name', '=', name).execute();
rmSync(dir, { recursive: true, force: true });
});
});