+
-_Sites page of Pangolin dashboard (dark mode) showing multiple tunnels connected to the central server._
+_Resources page of Pangolin dashboard (dark mode) showing multiple resources available to connect._
## Key Features
### Reverse Proxy Through WireGuard Tunnel
-- Expose private resources on your network **without opening ports** (firewall punching).
-- Secure and easy to configure site-to-site connectivity via a custom **user space WireGuard client**, [Newt](https://github.com/fosrl/newt).
-- Built-in support for any WireGuard client.
-- Automated **SSL certificates** (https) via [LetsEncrypt](https://letsencrypt.org/).
-- Support for HTTP/HTTPS and **raw TCP/UDP services**.
-- Load balancing.
+- Expose private resources on your network **without opening ports** (firewall punching).
+- Secure and easy to configure site-to-site connectivity via a custom **user space WireGuard client**, [Newt](https://github.com/fosrl/newt).
+- Built-in support for any WireGuard client.
+- Automated **SSL certificates** (https) via [LetsEncrypt](https://letsencrypt.org/).
+- Support for HTTP/HTTPS and **raw TCP/UDP services**.
+- Load balancing.
### Identity & Access Management
-- Centralized authentication system using platform SSO. **Users will only have to manage one login.**
-- **Define access control rules for IPs, IP ranges, and URL paths per resource.**
-- TOTP with backup codes for two-factor authentication.
-- Create organizations, each with multiple sites, users, and roles.
-- **Role-based access control** to manage resource access permissions.
-- Additional authentication options include:
- - Email whitelisting with **one-time passcodes.**
- - **Temporary, self-destructing share links.**
- - Resource specific pin codes.
- - Resource specific passwords.
+- Centralized authentication system using platform SSO. **Users will only have to manage one login.**
+- **Define access control rules for IPs, IP ranges, and URL paths per resource.**
+- TOTP with backup codes for two-factor authentication.
+- Create organizations, each with multiple sites, users, and roles.
+- **Role-based access control** to manage resource access permissions.
+- Additional authentication options include:
+ - Email whitelisting with **one-time passcodes.**
+ - **Temporary, self-destructing share links.**
+ - Resource specific pin codes.
+ - Resource specific passwords.
+- External identity provider (IdP) support with OAuth2/OIDC, such as Authentik, Keycloak, Okta, and others.
+ - Auto-provision users and roles from your IdP.
### Simple Dashboard UI
-- Manage sites, users, and roles with a clean and intuitive UI.
-- Monitor site usage and connectivity.
-- Light and dark mode options.
-- Mobile friendly.
+- Manage sites, users, and roles with a clean and intuitive UI.
+- Monitor site usage and connectivity.
+- Light and dark mode options.
+- Mobile friendly.
### Easy Deployment
-- Run on any cloud provider or on-premises.
-- **Docker Compose based setup** for simplified deployment.
-- Future-proof installation script for streamlined setup and feature additions.
-- Use any WireGuard client to connect, or use **Newt, our custom user space client** for the best experience.
+- Run on any cloud provider or on-premises.
+- **Docker Compose based setup** for simplified deployment.
+- Future-proof installation script for streamlined setup and feature additions.
+- Use any WireGuard client to connect, or use **Newt, our custom user space client** for the best experience.
+- Use the API to create custom integrations and scripts.
+ - Fine-grained access control to the API via scoped API keys.
+ - Comprehensive Swagger documentation for the API.
### Modular Design
-- Extend functionality with existing [Traefik](https://github.com/traefik/traefik) plugins, such as [CrowdSec](https://plugins.traefik.io/plugins/6335346ca4caa9ddeffda116/crowdsec-bouncer-traefik-plugin) and [Geoblock](https://github.com/PascalMinder/geoblock).
+- Extend functionality with existing [Traefik](https://github.com/traefik/traefik) plugins, such as [CrowdSec](https://plugins.traefik.io/plugins/6335346ca4caa9ddeffda116/crowdsec-bouncer-traefik-plugin) and [Geoblock](github.com/PascalMinder/geoblock).
- **Automatically install and configure Crowdsec via Pangolin's installer script.**
-- Attach as many sites to the central server as you wish.
+- Attach as many sites to the central server as you wish.
@@ -88,7 +93,7 @@ _Sites page of Pangolin dashboard (dark mode) showing multiple tunnels connected
1. **Deploy the Central Server**:
- - Deploy the Docker Compose stack onto a VPS hosted on a cloud platform like RackNerd, Amazon EC2, DigitalOcean Droplet, or similar. There are many cheap VPS hosting options available to suit your needs.
+ - Deploy the Docker Compose stack onto a VPS hosted on a cloud platform like RackNerd, Amazon EC2, DigitalOcean Droplet, or similar. There are many cheap VPS hosting options available to suit your needs.
> [!TIP]
> Many of our users have had a great experience with [RackNerd](https://my.racknerd.com/aff.php?aff=13788). Depending on promotions, you can likely get a **VPS with 1 vCPU, 1GB RAM, and ~20GB SSD for just around $12/year**. That's a great deal!
@@ -108,24 +113,24 @@ _Sites page of Pangolin dashboard (dark mode) showing multiple tunnels connected
- Add resources to the central server and configure access control rules.
- Access these resources securely from anywhere.
-**Use Case Example - Bypassing Port Restrictions in Home Lab**:
+**Use Case Example - Bypassing Port Restrictions in Home Lab**:
Imagine private sites where the ISP restricts port forwarding. By connecting these sites to Pangolin via WireGuard, you can securely expose HTTP and HTTPS resources on the private network without any networking complexity.
-**Use Case Example - IoT Networks**:
+**Use Case Example - Deploying Services For Your Business**:
+You can use Pangolin as an easy way to expose your business applications to your users behind a safe authentication portal you can integrate into your IdP solution. Expose resources on prem and on the cloud.
+
+**Use Case Example - IoT Networks**:
IoT networks are often fragmented and difficult to manage. By deploying Pangolin on a central server, you can connect all your IoT sites via Newt or another WireGuard client. This creates a simple, secure, and centralized way to access IoT resources without the need for intricate networking setups.
-
-
-
_Resources page of Pangolin dashboard (dark mode) showing HTTPS and TCP resources with access control rules._
## Similar Projects and Inspirations
-**Cloudflare Tunnels**:
- A similar approach to proxying private resources securely, but Pangolin is a self-hosted alternative, giving you full control over your infrastructure.
+**Cloudflare Tunnels**:
+ A similar approach to proxying private resources securely, but Pangolin is a self-hosted alternative, giving you full control over your infrastructure.
-**Authentik and Authelia**:
- These projects inspired Pangolin’s centralized authentication system for proxies, enabling robust user and role management.
+**Authelia**:
+ This inspired Pangolin’s centralized authentication system for proxies, enabling robust user and role management.
## Project Development / Roadmap
@@ -136,7 +141,7 @@ View the [project board](https://github.com/orgs/fosrl/projects/1) for more deta
## Licensing
-Pangolin is dual licensed under the AGPL-3 and the Fossorial Commercial license. To see our commercial offerings, please see our [website](https://fossorial.io) for details. For inquiries about commercial licensing, please contact us at [numbat@fossorial.io](mailto:numbat@fossorial.io).
+Pangolin is dual licensed under the AGPL-3 and the Fossorial Commercial license. Please see the [LICENSE](./LICENSE) file in the repository for details. For inquiries about commercial licensing, please contact us at [numbat@fossorial.io](mailto:numbat@fossorial.io).
## Contributions
diff --git a/config/config.example.yml b/config/config.example.yml
index f3ab8d6e..7b5c144d 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -18,6 +18,7 @@ server:
internal_hostname: "pangolin"
session_cookie_name: "p_session_token"
resource_access_token_param: "p_token"
+ secret: "your_secret_key_here"
resource_access_token_headers:
id: "P-Access-Token-Id"
token: "P-Access-Token"
diff --git a/docker-compose.example.yml b/docker-compose.example.yml
index ad755174..973d27fa 100644
--- a/docker-compose.example.yml
+++ b/docker-compose.example.yml
@@ -10,7 +10,7 @@ services:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "3s"
timeout: "3s"
- retries: 5
+ retries: 15
gerbil:
image: fosrl/gerbil:latest
diff --git a/esbuild.mjs b/esbuild.mjs
index 321c6288..48a2fb31 100644
--- a/esbuild.mjs
+++ b/esbuild.mjs
@@ -52,6 +52,7 @@ esbuild
bundle: true,
outfile: argv.out,
format: "esm",
+ minify: true,
banner: {
js: banner,
},
diff --git a/install/config/config.yml b/install/config/config.yml
index de406ee9..f7d4552d 100644
--- a/install/config/config.yml
+++ b/install/config/config.yml
@@ -22,6 +22,7 @@ server:
id: "P-Access-Token-Id"
token: "P-Access-Token"
resource_session_request_param: "p_session_request"
+ secret: {{.Secret}}
cors:
origins: ["https://{{.DashboardDomain}}"]
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
diff --git a/install/config/crowdsec/docker-compose.yml b/install/config/crowdsec/docker-compose.yml
index 20c69387..28470d14 100644
--- a/install/config/crowdsec/docker-compose.yml
+++ b/install/config/crowdsec/docker-compose.yml
@@ -9,6 +9,9 @@ services:
PARSERS: crowdsecurity/whitelists
ENROLL_TAGS: docker
healthcheck:
+ interval: 10s
+ retries: 15
+ timeout: 10s
test: ["CMD", "cscli", "capi", "status"]
labels:
- "traefik.enable=false" # Disable traefik for crowdsec
diff --git a/install/crowdsec.go b/install/crowdsec.go
index 9fadadc6..c17bf540 100644
--- a/install/crowdsec.go
+++ b/install/crowdsec.go
@@ -3,9 +3,12 @@ package main
import (
"bytes"
"fmt"
+ "log"
"os"
"os/exec"
"strings"
+
+ "gopkg.in/yaml.v3"
)
func installCrowdsec(config Config) error {
@@ -63,6 +66,12 @@ func installCrowdsec(config Config) error {
os.Exit(1)
}
+ // check and add the service dependency of crowdsec to traefik
+ if err := CheckAndAddCrowdsecDependency("docker-compose.yml"); err != nil {
+ fmt.Printf("Error adding crowdsec dependency to traefik: %v\n", err)
+ os.Exit(1)
+ }
+
if err := startContainers(); err != nil {
return fmt.Errorf("failed to start containers: %v", err)
}
@@ -135,3 +144,58 @@ func checkIfTextInFile(file, text string) bool {
// Check for text
return bytes.Contains(content, []byte(text))
}
+
+func CheckAndAddCrowdsecDependency(composePath string) error {
+ // Read the docker-compose.yml file
+ data, err := os.ReadFile(composePath)
+ if err != nil {
+ return fmt.Errorf("error reading compose file: %w", err)
+ }
+
+ // Parse YAML into a generic map
+ var compose map[string]interface{}
+ if err := yaml.Unmarshal(data, &compose); err != nil {
+ return fmt.Errorf("error parsing compose file: %w", err)
+ }
+
+ // Get services section
+ services, ok := compose["services"].(map[string]interface{})
+ if !ok {
+ return fmt.Errorf("services section not found or invalid")
+ }
+
+ // Get traefik service
+ traefik, ok := services["traefik"].(map[string]interface{})
+ if !ok {
+ return fmt.Errorf("traefik service not found or invalid")
+ }
+
+ // Get dependencies
+ dependsOn, ok := traefik["depends_on"].(map[string]interface{})
+ if ok {
+ // Append the new block for crowdsec
+ dependsOn["crowdsec"] = map[string]interface{}{
+ "condition": "service_healthy",
+ }
+ } else {
+ // No dependencies exist, create it
+ traefik["depends_on"] = map[string]interface{}{
+ "crowdsec": map[string]interface{}{
+ "condition": "service_healthy",
+ },
+ }
+ }
+
+ // Marshal the modified data back to YAML with indentation
+ modifiedData, err := MarshalYAMLWithIndent(compose, 2) // Set indentation to 2 spaces
+ if err != nil {
+ log.Fatalf("error marshaling YAML: %v", err)
+ }
+
+ if err := os.WriteFile(composePath, modifiedData, 0644); err != nil {
+ return fmt.Errorf("error writing updated compose file: %w", err)
+ }
+
+ fmt.Println("Added dependency of crowdsec to traefik")
+ return nil
+}
diff --git a/install/go.mod b/install/go.mod
index 536ac2dd..1d12aa12 100644
--- a/install/go.mod
+++ b/install/go.mod
@@ -3,7 +3,8 @@ module installer
go 1.23.0
require (
- golang.org/x/sys v0.29.0 // indirect
- golang.org/x/term v0.28.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
+ golang.org/x/term v0.28.0
+ gopkg.in/yaml.v3 v3.0.1
)
+
+require golang.org/x/sys v0.29.0 // indirect
diff --git a/install/go.sum b/install/go.sum
index 3316e039..169165e4 100644
--- a/install/go.sum
+++ b/install/go.sum
@@ -2,6 +2,7 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/install/main.go b/install/main.go
index 9f07bbcf..abb67acd 100644
--- a/install/main.go
+++ b/install/main.go
@@ -16,6 +16,7 @@ import (
"text/template"
"time"
"unicode"
+ "math/rand"
"golang.org/x/term"
)
@@ -50,6 +51,7 @@ type Config struct {
InstallGerbil bool
TraefikBouncerKey string
DoCrowdsecInstall bool
+ Secret string
}
func main() {
@@ -63,6 +65,7 @@ func main() {
var config Config
config.DoCrowdsecInstall = false
+ config.Secret = generateRandomSecretKey()
// check if there is already a config file
if _, err := os.Stat("config/config.yml"); err != nil {
@@ -87,7 +90,15 @@ func main() {
if isDockerInstalled() {
if readBool(reader, "Would you like to install and start the containers?", true) {
- pullAndStartContainers()
+ if err := pullContainers(); err != nil {
+ fmt.Println("Error: ", err)
+ return
+ }
+
+ if err := startContainers(); err != nil {
+ fmt.Println("Error: ", err)
+ return
+ }
}
}
} else {
@@ -427,24 +438,24 @@ func installDocker() error {
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
`, dockerArch))
case strings.Contains(osRelease, "ID=fedora"):
- installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
+ installCmd = exec.Command("bash", "-c", `
dnf -y install dnf-plugins-core &&
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo &&
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
- `))
+ `)
case strings.Contains(osRelease, "ID=opensuse") || strings.Contains(osRelease, "ID=\"opensuse-"):
installCmd = exec.Command("bash", "-c", `
zypper install -y docker docker-compose &&
systemctl enable docker
`)
case strings.Contains(osRelease, "ID=rhel") || strings.Contains(osRelease, "ID=\"rhel"):
- installCmd = exec.Command("bash", "-c", fmt.Sprintf(`
+ installCmd = exec.Command("bash", "-c", `
dnf remove -y runc &&
dnf -y install yum-utils &&
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo &&
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin &&
systemctl enable docker
- `))
+ `)
case strings.Contains(osRelease, "ID=amzn"):
installCmd = exec.Command("bash", "-c", `
yum update -y &&
@@ -468,162 +479,76 @@ func isDockerInstalled() bool {
return true
}
-func getCommandString(useNewStyle bool) string {
- if useNewStyle {
- return "'docker compose'"
- }
- return "'docker-compose'"
-}
-
-func pullAndStartContainers() error {
- fmt.Println("Starting containers...")
-
- // Check which docker compose command is available
+// executeDockerComposeCommandWithArgs executes the appropriate docker command with arguments supplied
+func executeDockerComposeCommandWithArgs(args ...string) error {
+ var cmd *exec.Cmd
var useNewStyle bool
+
+ if !isDockerInstalled() {
+ return fmt.Errorf("docker is not installed")
+ }
+
checkCmd := exec.Command("docker", "compose", "version")
if err := checkCmd.Run(); err == nil {
useNewStyle = true
} else {
- // Check if docker-compose (old style) is available
checkCmd = exec.Command("docker-compose", "version")
- if err := checkCmd.Run(); err != nil {
- return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available: %v", err)
- }
- }
-
- // Helper function to execute docker compose commands
- executeCommand := func(args ...string) error {
- var cmd *exec.Cmd
- if useNewStyle {
- cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
+ if err := checkCmd.Run(); err == nil {
+ useNewStyle = false
} else {
- cmd = exec.Command("docker-compose", args...)
+ return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available")
}
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- return cmd.Run()
+ }
+
+ if useNewStyle {
+ cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
+ } else {
+ cmd = exec.Command("docker-compose", args...)
}
- // Pull containers
- fmt.Printf("Using %s command to pull containers...\n", getCommandString(useNewStyle))
- if err := executeCommand("-f", "docker-compose.yml", "pull"); err != nil {
- return fmt.Errorf("failed to pull containers: %v", err)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+// pullContainers pulls the containers using the appropriate command.
+func pullContainers() error {
+ fmt.Println("Pulling the container images...")
+
+ if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "pull", "--policy", "always"); err != nil {
+ return fmt.Errorf("failed to pull the containers: %v", err)
}
- // Start containers
- fmt.Printf("Using %s command to start containers...\n", getCommandString(useNewStyle))
- if err := executeCommand("-f", "docker-compose.yml", "up", "-d"); err != nil {
+ return nil
+}
+
+// startContainers starts the containers using the appropriate command.
+func startContainers() error {
+ fmt.Println("Starting containers...")
+ if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "up", "-d", "--force-recreate"); err != nil {
return fmt.Errorf("failed to start containers: %v", err)
}
return nil
}
-// bring containers down
+// stopContainers stops the containers using the appropriate command.
func stopContainers() error {
fmt.Println("Stopping containers...")
-
- // Check which docker compose command is available
- var useNewStyle bool
- checkCmd := exec.Command("docker", "compose", "version")
- if err := checkCmd.Run(); err == nil {
- useNewStyle = true
- } else {
- // Check if docker-compose (old style) is available
- checkCmd = exec.Command("docker-compose", "version")
- if err := checkCmd.Run(); err != nil {
- return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available: %v", err)
- }
- }
-
- // Helper function to execute docker compose commands
- executeCommand := func(args ...string) error {
- var cmd *exec.Cmd
- if useNewStyle {
- cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
- } else {
- cmd = exec.Command("docker-compose", args...)
- }
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- return cmd.Run()
- }
-
- if err := executeCommand("-f", "docker-compose.yml", "down"); err != nil {
+
+ if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "down"); err != nil {
return fmt.Errorf("failed to stop containers: %v", err)
}
return nil
}
-// just start containers
-func startContainers() error {
- fmt.Println("Starting containers...")
-
- // Check which docker compose command is available
- var useNewStyle bool
- checkCmd := exec.Command("docker", "compose", "version")
- if err := checkCmd.Run(); err == nil {
- useNewStyle = true
- } else {
- // Check if docker-compose (old style) is available
- checkCmd = exec.Command("docker-compose", "version")
- if err := checkCmd.Run(); err != nil {
- return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available: %v", err)
- }
- }
-
- // Helper function to execute docker compose commands
- executeCommand := func(args ...string) error {
- var cmd *exec.Cmd
- if useNewStyle {
- cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
- } else {
- cmd = exec.Command("docker-compose", args...)
- }
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- return cmd.Run()
- }
-
- if err := executeCommand("-f", "docker-compose.yml", "up", "-d"); err != nil {
- return fmt.Errorf("failed to start containers: %v", err)
- }
-
- return nil
-}
-
+// restartContainer restarts a specific container using the appropriate command.
func restartContainer(container string) error {
- fmt.Printf("Restarting %s container...\n", container)
-
- // Check which docker compose command is available
- var useNewStyle bool
- checkCmd := exec.Command("docker", "compose", "version")
- if err := checkCmd.Run(); err == nil {
- useNewStyle = true
- } else {
- // Check if docker-compose (old style) is available
- checkCmd = exec.Command("docker-compose", "version")
- if err := checkCmd.Run(); err != nil {
- return fmt.Errorf("neither 'docker compose' nor 'docker-compose' command is available: %v", err)
- }
- }
-
- // Helper function to execute docker compose commands
- executeCommand := func(args ...string) error {
- var cmd *exec.Cmd
- if useNewStyle {
- cmd = exec.Command("docker", append([]string{"compose"}, args...)...)
- } else {
- cmd = exec.Command("docker-compose", args...)
- }
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- return cmd.Run()
- }
-
- if err := executeCommand("-f", "docker-compose.yml", "restart", container); err != nil {
- return fmt.Errorf("failed to restart %s container: %v", container, err)
+ fmt.Println("Restarting containers...")
+
+ if err := executeDockerComposeCommandWithArgs("-f", "docker-compose.yml", "restart", container); err != nil {
+ return fmt.Errorf("failed to stop the container \"%s\": %v", container, err)
}
return nil
@@ -681,3 +606,17 @@ func waitForContainer(containerName string) error {
return fmt.Errorf("container %s did not start within %v seconds", containerName, maxAttempts*int(retryInterval.Seconds()))
}
+
+func generateRandomSecretKey() string {
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ const length = 32
+
+ var seededRand *rand.Rand = rand.New(
+ rand.NewSource(time.Now().UnixNano()))
+
+ b := make([]byte, length)
+ for i := range b {
+ b[i] = charset[seededRand.Intn(len(charset))]
+ }
+ return string(b)
+}
\ No newline at end of file
diff --git a/internationalization/de.md b/internationalization/de.md
index 1acd5b12..c84249f7 100644
--- a/internationalization/de.md
+++ b/internationalization/de.md
@@ -1,3 +1,23 @@
+## Authentication Site
+
+| EN | DE | Notes |
+| -------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------- |
+| Powered by [Pangolin](https://github.com/fosrl/pangolin) | Bereitgestellt von [Pangolin](https://github.com/fosrl/pangolin) | |
+| Authentication Required | Authentifizierung erforderlich | |
+| Choose your preferred method to access {resource} | Wählen Sie Ihre bevorzugte Methode, um auf {resource} zuzugreifen | |
+| PIN | PIN | |
+| User | Benutzer | |
+| 6-digit PIN Code | 6-stelliger PIN-Code | pin login |
+| Login in with PIN | Mit PIN anmelden | pin login |
+| Email | E-Mail | user login |
+| Enter your email | Geben Sie Ihre E-Mail-Adresse ein | user login |
+| Password | Passwort | user login |
+| Enter your password | Geben Sie Ihr Passwort ein | user login |
+| Forgot your password? | Passwort vergessen? | user login |
+| Log in | Anmelden | user login |
+
+---
+
## Login site
| EN | DE | Notes |
diff --git a/internationalization/tr.md b/internationalization/tr.md
new file mode 100644
index 00000000..9e5bd274
--- /dev/null
+++ b/internationalization/tr.md
@@ -0,0 +1,310 @@
+## Authentication Site
+
+| EN | TR | Notes |
+| -------------------------------------------------------- | ---------------------------------------------------------------------------------- | ---------- |
+| Powered by [Pangolin](https://github.com/fosrl/pangolin) | Pangolin Tarafından Destekleniyor | |
+| Authentication Required | Kimlik Doğrulaması Gerekli | |
+| Choose your preferred method to access {resource} | {resource}'a erişmek için tercih ettiğiniz yöntemi seçin | |
+| PIN | PIN | |
+| User | Kullanıcı | |
+| 6-digit PIN Code | 6 haneli PIN Kodu | pin login |
+| Login in with PIN | PIN ile Giriş Yap | pin login |
+| Email | E-posta | user login |
+| Enter your email | E-postanızı girin | user login |
+| Password | Şifre | user login |
+| Enter your password | Şifrenizi girin | user login |
+| Forgot your password? | Şifrenizi mi unuttunuz? | user login |
+| Log in | Giriş Yap | user login |
+
+---
+
+## Login site
+
+| EN | TR | Notes |
+| --------------------- | ------------------------------------------------------ | ----------- |
+| Welcome to Pangolin | Pangolin'e Hoşgeldiniz | |
+| Log in to get started | Başlamak için giriş yapın | |
+| Email | E-posta | |
+| Enter your email | E-posta adresinizi girin | placeholder |
+| Password | Şifre | |
+| Enter your password | Şifrenizi girin | placeholder |
+| Forgot your password? | Şifrenizi mi unuttunuz? | |
+| Log in | Giriş Yap | |
+
+---
+
+# Organization site after successful login
+
+| EN | TR | Notes |
+| ----------------------------------------- | ------------------------------------------------------------------- | ----- |
+| Welcome to Pangolin | Pangolin'e Hoşgeldiniz | |
+| You're a member of {number} organization. | {number} organizasyonunun üyesiniz. | |
+
+---
+
+## Shared Header, Navbar and Footer
+
+##### Header
+
+| EN | TR | Notes |
+| ------------------- | -------------------------- | ----- |
+| Documentation | Dokümantasyon | |
+| Support | Destek | |
+| Organization {name} | Organizasyon {name} | |
+
+##### Organization selector
+
+| EN | TR | Notes |
+| ---------------- | ---------------------- | ----- |
+| Search… | Ara… | |
+| Create | Oluştur | |
+| New Organization | Yeni Organizasyon | |
+| Organizations | Organizasyonlar | |
+
+##### Navbar
+
+| EN | TR | Notes |
+| --------------- | ------------------------------- | ----- |
+| Sites | Siteler | |
+| Resources | Kaynaklar | |
+| User & Roles | Kullanıcılar ve Roller | |
+| Shareable Links | Paylaşılabilir Linkler | |
+| General | Genel | |
+
+##### Footer
+
+| EN | TR | Notes |
+| ------------------------- | ------------------------------------------------ | -------------------- |
+| Page {number} of {number} | Sayfa {number} / {number} | |
+| Rows per page | Sayfa başına satırlar | |
+| Pangolin | Pangolin | Footer'da yer alır |
+| Built by Fossorial | Fossorial tarafından oluşturuldu | Footer'da yer alır |
+| Open Source | Açık Kaynak | Footer'da yer alır |
+| Documentation | Dokümantasyon | Footer'da yer alır |
+| {version} | {version} | Footer'da yer alır |
+
+---
+
+## Main “Sites”
+
+##### “Hero” section
+
+| EN | TR | Notes |
+| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | ----- |
+| Newt (Recommended) | Newt (Tavsiye Edilen) | |
+| For the best user experience, use Newt. It uses WireGuard under the hood and allows you to address your private resources by their LAN address on your private network from within the Pangolin dashboard. | En iyi kullanıcı deneyimi için Newt'i kullanın. Newt, arka planda WireGuard kullanır ve Pangolin kontrol paneli üzerinden özel ağınızdaki kaynaklarınıza LAN adresleriyle erişmenizi sağlar. | |
+| Runs in Docker | Docker üzerinde çalışır | |
+| Runs in shell on macOS, Linux, and Windows | macOS, Linux ve Windows’ta komut satırında çalışır | |
+| Install Newt | Newt'i Yükle | |
+| Basic WireGuard+ Are you sure you want to remove the invitation for{" "} + {selectedInvitation?.email}? +
++ Once removed, this invitation will no longer be + valid. You can always re-invite the user later. +
++ To confirm, please type the email address of the + invitation below. +
++ Are you sure you want to regenerate the + invitation for {invitation?.email}? This + will revoke the previous invitation. +
++ The invitation has been regenerated. The user + must access the link below to accept the + invitation. +
+- An email has been sent to the user - with the access link below. They - must access the link to accept the - invitation. -
- )} - {!sendEmail && ( -- The user has been invited. They must - access the link below to accept the - invitation. -
- )} -- The invite will expire in{" "} - - {expiresInDays}{" "} - {expiresInDays === 1 - ? "day" - : "days"} - - . -
-