brook Documentation

A cross-platform programmable network tool

15,189 stars Go Go 7 files ยท ~7,244 tokens #android#cross-platform#decryption#encryption#ios#linux#macos#mitm
RAW Doc

File: README.md

Brook

A cross-platform programmable network tool.

Sponsor: Shiliew - Focuses on providing stable network services

Server

text
bash <(curl https://bash.ooo/nami.sh)
text
nami install brook
text
brook server -l :9999 -p hello

Client

You may want to use brook link to customize some parameters

Docs

https://www.txthinking.com/brook.html

https://brook.app


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

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, dnsserveroverbrook
  • server: script for brook server, wsserver, wssserver, quicserver
  • module: module for Brook GUI Client
  • client: script for ipio and brook.openwrt

[
{
"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


text
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

text
brook://server?password=hello&server=1.2.3.4%3A9999

File: 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 port

    text
    ATYP + IP/Domain + PORT
    • ATYP: 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 bytes

    • KEY: HKDF_SHA256(Password, Nonce, Info)
      • Password: User-defined password
      • Nonce: 12 bytes
      • Info: [0x62, 0x72, 0x6f, 0x6f, 0x6b]. Note that this can be overwrite by brook link --clientHKDFInfo and brook link --serverHKDFInfo
  • HKDF: Defined in RFC 5869

  • SHA256: Defined in FIPS 180-4

  • AES: Defined in U.S. Federal Information Processing Standards Publication 197

  • AES-GCM: Defined in RFC 5246, 5869

Client --TCP--> Server

text
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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: Actual data being proxied
    • The first Fragment should be:
      text
      Unix Timestamp + DST Address
      • Unix Timestamp: If it is not even, it should be increased by 1. Big Endian 32-bit unsigned integer

Server --TCP--> Client

text
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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: Actual data being proxied

Client --UDP--> Server

text
Client Nonce + AES_GCM(Fragment)

The maximum length of Client Nonce + AES_GCM(Fragment) is 65507 bytes

  • Client Nonce: 12 bytes, randomly generated each time
  • Fragment:
    text
    Unix Timestamp + DST Address + Data
    • Unix Timestamp: Big Endian 32-bit unsigned integer
    • Data: Actual data being proxied

Server --UDP--> Client

text
Server Nonce + AES_GCM(Fragment)

The maximum length of Server Nonce + AES_GCM(Fragment) is 65507 bytes

  • Server Nonce: 12 bytes, randomly generated each time
  • Fragment:
    text
    DST Address + Data
    • Data: Actual data being proxied

Client --UDP(UDP over TCP)--> Server

text
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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: Actual data being proxied
    • The first Fragment should be:
      text
      Unix Timestamp + DST Address
      • Unix Timestamp: If it is not odd, it should be increased by 1. Big Endian 32-bit unsigned integer

Server --UDP(UDP over TCP)--> Client

text
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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: 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 port

    text
    ATYP + IP/Domain + PORT
    • ATYP: 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 bytes

    • KEY: HKDF_SHA256(Password, Nonce, Info)
      • Password: User-defined password
      • Nonce: 12 bytes
      • Info: [0x62, 0x72, 0x6f, 0x6f, 0x6b]. Note that this can be overwrite by brook link --clientHKDFInfo and brook link --serverHKDFInfo
  • HKDF: Defined in RFC 5869

  • SHA256: Defined in FIPS 180-4

  • AES: Defined in U.S. Federal Information Processing Standards Publication 197

  • AES-GCM: Defined in RFC 5246, 5869

Client --TCP--> Server

text
[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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: Actual data being proxied
    • The first Fragment should be:
      text
      Unix Timestamp + DST Address
      • Unix Timestamp: If it is not even, it should be increased by 1. Big Endian 32-bit unsigned integer

Server --TCP--> Client

text
[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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: Actual data being proxied

Client --UDP(UDP over TCP)--> Server

text
[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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: Actual data being proxied
    • The first Fragment should be:
      text
      Unix Timestamp + DST Address
      • Unix Timestamp: If it is not odd, it should be increased by 1. Big Endian 32-bit unsigned integer

Server --UDP(UDP over TCP)--> Client

text
[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 1 to the first 8 bytes according to the Little Endian 64-bit unsigned integer
  • Fragment Length: Big Endian 16-bit unsigned integer
  • Fragment: Actual data being proxied

File: protocol/brook-wssserver-protocol.md

brook wssserver protocol

text
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:

text
hex_encode(your_encrypt_or_session_function(user id))
// 3ae6afc9fad94abd8985d8ecc77afb273ae6afc9fad94abd8985d8ecc77afb273ae6afc9fad94abd8985d8ecc77afb27

For example, UUID:

javascript
crypto.randomUUID().replaceAll('-', '')
// 3ae6afc9fad94abd8985d8ecc77afb27

User 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:

text
https://your-api-server.com/a_unpredictable_path

Brook Server will send GET request with token to your User API:

text
GET https://your-api-server.com/a_unpredictable_path?token=xxx

If 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:

text
HTTP/1.1 200 OK
Content-Length: 1
Content-Type: text/plain; charset=utf-8

9

If 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:

text
HTTP/1.1 400 BAD REQUEST
Content-Length: 22
Content-Type: text/plain; charset=utf-8

The user 9 has expired

Run Brook Server with your User API

text
brook --userLog /path/to/log.txt --userAPI https://your-api-server.com/a_unpredictable_path server --listen :9999 --password hello

You can count the traffic of each user from userLog

text
{"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"}
text
brook link --server 1.2.3.4:9999 --password hello --token xxx

A 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.

  1. Create an issue first
  2. Don't use internal package
  3. Don't use generics
  4. Single function better than multiple functions
  5. Single file better than multiple files
  6. Single diretory better than multiple directories
  7. Prefer struct and function to be exported.
  8. Keep it simple, stupid
  9. Create PR on master branch

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