Make admin quotas editable and simplify uploader storage
This commit is contained in:
@@ -115,10 +115,11 @@ try {
|
||||
if (verifiedAccount) {
|
||||
const saved = JSON.parse(
|
||||
node(
|
||||
`const base='http://127.0.0.1:${port}', cookie=${JSON.stringify(verifiedAccount.cookie)};const session=await(await fetch(base+'/api/guest/session',{method:'POST',headers:{Origin:'https://hop.test',Cookie:cookie,'Content-Type':'application/json'},body:'{}'})).json();const r=await fetch(base+'/api/admin/settings',{method:'PATCH',headers:{Origin:'https://hop.test',Cookie:cookie,'X-CSRF-Token':session.csrfToken,'Content-Type':'application/json'},body:JSON.stringify({revision:0,maxConcurrentUploads:3,blockedCountries:['CN'],allowedIps:['127.0.0.1'],autoBlockEnabled:true})});if(!r.ok)throw Error(await r.text());console.log(JSON.stringify(await r.json()));`,
|
||||
`const base='http://127.0.0.1:${port}', cookie=${JSON.stringify(verifiedAccount.cookie)};const session=await(await fetch(base+'/api/guest/session',{method:'POST',headers:{Origin:'https://hop.test',Cookie:cookie,'Content-Type':'application/json'},body:'{}'})).json();const r=await fetch(base+'/api/admin/settings',{method:'PATCH',headers:{Origin:'https://hop.test',Cookie:cookie,'X-CSRF-Token':session.csrfToken,'Content-Type':'application/json'},body:JSON.stringify({revision:0,maxConcurrentUploads:3,accountBytes:3e9,deviceBytes:4e9,devicesTotalBytes:12e9,perFileBytes:1e9,minDiskFreeBytes:0,blockedCountries:['CN'],allowedIps:['127.0.0.1'],autoBlockEnabled:true})});if(!r.ok)throw Error(await r.text());console.log(JSON.stringify(await r.json()));`,
|
||||
),
|
||||
);
|
||||
assert.equal(saved.maxConcurrentUploads, 3);
|
||||
assert.equal(saved.accountBytes, 3e9);
|
||||
node(
|
||||
`import Database from 'better-sqlite3';const db=new Database('/data/database/hop.sqlite');db.prepare('INSERT INTO automatic_blocks (ip,countryCode,reason,createdAt,expiresAt) VALUES (?,?,?,?,?)').run('8.8.8.8','KR','login-failures',Date.now(),Date.now()+1800000);db.close();`,
|
||||
);
|
||||
@@ -153,6 +154,14 @@ try {
|
||||
),
|
||||
);
|
||||
assert.equal(settings.maxConcurrentUploads, 3);
|
||||
for (const [key, value] of Object.entries({
|
||||
accountBytes: 3e9,
|
||||
deviceBytes: 4e9,
|
||||
devicesTotalBytes: 12e9,
|
||||
perFileBytes: 1e9,
|
||||
minDiskFreeBytes: 0,
|
||||
}))
|
||||
assert.equal(settings[key], value, key);
|
||||
assert.deepEqual(settings.blockedCountries, ['CN']);
|
||||
assert.deepEqual(settings.allowedIps, ['127.0.0.1']);
|
||||
assert.equal(settings.autoBlockEnabled, true);
|
||||
@@ -216,7 +225,7 @@ try {
|
||||
assert.deepEqual(mode, { uid: 12001, gid: 12001, mode: 0o700 });
|
||||
const originals = resolve(folder, 'originals');
|
||||
const stored = readdirSync(originals)
|
||||
.filter((key) => key.startsWith('guest-'))
|
||||
.filter((key) => /^[a-f0-9-]{36}$/.test(key))
|
||||
.flatMap((owner) =>
|
||||
readdirSync(resolve(originals, owner)).map((key) => ({
|
||||
owner,
|
||||
@@ -226,7 +235,7 @@ try {
|
||||
)
|
||||
.find(({ path }) => readFileSync(path).toString() === 'Docker file persistence');
|
||||
assert(stored, 'uploaded file is visible under its guest owner in the host project');
|
||||
assert.match(stored.owner, /^guest-[a-f0-9-]{36}$/);
|
||||
assert.match(stored.owner, /^[a-f0-9-]{36}$/);
|
||||
assert.match(stored.key, /^[a-f0-9-]{36}\.txt$/);
|
||||
assert.equal(existsSync(resolve(folder, 'completed')), false);
|
||||
unlinkSync(stored.path);
|
||||
@@ -249,8 +258,46 @@ try {
|
||||
assert(visible.some((item) => item.file?.name === 'resume.bin'));
|
||||
}
|
||||
assert.equal(docker('inspect', '--format', '{{json .NetworkSettings.Ports}}', name), '{}');
|
||||
if (process.env.HOP_CHECK_RESET === '1') {
|
||||
const protectedState = `import Database from 'better-sqlite3';import {createHash} from 'node:crypto';const db=new Database('/data/database/hop.sqlite');const tables=['users','account_sessions','account_tokens','user_rules','admin_settings','user_activity'];const rows=tables.map(t=>db.prepare('SELECT * FROM '+t).all());console.log(createHash('sha256').update(JSON.stringify(rows)).digest('hex'));db.close();`;
|
||||
const beforeReset = node(protectedState);
|
||||
docker('stop', name);
|
||||
const reset = docker(
|
||||
'run',
|
||||
'--rm',
|
||||
'--network',
|
||||
'none',
|
||||
'--volumes-from',
|
||||
name,
|
||||
'--user',
|
||||
runtimeUser,
|
||||
'--mount',
|
||||
`type=bind,source=${resolve('scripts/reset-transfer-records.mjs')},target=/app/scripts/reset-transfer-records.mjs,readonly`,
|
||||
'--env',
|
||||
'DATABASE_PATH=/data/database/hop.sqlite',
|
||||
'--entrypoint',
|
||||
'node',
|
||||
process.env.HOP_CHECK_IMAGE ?? 'hop-phase1-check:local',
|
||||
'/app/scripts/reset-transfer-records.mjs',
|
||||
'--confirm-reset-transfer-records',
|
||||
);
|
||||
assert.match(reset, /초기화 완료/);
|
||||
docker('start', name);
|
||||
wait();
|
||||
assert.equal(
|
||||
node(protectedState),
|
||||
beforeReset,
|
||||
'offline reset preserves user/auth/admin data exactly',
|
||||
);
|
||||
const emptied = JSON.parse(
|
||||
node(
|
||||
`import Database from 'better-sqlite3';const db=new Database('/data/database/hop.sqlite');console.log(JSON.stringify({files:db.prepare('SELECT count(*) AS n FROM files').get().n,requests:db.prepare('SELECT count(*) AS n FROM transfer_requests').get().n,guests:db.prepare('SELECT count(*) AS n FROM guest_sessions').get().n,integrity:db.pragma('integrity_check',{simple:true})}));db.close();`,
|
||||
),
|
||||
);
|
||||
assert.deepEqual(emptied, { files: 0, requests: 0, guests: 0, integrity: 'ok' });
|
||||
}
|
||||
console.log(
|
||||
`PASS: production Docker startup, HTTPS Secure session cookie, migration, anonymous session/name/file and Unicode text persistence and partial chunk resume after restart, SQLite integrity, no host ports published.${bindUploads ? ' Bind uploads visible on host, custom NAS UID ownership, manual host deletion reconciliation.' : ''}${verifiedAccount ? ' Verified account auto-login, protected cookie, previous session revocation, one-use token, admin role and persisted admin controls after restart.' : ''}`,
|
||||
`PASS: production Docker startup, HTTPS Secure session cookie, migration, anonymous session/name/file and Unicode text persistence and partial chunk resume after restart, SQLite integrity, no host ports published.${process.env.HOP_CHECK_RESET === '1' ? ' Offline transfer reset preserves accounts and admin settings.' : ''}${bindUploads ? ' Bind uploads visible on host, custom NAS UID ownership, manual host deletion reconciliation.' : ''}${verifiedAccount ? ' Verified account auto-login, protected cookie, previous session revocation, one-use token, admin role and persisted admin controls after restart.' : ''}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(execFileSync('docker', ['logs', name], { encoding: 'utf8', stdio: 'inherit' }));
|
||||
|
||||
Reference in New Issue
Block a user