File: README.md
Brook
A cross-platform programmable network tool.
Sponsor: Shiliew - Focuses on providing stable network services
Server
bash <(curl https://bash.ooo/nami.sh)nami install brookbrook server -l :9999 -p helloClient
You may want to use
brook linkto customize some parameters
Docs
https://www.txthinking.com/brook.html
Brook Script Gallery
File: SECURITY.md
Security Policy
Reporting a Vulnerability
If you discover a security vulnerability, please send an e-mail to [email protected].
It would be much appreciated if the POC is directly included.
All security vulnerabilities will be promptly addressed.
File: .travis.yml
language: go
sudo: false
os:
- linux
- osx
- windows
go: - "1.16"
script:- go test -v .
- cd cli/brook && go get -t -v . && go build .
File: ping/ping.json
{
"version": "20270101",
"text": "",
"link": "",
"text_zh": "",
"link_zh": ""
}
File: plugins/readme.md
It is very simple to write brook plugin, just overwrite the public function variable.
File: plugins/block/readme.md
block plugin can block the dst address on the server side, such as domain or ip. If it is the domain dst, and the IP will be resolved in the domain and check again.
File: plugins/dialwithdns/readme.md
dialwithdns plugin resolve domain with custom dns or doh, instead of local dns
File: plugins/dialwithip/readme.md
dialwithip plugin can dial with specified ip
File: plugins/dialwithnic/readme.md
dialwithnic plugin can dial with specified nic
File: plugins/logger/readme.md
log plugin can log to console or log to file.
File: plugins/pprof/readme.md
pprof plugin enable go http pprof
File: plugins/prometheus/readme.md
prometheus plugin can send log into prometheus
File: plugins/socks5dial/readme.md
socks5dial plugin can overwrite default dial, both server-side and client-side.
File: plugins/thedns/readme.md
thedns plugin can block/bypass/disable ipv4/disable ipv6 dns query for brook dnsoverbrook/dnsserver.
File: programmable/readme.md
How to submit your own scripts to the Brook Script Gallery
Just add an object to gallery.json
| Key | Type | Description |
|---|---|---|
| name | string | Your script or module name |
| url | string | Your script or module url. It can be placed in the programmable directory of this project, or anywhere else no CORS limit |
| kind | string | one of dnsserver/server/module/client |
| ca | bool | Need to install CA or not |
| author | string | Your name |
| author_url | string | Your url |
kind:
dnsserver: script for brook dnsserver, dohserver, dnsserveroverbrookserver: script for brook server, wsserver, wssserver, quicservermodule: module for Brook GUI Clientclient: script for ipio and brook.openwrt
File: programmable/gallery.json
[
{
"name": "Example",
"url": "https://raw.githubusercontent.com/txthinking/brook/refs/heads/master/programmable/dnsserver/example.tengo",
"kind": "dnsserver",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example",
"url": "https://raw.githubusercontent.com/txthinking/brook/refs/heads/master/programmable/server/example.tengo",
"kind": "server",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Block Google DOH",
"url": "https://raw.githubusercontent.com/txthinking/brook/refs/heads/master/programmable/modules/block_google_secure_dns.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Block AAAA",
"url": "https://raw.githubusercontent.com/txthinking/brook/refs/heads/master/programmable/modules/block_aaaa.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Block A",
"url": "https://raw.githubusercontent.com/txthinking/brook/refs/heads/master/programmable/modules/block_a.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example of predefining multiple brook links",
"url": "https://raw.githubusercontent.com/txthinking/brook/refs/heads/master/programmable/modules/brooklinks.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Block some AD domains",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/block_ad_domain.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "iOS ่ฑ็ฃ v7.66.0 ๅปๅนฟๅ",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/douban.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "ๅฐ็บขไนฆๅฎๅถ IP ๅฝๅฑๅฐ",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/xiaohongshu.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Bypass Geo CN or private IP",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/bypass_geo.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Bypass Apple",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/bypass_apple.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Bypass Apple A",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/bypass_apple_a.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Disalbe Fake DNS for Instagram",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/instagram_system_dns.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Disable Fake DNS for ChatGPT Advanced Voice",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/chatgpt_advanced_voice.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Unlock Xbox country limit",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/xbox.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Block iOS YouTube v17.15.1 AD",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/block_youtube_ad.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example for hosts",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/hosts.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "้ๅฎๅ www.google.cn ๅฐ www.google.com",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/redirect_google_cn.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Windows ๅ macOS ไธ Bypass ๅพฎไฟก",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/bypass_app.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example for blocking app on macOS, Windows and Linux",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/block_app.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example for only allow some apps to connect internet on macOS, Windows and Linux",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/allow_app.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "ไธๅฝๆๅผ้ป",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/sanguosha.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Bypass ๅฐ้จๅไธปๆตไธญๅฝๅคง้ๅๅ",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/bypass_china_domain_a.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Bypass ๅคง้จๅไธญๅฝๅคง้ๅๅ",
"url": "https://bot.txthinking.com/bypass_more_china_domain_a.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example for blacklist mode",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/blacklist_mode.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "mitmproxy client",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/mitmproxy_client.tengo",
"kind": "module",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example for packet capture",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/packet_capture.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Get iOS app history version list",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/ios_app_downgrade_history.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Download the specified version of iOS app",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/ios_app_downgrade.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example of modifying HTTP(S) responses",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/modules/response_sample.tengo",
"kind": "module",
"ca": true,
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
},
{
"name": "Example",
"url": "https://raw.githubusercontent.com/txthinking/brook/master/programmable/client/example.tengo",
"kind": "client",
"author": "TxThinking",
"author_url": "https://www.txthinking.com"
}
]
File: programmable/client/readme.md
ipio, brook.openwrt
File: programmable/dnsserver/readme.md
brook dnsserver, dohserver, dnsserveroverbrook
File: programmable/modules/readme.md
Brook GUI, ipio, brook.openwrt
File: programmable/server/readme.md
brook server, wsserver, wssserver, quicserver
File: protocol/brook-link-protocol.md
brook link protocol
brook://KIND?QUERY- KIND:
server,wsserver,wssserver,socks5,quicserver - QUERY: key=value, key and value should be urlencoded(RFC3986), such as
key0=xxx&key1=xxx
Checkout brook link --help
Example
brook://server?password=hello&server=1.2.3.4%3A9999File: protocol/brook-quicserver-protocol.md
brook quicserver protocol
Client --TCP over QUIC Stream--> Server
This is the same as the brook server protocol, except that the QUIC Stream is used instead of TCP
Server --TCP over QUIC Stream--> Client
This is the same as the brook server protocol, except that the QUIC Stream is used instead of TCP
Client --UDP over QUIC Datagram--> Server
This is the same as the brook server protocol, except that the QUIC Datagram is used instead of UDP
The maximum length of datagram is 1197 now, and may change in the future
Server --UDP over QUIC Datagram--> Client
This is the same as the brook server protocol, except that the QUIC Datagram is used instead of UDP
The maximum length of datagram is 1197 now, and may change in the future
File: protocol/brook-server-protocol.md
brook server protocol
Terminology
DST Address: The address that the application actually wants to request, address contains IP/domain and porttextATYP + IP/Domain + PORTATYP: 1 byte- 0x01: IPv4
- 0x03: Domain
- 0x04: IPv6
IP/Domain: 4/n/16 bytes- If ATYP is 0x01, then this is IPv4, 4 bytes
- If ATYP is 0x03, then this is domain, n bytes, and the first byte is the domain length
- If ATYP is 0x04, then this is IPv6, 16 bytes
Port: 2 bytes- Big Endian 16-bit unsigned integer
KEY: AES key, 32 bytesKEY: HKDF_SHA256(Password, Nonce, Info)Password: User-defined passwordNonce: 12 bytesInfo: [0x62, 0x72, 0x6f, 0x6f, 0x6b]. Note that this can be overwrite bybrook link --clientHKDFInfoandbrook link --serverHKDFInfo
HKDF: Defined in RFC 5869SHA256: Defined in FIPS 180-4AES: Defined in U.S. Federal Information Processing Standards Publication 197AES-GCM: Defined in RFC 5246, 5869
Client --TCP--> Server
Client Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 2048 bytes
Client Nonce: 12 bytes, randomly generated- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied- The first Fragment should be:text
Unix Timestamp + DST AddressUnix Timestamp: If it is not even, it should be increased by 1. Big Endian 32-bit unsigned integer
- The first Fragment should be:
Server --TCP--> Client
Server Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 2048 bytes
- Server Nonce: 12 bytes, randomly generated
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied
Client --UDP--> Server
Client Nonce + AES_GCM(Fragment)The maximum length of
Client Nonce + AES_GCM(Fragment)is 65507 bytes
Client Nonce: 12 bytes, randomly generated each timeFragment:textUnix Timestamp + DST Address + DataUnix Timestamp: Big Endian 32-bit unsigned integerData: Actual data being proxied
Server --UDP--> Client
Server Nonce + AES_GCM(Fragment)The maximum length of
Server Nonce + AES_GCM(Fragment)is 65507 bytes
Server Nonce: 12 bytes, randomly generated each timeFragment:textDST Address + DataData: Actual data being proxied
Client --UDP(UDP over TCP)--> Server
Client Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 65507 bytes, but the maximum length if the first one is 2048 bytes
Client Nonce: 12 bytes, randomly generated- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied- The first Fragment should be:text
Unix Timestamp + DST AddressUnix Timestamp: If it is not odd, it should be increased by 1. Big Endian 32-bit unsigned integer
- The first Fragment should be:
Server --UDP(UDP over TCP)--> Client
Server Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 65507 bytes
- Server Nonce: 12 bytes, randomly generated
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied
File: protocol/brook-wsserver-protocol.md
brook wsserver protocol
Terminology
DST Address: The address that the application actually wants to request, address contains IP/domain and porttextATYP + IP/Domain + PORTATYP: 1 byte- 0x01: IPv4
- 0x03: Domain
- 0x04: IPv6
IP/Domain: 4/n/16 bytes- If ATYP is 0x01, then this is IPv4, 4 bytes
- If ATYP is 0x03, then this is domain, n bytes, and the first byte is the domain length
- If ATYP is 0x04, then this is IPv6, 16 bytes
Port: 2 bytes- Big Endian 16-bit unsigned integer
KEY: AES key, 32 bytesKEY: HKDF_SHA256(Password, Nonce, Info)Password: User-defined passwordNonce: 12 bytesInfo: [0x62, 0x72, 0x6f, 0x6f, 0x6b]. Note that this can be overwrite bybrook link --clientHKDFInfoandbrook link --serverHKDFInfo
HKDF: Defined in RFC 5869SHA256: Defined in FIPS 180-4AES: Defined in U.S. Federal Information Processing Standards Publication 197AES-GCM: Defined in RFC 5246, 5869
Client --TCP--> Server
[Standard WebSocket Protocol Header] + Client Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 2048 bytes
Client Nonce: 12 bytes, randomly generated- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied- The first Fragment should be:text
Unix Timestamp + DST AddressUnix Timestamp: If it is not even, it should be increased by 1. Big Endian 32-bit unsigned integer
- The first Fragment should be:
Server --TCP--> Client
[Standard WebSocket Protocol Header] + Server Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 2048 bytes
- Server Nonce: 12 bytes, randomly generated
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied
Client --UDP(UDP over TCP)--> Server
[Standard WebSocket Protocol Header] + Client Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 65507 bytes, but the maximum length if the first one is 2048 bytes
Client Nonce: 12 bytes, randomly generated- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied- The first Fragment should be:text
Unix Timestamp + DST AddressUnix Timestamp: If it is not odd, it should be increased by 1. Big Endian 32-bit unsigned integer
- The first Fragment should be:
Server --UDP(UDP over TCP)--> Client
[Standard WebSocket Protocol Header] + Server Nonce + [AES_GCM(Fragment Length) + AES_GCM(Fragment)]...The maximum length of
AES_GCM(Fragment Length) + AES_GCM(Fragment)is 65507 bytes
- Server Nonce: 12 bytes, randomly generated
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
1to the first 8 bytes according to the Little Endian 64-bit unsigned integer
- The nonce should be recalculated when it is not used for the first time, the calculation method: add
Fragment Length: Big Endian 16-bit unsigned integerFragment: Actual data being proxied
File: protocol/brook-wssserver-protocol.md
brook wssserver protocol
TLS(brook wsserver protocol)The simple explanation is brook wsserver + tls = brook wssserver, and brook wsserver is a standard http server, so http + tls = https, brook wssserver is a standard https server
File: protocol/user.md
User System
This content introduces how to develop a user system with Brook. Your system only needs to focus on two concepts: Token and User API. To support user system, you must use brook server/wsserver/wssserver/quicserver with the brook protocol.
Token
The concept of a token is similar to the user authentication systems of many other systems, such as cookie, session, it represents an identifier that is visible to the user, so it shoud be unpredictable. For example, suppose your user system has the concept of an auto-incrementing user ID. You might need to encrypt this user ID to generate a token to be placed on the client side. Alternatively, if your user ID is a UUID, or if you have an auto-incrementing user ID along with an additional UUID, you can also use the UUID directly as a token. This entirely depends on your own decision. If you have developed any user authentication system, this is basic knowledge. No matter what method you use to generate the token, you need to encode it as hexadecimal. The length should be controlled at around a few dozen bytes, do not make it too long.
For example, encrypt user id or make session, and encode in hexadecimal:
hex_encode(your_encrypt_or_session_function(user id))
// 3ae6afc9fad94abd8985d8ecc77afb273ae6afc9fad94abd8985d8ecc77afb273ae6afc9fad94abd8985d8ecc77afb27For example, UUID:
crypto.randomUUID().replaceAll('-', '')
// 3ae6afc9fad94abd8985d8ecc77afb27User API
Your system must provide an API for Brook Server to validate token. For example: https://your-api-server.com/a_unpredictable_path, yes, it is recommended to add an unpredictable path to your https API, of course, you can also use the http api for internal network communication. Brook Server will send GET request to your User API to check if token is valid, the request format is https://your-api-server.com/a_unpredictable_path?token=xxx. When the response is 200, the body should be the user's unique identifier, such as user ID; all other status codes are considered to represent an invalid user, and in these cases, the body should be a string describing the error.
For example, your User API is:
https://your-api-server.com/a_unpredictable_pathBrook Server will send GET request with token to your User API:
GET https://your-api-server.com/a_unpredictable_path?token=xxxIf the token is valid, your User API should response status code 200 and body should be user's unique identifier, such as user ID 9:
HTTP/1.1 200 OK
Content-Length: 1
Content-Type: text/plain; charset=utf-8
9If the token is invalid, or because of any other reasons, the service cannot be provided to this user, such as the user has expired, your User API should response status code non-200 and body should be the short reason:
HTTP/1.1 400 BAD REQUEST
Content-Length: 22
Content-Type: text/plain; charset=utf-8
The user 9 has expiredRun Brook Server with your User API
brook --userLog /path/to/log.txt --userAPI https://your-api-server.com/a_unpredictable_path server --listen :9999 --password helloYou can count the traffic of each user from userLog
{"bytes":"2190","dst":"8.8.8.8:53","from":"34.105.110.232:49514","network":"tcp","time":"2024-02-26T09:56:12Z","user":"9"}
{"bytes":"2237","dst":"8.8.8.8:53","from":"34.105.110.232:49331","network":"udp","time":"2024-02-26T09:57:12Z","user":"9"}Generate brook link with token
brook link --server 1.2.3.4:9999 --password hello --token xxxA sample implementation
https://github.com/TxThinkingInc/brook_store
Read more
Brook Business: Powering Your Own Branded Client
File: .github/CONTRIBUTING.md
I want to create a PR.
- Create an issue first
- Don't use internal package
- Don't use generics
- Single function better than multiple functions
- Single file better than multiple files
- Single diretory better than multiple directories
- Prefer struct and function to be exported.
- Keep it simple, stupid
- Create PR on
masterbranch
File: .github/workflows/static.yml
Simple workflow for deploying static content to GitHub Pages
name: Deploy static content to Pages
on:
Runs on pushes targeting the default branch
push:
branches: ["master"]
Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
Single deploy job since we're just deploying
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: './docs'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4