Switch to Tailwind and add API doc

This commit is contained in:
Misha Bragin
2023-05-22 18:54:43 +02:00
committed by GitHub
262 changed files with 24473 additions and 9919 deletions

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@ yarn-error.log*
.idea
package-lock.json
/.next/
/yarn.lock

View File

@@ -1,3 +0,0 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View File

@@ -1,22 +0,0 @@
---
id: firewall-ports
title: What firewall ports should I open to use NetBird?
sidebar_position: 1
---
### Incoming ports
NetBird's agent doesn't require any incoming port to be open; It negotiates the connection with the support of the signal and relay services.
### Outgoing ports
NetBird usually won't need open ports, but sometimes you or your IT team needs to secure and verify
all outgoing traffic, and that may affect how NetBird clients connect to the [control layer](/overview/architecture)
and negotiate the peer-to-peer connections.
Below is the list of NetBird hosted endpoints and ports they listen to:
* Management service:
* **Endpoint**: api.wiretrustee.com
* **Port**: TCP/443
* Signal service:
* **Endpoint**: signal2.wiretrustee.com
* **Port**: TCP/443
* Relay (TURN) service:
* **Endpoint**: turn.netbird.io
* **Port range**: UDP/5555-65535

View File

@@ -1,4 +0,0 @@
{
"label": "Examples",
"position": 5
}

View File

@@ -1,27 +0,0 @@
---
sidebar_position: 2
---
# NetBird Client in Docker
One of the simplest ways of running NetBird client application is to use a pre-built [Docker image](https://hub.docker.com/r/netbirdio/netbird).
**Prerequisites:**
* **Docker installed.**
If you don't have docker installed, please refer to the installation guide on the official [Docker website](https://docs.docker.com/get-docker/).
* **NetBird account.**
Register one at [app.netbird.io](https://app.netbird.io/).
You would need to obtain a [setup key](/overview/setup-keys) to associate NetBird client with your account.
The setup key could be found in the NetBird Management dashboard under the Setup Keys tab - [https://app.netbird.io/setup-keys](https://app.netbird.io/setup-keys).
Set the ```NB_SETUP_KEY``` environment variable and run the command.
```bash
docker run --rm --name PEER_NAME --hostname PEER_NAME --cap-add=NET_ADMIN -d -e NB_SETUP_KEY=<SETUP KEY> -v netbird-client:/etc/netbird netbirdio/netbird:latest
```
That is it! Enjoy using NetBird.
If you would like to learn how to run NetBird Client as an ECS agent on AWS, please refer to [this guide](/examples/aws-ecs-client-daemon).

View File

@@ -1,4 +0,0 @@
{
"label": "Getting Started",
"position": 4
}

View File

@@ -1,75 +0,0 @@
---
sidebar_position: 1
title: Quickstart Guide
---
Step-by-step video guide on YouTube:
<div class="videowrapper">
<iframe src="https://www.youtube.com/embed/HYlhvr_eu2U" allow="fullscreen;"></iframe>
</div>
<br/>
This guide describes how to quickly get started with NetBird and create a secure private network with 2 connected machines.
One machine is a Linux laptop, and the other one a EC2 node running on AWS.
Both machines are running Linux but NetBird also works on Windows and MacOS.
1. Sign-up at [https://app.netbird.io/](https://app.netbird.io/)
You can use your Google, GitHub or Microsoft account.
![](/img/getting-started/auth.png)
2. After a successful login you will be redirected to the ```Peers``` screen which is empty because you don't have any peers yet.
Click ```Add peer``` to add a new machine.
![](/img/getting-started/empty-peers.png)
3. Choose your machine operating system (in our case it is ```Linux```) and proceed with the installation steps.
![](/img/getting-started/add-peer.png)
4. If you installed NetBird Desktop UI you can use it to connect to the network instead of running `netbird up` command. Look for `NetBird` in your application list, run it, and click `Connect`.
>
![](/img/getting-started/systray.png)
5. At this point a browser window pops up starting a device registration process. Click confirm and follow the steps if required.
![](/img/getting-started/device-confirmation.png)
6. On the EC2 node repeat the installation steps and run `netbird up` command.
```bash
sudo netbird up
```
7. Copy the verification URL from the terminal output and paste it in your browser. Repeat step #5
![](/img/getting-started/netbird-up.png)
8. Return to ```Peers``` and you should notice 2 new machines with status ```online```
![](/img/getting-started/peers.png)
9. To test the connection you could try pinging devices:
On your laptop:
```bash
ping 100.64.0.2
```
On the EC2 node:
```bash
ping 100.64.0.1
```
10. Done! You now have a secure peer-to-peer private network configured.
<br/>
- Make sure to [star us on GitHub](https://github.com/netbirdio/netbird)
- Follow us [on Twitter](https://twitter.com/netbird)
- Join our [Slack Channel](https://join.slack.com/t/netbirdio/shared_invite/zt-vrahf41g-ik1v7fV8du6t0RwxSrJ96A)
- NetBird release page on GitHub: [releases](https://github.com/netbirdio/netbird/releases/latest)

View File

@@ -1,4 +0,0 @@
{
"label": "How-to Guides",
"position": 3
}

View File

@@ -1,4 +0,0 @@
{
"label": "Integrations",
"position": 4
}

View File

@@ -1,4 +0,0 @@
{
"label": "Identity Providers",
"position": 4
}

View File

@@ -1,4 +0,0 @@
{
"label": "Self-Hosted NetBird",
"position": 4
}

View File

@@ -1,91 +0,0 @@
---
id: using-netbird-with-auth0
title: Using NetBird with Auth0
sidebar_position: 2
tags:
- integrations
- idp
- auth0
- oidc
- how-to
---
This guide is a part of the [NetBird Self-hosting Guide](/getting-started/self-hosting) and explains how to integrate **self-hosted** NetBird with [Auth0](https://auth0.com/).
Auth0 is a flexible, drop-in solution to add authentication and authorization services to your applications.
It is a 3rd party managed service and can't be self-hosted. Auth0 is the right choice if you don't want to manage an Identity Provider (IDP)
instance on your own.
:::tip self-hosted idp
If you prefer to have full control over authentication and authorization of your NetBird network, there are good
self-hosted alternatives to the managed Auth0 service like [Keycloak](/integrations/identity-providers/self-hosted/using-netbird-with-keycloak).
:::
### Step 1: Create Auth0 account
To create an Auth0 account, sign up at [https://auth0.com](https://auth0.com/).
There are five properties of the **`setup.env`** file that we will configure in this guide:
- `NETBIRD_AUTH_CLIENT_ID`
- `NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT`
- `NETBIRD_USE_AUTH0`
- `NETBIRD_AUTH_AUDIENCE`
- `NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID` (Optional)
### Step 2: Create and configure Auth0 application
This Auth0 application will be used to authorize access to NetBird Dashboard (Web UI).
- Follow the steps in the [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0)
up until "Install the Auth0 React SDK".
- Use **`https://YOUR DOMAIN`** as: `Allowed Callback URLs`, `Allowed Logout URLs`, `Allowed Web Origins`, `Allowed Origins (CORS)`
:::caution
Make sure that **`Token Endpoint Authentication Method`** is set to **`None`**.
:::
- Use **`Client ID`** to set ```NETBIRD_AUTH_CLIENT_ID``` property in the `setup.env` file.
- Use **`Domain`** to configure ```NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT``` property in the `setup.env` file like so:
```
https://<DOMAIN>/.well-known/openid-configuration
```
:::caution
Double-check if the endpoint returns a JSON response by calling it from your browser.
:::
### Step 3: Create and configure Auth0 API
This Auth0 API will be used to access NetBird Management Service API.
- Follow the steps in the [Auth0 Create An API](https://auth0.com/docs/quickstart/backend/golang#create-an-api).
- Use API **`Identifier`** to set ```NETBIRD_AUTH_AUDIENCE``` property in the `setup.env` file.
- Set ```NETBIRD_USE_AUTH0``` to `true`in the `setup.env` file.
### Step 4: Enable Interactive SSO Login (Optional)
The [Interactive SSO Login feature](/getting-started/installation#running-netbird-with-sso-login) allows for machine
authorization with your Identity Provider. This feature can be used as an alternative to [setup keys](/overview/setup-keys)
and is optional.
You can enable it by following these steps:
- Log in to your Auth0 account https://manage.auth0.com/
- Go to `Applications` (left-hand menu)
- Click `Create Application` button (top right)
- Fill in the form with the following values:
- Name: `Interactive Login`
- Application type: `Native`
- Click `Create`
![](/img/integrations/identity-providers/self-hosted/auth0-create-interactive-login-app.png)
- Click `Settings` tab
- Copy **`Client ID`** to `NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID` in the `setup.env` file
![](/img/integrations/identity-providers/self-hosted/auth0-interactive-login-settings.png)
- Scroll down to the `Advanced Settings` section
- Enable **`Device Code`**
- Click `Save Changes`
![](/img/integrations/identity-providers/self-hosted/auth0-grant-types.png)
### Step 4: Continue with the self-hosting guide
You can now continue with the [NetBird Self-hosting Guide](/getting-started/self-hosting#step-3-configure-identity-provider).

View File

@@ -1,17 +0,0 @@
---
id: available-idp-integrations
title: Available IDP Integrations
sidebar_position: 1
---
There are a few Identity Provider options that you can choose to run a self-hosted version NetBird.
:::tip OpenID
NetBird supports generic OpenID (OIDC) protocol allowing for the integration with any IDP that follows the specification.
:::
List of available guides:
- [Auth0](/integrations/identity-providers/self-hosted/using-netbird-with-auth0) (managed service)
- [Azure AD](/integrations/identity-providers/self-hosted/using-netbird-with-azure-ad) (managed service)
- [Keycloak](/integrations/identity-providers/self-hosted/using-netbird-with-keycloak)
- [Zitadel](/integrations/identity-providers/self-hosted/using-netbird-with-zitadel)

View File

@@ -1,238 +0,0 @@
---
id: using-netbird-with-keycloak
title: Using NetBird with Keycloak
sidebar_position: 3
tags:
- integrations
- idp
- keycloak
- oidc
- how-to
---
This guide is a part of the [NetBird Self-hosting Guide](/getting-started/self-hosting) and explains how to integrate
**self-hosted** NetBird with [Keycloak](https://www.keycloak.org/).
Keycloak is an open source software product to allow single sign-on with Identity and Access Management aimed at modern applications and services.
:::tip managed idp
If you prefer not to self-host an Identity and Access Management solution, then you could use a managed alternative like
[Auth0](/integrations/identity-providers/self-hosted/using-netbird-with-auth0).
:::
The following guide is an adapted version of the original
[Keycloak on Docker](https://www.keycloak.org/getting-started/getting-started-docker) guide from the official website.
### Expected Result
After completing this guide, you can log in to your self-hosted NetBird Dashboard and add your machines
to your network using the [Interactive SSO Login feature](/getting-started/installation#running-netbird-with-sso-login)
over Keycloak.
![](/img/integrations/identity-providers/self-hosted/keycloak-auth-grant.gif)
### Step 1: Check your Keycloak Instance
For this guide, you need a fully configured Keycloak instance running with SSL.
We assume that your Keycloak instance is available at **`https://YOUR-KEYCLOAK-HOST-AND_PORT`**.
Feel free to change the port if you have configured Keycloak with a different one.
Most of the OIDC software requires SSL for production use.
We encourage you to comply with this requirement to make the world more secure 😊.
### Step 2: Create a realm
To create a realm you need to:
- Open the Keycloak Admin Console
- Hover the mouse over the dropdown in the top-left corner where it says `Master`, then click on `Create Realm`
- Fill in the form with the following values:
- Realm name: `netbird`
- Click `Create`
-
![](/img/integrations/identity-providers/self-hosted/keycloak-create-realm.png)
### Step 3: Create a user
In this step we will create a NetBird administrator user.
- Open the Keycloak Admin Console
- Make sure, that the selected realm is `Netbird`
- Click `Users` (left-hand menu)
- Click `Create new user`
- Fill in the form with the following values:
- Username: `netbird`
- Click `Create`
![](/img/integrations/identity-providers/self-hosted/keycloak-create-user.png)
The user will need an initial password set to be able to log in. To do this:
- Click `Credentials` tab
- Click `Set password` button
- Fill in the password form with a password
- Set the `Temporary` field to `Off` to prevent having to update password on first login
- Click `Save`
![](/img/integrations/identity-providers/self-hosted/keycloak-set-password.png)
### Step 4: Create a NetBird client
In this step we will create NetBird application client and register with the Keycloak instance.
- Open the Keycloak Admin Console
- Make sure, that the selected realm is `Netbird`
- Click `Clients`
- Click `Create client` button
- Fill in the form with the following values and click Next:
- Client Type: `OpenID Connect`
- Client ID: `netbird-client`
- Your newly client `netbird-client` will be used later to set `NETBIRD_AUTH_CLIENT_ID` in the `setup.env`
![](/img/integrations/identity-providers/self-hosted/keycloak-create-client.png)
- Check the checkboxes as on the screenshot below and click Save
![](/img/integrations/identity-providers/self-hosted/keycloak-enable-auth.png)
### Step 5: Adjust NetBird client access settings
In this step we will configure NetBird application client access with the NetBird URLs.
- Open the Keycloak Admin Console
- Make sure, that the selected realm is `Netbird`
- Click `Clients`
- Choose `netbird-client` from the list
- Go to `Access Settings` section
- Fill in the fields with the following values:
- Root URL: `https://YOUR DOMAIN/` (this is the NetBird Dashboard root URL)
- Valid redirect URIs: `https://YOUR DOMAIN/*`
- Valid post logout redirect URIs: `https://YOUR DOMAIN/*`
- Web origins: `+`
- Click `Save`
![](/img/integrations/identity-providers/self-hosted/keycloak-access-settings.png)
### Step 6: Create a NetBird client scope
In this step, we will create and configure the NetBird client audience for Keycloak to add it to the generated JWT tokens.
- Open the Keycloak Admin Console
- Make sure, that the selected realm is `Netbird`
- Click `Client scopes` (left-hand menu)
- Click `Create client scope` button
- Fill in the form with the following values:
- Name: `api`
- Type: `Default`
- Protocol: `OpenID Connect`
- Click `Save`
![](/img/integrations/identity-providers/self-hosted/keycloak-create-client-scope.png)
- While in the newly created Client Scope, switch to the `Mappers` tab
- Click `Configure a new mapper`
- Choose the `Audience` mapping
![](/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper.png)
- Fill in the form with the following values:
- Name: `Audience for NetBird Management API`
- Included Client Audience: `netbird-client`
- Add to access token: `On`
- Click `Save`
![](/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper-2.png)
### Step 7: Add client scope to NetBird client
- Open the Keycloak Admin Console
- Make sure, that the selected realm is `Netbird`
- Click `Clients`
- Choose `netbird-client` from the list
- Switch to `Client scopes` tab
- Click `Add client scope` button
- Choose `api`
- Click `Add` choosing `Default`
- The value `netbird-client` will be used as audience
![](/img/integrations/identity-providers/self-hosted/keycloack-add-client-scope.png)
### Step 8: Create a NetBird-Backend client
In this step we will create NetBird backend client and register with the Keycloak instance.
- Open the Keycloak Admin Console
- Make sure, that the selected realm is `Netbird`
- Click `Clients`
- Click `Create client` button
- Fill in the form with the following values and click Next:
- Client Type: `OpenID Connect`
- Client ID: `netbird-backend`
- Your newly client `netbird-backend` will be used later to set `KeycloakClientCredentials` in the `management.json`
![](/img/integrations/identity-providers/self-hosted/keycloak-create-backend-client.png)
- Check the checkboxes as on the screenshot below and click Save
![](/img/integrations/identity-providers/self-hosted/keycloak-backend-client-auth.png)
The client will need secret to authenticate. To do this:
- Click `Credentials` tab
- Copy `client secret` will be used later to set `ClientSecret` in the `management.json`
![](/img/integrations/identity-providers/self-hosted/keycloak-backend-client-credentials.png)
### Step 9: Add manage-users role to netbird-backend
- Open the Keycloak Admin Console
- Make sure, that the selected realm is `Netbird`
- Click `Clients`
- Choose `netbird-backend` from the list
- Switch to `Service accounts roles` tab
- Click `Assign roles` button
- Select `Filter by clients` and search for `manage-users`
![](/img/integrations/identity-providers/self-hosted/keycloak-service-account-role.png)
- Check the role checkbox and click assign
![](/img/integrations/identity-providers/self-hosted/keycloak-add-role.png)
Your authority OIDC configuration will be available under:
```
https://<YOUR-KEYCLOAK-HOST-AND-PORT>/realms/netbird/.well-known/openid-configuration
```
:::caution
Double-check if the endpoint returns a JSON response by calling it from your browser.
:::
- Set properties in the `setup.env` file:
- NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=`https://<YOUR-KEYCLOAK-HOST-AND-PORT>/realms/netbird/.well-known/openid-configuration`.
- NETBIRD_AUTH_CLIENT_ID=`netbird-client`
- NETBIRD_AUTH_AUDIENCE=`netbird-client`
- NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID=`netbird-client`. Optional,
it enables the [Interactive SSO Login feature](/getting-started/installation#running-netbird-with-sso-login) (Oauth 2.0 Device Authorization Flow)
- You can now continue with the [NetBird Self-hosting Guide](/getting-started/self-hosting#step-3-configure-identity-provider).
- Set property `IdpManagerConfig` in the `management.json` file with:
:::caution
The file management.json is created automatically. Please refer [here](/getting-started/self-hosting#step-5-run-configuration-script) for more information.
:::
```json
{
"ManagerType": "keycloak",
"KeycloakClientCredentials": {
"ClientID": "netbird-backend",
"ClientSecret": "<netbird-backend-client-secret>",
"GrantType": "client_credentials",
"TokenEndpoint": "https://<YOUR-KEYCLOAK-HOST-AND-PORT>/realms/netbird/protocol/openid-connect/token",
"AdminEndpoint": "https://<YOUR-KEYCLOAK-HOST-AND-PORT>/admin/realms/netbird"
}
}
```
:::note
Make sure that your Keycloak instance use HTTPS. Otherwise, the setup won't work.
:::

View File

@@ -1,23 +0,0 @@
---
id: introduction
slug: /
sidebar_position: 1
---
# Introduction
NetBird is a simple and fast alternative to corporate VPNs built on top of [WireGuard®](https://www.wireguard.com/) making it easy to create secure private networks for your organization or home.
NetBird can connect machines running anywhere in just a few clicks.
It requires near zero configuration effort leaving behind the hassle of opening ports, complex firewall rules, vpn gateways, and so forth.
:::tip open-source
NetBird is an **open-source** project.
Check it out on GitHub: **[https://github.com/netbirdio/netbird](https://github.com/netbirdio/netbird)**
:::
There is no centralized VPN server with NetBird - your computers, devices, machines, and servers connect to each other directly over a fast encrypted tunnel.
It literally takes less than 5 minutes to deploy a secure peer-to-peer VPN with NetBird. Check our [Quick Start Guide](/getting-started/quickstart) to get started.

View File

@@ -1,4 +0,0 @@
{
"label": "Other",
"position": 8
}

View File

@@ -1,4 +0,0 @@
{
"label": "How NetBird Works",
"position": 2
}

View File

@@ -1,123 +0,0 @@
---
sidebar_position: 1
---
# Architecture
### Overview
NetBird is an open source platform consisting of a collection of components, responsible for handling peer-to-peer connections, tunneling, authentication, and network management (IPs, keys, ACLs, etc).
It uses open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn),
and [software](https://github.com/netbirdio/netbird) developed by NetBird authors to make secure private networks deployment and management simple.
NetBird relies on four components - **Client** application (or agent), **Management**, **Signal** and **Relay** services.
The combination of these elements ensures that direct point-to-point connections are established and only authenticated users (or machines) have access to the resources for which they are authorized.
A **Peer** is a machine or any device that is connected to the network.
It can be a Linux server running in the cloud or on-premises, a personal laptop, or even a Raspberry PI.
<p align="center">
<img src="/docs/img/architecture/high-level-dia.png" alt="high-level-dia" width="781"/>
</p>
With NetBird clients installed and authorized on the Management service, machines form a mesh network connecting to each other directly via an encrypted point-to-point Wireguard tunnel.
<p align="center">
<img src="/docs/img/architecture/mesh.png" alt="high-level-dia"/>
</p>
While it is possible to create a full mesh network, it might be not a desirable outcome. In this case, [ACLs](/overview/acls) can be utilized to limit the access to certain machines.
Let's now take a closer look at each of NetBird's components.
### Management Service
The Management service is the central coordination component for NetBird with a UI dashboard.
It keeps the network state, public Wireguard keys of the peers, authenticates and distributes network changes to peers.
The Management Service's responsibilities include:
* **Registering and authenticating new peers.** Every new machine has to register itself in the network in order to connect to other machines.
After installation, NetBird client requires login that can be done through Identity Provider (IDP) or with a [setup key](/overview/setup-keys).
* **Keeping the network map.** The Management service stores information about all the registered peers including Wireguard public key that was sent during the registration process.
* **Managing private IP addresses.** Each peer receives a unique private IP with which it can be identified in the network.
We use [Carrier Grade NAT](https://en.wikipedia.org/wiki/Carrier-grade_NAT) address space with an allocated address block <em>100.64.0.0/10</em>.
* **Synchronizing network changes to peers.** The Management Service keeps a control channel open to each peer sending network updates.
Whenever a new peer joins the network, all other peers that are authorized to connect to it receive an update.
After that, they are able to establish a connection to the new peer.
* **Creating and managing ACLs.** ACL is a list of peers that a given peer has access to. <em>Coming Soon</em>.
* **Managing private DNS.** [DNS](/overview/dns) allows referring to each of the peers with a fully qualified domain name (FQDN). <em>Coming Soon</em>.
* **Monitoring network activity.** <em>Coming Soon</em>.
* **Wireguard key rotation.** <em>Coming Soon</em>.
The Management service runs in the cloud NetBird-managed. It can also be self-hosted.
<p align="center">
<img src="/docs/img/architecture/management.png" alt="management-dia"/>
</p>
### Client Application
The NetBird Client application (or agent) is a software that is installed on your machines.
It is an entry point to you private network that makes it possible for machines to communicate with each other.
Once installed and registered, a machine becomes a peer in the network.
The Client's roles are the following:
* **Generating private and public Wireguard keys.** These keys are used for packet encryption between peers and for [Wireguard Cryptokey Routing](https://www.wireguard.com/#cryptokey-routing).
To accept the incoming connections, peers have to know each other, therefore, the generated public keys have to be pre-shared on the machines. The client application sends its public key to the Management service which then distributes it to the authorized peers.
* **Handling peer registration and authentication.** Each peer has to be authenticated and registered in the system. The client application requests a user to log in with an Identity Provider (IDP) or a [setup key](/overview/setup-keys) so that the peer can be associated with the organization's account.
* **Receiving network updates from the Management service.**
Each peer receives initial configuration and a list of peers with corresponding public keys and IP addresses so that it can establish a peer-to-peer connection.
* **Establishing peer-to-peer Wireguard connection.** To establish a connection with a remote peer, the Client first discovers the most suitable connection candidate, or simply address (IP:port) that other peer can use to connect to it.
Then sends it to the remote peer via Signal. This message is encrypted with the peer's private key and a public key of the remote peer.
The remote peer does the same and once the peers can reach each other, they establish an encrypted Wireguard tunnel.
:::important
The **private key**, generated by the Client, **never leaves the machine**, ensuring that only the machine that owns the key can decrypt traffic addressed to it.
:::
### Signal Service
The Signal Service or simply Signal is a lightweight piece of software that helps peers to negotiate direct connections.
It does not store any data and no traffic passes through it.
The only Signal's responsibility is:
* **Serve as a notification mechanism for peers.** Before a connection can be established, peers need to find each other and exchange the most suitable connection candidates.
This is done through Signal. After a connection has been established, Signal steps out.
<p align="center">
<img src="/docs/img/architecture/signal.png" alt="signal-dia"/>
</p>
:::important
Messages that are sent over Signal are **peer-to-peer encrypted**, so Signal can't see the contents.
:::
NetBird Signal is very similar to the signaling servers used in [WebRTC](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#the_signaling_server).
It runs in the cloud NetBird-managed and can be self-hosted.
### Relay Service
The Relay service is a [TURN server](https://webrtc.org/getting-started/turn-server) in WebRTC terminology.
In fact, we use an open-source implementation called [Coturn](https://github.com/coturn/coturn).
The purpose of this service is to be a "plan B" and relay traffic between peers in case a peer-to-peer connection isn't possible.
<p align="center">
<img src="/docs/img/architecture/relay.png" alt="relay-dia"/>
</p>
:::important
Similar to Signal, traffic that flows through the Relay can't be decrypted due to the **Wireguard peer-to-peer encryption**.
:::
It runs in the cloud or can be self-hosted.
### STUN (NAT Traversal)

View File

@@ -1,5 +0,0 @@
---
sidebar_position: 5
---
# Private DNS

View File

@@ -1,26 +0,0 @@
---
sidebar_position: 2
---
# General Flow Overview
Below is a high level, step-by-step overview of the flow of communications within NetBird.
1. Administrator creates an account at [app.netbird.io](https://app.netbird.io/).
2. The system automatically generates a new network with an allocated address block <em>100.64.0.0/10</em>.
3. The system automatically generates 2 [setup keys](/overview/setup-keys) that can be used for authenticating new machines.
4. Administrator (or a user) installs NetBird client and runs ```netbird up``` command providing one of the setup keys.
5. NetBird client generates Wireguard private and public keys along with the initial configuration.
6. NetBird client sends a registration request to the NetBird Management service calling Login gRPC endpoint, providing setup key, Wireguard public key and additional information about the machine.
7. NetBird Management service checks the provided setup key, registers the machine and returns initial configuration to the NetBird client.
8. NetBird client receives initial configuration and starts the engine configuring Wireguard, connecting to the Signal Service channel, and the Management Service network updates channel.
9. NetBird client receives network map update from the Management Service that includes a list of peers/machines to connect to, and a private IP address.
10. For each peer NetBird client initiates a connection process by sending a connection offer message through the Signal service indicating its intent to connect, and a Wireguard public key.
11. If the client wasn't the initiator of the connection and receives an offer message, it checks whether the initiator is in the allowed peers list and sends an acknowledgement message through Signal.
12. Once the acknowledgement message has been received, NetBird Client (on both ends) starts a connection negotiation process using [Interactive Connectivity Establishment protocol (ICE)](https://datatracker.ietf.org/doc/html/rfc8445).
13. Once the direct connection between peers has been established successfully, NetBird Client starts proxying data to Wireguard.
14. In case a direct Wireguard connection is possible (e.g., peers are in the same network or one of the peers has a public IP), NetBird Client establishes a direct Wireguard connection avoiding proxy.
15. NetBird Client keeps a connection to the Management service receiving network updates such as new peers joining the network or peers deleted from the network.
16. When a new peer joins the network, the NetBird client receives an update and triggers connection (see #10).
17. When network administrator removes a peer, the NetBird client receives an update and removes the connection.

View File

@@ -1,4 +0,0 @@
{
"label": "Reference",
"position": 6
}

View File

@@ -1,91 +0,0 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'NetBird Documentation',
tagline: '',
url: 'https://netbird.io',
baseUrl: '/docs/',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
favicon: '/img/favicon.ico',
organizationName: 'netbirdio',
projectName: 'netbird',
presets: [
[
'@docusaurus/preset-classic',
({
docs: {
sidebarPath: require.resolve('./sidebars.js'),
routeBasePath: '/',
editUrl: 'https://github.com/netbirdio/docs/tree/main',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
googleAnalytics: {
trackingID: 'UA-188381981-2',
anonymizeIP: true,
},
})
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
navbar: {
title: '',
logo: {
alt: 'netbird Logo',
src: '/img/logo.svg',
srcDark: '/img/logo-dark.svg',
href: '/',
},
items: [
{
to: 'https://netbird.io/',
label: 'Home',
position: 'right',
target: '_self'
}, // or position: 'right'
{
type: 'doc',
docId: 'introduction',
position: 'right',
label: 'Docs',
},
{
to: 'https://netbird.io/blog/',
label: 'Blog',
position: 'right',
target: '_self'
},
{
href: 'https://github.com/netbirdio/netbird',
label: 'GitHub',
position: 'right',
},
],
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
announcementBar: {
id: 'announcementBar-docs',
content:
'⭐ If you like NetBird, give it a star on <a target="_blank" rel="noopener noreferrer" href="https://github.com/netbirdio/netbird">GitHub</a></a>',
backgroundColor: '#e3e3e3',
textColor: '#091E42',
isCloseable: true,
},
}),
};
module.exports = config;

View File

@@ -0,0 +1,23 @@
# Swagger Codegen Ignore
# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@@ -0,0 +1 @@
3.0.42

File diff suppressed because it is too large Load Diff

233
generator/api.ts Normal file
View File

@@ -0,0 +1,233 @@
import template from './templates/ApiTemplate'
import { slugify, toArrayWithKey, toTitle, writeToDisk } from './helpers'
import { OpenAPIV3, OpenAPIV2 } from 'openapi-types'
import * as fs from 'fs'
import * as ejs from 'ejs'
export default async function gen(inputFileName: string, outputDir: string) {
const specRaw = fs.readFileSync(inputFileName, 'utf8')
const spec = JSON.parse(specRaw) as any
// console.log('spec', spec)
switch (spec.openapi || spec.swagger) {
case '3.0.0':
case '3.0.1':
case '3.0.3':
await gen_v3(spec, outputDir)
break
default:
console.log('Unrecognized specification version:', spec.openapi)
break
}
}
/**
* Versioned Generator
*/
// OPENAPI-SPEC-VERSION: 3.0.0
type v3OperationWithPath = OpenAPIV3.OperationObject & {
path: string
}
export type enrichedOperation = OpenAPIV3.OperationObject & {
path: string
fullPath: string
operationId: string
}
export type schemaParameter = {
name: string
type: string
description: string
required: boolean
minimum?: number
maximum?: number
minLength?: number
maxLength?: number
enum?: string[]
}
export type component = {
example: Object
schema: Object
parameters: schemaParameter[]
}
async function gen_v3(spec: OpenAPIV3.Document, dest: string) {
const specLayout = spec.tags || []
// const operations: enrichedOperation[] = []
const tagGroups = new Map<string, enrichedOperation[]>()
const server = spec.servers.pop().url
Object.entries(spec.paths).forEach(([key, val]) => {
const fullPath = `${server}${key}`
toArrayWithKey(val!, 'operation').forEach((o) => {
const operation = o as v3OperationWithPath
const enriched = {
...operation,
path: key,
fullPath,
operationId: slugify(operation.summary!),
responseList: toArrayWithKey(operation.responses!, 'responseCode') || [],
}
let tag = operation.tags.pop()
let tagOperations = tagGroups.get(tag) ?? []
tagGroups.set(tag, tagOperations.concat(enriched))
})
})
let components = new Map<string, component>();
Object.entries(spec.components?.schemas).forEach(([key, val]) => {
const schema = val as OpenAPIV3.SchemaObject
let outputSchema = new Map<string, any>();
let outputExample = new Map<string, any>();
let parameters : schemaParameter[] = []
if(schema.allOf){
schema.allOf.forEach((item) => {
if((item as OpenAPIV3.ReferenceObject).$ref){
let component = components.get((item as OpenAPIV3.ReferenceObject).$ref.split('/').pop())
let schemaMap = new Map(Object.entries(component.schema))
let exampleMap = new Map(Object.entries(component.example))
schemaMap.forEach((value, key) => {
outputSchema.set(key, value)
})
exampleMap.forEach((value, key) => {
outputExample.set(key, value)
})
parameters = parameters.concat(component.parameters)
}
if((item as OpenAPIV3.SchemaObject).properties){
Object.entries((item as OpenAPIV3.SchemaObject).properties).forEach(([key, val]) => {
let property = val as OpenAPIV3.SchemaObject
let type, exampleValue
if (property.type === "array") {
type = new Array(resolveType(property.items, spec.components?.schemas))
exampleValue = new Array(resolveExampleValue(property.items, spec.components?.schemas))
} else {
type = resolveType(property, spec.components?.schemas)
exampleValue = resolveExampleValue(property, spec.components?.schemas)
}
outputSchema.set(key, type)
outputExample.set(key, exampleValue)
let parameter: schemaParameter = {
name: key,
type: property.type === "array" ? ((property.items as OpenAPIV3.SchemaObject).type || (property.items as OpenAPIV3.ReferenceObject).$ref.split('/').pop()) + "[]" : property.type,
description: property.description,
required: schema.required?.includes(key) || false,
minimum: property.minimum,
maximum: property.maximum,
minLength: property.minLength,
maxLength: property.maxLength,
enum: property.enum
}
parameters.push(parameter)
})
}
})
} else {
Object.entries(schema.properties).forEach(([key, val]) => {
let property = val as OpenAPIV3.SchemaObject
let type, exampleValue
if(property.type === "array"){
type = new Array(resolveType(property.items, spec.components?.schemas))
exampleValue = new Array(resolveExampleValue(property.items, spec.components?.schemas))
} else {
type = resolveType(property, spec.components?.schemas)
exampleValue = resolveExampleValue(property, spec.components?.schemas)
}
outputSchema.set(key, type)
outputExample.set(key, exampleValue)
let parameter : schemaParameter = {
name: key,
type: property.type === "array" ? ((property.items as OpenAPIV3.SchemaObject).type || (property.items as OpenAPIV3.ReferenceObject).$ref.split('/').pop()) + "[]" : property.type,
description: property.description,
required: schema.required?.includes(key) || false,
minimum: property.minimum,
maximum: property.maximum,
minLength: property.minLength,
maxLength: property.maxLength,
enum: property.enum
}
parameters.push(parameter)
})
}
let output : component = {
example: Object.fromEntries(outputExample),
schema: Object.fromEntries(outputSchema),
parameters: parameters
}
components.set(key, output)
})
tagGroups.forEach((value: enrichedOperation[], key: string) => {
const operations = value
const sections = specLayout.map((section) => {
return {
...section,
title: toTitle(section.name),
id: slugify(section.name),
operations: operations,
}
})
const content = ejs.render(template, {
info: spec.info,
tag: key,
sections,
operations,
components,
})
// Write to disk
let outputFile = dest + "/" + key.toLowerCase().replace(" ", "-") + ".mdx"
writeToDisk(outputFile, content)
// console.log('Saved: ', outputFile)
})
}
function resolveType(items: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | OpenAPIV3.NonArraySchemaObjectType, schemas) : any {
if((items as OpenAPIV3.ReferenceObject).$ref){
let ref = (items as OpenAPIV3.ReferenceObject).$ref
let map = new Map<string, any>()
Object.entries(schemas[ref.split('/').pop()].properties).forEach(([key, val]) => {
let property = val as OpenAPIV3.SchemaObject
let type
if(property.type === "array"){
type = new Array(resolveType(property.items, schemas))
} else {
type = resolveType(property, schemas)
}
map.set(key, type)
})
return Object.fromEntries(map)
}
return (items as OpenAPIV3.ArraySchemaObject).type
}
function resolveExampleValue(items: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | OpenAPIV3.NonArraySchemaObjectType, schemas) : any {
if((items as OpenAPIV3.ReferenceObject).$ref){
let ref = (items as OpenAPIV3.ReferenceObject).$ref
let map = new Map<string, any>()
Object.entries(schemas[ref.split('/').pop()].properties).forEach(([key, val]) => {
let property = val as OpenAPIV3.SchemaObject
let exampleValue
if(property.type === "array"){
exampleValue = new Array(resolveExampleValue(property.items, schemas))
} else {
exampleValue = resolveExampleValue(property, schemas)
}
map.set(key, exampleValue)
})
return Object.fromEntries(map)
}
return (items as OpenAPIV3.ArraySchemaObject).example
}

42
generator/helpers.ts Normal file
View File

@@ -0,0 +1,42 @@
import * as _ from 'lodash'
import * as fs from 'fs'
export const slugify = (text: string) => {
if (!text) return ''
return text
.toString()
.toLowerCase()
.replace(/[. )(]/g, '-') // Replace spaces and brackets -
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
// Uppercase the first letter of a string
export const toTitle = (text: string) => {
return text.charAt(0).toUpperCase() + text.slice(1)
}
/**
* writeToDisk()
*/
export const writeToDisk = (fileName: string, content: any) => {
return new Promise((resolve, reject) => {
fs.writeFile(fileName, content, (err: any) => {
if (err) return reject(err)
else return resolve(true)
})
})
}
/**
* Convert Object to Array of values
*/
export const toArrayWithKey = (obj: object, keyAs: string) =>
_.values(
_.mapValues(obj, (value: any, key: string) => {
value[keyAs] = key
return value
})
)

33
generator/index.ts Normal file
View File

@@ -0,0 +1,33 @@
import ApiGenerator from './api'
const main = (command: string[], options: any) => {
handleInput(command[0], options)
}
// Run everything
const argv = require('minimist')(process.argv.slice(2))
main(argv['_'], argv)
function handleInput(command: string, options: any) {
switch (command) {
case 'gen':
DocGenerator(options)
break
default:
console.log('Unrecognized command:', command)
break
}
}
export default async function DocGenerator({
input,
output,
}: {
input: string
output: string
}) {
await ApiGenerator(input, output)
return 'Done'
}

2219
generator/openapi.yml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,272 @@
const template = `
export const title = '<%- tag %>'
<% operations.forEach(function(operation){ %>
## <%- operation.summary %> {{ tag: '<%- operation.operation.toUpperCase() %>' , label: '<%- operation.path %>' }}
<Row>
<Col>
<%- operation.description %>
<% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'path').length > 0){ %>
#### Path Parameters
<Properties>
<% operation.parameters.filter((parameter) => parameter.in === 'path').forEach(function(parameter){ %>
<Property name="<%- parameter.name %>" type="string" required=\{true\}>
<%- parameter.description %>
</Property>
<% }); -%>
</Properties>
<% }; -%>
<% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'query').length > 0){ %>
#### Query Parameters
<Properties>
<% operation.parameters.filter((parameter) => parameter.in === 'query').forEach(function(parameter){ %>
<Property name="<%- parameter.name %>" type="<%- parameter.schema.type %>" required=\{false\}>
<%- parameter.description %>
</Property>
<% }); -%>
</Properties>
<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
#### Request-Body Parameters
<Properties>
<% components.get(operation.requestBody?.content['application/json'].schema.$ref.split('/').pop())?.parameters.forEach(function(parameter){ %>
<Property name="<%- parameter.name %>" type="<%- parameter.type %>" required=\{<%- parameter.required %>\}
<% if(parameter.enum){ %>
enumList="<%- parameter.enum %>"
<% }; -%>
<% if(parameter.minimum){ %>
min=\{<%- parameter.minimum %>\}
<% }; -%>
<% if(parameter.maximum){ %>
max=\{<%- parameter.maximum %>\}
<% }; -%>
<% if(parameter.minLength){ %>
minLen=\{<%- parameter.minLength %>\}
<% }; -%>
<% if(parameter.maxLength){ %>
maxLen=\{<%- parameter.maxLength %>\}
<% }; -%> >
<%- parameter.description %>
</Property>
<% }); -%>
</Properties>
<% }; -%>
</Col>
<Col sticky>
<CodeGroup title="Request" tag="<%- operation.operation.toUpperCase() %>" label="<%- operation.path %>">
\`\`\`bash {{ title: 'cURL' }}
<% if(true){ -%>
curl -X <%- operation.operation.toUpperCase() %> <%- operation.fullPath %> \\<% }; -%>
<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %>
-H 'Accept: application/json' \\<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
-H 'Content-Type: application/json' \\<% }; %>
-H 'Authorization: Token <TOKEN>' <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>\\\n--data-raw '<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) -%>'<% }; %>
\`\`\`
\`\`\`js
const axios = require('axios');
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%>
let data = JSON.stringify(<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>);<% }; -%>
let config = {
method: '<%- operation.operation.toLowerCase() %>',
maxBodyLength: Infinity,
url: '<%- operation.path %>',
<% if(true){%>headers: { <% }; -%>
<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %>
'Accept': 'application/json',<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
'Content-Type': 'application/json',<% }; %>
'Authorization': 'Token <TOKEN>'
<% if(true){ -%>}<% }; -%><% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%>,<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
data : data<% }; %>
};
axios(config)
.then((response) => {
console.log(JSON.stringify(response.data));
})
.catch((error) => {
console.log(error);
});
\`\`\`
\`\`\`python
import requests
import json
url = "<%- operation.fullPath %>"
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%>
payload = json.dumps(<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>)<% }; -%>
<% if(true){%>headers: { <% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
'Content-Type': 'application/json',<% }; -%>
<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %>
'Accept': 'application/json',<% }; %>
'Authorization': 'Token <TOKEN>'
}
response = requests.request("<%- operation.operation.toUpperCase() %>", url, headers=headers<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>, data=payload<% }; %>)
print(response.text)
\`\`\`
\`\`\`go
package main
import (
"fmt"
"strings"
"net/http"
"io/ioutil"
)
func main() {
url := "<%- operation.fullPath %>"
method := "<%- operation.operation.toUpperCase() %>"
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
payload := strings.NewReader(\`<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>\`)<% }; -%>
client := &http.Client {
}
req, err := http.NewRequest(method, url, <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>payload<% } else { %>nil<% }; %>)
if err != nil {
fmt.Println(err)
return
<% if(true){%>{<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
req.Header.Add("Content-Type", "application/json")<% }; -%>
<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %>
req.Header.Add("Accept", "application/json")<% }; %>
req.Header.Add("Authorization", "Token <TOKEN>")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}
\`\`\`
\`\`\`ruby
require "uri"
require "json"
require "net/http"
url = URI("<%- operation.fullPath %>")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
<% if(true){ %>request = Net::HTTP::<%- operation.operation.slice(0,1).toUpperCase() + operation.operation.slice(1).toLowerCase()%>.new(url)<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
request["Content-Type"] = "application/json"<% }; -%>
<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %>
request["Accept"] = "application/json"<% }; %>
request["Authorization"] = "Token <TOKEN>"
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
request.body = JSON.dump(<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>)<% }; -%>
response = https.request(request)
puts response.read_body
\`\`\`
\`\`\`java
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%>
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, '<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>');<% }; %>
Request request = new Request.Builder()
.url("<%- operation.fullPath %>")
<% if(true){ %>.method("<%- operation.operation.toUpperCase() %>"<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>, body<% }; %>)<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
.addHeader("Content-Type", "application/json")<% }; -%>
<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %>
.addHeader("Accept", "application/json")<% }; %>
.addHeader("Authorization: Token <TOKEN>")
.build();
Response response = client.newCall(request).execute();
\`\`\`
\`\`\`php
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => '<%- operation.fullPath %>',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
<% if(true){ %>CURLOPT_CUSTOMREQUEST => '<%- operation.operation.toUpperCase() %>',<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
CURLOPT_POSTFIELDS => '<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>',<% }; %>
<% if(true){ %>CURLOPT_HTTPHEADER => array(<% }; -%>
<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>
'Content-Type: application/json',<% }; -%>
<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %>
'Accept: application/json',<% }; %>
'Authorization: Token <TOKEN>'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
\`\`\`
</CodeGroup>
<% operation.responseList.forEach(function(response){ %>
<% if(response?.content && response?.content['application/json']){ %>
<% if(response?.content['application/json'].schema.type === 'array'){ %>
<CodeGroup title="Response">
\`\`\`json {{ title: 'Example' }}
<%- JSON.stringify(new Array(components.get(response?.content['application/json'].schema.items.$ref?.split('/').pop())?.example), null, 2) %>
\`\`\`
\`\`\`json {{ title: 'Schema' }}
<%- JSON.stringify(new Array(components.get(response?.content['application/json'].schema.items.$ref?.split('/').pop())?.schema), null, 2) %>
\`\`\`
</CodeGroup>
<% } else { %>
<CodeGroup title="Response">
\`\`\`json {{ title: 'Example' }}
<%- JSON.stringify(components.get(response?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>
\`\`\`
\`\`\`json {{ title: 'Schema' }}
<%- JSON.stringify(components.get(response?.content['application/json'].schema.$ref?.split('/').pop())?.schema, null, 2) %>
\`\`\`
</CodeGroup>
<% }; -%>
<% }; -%>
<% }); -%>
</Col>
</Row>
---
<% }); -%>
`.trim()
export default template

13
generator/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
// generator files need their own tsconfig.json because they don't like "module": "esnext" setting that nextjs requires in the main tsconfig
{
"compilerOptions": {
"incremental": true,
"noImplicitAny": false,
"baseUrl": ".",
"paths": {
"~/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

8
jsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}

19
mdx/recma.mjs Normal file
View File

@@ -0,0 +1,19 @@
import { mdxAnnotations } from 'mdx-annotations'
import recmaNextjsStaticProps from 'recma-nextjs-static-props'
function recmaRemoveNamedExports() {
return (tree) => {
tree.body = tree.body.map((node) => {
if (node.type === 'ExportNamedDeclaration') {
return node.declaration
}
return node
})
}
}
export const recmaPlugins = [
mdxAnnotations.recma,
recmaRemoveNamedExports,
recmaNextjsStaticProps,
]

128
mdx/rehype.mjs Normal file
View File

@@ -0,0 +1,128 @@
import { mdxAnnotations } from 'mdx-annotations'
import { visit } from 'unist-util-visit'
import rehypeMdxTitle from 'rehype-mdx-title'
import shiki from 'shiki'
import { toString } from 'mdast-util-to-string'
import * as acorn from 'acorn'
import { slugifyWithCounter } from '@sindresorhus/slugify'
function rehypeParseCodeBlocks() {
return (tree) => {
visit(tree, 'element', (node, _nodeIndex, parentNode) => {
if (node.tagName === 'code' && node.properties.className) {
parentNode.properties.language = node.properties.className[0]?.replace(
/^language-/,
''
)
}
})
}
}
let highlighter
function rehypeShiki() {
return async (tree) => {
highlighter =
highlighter ?? (await shiki.getHighlighter({ theme: 'css-variables' }))
visit(tree, 'element', (node) => {
if (node.tagName === 'pre' && node.children[0]?.tagName === 'code') {
let codeNode = node.children[0]
let textNode = codeNode.children[0]
node.properties.code = textNode.value
if (node.properties.language) {
let tokens = highlighter.codeToThemedTokens(
textNode.value,
node.properties.language
)
textNode.value = shiki.renderToHtml(tokens, {
elements: {
pre: ({ children }) => children,
code: ({ children }) => children,
line: ({ children }) => `<span>${children}</span>`,
},
})
}
}
})
}
}
function rehypeSlugify() {
return (tree) => {
let slugify = slugifyWithCounter()
visit(tree, 'element', (node) => {
if ( ['h2', 'h3', 'h4', 'h5', 'h6'].includes(node.tagName) && !node.properties.id) {
node.properties.id = slugify(toString(node))
}
})
}
}
function rehypeAddMDXExports(getExports) {
return (tree) => {
let exports = Object.entries(getExports(tree))
for (let [name, value] of exports) {
for (let node of tree.children) {
if (
node.type === 'mdxjsEsm' &&
new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value)
) {
return
}
}
let exportStr = `export const ${name} = ${value}`
tree.children.push({
type: 'mdxjsEsm',
value: exportStr,
data: {
estree: acorn.parse(exportStr, {
sourceType: 'module',
ecmaVersion: 'latest',
}),
},
})
}
}
}
function getSections(node) {
let sections = []
for (let child of node.children ?? []) {
if (child.type === 'element' && ['h2', 'h3', 'h4', 'h5', 'h6'].includes(child.tagName)) {
sections.push(`{
title: ${JSON.stringify(toString(child))},
id: ${JSON.stringify(child.properties.id)},
tagName: ${JSON.stringify(child.tagName)},
tag: ${JSON.stringify(child.tag)},
...${child.properties.annotation}
}`)
} else if (child.children) {
sections.push(...getSections(child))
}
}
return sections
}
export const rehypePlugins = [
mdxAnnotations.rehype,
rehypeParseCodeBlocks,
rehypeShiki,
rehypeSlugify,
rehypeMdxTitle,
[
rehypeAddMDXExports,
(tree) => ({
sections: `[${getSections(tree).join()}]`,
}),
],
]

4
mdx/remark.mjs Normal file
View File

@@ -0,0 +1,4 @@
import { mdxAnnotations } from 'mdx-annotations'
import remarkGfm from 'remark-gfm'
export const remarkPlugins = [mdxAnnotations.remark, remarkGfm]

45
next.config.mjs Normal file
View File

@@ -0,0 +1,45 @@
import nextMDX from '@next/mdx'
import { remarkPlugins } from './mdx/remark.mjs'
import { rehypePlugins } from './mdx/rehype.mjs'
import { recmaPlugins } from './mdx/recma.mjs'
import rehypeSlug from "rehype-slug";
const withMDX = nextMDX({
options: {
remarkPlugins,
// rehypeSlug,
rehypePlugins,
recmaPlugins,
providerImportSource: '@mdx-js/react',
},
})
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'],
experimental: {
scrollRestoration: true,
},
redirects: async () => {
return [
{
source: '/',
destination: '/docs/introduction',
permanent: true,
},
{
source: '/docs',
destination: '/docs/introduction',
permanent: true,
},
{
source: '/ipa',
destination: '/ipa/introduction',
permanent: true,
},
]
}
}
export default withMDX(nextConfig)

View File

@@ -3,40 +3,62 @@
"version": "1.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus serve",
"dev": "docusaurus start --host 0.0.0.0",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.20",
"@docusaurus/plugin-google-analytics": "^2.0.0-beta.21",
"@docusaurus/preset-classic": "2.0.0-beta.20",
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
"file-loader": "^6.2.0",
"prism-react-renderer": "^1.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"url-loader": "^4.1.1"
"dev": "next dev",
"build": "next build",
"gen": "swagger-codegen generate -i generator/openapi.yml -l openapi -o generator/api.json && npx ts-node generator/index.ts gen --input generator/api.json/openapi.json --output src/pages/ipa/resources",
"start": "next start",
"lint": "next lint"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
"not op_mini all",
"defaults, not ie <= 11"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"dependencies": {
"@algolia/autocomplete-core": "^1.9.2",
"@algolia/autocomplete-preset-algolia": "^1.9.2",
"@headlessui/react": "^1.7.13",
"@mdx-js/loader": "^2.1.5",
"@mdx-js/react": "^2.1.5",
"@next/mdx": "^13.4.1",
"@sindresorhus/slugify": "^2.1.1",
"@tailwindcss/typography": "^0.5.8",
"acorn": "^8.8.2",
"algoliasearch": "^4.17.0",
"autoprefixer": "^10.4.7",
"clsx": "^1.2.0",
"ejs": "^3.1.9",
"focus-visible": "^5.2.0",
"framer-motion": "10.12.9",
"lodash": "^4.17.21",
"mdast-util-to-string": "^3.2.0",
"mdx-annotations": "^0.1.1",
"next": "13.4.1",
"openapi-types": "^12.1.0",
"postcss-focus-visible": "^8.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"recma-nextjs-static-props": "^1.0.0",
"rehype-mdx-title": "^2.0.0",
"rehype-slug": "^5.1.0",
"remark-gfm": "^3.0.1",
"shiki": "^0.14.2",
"tailwindcss": "^3.3.0",
"unist-util-visit": "^4.1.2",
"zustand": "^4.3.8"
},
"devDependencies": {
"eslint": "8.40.0",
"eslint-config-next": "13.4.1",
"prettier": "^2.8.7",
"prettier-plugin-tailwindcss": "^0.2.6"
}
}

9
postcss.config.js Normal file
View File

@@ -0,0 +1,9 @@
module.exports = {
plugins: {
tailwindcss: {},
'postcss-focus-visible': {
replaceWith: '[data-focus-visible-added]',
},
autoprefixer: {},
},
}

5
prettier.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
singleQuote: true,
semi: false,
plugins: [require('prettier-plugin-tailwindcss')],
}

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 386 KiB

View File

Before

Width:  |  Height:  |  Size: 327 KiB

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 409 KiB

After

Width:  |  Height:  |  Size: 409 KiB

View File

Before

Width:  |  Height:  |  Size: 526 KiB

After

Width:  |  Height:  |  Size: 526 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 MiB

After

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 126 KiB

View File

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 149 KiB

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 163 KiB

View File

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

View File

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

View File

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 273 KiB

View File

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Some files were not shown because too many files have changed in this diff Show More