@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 +6 -18
- package/dist/database.js +6 -29
- package/dist/io.d.ts +10 -3
- package/dist/io.js +46 -30
- package/package.json +1 -1
- package/web/lib/account.css +36 -0
- package/web/lib/styles.css +5 -0
- package/web/routes/+page.svelte +9 -47
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
|
-
.
|
|
170
|
-
|
|
171
|
-
|
|
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(
|
|
187
|
-
|
|
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) =>
|
|
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
|
-
|
|
256
|
-
await
|
|
257
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
147
|
-
opt.output('start', '
|
|
148
|
-
|
|
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
|
@@ -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
|
+
}
|
package/web/lib/styles.css
CHANGED
package/web/routes/+page.svelte
CHANGED
|
@@ -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
|
}
|