diff --git a/.gitea/release-upload.ts b/.gitea/release-upload.ts new file mode 100644 index 0000000..a8f6bfe --- /dev/null +++ b/.gitea/release-upload.ts @@ -0,0 +1,90 @@ +/** + * Release asset uploader for Gitea + * Streams large files without loading them into memory (bypasses curl's 2GB multipart limit) + * + * Usage: GITEA_TOKEN=xxx RELEASE_ID=123 GITEA_REPO=owner/repo tsx release-upload.ts + */ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as https from 'https'; + +const token = process.env.GITEA_TOKEN; +const releaseId = process.env.RELEASE_ID; +const repo = process.env.GITEA_REPO; + +if (!token || !releaseId || !repo) { + console.error('Missing required env vars: GITEA_TOKEN, RELEASE_ID, GITEA_REPO'); + process.exit(1); +} + +const boundary = '----FormBoundary' + Date.now().toString(16); + +async function uploadFile(filepath: string): Promise { + const filename = path.basename(filepath); + const stats = fs.statSync(filepath); + console.log(`Uploading ${filename} (${stats.size} bytes)...`); + + const header = Buffer.from( + `--${boundary}\r\n` + + `Content-Disposition: form-data; name="attachment"; filename="${filename}"\r\n` + + `Content-Type: application/octet-stream\r\n\r\n` + ); + const footer = Buffer.from(`\r\n--${boundary}--\r\n`); + const contentLength = header.length + stats.size + footer.length; + + return new Promise((resolve, reject) => { + const req = https.request({ + hostname: 'code.foss.global', + path: `/api/v1/repos/${repo}/releases/${releaseId}/assets?name=${encodeURIComponent(filename)}`, + method: 'POST', + headers: { + 'Authorization': `token ${token}`, + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + 'Content-Length': contentLength + } + }, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + console.log(data); + if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { + console.log(`✓ ${filename} uploaded successfully`); + resolve(); + } else { + reject(new Error(`Upload failed: ${res.statusCode} ${data}`)); + } + }); + }); + + req.on('error', reject); + + // Stream: write header, pipe file, write footer + req.write(header); + const stream = fs.createReadStream(filepath); + stream.on('error', reject); + stream.on('end', () => { + req.write(footer); + req.end(); + }); + stream.pipe(req, { end: false }); + }); +} + +async function main() { + const distDir = 'dist'; + const files = fs.readdirSync(distDir) + .map(f => path.join(distDir, f)) + .filter(f => fs.statSync(f).isFile()); + + for (const file of files) { + await uploadFile(file); + } + + console.log('All assets uploaded successfully'); +} + +main().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 83e8be8..46f8f0b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -75,16 +75,13 @@ jobs: echo "Created release with ID: $RELEASE_ID" - # Upload assets - for asset in dist/*; do - filename=$(basename "$asset") - echo "Uploading $filename..." - curl -X POST -s \ - -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - -T "$asset" \ - "https://code.foss.global/api/v1/repos/${{ gitea.repository }}/releases/$RELEASE_ID/assets?name=$filename" - done + # Upload assets using TypeScript (curl has 2GB multipart limit) + pnpm install -g tsx + + GITEA_TOKEN="${{ secrets.GITHUB_TOKEN }}" \ + GITEA_REPO="${{ gitea.repository }}" \ + RELEASE_ID="$RELEASE_ID" \ + tsx .gitea/release-upload.ts - name: Cleanup old releases (keep 3 latest) run: | diff --git a/changelog.md b/changelog.md index 488feeb..6b84a3b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2026-01-09 - 0.3.5 - fix(ci) +add Gitea release asset uploader and switch release workflow to use it; bump package and daemon versions to 0.3.4 + +- Add .gitea/release-upload.ts: streams assets to Gitea to avoid curl's 2GB multipart limit +- Update CI workflow (.gitea/workflows/release.yml) to run the TypeScript uploader via tsx +- Bump package.json and ecoos_daemon/ts/version.ts to 0.3.4 +- Update bundled eco-daemon binary in isobuild/config/includes.chroot/opt/eco/bin/ + ## 2026-01-09 - 0.3.2 - fix(release) bump package and daemon to v0.3.1, add project README, and fix Gitea release upload flag diff --git a/ecoos_daemon/ts/version.ts b/ecoos_daemon/ts/version.ts index 767a57d..5f0ba60 100644 --- a/ecoos_daemon/ts/version.ts +++ b/ecoos_daemon/ts/version.ts @@ -1 +1 @@ -export const VERSION = "0.3.1"; +export const VERSION = "0.3.4"; diff --git a/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon b/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon index e26d912..b7bcda2 100755 Binary files a/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon and b/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon differ diff --git a/package.json b/package.json index 9964be7..ed655cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ecobridge/eco-os", - "version": "0.3.2", + "version": "0.3.4", "private": true, "scripts": { "build": "[ -z \"$CI\" ] && npm version patch --no-git-tag-version || true && node -e \"const v=require('./package.json').version; require('fs').writeFileSync('ecoos_daemon/ts/version.ts', 'export const VERSION = \\\"'+v+'\\\";\\n');\" && pnpm run daemon:bundle && cp ecoos_daemon/bundle/eco-daemon isobuild/config/includes.chroot/opt/eco/bin/ && mkdir -p .nogit/iso && docker build --no-cache -t ecoos-builder -f isobuild/Dockerfile . && docker run --privileged --name ecoos-build ecoos-builder && docker cp ecoos-build:/output/ecoos.iso .nogit/iso/ecoos.iso && docker rm ecoos-build",