Skip to content

Commit 3cfe9fe

Browse files
authored
build: replace hash with build increment in slackware txz pkg (#1449)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced support for specifying and propagating a build number throughout the build process, including command-line options and workflow inputs. * TXZ package naming and URLs now include the build number for improved traceability. * **Improvements** * Enhanced robustness in locating TXZ files with improved fallback logic, especially in CI environments. * Improved flexibility and validation of environment schema for plugin builds. * **Style** * Minor formatting corrections for consistency and readability. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1210677942019563
1 parent e539d7f commit 3cfe9fe

File tree

11 files changed

+157
-59
lines changed

11 files changed

+157
-59
lines changed

.github/workflows/build-plugin.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ on:
2323
type: string
2424
required: true
2525
description: "Base URL for the plugin builds"
26+
BUILD_NUMBER:
27+
type: string
28+
required: true
29+
description: "Build number for the plugin builds"
2630
secrets:
2731
CF_ACCESS_KEY_ID:
2832
required: true
@@ -108,8 +112,8 @@ jobs:
108112
id: build-plugin
109113
run: |
110114
cd ${{ github.workspace }}/plugin
111-
pnpm run build:txz --tag="${{ inputs.TAG }}" --base-url="${{ inputs.BASE_URL }}" --api-version="${{ steps.vars.outputs.API_VERSION }}"
112-
pnpm run build:plugin --tag="${{ inputs.TAG }}" --base-url="${{ inputs.BASE_URL }}" --api-version="${{ steps.vars.outputs.API_VERSION }}"
115+
pnpm run build:txz --tag="${{ inputs.TAG }}" --base-url="${{ inputs.BASE_URL }}" --api-version="${{ steps.vars.outputs.API_VERSION }}" --build-number="${{ inputs.BUILD_NUMBER }}"
116+
pnpm run build:plugin --tag="${{ inputs.TAG }}" --base-url="${{ inputs.BASE_URL }}" --api-version="${{ steps.vars.outputs.API_VERSION }}" --build-number="${{ inputs.BUILD_NUMBER }}"
113117
114118
- name: Ensure Plugin Files Exist
115119
run: |

.github/workflows/main.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ jobs:
156156
build-api:
157157
name: Build API
158158
runs-on: ubuntu-latest
159+
outputs:
160+
build_number: ${{ steps.buildnumber.outputs.build_number }}
159161
defaults:
160162
run:
161163
working-directory: api
@@ -210,6 +212,14 @@ jobs:
210212
API_VERSION=$([[ -n "$IS_TAGGED" ]] && echo "$PACKAGE_LOCK_VERSION" || echo "${PACKAGE_LOCK_VERSION}+${GIT_SHA}")
211213
export API_VERSION
212214
echo "API_VERSION=${API_VERSION}" >> $GITHUB_ENV
215+
echo "PACKAGE_LOCK_VERSION=${PACKAGE_LOCK_VERSION}" >> $GITHUB_OUTPUT
216+
217+
- name: Generate build number
218+
id: buildnumber
219+
uses: onyxmueller/build-tag-number@v1
220+
with:
221+
token: ${{secrets.github_token}}
222+
prefix: ${{steps.vars.outputs.PACKAGE_LOCK_VERSION}}
213223

214224
- name: Build
215225
run: |
@@ -365,6 +375,7 @@ jobs:
365375
TAG: ${{ github.event.pull_request.number && format('PR{0}', github.event.pull_request.number) || '' }}
366376
BUCKET_PATH: ${{ github.event.pull_request.number && format('unraid-api/tag/PR{0}', github.event.pull_request.number) || 'unraid-api' }}
367377
BASE_URL: "https://preview.dl.unraid.net/unraid-api"
378+
BUILD_NUMBER: ${{ needs.build-api.outputs.build_number }}
368379
secrets:
369380
CF_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
370381
CF_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
@@ -387,6 +398,7 @@ jobs:
387398
TAG: ""
388399
BUCKET_PATH: unraid-api
389400
BASE_URL: "https://stable.dl.unraid.net/unraid-api"
401+
BUILD_NUMBER: ${{ needs.build-api.outputs.build_number }}
390402
secrets:
391403
CF_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
392404
CF_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}

plugin/builder/build-plugin.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { readFile, writeFile, mkdir, rename } from "fs/promises";
22
import { $ } from "zx";
33
import { escape as escapeHtml } from "html-sloppy-escaper";
44
import { dirname, join } from "node:path";
5-
import { getTxzName, pluginName, startingDir, defaultArch, defaultBuild } from "./utils/consts";
5+
import {
6+
getTxzName,
7+
pluginName,
8+
startingDir,
9+
defaultArch,
10+
defaultBuild,
11+
} from "./utils/consts";
612
import { getPluginUrl } from "./utils/bucket-urls";
713
import { getMainTxzUrl } from "./utils/bucket-urls";
814
import {
@@ -25,10 +31,17 @@ const checkGit = async () => {
2531
}
2632
};
2733

28-
const moveTxzFile = async ({txzPath, apiVersion}: Pick<PluginEnv, "txzPath" | "apiVersion">) => {
29-
const txzName = getTxzName(apiVersion);
34+
const moveTxzFile = async ({
35+
txzPath,
36+
apiVersion,
37+
buildNumber,
38+
}: Pick<PluginEnv, "txzPath" | "apiVersion" | "buildNumber">) => {
39+
const txzName = getTxzName({
40+
version: apiVersion,
41+
build: buildNumber.toString(),
42+
});
3043
const targetPath = join(deployDir, txzName);
31-
44+
3245
// Ensure the txz always has the full version name
3346
if (txzPath !== targetPath) {
3447
console.log(`Ensuring TXZ has correct name: ${txzPath} -> ${targetPath}`);
@@ -54,13 +67,14 @@ function updateEntityValue(
5467
const buildPlugin = async ({
5568
pluginVersion,
5669
baseUrl,
70+
buildNumber,
5771
tag,
5872
txzSha256,
5973
releaseNotes,
6074
apiVersion,
6175
}: PluginEnv) => {
6276
console.log(`API version: ${apiVersion}`);
63-
77+
6478
// Update plg file
6579
let plgContent = await readFile(getRootPluginPath({ startingDir }), "utf8");
6680

@@ -70,11 +84,19 @@ const buildPlugin = async ({
7084
version: pluginVersion,
7185
api_version: apiVersion,
7286
arch: defaultArch,
73-
build: defaultBuild,
87+
build: buildNumber.toString(),
7488
plugin_url: getPluginUrl({ baseUrl, tag }),
75-
txz_url: getMainTxzUrl({ baseUrl, apiVersion, tag }),
89+
txz_url: getMainTxzUrl({
90+
baseUrl,
91+
tag,
92+
version: apiVersion,
93+
build: buildNumber.toString(),
94+
}),
7695
txz_sha256: txzSha256,
77-
txz_name: getTxzName(apiVersion),
96+
txz_name: getTxzName({
97+
version: apiVersion,
98+
build: buildNumber.toString(),
99+
}),
78100
...(tag ? { tag } : {}),
79101
};
80102

plugin/builder/build-txz.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ const buildTxz = async (validatedEnv: TxzEnv) => {
158158
const version = validatedEnv.apiVersion;
159159

160160
// Always use version when getting txz name
161-
const txzName = getTxzName(version);
161+
const txzName = getTxzName({ version, build: validatedEnv.buildNumber.toString() });
162162
console.log(`Package name: ${txzName}`);
163163
const txzPath = join(validatedEnv.txzOutputDir, txzName);
164164

plugin/builder/cli/common-environment.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const baseEnvSchema = z.object({
1010
apiVersion: z.string(),
1111
baseUrl: z.string().url(),
1212
tag: z.string().optional().default(""),
13+
/** i.e. Slackware build number */
14+
buildNumber: z.coerce.number().int().default(1),
1315
});
1416

1517
export type BaseEnv = z.infer<typeof baseEnvSchema>;
@@ -43,5 +45,6 @@ export const addCommonOptions = (program: Command) => {
4345
"--tag <tag>",
4446
"Tag (used for PR and staging builds)",
4547
process.env.TAG
46-
);
48+
)
49+
.option("--build-number <number>", "Build number");
4750
};

plugin/builder/cli/setup-plugin-environment.ts

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,47 @@ import { existsSync } from "node:fs";
88
import { join } from "node:path";
99
import { baseEnvSchema, addCommonOptions } from "./common-environment";
1010

11-
const safeParseEnvSchema = baseEnvSchema.extend({
12-
txzPath: z.string().refine((val) => val.endsWith(".txz"), {
13-
message: "TXZ Path must end with .txz",
14-
}),
11+
const basePluginSchema = baseEnvSchema.extend({
12+
txzPath: z
13+
.string()
14+
.refine((val) => val.endsWith(".txz"), {
15+
message: "TXZ Path must end with .txz",
16+
})
17+
.optional(),
1518
pluginVersion: z.string().regex(/^\d{4}\.\d{2}\.\d{2}\.\d{4}$/, {
1619
message: "Plugin version must be in the format YYYY.MM.DD.HHMM",
1720
}),
1821
releaseNotesPath: z.string().optional(),
1922
});
2023

21-
const pluginEnvSchema = safeParseEnvSchema.extend({
22-
releaseNotes: z.string().nonempty("Release notes are required"),
23-
txzSha256: z.string().refine((val) => val.length === 64, {
24-
message: "TXZ SHA256 must be 64 characters long",
25-
}),
26-
});
24+
const safeParseEnvSchema = basePluginSchema.transform((data) => ({
25+
...data,
26+
txzPath:
27+
data.txzPath ||
28+
getTxzPath({
29+
startingDir: process.cwd(),
30+
version: data.apiVersion,
31+
build: data.buildNumber.toString(),
32+
}),
33+
}));
34+
35+
const pluginEnvSchema = basePluginSchema
36+
.extend({
37+
releaseNotes: z.string().nonempty("Release notes are required"),
38+
txzSha256: z.string().refine((val) => val.length === 64, {
39+
message: "TXZ SHA256 must be 64 characters long",
40+
}),
41+
})
42+
.transform((data) => ({
43+
...data,
44+
txzPath:
45+
data.txzPath ||
46+
getTxzPath({
47+
startingDir: process.cwd(),
48+
version: data.apiVersion,
49+
build: data.buildNumber.toString(),
50+
}),
51+
}));
2752

2853
export type PluginEnv = z.infer<typeof pluginEnvSchema>;
2954

@@ -36,7 +61,11 @@ export type PluginEnv = z.infer<typeof pluginEnvSchema>;
3661
* @returns Object containing the resolved txz path and SHA256 hash
3762
* @throws Error if no valid txz file can be found
3863
*/
39-
export const resolveTxzPath = async (txzPath: string, apiVersion: string, isCi?: boolean): Promise<{path: string, sha256: string}> => {
64+
export const resolveTxzPath = async (
65+
txzPath: string,
66+
apiVersion: string,
67+
isCi?: boolean
68+
): Promise<{ path: string; sha256: string }> => {
4069
if (existsSync(txzPath)) {
4170
await access(txzPath, constants.F_OK);
4271
console.log("Reading txz file from:", txzPath);
@@ -46,43 +75,45 @@ export const resolveTxzPath = async (txzPath: string, apiVersion: string, isCi?:
4675
}
4776
return {
4877
path: txzPath,
49-
sha256: getSha256(txzFile)
78+
sha256: getSha256(txzFile),
5079
};
5180
}
5281

5382
console.log(`TXZ path not found at: ${txzPath}`);
5483
console.log(`Attempting to find TXZ using apiVersion: ${apiVersion}`);
55-
84+
5685
// Try different formats of generated TXZ name
5786
const deployDir = join(process.cwd(), "deploy");
58-
87+
5988
// Try with exact apiVersion format
6089
const alternativePaths = [
6190
join(deployDir, `dynamix.unraid.net-${apiVersion}-x86_64-1.txz`),
6291
];
63-
92+
6493
// In CI, we sometimes see unusual filenames, so try a glob-like approach
6594
if (isCi) {
6695
console.log("Checking for possible TXZ files in deploy directory");
67-
96+
6897
try {
6998
// Using node's filesystem APIs to scan the directory
70-
const fs = require('fs');
99+
const fs = require("fs");
71100
const deployFiles = fs.readdirSync(deployDir);
72-
101+
73102
// Find any txz file that contains the apiVersion
74103
for (const file of deployFiles) {
75-
if (file.endsWith('.txz') &&
76-
file.includes('dynamix.unraid.net') &&
77-
file.includes(apiVersion.split('+')[0])) {
104+
if (
105+
file.endsWith(".txz") &&
106+
file.includes("dynamix.unraid.net") &&
107+
file.includes(apiVersion.split("+")[0])
108+
) {
78109
alternativePaths.push(join(deployDir, file));
79110
}
80111
}
81112
} catch (error) {
82113
console.log(`Error scanning deploy directory: ${error}`);
83114
}
84115
}
85-
116+
86117
// Check each path
87118
for (const path of alternativePaths) {
88119
if (existsSync(path)) {
@@ -96,14 +127,16 @@ export const resolveTxzPath = async (txzPath: string, apiVersion: string, isCi?:
96127
}
97128
return {
98129
path,
99-
sha256: getSha256(txzFile)
130+
sha256: getSha256(txzFile),
100131
};
101132
}
102133
console.log(`Could not find TXZ at: ${path}`);
103134
}
104-
135+
105136
// If we get here, we couldn't find a valid txz file
106-
throw new Error(`Could not find any valid TXZ file. Tried original path: ${txzPath} and alternatives.`);
137+
throw new Error(
138+
`Could not find any valid TXZ file. Tried original path: ${txzPath} and alternatives.`
139+
);
107140
};
108141

109142
export const validatePluginEnv = async (
@@ -127,7 +160,11 @@ export const validatePluginEnv = async (
127160
}
128161

129162
// Resolve and validate the txz path
130-
const { path, sha256 } = await resolveTxzPath(safeEnv.txzPath, safeEnv.apiVersion, safeEnv.ci);
163+
const { path, sha256 } = await resolveTxzPath(
164+
safeEnv.txzPath,
165+
safeEnv.apiVersion,
166+
safeEnv.ci
167+
);
131168
envArgs.txzPath = path;
132169
envArgs.txzSha256 = sha256;
133170

@@ -142,8 +179,9 @@ export const validatePluginEnv = async (
142179

143180
export const getPluginVersion = () => {
144181
const now = new Date();
145-
146-
const formatUtcComponent = (component: number) => String(component).padStart(2, '0');
182+
183+
const formatUtcComponent = (component: number) =>
184+
String(component).padStart(2, "0");
147185

148186
const year = now.getUTCFullYear();
149187
const month = formatUtcComponent(now.getUTCMonth() + 1);
@@ -162,13 +200,12 @@ export const setupPluginEnv = async (argv: string[]): Promise<PluginEnv> => {
162200

163201
// Add common options
164202
addCommonOptions(program);
165-
203+
166204
// Add plugin-specific options
167205
program
168206
.option(
169207
"--txz-path <path>",
170-
"Path to built package, will be used to generate the SHA256 and renamed with the plugin version",
171-
getTxzPath({ startingDir: process.cwd(), pluginVersion: process.env.API_VERSION })
208+
"Path to built package, will be used to generate the SHA256 and renamed with the plugin version"
172209
)
173210
.option(
174211
"--plugin-version <version>",

plugin/builder/utils/bucket-urls.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
import { getTxzName, LOCAL_BUILD_TAG, pluginNameWithExt, defaultArch, defaultBuild } from "./consts";
1+
import {
2+
getTxzName,
3+
LOCAL_BUILD_TAG,
4+
pluginNameWithExt,
5+
defaultArch,
6+
defaultBuild,
7+
TxzNameParams,
8+
} from "./consts";
29

310
// Define a common interface for URL parameters
411
interface UrlParams {
512
baseUrl: string;
613
tag?: string;
714
}
815

9-
interface TxzUrlParams extends UrlParams {
10-
apiVersion: string;
11-
}
16+
interface TxzUrlParams extends UrlParams, TxzNameParams {}
1217

1318
/**
1419
* Get the bucket path for the given tag
@@ -47,4 +52,4 @@ export const getPluginUrl = (params: UrlParams): string =>
4752
* ex. returns = BASE_URL/TAG/dynamix.unraid.net-4.1.3-x86_64-1.txz
4853
*/
4954
export const getMainTxzUrl = (params: TxzUrlParams): string =>
50-
getAssetUrl(params, getTxzName(params.apiVersion, defaultArch, defaultBuild));
55+
getAssetUrl(params, getTxzName(params));

0 commit comments

Comments
 (0)