@notur/sdk 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -89,6 +89,37 @@ This creates a `.notur` file (tar.gz archive) containing:
89
89
 
90
90
  Upload the resulting `.notur` file to your Pterodactyl admin panel at `/admin/notur/extensions`.
91
91
 
92
+ ## Signing Extensions
93
+
94
+ The SDK provides tools for Ed25519 signing of extension archives, compatible with the PHP `notur:keygen` and `notur:export --sign` commands.
95
+
96
+ ### Generating a Keypair
97
+
98
+ ```bash
99
+ npx notur-keygen
100
+ ```
101
+
102
+ This generates a new Ed25519 keypair and outputs:
103
+ - **Public Key** (64 hex characters) -- Share with panel administrators
104
+ - **Secret Key** (128 hex characters) -- Keep private, used for signing
105
+
106
+ ### Signing an Archive
107
+
108
+ ```bash
109
+ # Using environment variable
110
+ NOTUR_SECRET_KEY=your_secret_key npx notur-pack --sign
111
+
112
+ # Or with --secret-key flag
113
+ npx notur-pack --sign --secret-key your_secret_key
114
+ ```
115
+
116
+ This produces three files:
117
+ - `vendor-name-1.0.0.notur` -- The extension archive
118
+ - `vendor-name-1.0.0.notur.sha256` -- SHA-256 checksum
119
+ - `vendor-name-1.0.0.notur.sig` -- Ed25519 signature (hex-encoded)
120
+
121
+ The `.sig` file format is compatible with PHP's `SignatureVerifier::verify()` and panels with `require_signatures` enabled.
122
+
92
123
  ## Build
93
124
 
94
125
  ```bash
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * notur-keygen - Generate Ed25519 keypair for signing Notur extensions
5
+ *
6
+ * Usage:
7
+ * npx notur-keygen
8
+ * bunx notur-keygen
9
+ *
10
+ * Generates a new Ed25519 keypair and outputs both keys in hex format.
11
+ * The output format matches the PHP `php artisan notur:keygen` command.
12
+ */
13
+
14
+ const sodium = require('libsodium-wrappers');
15
+
16
+ async function main() {
17
+ await sodium.ready;
18
+
19
+ const keypair = sodium.crypto_sign_keypair();
20
+
21
+ const publicKeyHex = sodium.to_hex(keypair.publicKey);
22
+ const secretKeyHex = sodium.to_hex(keypair.privateKey);
23
+
24
+ console.log('Ed25519 keypair generated successfully.');
25
+ console.log('');
26
+ console.log('Public Key:');
27
+ console.log(publicKeyHex);
28
+ console.log('');
29
+ console.log('Secret Key:');
30
+ console.log(secretKeyHex);
31
+ console.log('');
32
+ console.log('Store the secret key securely. It is used to sign extension archives.');
33
+ console.log('Add the public key to your panel configuration:');
34
+ console.log('');
35
+ console.log(` NOTUR_PUBLIC_KEY=${publicKeyHex}`);
36
+ console.log('');
37
+ console.log('To sign an archive, set the secret key as an environment variable:');
38
+ console.log('');
39
+ console.log(` NOTUR_SECRET_KEY=${secretKeyHex}`);
40
+ console.log(' npx notur-pack --sign');
41
+ }
42
+
43
+ main().catch(err => {
44
+ console.error('Error:', err.message);
45
+ process.exit(1);
46
+ });
package/bin/notur-pack.js CHANGED
@@ -6,6 +6,8 @@
6
6
  * Usage:
7
7
  * npx notur-pack [path] # Pack extension at path (default: current dir)
8
8
  * npx notur-pack --output foo.notur
9
+ * npx notur-pack --sign # Sign using NOTUR_SECRET_KEY env var
10
+ * npx notur-pack --sign --secret-key xxx
9
11
  * bunx notur-pack
10
12
  *
11
13
  * A .notur file is a tar.gz archive containing:
@@ -34,11 +36,17 @@ function parseArgs() {
34
36
  const options = {
35
37
  path: '.',
36
38
  output: null,
39
+ sign: false,
40
+ secretKey: null,
37
41
  };
38
42
 
39
43
  for (let i = 0; i < args.length; i++) {
40
44
  if (args[i] === '--output' || args[i] === '-o') {
41
45
  options.output = args[++i];
46
+ } else if (args[i] === '--sign' || args[i] === '-s') {
47
+ options.sign = true;
48
+ } else if (args[i] === '--secret-key') {
49
+ options.secretKey = args[++i];
42
50
  } else if (!args[i].startsWith('-')) {
43
51
  options.path = args[i];
44
52
  }
@@ -123,7 +131,45 @@ function computeChecksums(dir, files) {
123
131
  return checksums;
124
132
  }
125
133
 
126
- function pack(sourceDir, outputPath) {
134
+ async function signArchive(archivePath, secretKeyHex) {
135
+ const sodium = require('libsodium-wrappers');
136
+ await sodium.ready;
137
+
138
+ const content = fs.readFileSync(archivePath);
139
+ const secretKey = sodium.from_hex(secretKeyHex);
140
+ const signature = sodium.crypto_sign_detached(content, secretKey);
141
+
142
+ return sodium.to_hex(signature);
143
+ }
144
+
145
+ function getSecretKey(options) {
146
+ // Check --secret-key argument first, then environment variable
147
+ const secretKey = options.secretKey || process.env.NOTUR_SECRET_KEY;
148
+
149
+ if (!secretKey) {
150
+ console.error('Error: --sign requires a secret key.');
151
+ console.error('');
152
+ console.error('Provide it via:');
153
+ console.error(' NOTUR_SECRET_KEY=xxx npx notur-pack --sign');
154
+ console.error(' npx notur-pack --sign --secret-key xxx');
155
+ console.error('');
156
+ console.error('Generate a keypair with: npx notur-keygen');
157
+ process.exit(1);
158
+ }
159
+
160
+ // Validate format: Ed25519 secret keys are 64 bytes = 128 hex chars
161
+ if (!/^[0-9a-fA-F]{128}$/.test(secretKey)) {
162
+ console.error('Error: Invalid secret key format.');
163
+ console.error('Expected 128 hexadecimal characters (64 bytes).');
164
+ console.error('');
165
+ console.error('Generate a valid keypair with: npx notur-keygen');
166
+ process.exit(1);
167
+ }
168
+
169
+ return secretKey;
170
+ }
171
+
172
+ async function pack(sourceDir, outputPath, options = {}) {
127
173
  const resolvedDir = path.resolve(sourceDir);
128
174
 
129
175
  if (!fs.existsSync(resolvedDir)) {
@@ -174,6 +220,16 @@ function pack(sourceDir, outputPath) {
174
220
 
175
221
  console.log(`\nCreated: ${outputFullPath}`);
176
222
  console.log(`Checksum: ${archiveChecksum}`);
223
+
224
+ // Sign the archive if requested
225
+ if (options.sign) {
226
+ const secretKey = getSecretKey(options);
227
+ const signature = await signArchive(outputFullPath, secretKey);
228
+ const sigPath = outputFullPath + '.sig';
229
+ fs.writeFileSync(sigPath, signature + '\n');
230
+ console.log(`Signature: ${sigPath}`);
231
+ }
232
+
177
233
  console.log(`\nUpload this file to your Pterodactyl admin panel at /admin/notur/extensions`);
178
234
 
179
235
  } finally {
@@ -186,4 +242,7 @@ function pack(sourceDir, outputPath) {
186
242
 
187
243
  // Main
188
244
  const options = parseArgs();
189
- pack(options.path, options.output);
245
+ pack(options.path, options.output, options).catch(err => {
246
+ console.error('Error:', err.message);
247
+ process.exit(1);
248
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@notur/sdk",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Notur Extension Developer SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -15,7 +15,8 @@
15
15
  "access": "public"
16
16
  },
17
17
  "bin": {
18
- "notur-pack": "./bin/notur-pack.js"
18
+ "notur-pack": "./bin/notur-pack.js",
19
+ "notur-keygen": "./bin/notur-keygen.js"
19
20
  },
20
21
  "keywords": [
21
22
  "notur",
@@ -40,6 +41,7 @@
40
41
  "react-dom": "^16.14.0"
41
42
  },
42
43
  "dependencies": {
44
+ "libsodium-wrappers": "^0.7.13",
43
45
  "yaml": "^2.3.0"
44
46
  },
45
47
  "devDependencies": {