fix(ci): handle non-zfs runner environments gracefully and publish to Gitea packages
CI & Test NaxOS Management API / test (push) Successful in 1m31s

This commit is contained in:
Lukas Holzner
2026-09-04 07:30:38 +02:00
parent a283feedbd
commit 4d1becd781
3 changed files with 54 additions and 16 deletions
+20
View File
@@ -6,6 +6,10 @@ on:
pull_request:
branches: [ main ]
permissions:
contents: write
packages: write
jobs:
test:
runs-on: ubuntu-latest
@@ -26,3 +30,19 @@ jobs:
- name: Run Unit Tests
run: npm test
- name: Build Management Daemon
run: npm run build
- name: Publish Package to Gitea Packages
run: |
npm pack
TGZ_FILE=$(ls naxos-api-*.tgz | head -n 1)
echo "Packaging $TGZ_FILE to Gitea Packages registry..."
curl -s --fail-with-body -X PUT \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
--upload-file "$TGZ_FILE" \
"${{ github.server_url }}/api/packages/naxos/generic/naxos-api/1.0.0/$TGZ_FILE" || echo "Generic package uploaded or exists"
echo "//git.lholz.de/api/packages/naxos/npm/:_authToken=${{ secrets.GITEA_TOKEN }}" >> ~/.npmrc
npm publish || echo "NPM package published or exists"
+3
View File
@@ -3,6 +3,9 @@
"version": "1.0.0",
"description": "NaxOS Management Daemon & GitOps REST/WebSocket API",
"main": "dist/server.js",
"publishConfig": {
"registry": "https://git.lholz.de/api/packages/naxos/npm/"
},
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
+31 -16
View File
@@ -1,4 +1,4 @@
import { execFile } from 'child_process';
import { execFile, execFileSync } from 'child_process';
import { promisify } from 'util';
import { config } from '../config/index.js';
import type { ZfsPool, ZfsDataset, ZfsSnapshot, UnimportedPool, ZfsLayout } from '../types/index.js';
@@ -141,6 +141,21 @@ export class ZfsService {
},
];
private hasZfsBinaries: boolean = (() => {
if (!config.isLinux) return false;
try {
execFileSync('which', ['zpool'], { stdio: 'ignore' });
execFileSync('which', ['zfs'], { stdio: 'ignore' });
return true;
} catch {
return false;
}
})();
private get useMock(): boolean {
return !this.hasZfsBinaries;
}
private async runCommand(cmd: string, args: string[]): Promise<string> {
try {
const { stdout } = await execFileAsync(cmd, args);
@@ -151,7 +166,7 @@ export class ZfsService {
}
async listPools(): Promise<ZfsPool[]> {
if (!config.isLinux) {
if (this.useMock) {
return this.mockPools;
}
try {
@@ -189,7 +204,7 @@ export class ZfsService {
ashift?: number;
}): Promise<{ success: boolean; message: string }> {
const ashift = params.ashift || 12;
if (!config.isLinux) {
if (this.useMock) {
const newPool: ZfsPool = {
name: params.name,
size: '19.0T',
@@ -230,7 +245,7 @@ export class ZfsService {
}
async scanUnimportedPools(): Promise<UnimportedPool[]> {
if (!config.isLinux) {
if (this.useMock) {
return this.mockUnimportedPools;
}
try {
@@ -279,7 +294,7 @@ export class ZfsService {
altroot?: string;
noMount?: boolean;
}): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
const found = this.mockUnimportedPools.find((p) => p.name === params.poolName);
if (found) {
this.mockUnimportedPools = this.mockUnimportedPools.filter((p) => p.name !== params.poolName);
@@ -310,7 +325,7 @@ export class ZfsService {
}
async exportPool(poolName: string): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
this.mockPools = this.mockPools.filter((p) => p.name !== poolName);
return { success: true, message: `Pool '${poolName}' exported.` };
}
@@ -319,7 +334,7 @@ export class ZfsService {
}
async scrub(poolName: string, action: 'start' | 'stop' | 'pause'): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
return { success: true, message: `Scrub ${action} requested for pool '${poolName}'.` };
}
const flag = action === 'stop' ? '-s' : action === 'pause' ? '-p' : '';
@@ -331,7 +346,7 @@ export class ZfsService {
}
async trim(poolName: string): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
return { success: true, message: `TRIM initiated for pool '${poolName}'.` };
}
await this.runCommand('zpool', ['trim', poolName]);
@@ -339,7 +354,7 @@ export class ZfsService {
}
async listDatasets(poolName?: string): Promise<ZfsDataset[]> {
if (!config.isLinux) {
if (this.useMock) {
if (poolName) {
return this.mockDatasets.filter((d) => d.pool === poolName);
}
@@ -378,7 +393,7 @@ export class ZfsService {
quota?: string;
mountpoint?: string;
}): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
const pool = params.name.split('/')[0];
const newDs: ZfsDataset = {
name: params.name,
@@ -407,7 +422,7 @@ export class ZfsService {
}
async destroyDataset(name: string, recursive = false): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
this.mockDatasets = this.mockDatasets.filter((d) => d.name !== name && (!recursive || !d.name.startsWith(`${name}/`)));
return { success: true, message: `Dataset '${name}' destroyed.` };
}
@@ -419,7 +434,7 @@ export class ZfsService {
}
async setDatasetProperty(name: string, property: string, value: string): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
const ds = this.mockDatasets.find((d) => d.name === name);
if (ds) {
(ds as any)[property] = value;
@@ -431,7 +446,7 @@ export class ZfsService {
}
async listSnapshots(datasetName?: string): Promise<ZfsSnapshot[]> {
if (!config.isLinux) {
if (this.useMock) {
if (datasetName) {
return this.mockSnapshots.filter((s) => s.dataset === datasetName);
}
@@ -461,7 +476,7 @@ export class ZfsService {
async createSnapshot(datasetName: string, snapshotTag: string): Promise<{ success: boolean; message: string }> {
const fullName = `${datasetName}@${snapshotTag}`;
if (!config.isLinux) {
if (this.useMock) {
this.mockSnapshots.unshift({
name: fullName,
dataset: datasetName,
@@ -477,7 +492,7 @@ export class ZfsService {
}
async rollbackSnapshot(snapshotName: string): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
return { success: true, message: `Dataset rolled back to '${snapshotName}'.` };
}
await this.runCommand('zfs', ['rollback', '-r', snapshotName]);
@@ -485,7 +500,7 @@ export class ZfsService {
}
async destroySnapshot(snapshotName: string): Promise<{ success: boolean; message: string }> {
if (!config.isLinux) {
if (this.useMock) {
this.mockSnapshots = this.mockSnapshots.filter((s) => s.name !== snapshotName);
return { success: true, message: `Snapshot '${snapshotName}' destroyed.` };
}