Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ jobs:
# node --check only looks at its first argument, so check each file in turn.
- name: Check syntax
run: |
for file in index.js shadowboxKey.js lib/*.js test/*.js; do
for file in index.js shadowtools.js lib/*.js test/*.js; do
node --check "$file"
done

- name: Run tests
run: npm test

- name: Check the CLI starts and prints help
run: node shadowboxKey.js --help
run: node shadowtools.js --help

- name: Check the module exports load
run: |
Expand Down
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ShadowboxKeys
# shadowtools

[![CI](https://github.com/rahmanow/ShadowboxKeys/actions/workflows/ci.yml/badge.svg)](https://github.com/rahmanow/ShadowboxKeys/actions/workflows/ci.yml)
[![CI](https://github.com/rahmanow/shadowtools/actions/workflows/ci.yml/badge.svg)](https://github.com/rahmanow/shadowtools/actions/workflows/ci.yml)

Manage access keys on your [Outline VPN](https://getoutline.org/) (Shadowbox) server, from the terminal, a local web interface, or your own code. It lists, creates, renames and deletes keys, sets per-key data limits, reports how much data each key has used, and prints scannable QR codes so people can onboard with the Outline app instead of copy-pasting `ss://` strings.

Expand All @@ -17,14 +17,14 @@ This project supersedes [outline-br](https://github.com/rahmanow/outline-br), wh
## Installation

```bash
git clone https://github.com/rahmanow/ShadowboxKeys.git
cd ShadowboxKeys
git clone https://github.com/rahmanow/shadowtools.git
cd shadowtools
npm install
```

## Configuration

Configure the tool either with environment variables (recommended — keeps secrets out of the code) or by editing the constants at the top of `shadowboxKey.js`.
Configure the tool either with environment variables (recommended — keeps secrets out of the code) or by editing the constants at the top of `shadowtools.js`.

| Setting | Environment variable | Description |
| --- | --- | --- |
Expand Down Expand Up @@ -56,7 +56,7 @@ source .env
## Commands

```
node shadowboxKey.js <command> [options]
node shadowtools.js <command> [options]
```

| Command | What it does |
Expand Down Expand Up @@ -89,7 +89,7 @@ Sizes accept a unit suffix: `10GB`, `500MB`, `2TB`, or a plain byte count.
List every key:

```console
$ node shadowboxKey.js list
$ node shadowtools.js list
ID NAME LIMIT ACCESS URL
-- ----- ----- -----------------------------------------------------
0 Alice 10 GB ss://YWVzOnBhc3N3b3Jk@vpn.example.com:443/?outline=1
Expand All @@ -99,7 +99,7 @@ ID NAME LIMIT ACCESS URL
Create a key with a 50 GB cap and show its QR code:

```console
$ node shadowboxKey.js add Carol --limit 50GB --qr
$ node shadowtools.js add Carol --limit 50GB --qr
Created key "Carol" (id 2)
Data limit: 50 GB
ss://bmV3a2V5@vpn.example.com:502/?outline=1
Expand All @@ -113,7 +113,7 @@ Carol:
See who is using how much:

```console
$ node shadowboxKey.js usage
$ node shadowtools.js usage
ID NAME USED LIMIT OF LIMIT
-- ----- ------ ----- --------
0 Alice 3.0 GB 10 GB 30%
Expand All @@ -125,22 +125,22 @@ Total transferred: 3.5 GB
Cap a heavy user, then lift the cap later:

```bash
node shadowboxKey.js limit Alice 10GB
node shadowboxKey.js limit Alice none
node shadowtools.js limit Alice 10GB
node shadowtools.js limit Alice none
```

Export usage for a spreadsheet:

```bash
node shadowboxKey.js usage --csv > usage.csv
node shadowtools.js usage --csv > usage.csv
```

## Web interface

If you would rather click than type:

```bash
node shadowboxKey.js ui
node shadowtools.js ui
```

It prints a URL to open:
Expand Down Expand Up @@ -186,7 +186,7 @@ port.
Everything the CLI does is available as a module. `require` the package and you get three helpers plus the underlying client:

```js
const { listKeys, getUsage, getKeys, OutlineClient } = require('shadowbox-keys');
const { listKeys, getUsage, getKeys, OutlineClient } = require('shadowtools');

const API = 'https://1.2.3.4:16942/AbCdEf123';
const options = { domain: 'vpn.example.com', certSha256: 'E3823F9B...52F5A584' };
Expand Down Expand Up @@ -221,7 +221,7 @@ await client.clearServerDataLimit();
Failures caused by bad input or a bad response throw `UserError`, which is also exported, so you can tell them apart from bugs:

```js
const { UserError } = require('shadowbox-keys');
const { UserError } = require('shadowtools');

try {
await listKeys(API, { certSha256: expected });
Expand All @@ -241,7 +241,7 @@ const keys = require('outline-br');
keys('https://1.2.3.4:16942/AbCdEf123', '87.65.43.21');

// after
const { getKeys } = require('shadowbox-keys');
const { getKeys } = require('shadowtools');
await getKeys('https://1.2.3.4:16942/AbCdEf123', '87.65.43.21');
```

Expand All @@ -253,7 +253,7 @@ New code should prefer `listKeys()`, which returns structured data and leaves pr

```
index.js Programmatic API: listKeys, getUsage, getKeys
shadowboxKey.js CLI entry point: argument parsing and commands
shadowtools.js CLI entry point: argument parsing and commands
lib/outline.js Outline Management API client
lib/format.js Byte formatting, size parsing, table/CSV output
lib/errors.js UserError, for messages shown without a stack trace
Expand Down Expand Up @@ -281,7 +281,7 @@ Outline servers use a self-signed TLS certificate for the Management API, so the
Setting `OUTLINE_CERT_SHA256` closes that gap. The tool then checks the certificate the server presents against the fingerprint you configured, and **aborts before sending anything** if they differ:

```console
$ OUTLINE_CERT_SHA256=aaaa...aaaa node shadowboxKey.js list
$ OUTLINE_CERT_SHA256=aaaa...aaaa node shadowtools.js list
The server presented an unexpected TLS certificate, so the request was not sent.
expected: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
received: e3823f9bb490d35487ee013ec7d23a3662f76b95eb01a40f4851905152f5a584
Expand All @@ -303,7 +303,7 @@ The HTTP calls use the built-in `node:https` module because the global `fetch()`

## Troubleshooting

- **`Please configure your Management API URL first`** — set `OUTLINE_API_URL`, or replace the placeholder in `shadowboxKey.js`.
- **`Please configure your Management API URL first`** — set `OUTLINE_API_URL`, or replace the placeholder in `shadowtools.js`.
- **`Could not reach the Outline server`** — check that the Management API URL is correct and that its port (usually `16942`) is reachable from your machine.
- **`Cannot find module 'qrcode-terminal'`** — run `npm install` in the project directory first.
- **`The server presented an unexpected TLS certificate`** — either `OUTLINE_CERT_SHA256` is stale (recopy `certSha256` from Outline Manager after rebuilding or migrating the server), or something other than your Outline server answered. See [certificate pinning](#certificate-pinning).
Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/**
* Programmatic entry point.
*
* The CLI in shadowboxKey.js is one consumer of this API; the exports below are
* The CLI in shadowtools.js is one consumer of this API; the exports below are
* the same building blocks it uses, so anything the CLI can do is reachable from
* code. getKeys() is kept signature-compatible with the outline-br module this
* project absorbed, so existing callers keep working.
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"name": "shadowbox-keys",
"version": "3.0.0",
"name": "shadowtools",
"version": "4.0.0",
"description": "Manage Outline VPN (Shadowbox) access keys from the command line or from code: list, create, rename, delete, set data limits, report usage and print QR codes",
"license": "MIT",
"main": "index.js",
"bin": {
"shadowbox-keys": "shadowboxKey.js"
"shadowtools": "shadowtools.js"
},
"files": [
"index.js",
"shadowboxKey.js",
"shadowtools.js",
"lib/"
],
"scripts": {
"start": "node shadowboxKey.js",
"start": "node shadowtools.js",
"test": "node --test"
},
"keywords": [
Expand All @@ -28,7 +28,7 @@
],
"repository": {
"type": "git",
"url": "git+https://github.com/rahmanow/ShadowboxKeys.git"
"url": "git+https://github.com/rahmanow/shadowtools.git"
},
"engines": {
"node": ">=14"
Expand Down
18 changes: 9 additions & 9 deletions shadowboxKey.js → shadowtools.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const { OutlineClient } = require('./lib/outline');
const { UserError } = require('./lib/errors');
const { formatBytes, parseBytes, rewriteAccessUrl, printTable, printCsv } = require('./lib/format');

const USAGE = `ShadowboxKeys - manage access keys on your Outline VPN server
const USAGE = `shadowtools - manage access keys on your Outline VPN server

Usage: node shadowboxKey.js <command> [options]
Usage: node shadowtools.js <command> [options]

Commands:
list List every access key with its access URL
Expand Down Expand Up @@ -49,11 +49,11 @@ Configuration (environment variables):
any server presenting a different certificate.

Examples:
node shadowboxKey.js list --qr
node shadowboxKey.js add Alice --limit 50GB
node shadowboxKey.js limit Alice 10GB
node shadowboxKey.js usage --csv
node shadowboxKey.js ui --port 9000
node shadowtools.js list --qr
node shadowtools.js add Alice --limit 50GB
node shadowtools.js limit Alice 10GB
node shadowtools.js usage --csv
node shadowtools.js ui --port 9000
`;

/** Splits argv into positional arguments and a flag map. */
Expand Down Expand Up @@ -285,7 +285,7 @@ async function main() {
return;
}

// Listing is the historical default, so bare `node shadowboxKey.js` still works.
// Listing is the historical default, so bare `node shadowtools.js` still works.
const command = commands[commandName || 'list'];
if (!command) {
throw new UserError(`Unknown command "${commandName}". Run with --help to see what is available.`);
Expand All @@ -294,7 +294,7 @@ async function main() {
if (managementApiUrl.includes('xx.xx.xx.xxx')) {
throw new UserError(
'Please configure your Management API URL first.\n' +
'Set the OUTLINE_API_URL environment variable, or edit the managementApiUrl constant in shadowboxKey.js.'
'Set the OUTLINE_API_URL environment variable, or edit the managementApiUrl constant in shadowtools.js.'
);
}

Expand Down
2 changes: 1 addition & 1 deletion test/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const hasOpenssl = (() => {

/** Creates a throwaway self-signed certificate, mimicking an Outline server's. */
function createCertificate() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shadowbox-keys-test-'));
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shadowtools-test-'));
const keyPath = path.join(dir, 'key.pem');
const certPath = path.join(dir, 'cert.pem');

Expand Down
2 changes: 1 addition & 1 deletion test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const test = require('node:test');
const assert = require('node:assert');

const { parseArgs, findKey } = require('../shadowboxKey');
const { parseArgs, findKey } = require('../shadowtools');
const { OutlineClient, normalizeFingerprint } = require('../lib/outline');
const { UserError } = require('../lib/errors');

Expand Down
Loading