initial commit

This commit is contained in:
Timofey Gelazoniya 2025-02-02 11:04:27 +03:00
commit d42b5323eb
Signed by: zeldon
GPG Key ID: 047886915281DD2A
2 changed files with 206 additions and 0 deletions

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# Gitea Release Attachments Cleaner
A Node.js script to manage and clean up release attachments in Gitea repositories. This tool helps maintain repository size by keeping only the N most recent release attachments and removing older ones.
## Prerequisites
- Node.js installed on your system
- Access to a Gitea instance
- Personal access token with repository access permissions
## Configuration
Edit the `config` object in `index.js`:
```javascript
const config = {
GITEA_URL: "https://git.zeldon.ru",
GITEA_TOKEN: "", // Your Gitea personal access token
REPO_OWNER: "", // Repository owner (user or organization)
REPO_NAME: "", // Repository name
KEEP_LAST_N: 10, // Number of latest attachments to keep
};
```
## Usage
1. Clone this repository
2. Run the script:
```bash
node index.js
```
The script will:
1. List all attachments and their total size
2. Show how many attachments will be kept and deleted
3. Ask for confirmation before deletion
4. Delete the selected attachments and show progress
Example output:
```
Found 25 total attachments (156.78 MB)
Keeping 10 most recent attachments (52.30 MB)
15 attachments will be deleted (104.48 MB)
Do you want to proceed with deletion? (y/N)
```

159
index.js Normal file
View File

@ -0,0 +1,159 @@
const readline = require("node:readline/promises");
const { stdin: input, stdout: output } = require("node:process");
// Configuration
const config = {
GITEA_URL: "https://git.zeldon.ru",
GITEA_TOKEN: "",
REPO_OWNER: "",
REPO_NAME: "",
KEEP_LAST_N: 10, // Number of latest attachments to keep
};
// Helper function for API calls
async function fetchApi(endpoint) {
const response = await fetch(`${config.GITEA_URL}/api/v1${endpoint}`, {
headers: {
Authorization: `token ${config.GITEA_TOKEN}`,
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(
`API call failed: ${response.status} ${response.statusText}`
);
}
return response.json();
}
async function getAllReleases() {
let page = 1;
let allReleases = [];
while (true) {
const releases = await fetchApi(
`/repos/${config.REPO_OWNER}/${config.REPO_NAME}/releases?page=${page}&limit=50`
);
if (releases.length === 0) {
break;
}
allReleases = [...allReleases, ...releases];
page++;
}
return allReleases;
}
async function getAttachmentsToDelete() {
try {
// Get all releases using pagination
const releases = await getAllReleases();
// Collect all attachments with their metadata
const allAttachments = releases.reduce((acc, release) => {
const attachments = (release.assets || []).map((asset) => ({
id: asset.id,
name: asset.name,
created: new Date(release.created_at),
releaseId: release.id,
size: asset.size,
}));
return [...acc, ...attachments];
}, []);
// Sort attachments by creation date (newest first)
allAttachments.sort((a, b) => b.created - a.created);
// Get attachments to delete (skip the first KEEP_LAST_N)
const attachmentsToDelete = allAttachments.slice(config.KEEP_LAST_N);
// Calculate total size in MB
const totalSizeMB = (
allAttachments.reduce((sum, att) => sum + att.size, 0) /
(1024 * 1024)
).toFixed(2);
const sizeToDeleteMB = (
attachmentsToDelete.reduce((sum, att) => sum + att.size, 0) /
(1024 * 1024)
).toFixed(2);
const sizeToKeepMB = (totalSizeMB - sizeToDeleteMB).toFixed(2);
console.log(
`Found ${allAttachments.length} total attachments (${totalSizeMB} MB)`
);
console.log(
`Keeping ${config.KEEP_LAST_N} most recent attachments (${sizeToKeepMB} MB)`
);
console.log(
`${attachmentsToDelete.length} attachments will be deleted (${sizeToDeleteMB} MB)`
);
// Return list of attachment IDs to delete
return attachmentsToDelete.map((att) => ({
id: att.id,
name: att.name,
releaseId: att.releaseId,
}));
} catch (error) {
console.error("Error getting attachments:", error.message);
throw error;
}
}
async function deleteAttachment(releaseId, attachmentId) {
const response = await fetch(
`${config.GITEA_URL}/api/v1/repos/${config.REPO_OWNER}/${config.REPO_NAME}/releases/${releaseId}/assets/${attachmentId}`,
{
method: "DELETE",
headers: {
Authorization: `token ${config.GITEA_TOKEN}`,
},
}
);
if (!response.ok) {
throw new Error(
`Failed to delete attachment ${attachmentId}: ${response.status} ${response.statusText}`
);
}
}
async function main() {
try {
const attachments = await getAttachmentsToDelete();
const rl = readline.createInterface({ input, output });
const answer = await rl.question(
"\nDo you want to proceed with deletion? (y/N) "
);
rl.close();
if (answer.toLowerCase() === "y") {
console.log("\nDeleting attachments...");
let deleted = 0;
for (const att of attachments) {
try {
await deleteAttachment(att.releaseId, att.id);
console.log(`Deleted: ${att.name}`);
deleted++;
} catch (error) {
console.error(`Failed to delete ${att.name}:`, error.message);
}
}
console.log(
`\nDeletion complete. Successfully deleted ${deleted}/${attachments.length} attachments.`
);
} else {
console.log("Operation cancelled by user.");
}
} catch (error) {
console.error("Script failed:", error);
process.exit(1);
}
}
main();