Compare commits

...

26 commits

Author SHA1 Message Date
Sarah Laplante
de566bfc9f
chore: upgrade lib versions for quay security scanner (#1704)
Some checks are pending
Build and Test / Lint (push) Waiting to run
Build and Test / Shadowbox (push) Blocked by required conditions
Build and Test / Shadowbox (arm64) (push) Blocked by required conditions
Build and Test / Manual Install Script (push) Waiting to run
Build and Test / Metrics Server (push) Blocked by required conditions
Build and Test / Sentry Webhook (push) Blocked by required conditions
CodeQL analysis / Analyze (push) Waiting to run
License checks / license-check (push) Waiting to run
* upgrade lib versions for quay security scanner

* update image version

* upgrade go to latest stable 1.26.3
2026-05-13 10:50:59 +02:00
Sarah Laplante
38eca18e93
Merge pull request #1700 from oceanapplications/master
chore(server): port shadowbox Docker image to arm64
2026-04-14 16:43:34 +02:00
Sean Baker
847d66f8e4 Use original test 2026-04-07 19:39:31 +08:00
Sean Baker
eb1ae915a8 Update build_and_test_debug.yml
Use go.mod
2026-04-07 18:44:45 +08:00
Sean Baker
1f1c03cfc4 Update build_and_test_debug.yml 2026-04-07 08:33:54 +08:00
Sean Baker
23e468b434 Skip Chromium install on arm64
Tests passing on ACT
2026-04-06 23:20:18 +08:00
Sean Baker
fa185b0f90 Added support for MacOS and Linux ARM64 2026-03-25 14:49:02 -06:00
Sarah Laplante
326b527d0f
Merge pull request #1690 from OutlineFoundation/new-watchtower
fix(server): switch to maintained watchtower fork
2026-02-23 15:39:42 +01:00
J. Yi
f9ea8edbb5
chore: update Jigsaw-Code references to OutlineFoundation (#1693) 2026-01-08 13:03:17 -05:00
Sarah Laplante
db19f85194 switch from github name to container name 2026-01-07 16:58:54 +01:00
Sarah Laplante
48d336d95b switch to maintained watchtower fork 2026-01-07 16:58:54 +01:00
Sarah Laplante
3370399012
Merge pull request #1691 from OutlineFoundation/update-name-linter
chore: update PR name linter
2026-01-07 16:58:08 +01:00
Sarah Laplante
3d2ce1f93e revert to node 18 2026-01-06 14:02:14 +01:00
Sarah Laplante
846c0685c3 update node version 2026-01-06 13:43:10 +01:00
Sarah Laplante
9b44fe58b1 update PR name linter 2026-01-06 13:38:45 +01:00
Daniel LaCosse
26803710c9
chore(server): remove bandwidth spot query (#1656) 2025-03-31 11:02:33 -04:00
Sander Bruens
4f59eea8a9
chore(server): update Prometheus to LTS (#1655)
* chore(server): update Prometheus to LTS

* Add arm64 archives.
2025-03-21 11:58:19 -04:00
dependabot[bot]
0bfa80262c
chore: bump braces from 3.0.2 to 3.0.3 (#1559)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 14:03:06 -04:00
dependabot[bot]
9dbfc44a06
chore: bump golang.org/x/crypto from 0.18.0 to 0.31.0 (#1625)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.18.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.18.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-19 13:23:27 -04:00
Sander Bruens
9013feafa7
chore(server): set Content-Type header (#1651) 2025-03-10 14:25:18 -04:00
Sander Bruens
d9aa8560ce
ci(sentry_webhook): pin Ubuntu due to Chromium bug (via Puppeteer) (#1652) 2025-03-04 20:24:13 -05:00
Sander Bruens
067d0d47ae
revert(server): revert upgrade of outline-ss-server version (#1645)
* Revert "fix(server): set empty IP to bind on all available networks (#1640)"

This reverts commit 47f24520d3.

* Revert "refactor(server): use the new service config format (#1628)"

This reverts commit 13f62390bf.
2025-02-24 18:07:13 -05:00
Daniel LaCosse
d262f5242f
feat: cache recent prometheus queries in memory (#1643)
* [DO NOT MERGE] prometheus query timer

* add cache layer

* add query result data

* ready for a look

* remove logger
2025-02-21 17:00:59 -05:00
Sander Bruens
15a9e54e5e
refactor(server): use a reusable http.Agent (#1644) 2025-02-21 16:15:14 -05:00
Sander Bruens
47f24520d3
fix(server): set empty IP to bind on all available networks (#1640)
* fix(server): set empty IP to bind on all available networks

* upgrade `outline-ss-server`
2025-02-19 08:54:25 -05:00
Sander Bruens
34b5e7a2e8
chore: remove lastConnected property and update OpenAPI spec (#1639)
* chore: remove `lastConnected` property

* Update OpenAPI spec.
2025-02-14 16:51:07 -05:00
25 changed files with 348 additions and 298 deletions

2
.github/CODEOWNERS vendored
View file

@ -1,4 +1,4 @@
* @Jigsaw-Code/outline-dev
* @OutlineFoundation/outline-dev
/src/server_manager/model/ @fortuna
/src/shadowbox/ @fortuna

View file

@ -43,8 +43,16 @@ jobs:
node-version: 18
cache: npm
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: false
- name: Install NPM Dependencies
run: npm ci
env:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true'
- name: Lint
run: ./task lint
@ -81,6 +89,40 @@ jobs:
files: |
src/shadowbox/server/api.yml
shadowbox-arm64:
name: Shadowbox (arm64)
runs-on: ubuntu-22.04-arm
needs: lint
steps:
- name: Checkout
uses: actions/checkout@v2.3.4
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
cache: npm
- name: Install Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: false
- name: Install NPM Dependencies
run: npm ci
env:
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true'
- name: Shadowbox Debug Build (arm64)
run: TARGET_ARCH=aarch64 ./task shadowbox:build
- name: Shadowbox Unit Test
run: ./task shadowbox:test
- name: Shadowbox Docker Build (arm64)
run: TARGET_ARCH=aarch64 ./task shadowbox:docker:build
manual-install-script:
name: Manual Install Script
runs-on: ubuntu-latest
@ -119,7 +161,8 @@ jobs:
sentry-webhook:
name: Sentry Webhook
runs-on: ubuntu-latest
# TODO(puppeteer/puppeteer#12818): Update when chromium bug is resolved.
runs-on: ubuntu-22.04
needs: lint
steps:
- name: Checkout

View file

@ -37,10 +37,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Clone Repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v2.2.0
uses: actions/setup-node@v6.1.0
with:
node-version: 18
cache: npm
@ -49,7 +49,7 @@ jobs:
run: npm ci
- name: Ensure Commitizen Format
uses: JulienKode/pull-request-name-linter-action@98794a8b815ec05560813c42e55fd8d32d3fd248
uses: JulienKode/pull-request-name-linter-action@v20.1.0
size_label:
name: Change Size Label

View file

@ -1,12 +1,12 @@
# Outline Server
![Build and Test](https://github.com/Jigsaw-Code/outline-server/actions/workflows/build_and_test_debug.yml/badge.svg?branch=master) [![Mattermost](https://badgen.net/badge/Mattermost/Outline%20Community/blue)](https://community.internetfreedomfestival.org/community/channels/outline-community) [![Reddit](https://badgen.net/badge/Reddit/r%2Foutlinevpn/orange)](https://www.reddit.com/r/outlinevpn/)
![Build and Test](https://github.com/OutlineFoundation/outline-server/actions/workflows/build_and_test_debug.yml/badge.svg?branch=master) [![Mattermost](https://badgen.net/badge/Mattermost/Outline%20Community/blue)](https://community.internetfreedomfestival.org/community/channels/outline-community) [![Reddit](https://badgen.net/badge/Reddit/r%2Foutlinevpn/orange)](https://www.reddit.com/r/outlinevpn/)
Outline Server is the component that provides the Shadowsocks service (via [outline-ss-server](https://github.com/Jigsaw-Code/outline-ss-server/)) and a service management API. You can deploy this server directly following simple instructions in this repository, or if you prefer a ready-to-use graphical interface you can use the [Outline Manager](https://github.com/Jigsaw-Code/outline-apps/).
Outline Server is the component that provides the Shadowsocks service (via [outline-ss-server](https://github.com/OutlineFoundation/tunnel-server/)) and a service management API. You can deploy this server directly following simple instructions in this repository, or if you prefer a ready-to-use graphical interface you can use the [Outline Manager](https://github.com/OutlineFoundation/outline-apps/).
**Components:**
- **Outline Server** ([`src/shadowbox`](src/shadowbox)): The core proxy server that runs and manages [outline-ss-server](https://github.com/Jigsaw-Code/outline-ss-server/), a Shadowsocks backend. It provides a REST API for access key management.
- **Outline Server** ([`src/shadowbox`](src/shadowbox)): The core proxy server that runs and manages [outline-ss-server](https://github.com/OutlineFoundation/tunnel-server/), a Shadowsocks backend. It provides a REST API for access key management.
- **Metrics Server** ([`src/metrics_server`](src/metrics_server)): A REST service for optional, anonymous metrics sharing.

View file

@ -20,6 +20,6 @@ The censors used to block Shadowsocks, but Shadowsocks has evolved, and in 2021,
In 2022 China started blocking seemingly random traffic ([report](https://www.opentech.fund/news/exposing-the-great-firewalls-dynamic-blocking-of-fully-encrypted-traffic/)). While there is no evidence they could detect Shadowsocks, the protocol ended up blocked.
As a reponse, we [added a feature to the Outline Client](https://github.com/Jigsaw-Code/outline-apps/pull/1454) that allows service managers to specify a **[connection prefix disguise](https://www.reddit.com/r/outlinevpn/wiki/index/prefixing/)** to be used in the Shadowsocks initialization, which can be used to bypass the blocking in China by making it look like a protocol that is allowed.
As a reponse, we [added a feature to the Outline Client](https://github.com/OutlineFoundation/outline-apps/pull/1454) that allows service managers to specify a **[connection prefix disguise](https://www.reddit.com/r/outlinevpn/wiki/index/prefixing/)** to be used in the Shadowsocks initialization, which can be used to bypass the blocking in China by making it look like a protocol that is allowed.
Shadowsocks remains our protocol of choice because it's simple, well understood and very performant. Furthermore, it has an enthusiastic community of very smart people behind it.

8
go.mod
View file

@ -1,6 +1,6 @@
module localhost
go 1.21
go 1.26.3
require (
github.com/Jigsaw-Code/outline-ss-server v1.7.3
@ -37,10 +37,10 @@ require (
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mvdan.cc/sh/v3 v3.8.0 // indirect

12
go.sum
View file

@ -91,8 +91,8 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -102,11 +102,11 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=

239
package-lock.json generated
View file

@ -1891,6 +1891,19 @@
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/browserslist": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
@ -3819,6 +3832,19 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"dev": true,
@ -4774,6 +4800,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-number-object": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
@ -5210,17 +5246,6 @@
"node": ">=8"
}
},
"node_modules/karma/node_modules/braces": {
"version": "3.0.2",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/karma/node_modules/chokidar": {
"version": "3.5.3",
"dev": true,
@ -5247,17 +5272,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/karma/node_modules/fill-range": {
"version": "7.0.1",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/karma/node_modules/fsevents": {
"version": "2.3.2",
"dev": true,
@ -5292,14 +5306,6 @@
"node": ">=8"
}
},
"node_modules/karma/node_modules/is-number": {
"version": "7.0.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/karma/node_modules/readdirp": {
"version": "3.6.0",
"dev": true,
@ -5325,17 +5331,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/karma/node_modules/to-regex-range": {
"version": "5.0.1",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@ -5614,51 +5609,6 @@
"node": ">=8.6"
}
},
"node_modules/micromatch/node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/micromatch/node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/micromatch/node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/micromatch/node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/mime": {
"version": "2.6.0",
"dev": true,
@ -6094,8 +6044,7 @@
},
"node_modules/outline-shadowsocksconfig": {
"version": "0.2.0",
"resolved": "git+ssh://git@github.com/Jigsaw-Code/outline-shadowsocksconfig.git#add590ed57277653d02dd2031ae301500ae881e1",
"license": "Apache-2.0",
"resolved": "git+ssh://git@github.com/OutlineFoundation/shadowsocksconfig.git#add590ed57277653d02dd2031ae301500ae881e1",
"dependencies": {
"ipaddr.js": "^2.0.0",
"js-base64": "^3.5.2",
@ -8131,6 +8080,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"license": "MIT",
@ -9056,7 +9018,7 @@
"dependencies": {
"ip-regex": "^4.1.0",
"js-yaml": "^3.12.0",
"outline-shadowsocksconfig": "github:Jigsaw-Code/outline-shadowsocksconfig#v0.2.0",
"outline-shadowsocksconfig": "github:OutlineFoundation/shadowsocksconfig#v0.2.0",
"prom-client": "^11.1.3",
"randomstring": "^1.1.5",
"restify": "^11.1.0",
@ -10423,6 +10385,15 @@
"concat-map": "0.0.1"
}
},
"braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"requires": {
"fill-range": "^7.1.1"
}
},
"browserslist": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
@ -11731,6 +11702,15 @@
"flat-cache": "^3.0.4"
}
},
"fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"finalhandler": {
"version": "1.1.2",
"dev": true,
@ -12352,6 +12332,12 @@
"integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
"dev": true
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"is-number-object": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
@ -12588,13 +12574,6 @@
"version": "2.2.0",
"dev": true
},
"braces": {
"version": "3.0.2",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"chokidar": {
"version": "3.5.3",
"dev": true,
@ -12609,13 +12588,6 @@
"readdirp": "~3.6.0"
}
},
"fill-range": {
"version": "7.0.1",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"fsevents": {
"version": "2.3.2",
"dev": true,
@ -12635,10 +12607,6 @@
"binary-extensions": "^2.0.0"
}
},
"is-number": {
"version": "7.0.0",
"dev": true
},
"readdirp": {
"version": "3.6.0",
"dev": true,
@ -12652,13 +12620,6 @@
"requires": {
"glob": "^7.1.3"
}
},
"to-regex-range": {
"version": "5.0.1",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
}
}
},
@ -12926,41 +12887,6 @@
"requires": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
},
"dependencies": {
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
}
}
},
"mime": {
@ -13264,7 +13190,7 @@
"@types/tmp": "^0.2.1",
"ip-regex": "^4.1.0",
"js-yaml": "^3.12.0",
"outline-shadowsocksconfig": "github:Jigsaw-Code/outline-shadowsocksconfig#v0.2.0",
"outline-shadowsocksconfig": "github:OutlineFoundation/shadowsocksconfig#v0.2.0",
"prom-client": "^11.1.3",
"randomstring": "^1.1.5",
"restify": "^11.1.0",
@ -13284,8 +13210,8 @@
}
},
"outline-shadowsocksconfig": {
"version": "git+ssh://git@github.com/Jigsaw-Code/outline-shadowsocksconfig.git#add590ed57277653d02dd2031ae301500ae881e1",
"from": "outline-shadowsocksconfig@github:Jigsaw-Code/outline-shadowsocksconfig#v0.2.0",
"version": "git+ssh://git@github.com/OutlineFoundation/shadowsocksconfig.git#add590ed57277653d02dd2031ae301500ae881e1",
"from": "outline-shadowsocksconfig@github:OutlineFoundation/shadowsocksconfig#v0.2.0",
"requires": {
"ipaddr.js": "^2.0.0",
"js-base64": "^3.5.2",
@ -14741,6 +14667,15 @@
}
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
},
"toidentifier": {
"version": "1.0.1"
},

View file

@ -34,7 +34,7 @@ const VALID_USER_REPORT2: HourlyUserConnectionMetricsReport = {
/*
* Legacy access key user reports to ensure backwards compatibility with servers not
* synced past https://github.com/Jigsaw-Code/outline-server/pull/1529).
* synced past https://github.com/OutlineFoundation/outline-server/pull/1529).
*/
const LEGACY_PER_KEY_USER_REPORT: HourlyUserConnectionMetricsReport = {
userId: 'foo',
@ -43,7 +43,7 @@ const LEGACY_PER_KEY_USER_REPORT: HourlyUserConnectionMetricsReport = {
/*
* Legacy multiple countries user reports to ensure backwards compatibility with servers
* not synced past https://github.com/Jigsaw-Code/outline-server/pull/1242.
* not synced past https://github.com/OutlineFoundation/outline-server/pull/1242.
*/
const LEGACY_PER_LOCATION_USER_REPORT: HourlyUserConnectionMetricsReport = {
userId: 'foobar',

View file

@ -1,5 +1,5 @@
# Outline Manager
> THIS PROJECT HAS MOVED TO A [NEW LOCATION](https://github.com/Jigsaw-Code/outline-apps/tree/master/): Outline Manager is now part of the [Outline Apps repository](https://github.com/Jigsaw-Code/outline-apps).
> THIS PROJECT HAS MOVED TO A [NEW LOCATION](https://github.com/OutlineFoundation/outline-apps/tree/master/server_manager): Outline Manager is now part of the [Outline Apps repository](https://github.com/OutlineFoundation/outline-apps).
We are keeping this folder to support legacy versions of the app that point to the old [server install script](https://github.com/Jigsaw-Code/outline-server/blob/master/src/server_manager/install_scripts/install_server.sh).
We are keeping this folder to support legacy versions of the app that point to the old [server install script](https://github.com/OutlineFoundation/outline-server/blob/master/src/server_manager/install_scripts/install_server.sh).

View file

@ -160,7 +160,7 @@ function fetch() {
function install_docker() {
(
# Change umask so that /usr/share/keyrings/docker-archive-keyring.gpg has the right permissions.
# See https://github.com/Jigsaw-Code/outline-server/issues/951.
# See https://github.com/OutlineFoundation/outline-server/issues/951.
# We do this in a subprocess so the umask for the calling process is unaffected.
umask 0022
fetch https://get.docker.com/ | sh
@ -376,7 +376,7 @@ function start_watchtower() {
-v /var/run/docker.sock:/var/run/docker.sock)
# By itself, local messes up the return code.
local STDERR_OUTPUT
STDERR_OUTPUT="$(docker run -d "${docker_watchtower_flags[@]}" containrrr/watchtower --cleanup --label-enable --scope=outline --tlsverify --interval "${WATCHTOWER_REFRESH_SECONDS}" 2>&1 >/dev/null)" && return
STDERR_OUTPUT="$(docker run -d "${docker_watchtower_flags[@]}" nickfedor/watchtower --cleanup --label-enable --scope=outline --tlsverify --interval "${WATCHTOWER_REFRESH_SECONDS}" 2>&1 >/dev/null)" && return
readonly STDERR_OUTPUT
log_error "FAILED"
if docker_container_exists watchtower; then
@ -440,7 +440,7 @@ Make sure to open the following ports on your firewall, router or cloud provider
function set_hostname() {
# These are URLs that return the client's apparent IP address.
# We have more than one to try in case one starts failing
# (e.g. https://github.com/Jigsaw-Code/outline-server/issues/776).
# (e.g. https://github.com/OutlineFoundation/outline-server/issues/776).
local -ar urls=(
'https://icanhazip.com/'
'https://ipinfo.io/ip'
@ -456,8 +456,8 @@ function set_hostname() {
install_shadowbox() {
local MACHINE_TYPE
MACHINE_TYPE="$(uname -m)"
if [[ "${MACHINE_TYPE}" != "x86_64" ]]; then
log_error "Unsupported machine type: ${MACHINE_TYPE}. Please run this script on a x86_64 machine"
if [[ "${MACHINE_TYPE}" != "x86_64" && "${MACHINE_TYPE}" != "aarch64" && "${MACHINE_TYPE}" != "arm64" ]]; then
log_error "Unsupported machine type: ${MACHINE_TYPE}. Supported architectures: x86_64, aarch64/arm64."
exit 1
fi

View file

@ -1,20 +1,22 @@
# 1.7.2
- Fixes
- Fix reporting of country metrics and improve logging output (https://github.com/Jigsaw-Code/outline-server/pull/1242)
- Fix reporting of country metrics and improve logging output (https://github.com/OutlineFoundation/outline-server/pull/1242)
# 1.7.1
- Fixes
- Corner case of isPortUsed that could result in infinite restart loop (https://github.com/Jigsaw-Code/outline-server/pull/1238)
- Prevent excessive logging (https://github.com/Jigsaw-Code/outline-server/pull/1232)
- Corner case of isPortUsed that could result in infinite restart loop (https://github.com/OutlineFoundation/outline-server/pull/1238)
- Prevent excessive logging (https://github.com/OutlineFoundation/outline-server/pull/1232)
# 1.7.0
- Features
- Add encryption cipher selection to create access key API (https://github.com/Jigsaw-Code/outline-server/pull/1002)
- Make access key secrets longer (https://github.com/Jigsaw-Code/outline-server/pull/1098)
- Add encryption cipher selection to create access key API (https://github.com/OutlineFoundation/outline-server/pull/1002)
- Make access key secrets longer (https://github.com/OutlineFoundation/outline-server/pull/1098)
- Fixes
- Race condition on concurrent API calls (https://github.com/Jigsaw-Code/outline-server/pull/995)
- Upgrades (https://github.com/Jigsaw-Code/outline-server/pull/1211)
- Race condition on concurrent API calls (https://github.com/OutlineFoundation/outline-server/pull/995)
- Upgrades (https://github.com/OutlineFoundation/outline-server/pull/1211)
- Base image to `node:16.18.0-alpine3.16`
- outline-ss-server from 1.3.5 to [1.4.0](https://github.com/Jigsaw-Code/outline-ss-server/releases/tag/v1.4.0)
- outline-ss-server from 1.3.5 to [1.4.0](https://github.com/OutlineFoundation/outline-ss-server/releases/tag/v1.4.0)
- Prometheus from 2.33.5 to [2.37.1](https://github.com/prometheus/prometheus/releases/tag/v2.37.1)

View file

@ -1,6 +1,6 @@
# Outline Server (Shadowbox)
The Outline Server, internal name "Shadowbox," is designed to streamline the setup and sharing of Shadowsocks servers. It includes a user management API and creates Shadowsocks instances when needed. It's managed by the [Outline Manager](https://github.com/Jigsaw-Code/outline-apps/) and used as proxy by the [Outline Client](https://github.com/Jigsaw-Code/outline-apps/) apps. Shadowbox is also compatible with standard Shadowsocks clients.
The Outline Server, internal name "Shadowbox," is designed to streamline the setup and sharing of Shadowsocks servers. It includes a user management API and creates Shadowsocks instances when needed. It's managed by the [Outline Manager](https://github.com/OutlineFoundation/outline-apps/) and used as proxy by the [Outline Client](https://github.com/OutlineFoundation/outline-apps/) apps. Shadowbox is also compatible with standard Shadowsocks clients.
## Installation
@ -9,7 +9,7 @@ The Outline Server, internal name "Shadowbox," is designed to streamline the set
1. **Run the Installation Script**
```sh
sudo bash -c "$(wget -qO- https://raw.githubusercontent.com/Jigsaw-Code/outline-apps/master/server_manager/install_scripts/install_server.sh)"
sudo bash -c "$(wget -qO- https://raw.githubusercontent.com/OutlineFoundation/outline-apps/master/server_manager/install_scripts/install_server.sh)"
```
1. **Customize (Optional)**
@ -17,7 +17,7 @@ The Outline Server, internal name "Shadowbox," is designed to streamline the set
Add flags for hostname, port, etc. Example:
```sh
sudo bash -c "$(wget -qO- https://raw.githubusercontent.com/Jigsaw-Code/outline-apps/master/server_manager/install_scripts/install_server.sh)" install_server.sh \
sudo bash -c "$(wget -qO- https://raw.githubusercontent.com/OutlineFoundation/outline-apps/master/server_manager/install_scripts/install_server.sh)" install_server.sh \
--hostname=myserver.com \
--keys-port=443
```
@ -113,7 +113,7 @@ The Outline Server provides a REST API for access key management. If you know th
1. **Further Options:**
Consult the [OpenAPI spec](./server/api.yml) and [documentation](https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/Jigsaw-Code/outline-server/master/src/shadowbox/server/api.yml) for more options.
Consult the [OpenAPI spec](./server/api.yml) and [documentation](https://redocly.github.io/redoc/?url=https://raw.githubusercontent.com/OutlineFoundation/outline-server/master/src/shadowbox/server/api.yml) for more options.
## Testing

View file

@ -23,7 +23,7 @@ tasks:
vars:
TARGET_OS: '{{.TARGET_OS | default "linux"}}'
TARGET_ARCH: '{{.TARGET_ARCH | default "x86_64"}}'
GOARCH: '{{get (dict "x86_64" "amd64") .TARGET_ARCH | default .TARGET_ARCH}}'
GOARCH: '{{get (dict "x86_64" "amd64" "aarch64" "arm64") .TARGET_ARCH | default .TARGET_ARCH}}'
TARGET_DIR: '{{.TARGET_DIR | default (joinPath .OUTPUT_BASE .TARGET_OS .TARGET_ARCH)}}'
NODE_DIR: '{{joinPath .TARGET_DIR "app"}}'
BIN_DIR: '{{joinPath .TARGET_DIR "bin"}}'
@ -76,13 +76,21 @@ tasks:
IMAGE_NAME: '{{.IMAGE_NAME | default "localhost/outline/shadowbox"}}'
TARGET_ARCH: '{{.TARGET_ARCH | default "x86_64"}}'
IMAGE_ROOT: '{{joinPath .OUTPUT_BASE "image_root" .TARGET_ARCH}}'
# Newer node images have no valid content trust data.
# Pin the image node:16.18.0-alpine3.16 by hash.
# See image at https://hub.docker.com/_/node/tags?page=1&name=18.18.0-alpine3.18
# Pin the image node:18.20.8-alpine3.21 by hash.
# See image at https://hub.docker.com/_/node/tags?page=1&name=18.20.8-alpine3.21
# Note: "aarch64" is an alias for "arm64" — Linux ARM64 hosts report "aarch64" via uname -m.
NODE_IMAGE: '{{get
(dict
"x86_64" "node@sha256:a0b787b0d53feacfa6d606fb555e0dbfebab30573277f1fe25148b05b66fa097"
"arm64" "node@sha256:b4b7a1dd149c65ee6025956ac065a843b4409a62068bd2b0cbafbb30ca2fab3b"
"x86_64" "node@sha256:929b04d7c782f04f615cf785488fed452b6569f87c73ff666ad553a7554f0006"
"arm64" "node@sha256:c2281c62c4aadf92ea71a6c05e6c8e640634b6a99dc52a6e54575f9cb298a037"
"aarch64" "node@sha256:c2281c62c4aadf92ea71a6c05e6c8e640634b6a99dc52a6e54575f9cb298a037"
) .TARGET_ARCH
}}'
DOCKER_PLATFORM: '{{get
(dict
"x86_64" "linux/amd64"
"arm64" "linux/arm64"
"aarch64" "linux/arm64"
) .TARGET_ARCH
}}'
env:
@ -103,6 +111,7 @@ tasks:
# Build image with given root
- |
"${DOCKER:-docker}" build --force-rm \
--platform '{{.DOCKER_PLATFORM}}' \
--build-arg NODE_IMAGE='{{.NODE_IMAGE}}' \
--build-arg VERSION='{{.VERSION}}' \
-f '{{joinPath .TASKFILE_DIR "docker" "Dockerfile"}}' \

View file

@ -18,16 +18,17 @@ FROM ${NODE_IMAGE}
ARG VERSION
# Save metadata on the software versions we are using.
LABEL shadowbox.node_version=16.18.0
LABEL shadowbox.node_version=18.20.8
LABEL shadowbox.github.release=${VERSION}
# The user management service doesn't quit with SIGTERM.
STOPSIGNAL SIGKILL
# Upgrade installed Alpine packages to pick up security fixes (musl-utils, busybox, ssl_client, etc.).
# We use curl to detect the server's public IP. We need to use the --date option in `date` to
# safely grab the ip-to-country database.
RUN apk add --no-cache --upgrade coreutils curl
RUN apk upgrade --no-cache && apk add --no-cache --upgrade coreutils curl
COPY . /

View file

@ -102,12 +102,17 @@ export interface PrometheusClient {
}
export class ApiPrometheusClient implements PrometheusClient {
constructor(private address: string) {}
private readonly agent: http.Agent;
constructor(private address: string) {
this.agent = new http.Agent({ keepAlive: true });
}
private request(url: string): Promise<QueryResultData> {
return new Promise<QueryResultData>((fulfill, reject) => {
const options = {agent: this.agent};
http
.get(url, (response) => {
.get(url, options, (response) => {
if (response.statusCode < 200 || response.statusCode > 299) {
reject(new Error(`Got error ${response.statusCode}`));
response.resume();

View file

@ -14,7 +14,7 @@
# Alpine 3.19 curl is using the c-ares resolver instead of the system resolver,
# which caused DNS issues. Upgrade once the Alpine image includes the fix. See
# https://github.com/Jigsaw-Code/outline-server/pull/1566.
# https://github.com/OutlineFoundation/outline-server/pull/1566.
FROM docker.io/golang:1-alpine3.18
# curl for fetching pages using the local proxy

View file

@ -11,7 +11,7 @@
"dependencies": {
"ip-regex": "^4.1.0",
"js-yaml": "^3.12.0",
"outline-shadowsocksconfig": "github:Jigsaw-Code/outline-shadowsocksconfig#v0.2.0",
"outline-shadowsocksconfig": "github:OutlineFoundation/shadowsocksconfig#v0.2.0",
"prom-client": "^11.1.3",
"randomstring": "^1.1.5",
"restify": "^11.1.0",

View file

@ -146,46 +146,94 @@ paths:
type: object
properties:
server:
type: array
items:
type: object
properties:
location:
type: string
asn:
type: number
asOrg:
type: string
tunnelTime:
type: object
properties:
tunnelTime:
type: object
properties:
seconds:
type: number
dataTransferred:
type: object
properties:
bytes:
type: number
bandwidth:
type: object
properties:
current:
type: object
properties:
data:
type: object
properties:
bytes:
type: number
timestamp:
type: integer
peak:
type: object
properties:
data:
type: object
properties:
bytes:
type: number
timestamp:
type: integer
locations:
type: array
items:
type: object
properties:
seconds:
type: number
dataTransferred:
type: object
properties:
bytes:
location:
type: string
asn:
type: number
asOrg:
type: string
tunnelTime:
type: object
properties:
seconds:
type: number
dataTransferred:
type: object
properties:
bytes:
type: number
accessKeys:
type: array
items:
type: object
properties:
accessKeyId:
type: string
type: integer
tunnelTime:
type: object
properties:
seconds:
seconds:
type: number
dataTransferred:
type: object
properties:
bytes:
bytes:
type: number
connection:
type: object
properties:
lastTrafficSeen:
type: number
peakDeviceCount:
type: object
properties:
data:
type: integer
timestamp:
type: integer
examples:
'0':
value: '{"server":[{"location":"US","asn":null,"asOrg":null,"tunnelTime":{"seconds":100},"dataTransferred":{"bytes":100}}],"accessKeys":[{"accessKeyId":"0","tunnelTime":{"seconds":100},"dataTransferred":{"bytes":100}}]}'
value: '{"server":{"tunnelTime":{"seconds":100},"dataTransferred":{"bytes":100},"bandwidth": {"current": {"data": {"bytes": 10},"timestamp": 1739284734},"peak": {"data": {"bytes": 80},"timestamp": 1738959398}},"locations": [{"location": "US","asn": null,"asOrg": null,"dataTransferred": {"bytes": 100},"tunnelTime": {"seconds": 100}}]},"accessKeys":[{"accessKeyId":0,"tunnelTime":{"seconds":100},"dataTransferred":{"bytes":100},"connection": {"lastTrafficSeen": 1739284734,"peakDeviceCount": {"data": 4,"timestamp": 1738959398}}}]}'
/name:
put:
description: Renames the server

View file

@ -48,6 +48,7 @@ const MMDB_LOCATION_ASN = '/var/lib/libmaxminddb/ip-asn.mmdb';
async function exportPrometheusMetrics(registry: prometheus.Registry, port): Promise<http.Server> {
return new Promise<http.Server>((resolve, _) => {
const server = http.createServer((_, res) => {
res.setHeader('Content-Type', registry.contentType);
res.write(registry.metrics());
res.end();
});
@ -122,7 +123,7 @@ async function main() {
const prometheusPort = await portProvider.reserveFirstFreePort(9090);
// Use 127.0.0.1 instead of localhost for Prometheus because it's resolving incorrectly for some users.
// See https://github.com/Jigsaw-Code/outline-server/issues/341
// See https://github.com/OutlineFoundation/outline-server/issues/341
const prometheusLocation = `127.0.0.1:${prometheusPort}`;
const nodeMetricsPort = await portProvider.reserveFirstFreePort(prometheusPort + 1);

View file

@ -202,7 +202,6 @@ describe('PrometheusManagerMetrics', () => {
"seconds": 1000
},
"connection": {
"lastConnected": 1738959398,
"lastTrafficSeen": 1739284734,
"peakDeviceCount": {
"data": 4,
@ -389,7 +388,6 @@ describe('PrometheusManagerMetrics', () => {
"seconds": 1000
},
"connection": {
"lastConnected": null,
"lastTrafficSeen": null,
"peakDeviceCount": {
"data": 0,
@ -406,7 +404,6 @@ describe('PrometheusManagerMetrics', () => {
"seconds": 0
},
"connection": {
"lastConnected": 1738959398,
"lastTrafficSeen": 1738959398,
"peakDeviceCount": {
"data": 4,

View file

@ -16,6 +16,7 @@ import {
PrometheusClient,
PrometheusMetric,
PrometheusValue,
QueryResultData,
} from '../infrastructure/prometheus_scraper';
import {DataUsageByUser, DataUsageTimeframe} from '../model/metrics';
@ -35,7 +36,6 @@ interface TimedData<T> {
}
interface ConnectionStats {
lastConnected: number | null;
lastTrafficSeen: number | null;
peakDeviceCount: TimedData<number>;
}
@ -112,8 +112,9 @@ export class PrometheusManagerMetrics implements ManagerMetrics {
Math.ceil(now / PROMETHEUS_RANGE_QUERY_STEP_SECONDS) * PROMETHEUS_RANGE_QUERY_STEP_SECONDS;
const start = end - timeframe.seconds;
this.prunePrometheusCache();
const [
bandwidth,
bandwidthRange,
dataTransferredByLocation,
tunnelTimeByLocation,
@ -122,34 +123,31 @@ export class PrometheusManagerMetrics implements ManagerMetrics {
dataTransferredByAccessKeyRange,
tunnelTimeByAccessKeyRange,
] = await Promise.all([
this.prometheusClient.query(
`sum(rate(shadowsocks_data_bytes_per_location{dir=~"c<p|p>t"}[${PROMETHEUS_RANGE_QUERY_STEP_SECONDS}s]))`
),
this.prometheusClient.queryRange(
this.cachedPrometheusClient.queryRange(
`sum(rate(shadowsocks_data_bytes_per_location{dir=~"c<p|p>t"}[${PROMETHEUS_RANGE_QUERY_STEP_SECONDS}s]))`,
start,
end,
`${PROMETHEUS_RANGE_QUERY_STEP_SECONDS}s`
),
this.prometheusClient.query(
this.cachedPrometheusClient.query(
`sum(increase(shadowsocks_data_bytes_per_location{dir=~"c<p|p>t"}[${timeframe.seconds}s])) by (location, asn, asorg)`
),
this.prometheusClient.query(
this.cachedPrometheusClient.query(
`sum(increase(shadowsocks_tunnel_time_seconds_per_location[${timeframe.seconds}s])) by (location, asn, asorg)`
),
this.prometheusClient.query(
this.cachedPrometheusClient.query(
`sum(increase(shadowsocks_data_bytes{dir=~"c<p|p>t"}[${timeframe.seconds}s])) by (access_key)`
),
this.prometheusClient.query(
this.cachedPrometheusClient.query(
`sum(increase(shadowsocks_tunnel_time_seconds[${timeframe.seconds}s])) by (access_key)`
),
this.prometheusClient.queryRange(
this.cachedPrometheusClient.queryRange(
`sum(increase(shadowsocks_data_bytes{dir=~"c<p|p>t"}[${PROMETHEUS_RANGE_QUERY_STEP_SECONDS}s])) by (access_key)`,
start,
end,
`${PROMETHEUS_RANGE_QUERY_STEP_SECONDS}s`
),
this.prometheusClient.queryRange(
this.cachedPrometheusClient.queryRange(
`sum(increase(shadowsocks_tunnel_time_seconds[${PROMETHEUS_RANGE_QUERY_STEP_SECONDS}s])) by (access_key)`,
start,
end,
@ -166,13 +164,15 @@ export class PrometheusManagerMetrics implements ManagerMetrics {
},
locations: [],
};
for (const result of bandwidth.result) {
if (result.value) {
serverMetrics.bandwidth.current.data.bytes = parseFloat(result.value[1]);
serverMetrics.bandwidth.current.timestamp = result.value[0];
}
break; // There should only be one result.
const bandwidthRangeValues = bandwidthRange.result[0].values ?? [];
const currentBandwidth = bandwidthRangeValues[bandwidthRangeValues.length - 1];
if (currentBandwidth) {
serverMetrics.bandwidth.current.data.bytes = parseFloat(currentBandwidth[1]);
serverMetrics.bandwidth.current.timestamp = currentBandwidth[0];
}
for (const result of bandwidthRange.result) {
const peakDataTransferred = findPeak(result.values ?? []);
if (peakDataTransferred !== null) {
@ -211,8 +211,6 @@ export class PrometheusManagerMetrics implements ManagerMetrics {
}
for (const result of tunnelTimeByAccessKeyRange.result) {
const entry = getServerMetricsAccessKeyEntry(accessKeyMap, result.metric);
const lastConnected = findLastNonZero(result.values ?? []);
entry.connection.lastConnected = lastConnected ? Math.min(now, lastConnected[0]) : null;
const peakTunnelTimeSec = findPeak(result.values ?? []);
if (peakTunnelTimeSec !== null) {
const peakValue = parseFloat(peakTunnelTimeSec[1]);
@ -234,6 +232,41 @@ export class PrometheusManagerMetrics implements ManagerMetrics {
accessKeys: Array.from(accessKeyMap.values()),
};
}
private prometheusCache = new Map<string, {timestamp: number; result: QueryResultData}>();
private get cachedPrometheusClient() {
return new Proxy(this.prometheusClient, {
get: (target, prop) => {
if (typeof target[prop] !== 'function') {
return target[prop];
}
return async (query, ...args) => {
const cacheId = `${String(prop)}: ${query} (args: ${args.join(', ')}))`;
if (this.prometheusCache.has(cacheId)) {
return this.prometheusCache.get(cacheId).result;
}
const result = await (target[prop] as Function)(query, ...args);
this.prometheusCache.set(cacheId, {timestamp: Date.now(), result});
return result;
};
},
});
}
private prunePrometheusCache() {
const now = Date.now();
for (const [key, value] of this.prometheusCache) {
if (now - value.timestamp > PROMETHEUS_RANGE_QUERY_STEP_SECONDS * 1000) {
this.prometheusCache.delete(key);
}
}
}
}
function getServerMetricsLocationEntry(
@ -268,7 +301,6 @@ function getServerMetricsAccessKeyEntry(
dataTransferred: {bytes: 0},
tunnelTime: {seconds: 0},
connection: {
lastConnected: null,
lastTrafficSeen: null,
peakDeviceCount: {
data: 0,

View file

@ -21,26 +21,12 @@ import * as file from '../infrastructure/file';
import * as logging from '../infrastructure/logging';
import {ShadowsocksAccessKey, ShadowsocksServer} from '../model/shadowsocks_server';
/** Represents an outline-ss-server configuration with multiple services. */
export interface OutlineSSServerConfig {
services: {
listeners: {
type: string;
address: string;
}[];
keys: {
id: string;
cipher: string;
secret: string;
}[];
}[];
}
// Runs outline-ss-server.
export class OutlineShadowsocksServer implements ShadowsocksServer {
private ssProcess: child_process.ChildProcess;
private ipCountryFilename?: string;
private ipAsnFilename?: string;
private isAsnMetricsEnabled = false;
private isReplayProtectionEnabled = false;
/**
@ -95,39 +81,22 @@ export class OutlineShadowsocksServer implements ShadowsocksServer {
private writeConfigFile(keys: ShadowsocksAccessKey[]): Promise<void> {
return new Promise((resolve, reject) => {
const validKeys: ShadowsocksAccessKey[] = keys.filter((key) => {
const keysJson = {keys: [] as ShadowsocksAccessKey[]};
for (const key of keys) {
if (!isAeadCipher(key.cipher)) {
logging.error(
`Cipher ${key.cipher} for access key ${key.id} is not supported: use an AEAD cipher instead.`
);
return false;
continue;
}
return true;
});
const config: OutlineSSServerConfig = {services: []};
const keysByPort: Record<number, ShadowsocksAccessKey[]> = {};
for (const key of validKeys) {
(keysByPort[key.port] ??= []).push(key);
}
for (const port in keysByPort) {
const service = {
listeners: [
{type: 'tcp', address: `[::]:${port}`},
{type: 'udp', address: `[::]:${port}`},
],
keys: keysByPort[port].map((key) => ({
id: key.id,
cipher: key.cipher,
secret: key.secret,
})),
};
config.services.push(service);
keysJson.keys.push(key);
}
mkdirp.sync(path.dirname(this.configFilename));
try {
file.atomicWriteFileSync(this.configFilename, jsyaml.safeDump(config, {sortKeys: true}));
file.atomicWriteFileSync(this.configFilename, jsyaml.safeDump(keysJson, {sortKeys: true}));
resolve();
} catch (error) {
reject(error);

View file

@ -11,17 +11,17 @@ tasks:
prometheus:download-*-*:
desc: Download and extract prometheus binary
vars:
VERSION: '2.37.1'
VERSION: '2.53.4'
GOOS: '{{index .MATCH 0}}'
GOARCH: '{{index .MATCH 1}}'
TEMPFILE: {sh: mktemp}
SHA256: '{{printf "%v/%v" .GOOS .GOARCH | get
(dict
"linux/amd64" "753f66437597cf52ada98c2f459aa8c03745475c249c9f2b40ac7b3919131ba6"
"linux/arm64" "b59a66fb5c7ec5acf6bf426793528a5789a1478a0dad8c64edc2843caf31b1b8"
"darwin/amd64" "e03a43d98955ac3500f57353ea74b5df829074205f195ea6b3b88f55c4575c79"
"darwin/arm64" "eb8a174c82a0fb6c84e81d9a73214318fb4a605115ad61505d7883d02e5a6f52"
)
"linux/amd64" "b8b497c4610d1b93208252b60c8f20f6b2e78596ae8df43397a2e805aa53d475"
"linux/arm64" "ec7236ecea7154d0bfe142921708b1ae7b5e921e100e0ee85ab92b7c444357e0"
"darwin/amd64" "10066a1aa21c4ddb8d5e61c31b52e898d8ac42c7e99fd757e2fc4b6c20b8075f"
"darwin/arm64" "cb3e638d8e9b4b27a6aa1f45a4faa3741b548aac67d4649aea7a2fad3c09f0a1"
)
}}'
TARGET_DIR: '{{joinPath .OUTPUT_BASE "prometheus" .GOOS .GOARCH}}'
TARGET: '{{joinPath .TARGET_DIR "prometheus"}}'

View file

@ -10,13 +10,21 @@ third_party {
}
url {
type: ARCHIVE
value: "https://github.com/prometheus/prometheus/releases/download/2.37.1/prometheus-2.37.1.linux-amd64.tar.gz"
value: "https://github.com/prometheus/prometheus/releases/download/v2.53.4/prometheus-2.53.4.linux-amd64.tar.gz"
}
url {
type: ARCHIVE
value: "https://github.com/prometheus/prometheus/releases/download/2.37.1/prometheus-2.37.1.darwin-amd64.tar.gz"
value: "https://github.com/prometheus/prometheus/releases/download/v2.53.4/prometheus-2.53.4.linux-arm64.tar.gz"
}
version: "2.37.1"
last_upgrade_date { year: 2022 month: 10 day: 24 }
url {
type: ARCHIVE
value: "https://github.com/prometheus/prometheus/releases/download/v2.53.4/prometheus-2.53.4.darwin-amd64.tar.gz"
}
url {
type: ARCHIVE
value: "https://github.com/prometheus/prometheus/releases/download/v2.53.4/prometheus-2.53.4.darwin-arm64.tar.gz"
}
version: "2.53.4"
last_upgrade_date { year: 2025 month: 3 day: 20 }
license_type: PERMISSIVE
}