@axium/server 0.3.0 → 0.3.2

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/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { getByString, isJSON, setByString } from 'utilium';
5
5
  import $pkg from '../package.json' with { type: 'json' };
6
6
  import * as config from './config.js';
7
7
  import * as db from './database.js';
8
- import { _portActions, _portMethods, exit, output, restrictedPorts } from './io.js';
8
+ import { _portActions, _portMethods, exit, handleError, output, restrictedPorts } from './io.js';
9
9
  program
10
10
  .version($pkg.version)
11
11
  .name('axium')
@@ -166,16 +166,9 @@ program
166
166
  .description('Enable or disable use of restricted ports (e.g. 443)')
167
167
  .addArgument(new Argument('<action>', 'The action to take').choices(_portActions))
168
168
  .addOption(new Option('-m, --method <method>', 'the method to use').choices(_portMethods).default('node-cap'))
169
- .action((action, opt) => {
170
- try {
171
- restrictedPorts({ ...opt, action });
172
- }
173
- catch (e) {
174
- if (typeof e == 'number')
175
- process.exit(e);
176
- else
177
- exit(e);
178
- }
169
+ .option('-N, --node <path>', 'the path to the node binary')
170
+ .action(async (action, opt) => {
171
+ await restrictedPorts({ ...opt, action }).catch(handleError);
179
172
  });
180
173
  program
181
174
  .command('init')
@@ -183,12 +176,7 @@ program
183
176
  .addOption(opts.force)
184
177
  .addOption(opts.host)
185
178
  .action(async (opt) => {
186
- await db.init({ ...opt, skip: opt.dbSkip }).catch((e) => {
187
- if (typeof e == 'number')
188
- process.exit(e);
189
- else
190
- exit(e);
191
- });
192
- restrictedPorts({ method: 'node-cap', action: 'enable' });
179
+ await db.init({ ...opt, skip: opt.dbSkip }).catch(handleError);
180
+ await restrictedPorts({ method: 'node-cap', action: 'enable' }).catch(handleError);
193
181
  });
194
182
  program.parse();
package/dist/database.js CHANGED
@@ -51,11 +51,10 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
53
  import { Kysely, PostgresDialect, sql } from 'kysely';
54
- import { exec } from 'node:child_process';
55
54
  import { randomBytes } from 'node:crypto';
56
55
  import pg from 'pg';
57
56
  import * as config from './config.js';
58
- import { _fixOutput } from './io.js';
57
+ import { _fixOutput, run } from './io.js';
59
58
  export let database;
60
59
  export function connect() {
61
60
  if (database)
@@ -87,29 +86,6 @@ export async function statusText() {
87
86
  throw typeof error == 'object' && 'message' in error ? error.message : error;
88
87
  }
89
88
  }
90
- /**
91
- * Convenience function for `sudo -u postgres psql -c "${command}"`, plus `report` coolness.
92
- * @internal
93
- */
94
- async function execSQL(opts, command, message) {
95
- let stderr;
96
- try {
97
- opts.output('start', message);
98
- const { promise, resolve, reject } = Promise.withResolvers();
99
- exec(`sudo -u postgres psql -c "${command}"`, opts, (err, _, _stderr) => {
100
- stderr = _stderr.startsWith('ERROR:') ? _stderr.slice(6).trim() : _stderr;
101
- if (err)
102
- reject('[command]');
103
- else
104
- resolve();
105
- });
106
- await promise;
107
- opts.output('done');
108
- }
109
- catch (error) {
110
- throw error == '[command]' ? stderr?.slice(0, 100) || 'failed.' : typeof error == 'object' && 'message' in error ? error.message : error;
111
- }
112
- }
113
89
  function shouldRecreate(opt) {
114
90
  if (opt.skip) {
115
91
  opt.output('warn', 'already exists. (skipped)\n');
@@ -130,7 +106,7 @@ export async function init(opt) {
130
106
  config.save({ db: { password: randomBytes(32).toString('base64') } }, true);
131
107
  opt.output('debug', 'Generated password and wrote to global config');
132
108
  }
133
- const _sql = (command, message) => execSQL(opt, command, message);
109
+ const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
134
110
  await _sql('CREATE DATABASE axium', 'Creating database').catch(async (error) => {
135
111
  if (error != 'database "axium" already exists')
136
112
  throw error;
@@ -252,9 +228,10 @@ export async function init(opt) {
252
228
  */
253
229
  export async function uninstall(opt) {
254
230
  _fixOutput(opt);
255
- await execSQL(opt, 'DROP DATABASE axium', 'Dropping database');
256
- await execSQL(opt, 'REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
257
- await execSQL(opt, 'DROP USER axium', 'Dropping user');
231
+ const _sql = (command, message) => run(opt, message, `sudo -u postgres psql -c "${command}"`);
232
+ await _sql('DROP DATABASE axium', 'Dropping database');
233
+ await _sql('REVOKE ALL PRIVILEGES ON SCHEMA public FROM axium', 'Revoking schema privileges');
234
+ await _sql('DROP USER axium', 'Dropping user');
258
235
  }
259
236
  /**
260
237
  * Removes all data from tables.
package/dist/io.d.ts CHANGED
@@ -15,10 +15,16 @@ export declare const output: {
15
15
  log(message: string): void;
16
16
  debug(message: string): void;
17
17
  };
18
+ /**
19
+ * Run a system command with the fancy "Example... done."
20
+ * @internal
21
+ */
22
+ export declare function run(opts: WithOutput & {
23
+ timeout?: number;
24
+ }, message: string, command: string): Promise<string>;
18
25
  /** Yet another convenience function */
19
26
  export declare function exit(message: string | Error, code?: number): never;
20
- /** Convenience function for `example... [done. / error]` */
21
- export declare function report<T>(promise: Promise<T>, message: string, success?: string): Promise<T>;
27
+ export declare function handleError(e: number | string | Error): void;
22
28
  export type OutputState = 'done' | 'log' | 'warn' | 'error' | 'start' | 'debug';
23
29
  export interface Output {
24
30
  (state: 'done'): void;
@@ -50,10 +56,11 @@ export declare const _portActions: readonly ["enable", "disable"];
50
56
  export interface PortOptions extends MaybeOutput {
51
57
  method: (typeof _portMethods)[number];
52
58
  action: (typeof _portActions)[number];
59
+ node?: string;
53
60
  }
54
61
  /**
55
62
  * This changes if Axium can use restricted ports (like 80 and 443) without root privileges.
56
63
  * Use of these ports is needed so the origin doesn't have a port.
57
64
  * If the origin has a port, passkeys do not work correctly with some password managers.
58
65
  */
59
- export declare function restrictedPorts(opt: PortOptions): void;
66
+ export declare function restrictedPorts(opt: PortOptions): Promise<void>;
package/dist/io.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Logger } from 'logzen';
2
- import { execSync } from 'node:child_process';
2
+ import { exec, execSync } from 'node:child_process';
3
3
  import * as fs from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
5
  import { join } from 'node:path/posix';
@@ -44,6 +44,30 @@ export const output = {
44
44
  },
45
45
  };
46
46
  logger.attach(output);
47
+ /**
48
+ * Run a system command with the fancy "Example... done."
49
+ * @internal
50
+ */
51
+ export async function run(opts, message, command) {
52
+ let stderr;
53
+ try {
54
+ opts.output('start', message);
55
+ const { promise, resolve, reject } = Promise.withResolvers();
56
+ exec(command, opts, (err, stdout, _stderr) => {
57
+ stderr = _stderr.startsWith('ERROR:') ? _stderr.slice(6).trim() : _stderr;
58
+ if (err)
59
+ reject('[command]');
60
+ else
61
+ resolve(stdout);
62
+ });
63
+ const value = await promise;
64
+ opts.output('done');
65
+ return value;
66
+ }
67
+ catch (error) {
68
+ throw error == '[command]' ? stderr?.slice(0, 100) || 'failed.' : typeof error == 'object' && 'message' in error ? error.message : error;
69
+ }
70
+ }
47
71
  /** Yet another convenience function */
48
72
  export function exit(message, code = 1) {
49
73
  if (message instanceof Error)
@@ -51,17 +75,11 @@ export function exit(message, code = 1) {
51
75
  output.error(message);
52
76
  process.exit(code);
53
77
  }
54
- /** Convenience function for `example... [done. / error]` */
55
- export async function report(promise, message, success = 'done.') {
56
- process.stdout.write(message + '... ');
57
- try {
58
- const result = await promise;
59
- console.log(success);
60
- return result;
61
- }
62
- catch (error) {
63
- throw typeof error == 'object' && 'message' in error ? error.message : error;
64
- }
78
+ export function handleError(e) {
79
+ if (typeof e == 'number')
80
+ process.exit(e);
81
+ else
82
+ exit(e);
65
83
  }
66
84
  export function defaultOutput(state, message = '') {
67
85
  switch (state) {
@@ -104,7 +122,7 @@ export const _portActions = ['enable', 'disable'];
104
122
  * Use of these ports is needed so the origin doesn't have a port.
105
123
  * If the origin has a port, passkeys do not work correctly with some password managers.
106
124
  */
107
- export function restrictedPorts(opt) {
125
+ export async function restrictedPorts(opt) {
108
126
  _fixOutput(opt);
109
127
  opt.output('start', 'Checking for root privileges');
110
128
  if (process.getuid?.() != 0)
@@ -120,33 +138,31 @@ export function restrictedPorts(opt) {
120
138
  opt.output('done');
121
139
  switch (opt.method) {
122
140
  case 'node-cap': {
123
- opt.output('start', 'Finding setcap');
124
- let setcap = execSync('command -v setcap', { encoding: 'utf-8' }).trim();
125
- if (setcap)
126
- opt.output('done');
127
- else {
141
+ const setcap = await run(opt, 'Finding setcap', 'command -v setcap')
142
+ .then(e => e.trim())
143
+ .catch(() => {
128
144
  opt.output('warn', 'not in path.');
129
145
  opt.output('start', 'Checking for /usr/sbin/setcap');
130
146
  fs.accessSync('/usr/sbin/setcap', fs.constants.X_OK);
131
- setcap = '/usr/sbin/setcap';
132
147
  opt.output('done');
133
- }
148
+ return '/usr/sbin/setcap';
149
+ });
134
150
  opt.output('debug', 'Using setup at ' + setcap);
135
- opt.output('start', 'Finding node');
136
- let node = execSync('command -v node', { encoding: 'utf-8' }).trim();
137
- if (node)
138
- opt.output('done');
139
- else {
151
+ let { node } = opt;
152
+ node ||= await run(opt, 'Finding node', 'command -v node')
153
+ .then(e => e.trim())
154
+ .catch(() => {
140
155
  opt.output('warn', 'not in path.');
141
156
  opt.output('start', 'Checking for /usr/bin/node');
142
157
  fs.accessSync('/usr/bin/node', fs.constants.X_OK);
143
- node = '/usr/bin/node';
144
158
  opt.output('done');
145
- }
146
- opt.output('debug', 'Using node at ' + node);
147
- opt.output('start', 'Setting ports capability');
148
- execSync(`${setcap} cap_net_bind_service=${opt.action == 'enable' ? '+' : '-'}ep ${node}`, { encoding: 'utf-8' });
159
+ return '/usr/bin/node';
160
+ });
161
+ opt.output('start', 'Resolving real path for node');
162
+ node = fs.realpathSync(node);
149
163
  opt.output('done');
164
+ opt.output('debug', 'Using node at ' + node);
165
+ await run(opt, 'Setting ports capability', `${setcap} cap_net_bind_service=${opt.action == 'enable' ? '+' : '-'}ep ${node}`);
150
166
  break;
151
167
  }
152
168
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -0,0 +1,36 @@
1
+ .account-section {
2
+ width: 50%;
3
+ padding-top: 4em;
4
+
5
+ > div:has(+ div) {
6
+ border-bottom: 1px solid #8888;
7
+ }
8
+ }
9
+
10
+ .account-section .account-item {
11
+ display: grid;
12
+ grid-template-columns: 10em 1fr 2em;
13
+ align-items: center;
14
+ width: 100%;
15
+ gap: 1em;
16
+ text-wrap: nowrap;
17
+ padding-bottom: 1em;
18
+
19
+ > :first-child {
20
+ margin: 0 5em 0 1em;
21
+ grid-column: 1;
22
+ }
23
+
24
+ > :nth-child(2) {
25
+ margin: 0;
26
+ grid-column: 2;
27
+ }
28
+
29
+ > :last-child:nth-child(3) {
30
+ margin: 0;
31
+ display: inline;
32
+ grid-column: 3;
33
+ font-size: 0.75em;
34
+ cursor: pointer;
35
+ }
36
+ }
@@ -65,3 +65,8 @@ button:hover {
65
65
  background-color: #733;
66
66
  color: #ccc;
67
67
  }
68
+
69
+ .subtle {
70
+ color: #bbbb;
71
+ font-size: 0.9em;
72
+ }
@@ -2,6 +2,7 @@
2
2
  import { getUserImage } from '@axium/core';
3
3
  import Icon from '../lib/Icon.svelte';
4
4
  import '../lib/styles.css';
5
+ import '../lib/account.css';
5
6
  import type { PageData } from './$types.js';
6
7
 
7
8
  const {
@@ -27,18 +28,18 @@
27
28
  <img id="pfp" src={image} alt="User profile" />
28
29
  <p id="greeting">Welcome, {user.name}</p>
29
30
  <div class="account-section main">
30
- <div>
31
- <p>Name</p>
31
+ <div class="account-item">
32
+ <p class="subtle">Name</p>
32
33
  <p>{user.name}</p>
33
- <a href="{prefix}/name"><Icon id="chevron-right" /></a>
34
+ <a class="change" href="{prefix}/name"><Icon id="chevron-right" /></a>
34
35
  </div>
35
- <div>
36
- <p>Email</p>
36
+ <div class="account-item">
37
+ <p class="subtle">Email</p>
37
38
  <p>{user.email}</p>
38
- <a href="{prefix}/email"><Icon id="chevron-right" /></a>
39
+ <a class="change" href="{prefix}/email"><Icon id="chevron-right" /></a>
39
40
  </div>
40
- <div>
41
- <p>User ID <dfn title="This is your UUID."><Icon id="regular/circle-info" /></dfn></p>
41
+ <div class="account-item">
42
+ <p class="subtle">User ID <dfn title="This is your UUID."><Icon id="regular/circle-info" /></dfn></p>
42
43
  <p>{user.id}</p>
43
44
  </div>
44
45
  <a id="signout" href="/auth/signout"><button>Sign out</button></a>
@@ -66,45 +67,6 @@
66
67
  font-size: 2em;
67
68
  }
68
69
 
69
- :global(.account-section) {
70
- width: 50%;
71
- padding-top: 4em;
72
-
73
- > div:has(+ div) {
74
- border-bottom: 1px solid #8888;
75
- }
76
- }
77
-
78
- :global(.account-section) > div {
79
- display: grid;
80
- grid-template-columns: 10em 1fr 2em;
81
- align-items: center;
82
- width: 100%;
83
- gap: 1em;
84
- text-wrap: nowrap;
85
- padding-bottom: 1em;
86
-
87
- > :first-child {
88
- margin: 0 5em 0 1em;
89
- color: #bbbb;
90
- font-size: 0.9em;
91
- grid-column: 1;
92
- }
93
-
94
- > :nth-child(2) {
95
- margin: 0;
96
- grid-column: 2;
97
- }
98
-
99
- > a:last-child {
100
- margin: 0;
101
- display: inline;
102
- grid-column: 3;
103
- font-size: 0.75em;
104
- cursor: pointer;
105
- }
106
- }
107
-
108
70
  #signout {
109
71
  margin-top: 2em;
110
72
  }