Merge branch 'main' into v1.next_canary

This commit is contained in:
Michael Quigley 2025-04-02 12:47:47 -04:00
commit 85eb3236d3
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
79 changed files with 1436 additions and 511 deletions

View File

@ -72,7 +72,7 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org' registry-url: https://registry.npmjs.org
- name: Build the zrok NodeJS-SDK - name: Build the zrok NodeJS-SDK
shell: bash shell: bash

View File

@ -63,6 +63,7 @@ jobs:
package_name: package_name:
- zrok - zrok
- zrok-share - zrok-share
- zrok-agent
arch: arch:
- deb: amd64 - deb: amd64
rpm: x86_64 rpm: x86_64

View File

@ -103,7 +103,7 @@ jobs:
else else
SEMVER_PRE=${SEMVER#*-} SEMVER_PRE=${SEMVER#*-}
fi fi
for PAX in zrok{,-share}; do for PAX in zrok{,-share,-agent}; do
_pattern="./dist/${PAX}-${SEMVER_CORE}${SEMVER_PRE:+~${SEMVER_PRE}}*.${ARCH}.rpm" _pattern="./dist/${PAX}-${SEMVER_CORE}${SEMVER_PRE:+~${SEMVER_PRE}}*.${ARCH}.rpm"
if ! compgen -G "$_pattern" > /dev/null; then if ! compgen -G "$_pattern" > /dev/null; then
echo "ERROR: No RPM files found matching pattern '${_pattern}'" >&2 echo "ERROR: No RPM files found matching pattern '${_pattern}'" >&2
@ -139,7 +139,7 @@ jobs:
else else
SEMVER_PRE=${SEMVER#*-} SEMVER_PRE=${SEMVER#*-}
fi fi
for PAX in zrok{,-share}; do for PAX in zrok{,-share,-agent}; do
_pattern="./dist/${PAX}_${SEMVER_CORE}${SEMVER_PRE:+~${SEMVER_PRE}}*_${ARCH}.deb" _pattern="./dist/${PAX}_${SEMVER_CORE}${SEMVER_PRE:+~${SEMVER_PRE}}*_${ARCH}.deb"
if ! compgen -G "$_pattern" > /dev/null; then if ! compgen -G "$_pattern" > /dev/null; then
echo "ERROR: No DEB files found matching pattern '${_pattern}'" >&2 echo "ERROR: No DEB files found matching pattern '${_pattern}'" >&2

View File

@ -23,7 +23,7 @@ nfpms:
license: Apache 2.0 license: Apache 2.0
# Build IDs for the builds you want to create NFPM packages for. # Build IDs for the builds you want to create NFPM packages for.
builds: ids:
- zrok-amd64 - zrok-amd64
# Formats to be generated. # Formats to be generated.
@ -137,3 +137,56 @@ nfpms:
- dst: /opt/openziti/etc/zrok/ - dst: /opt/openziti/etc/zrok/
src: ./etc/caddy/multiple_upstream.Caddyfile src: ./etc/caddy/multiple_upstream.Caddyfile
type: config|noreplace type: config|noreplace
- package_name: zrok-agent
id: zrok-agent
vendor: NetFoundry
homepage: https://zrok.io/
maintainer: support@zrok.io
description: |
This package provides zrok-agent.service. Enable your zrok account on this device with "zrok enable". Run
"systemctl enable --user --now zrok-agent.service" to enable the service for the current user and visit the agent
UI by running "zrok agent console".
license: Apache 2.0
# do not bundle the built binaries, only supporting files
meta: true
# Formats to be generated.
formats:
- deb
- rpm
# {{ .ConventionalFileName }} satisfies the RPM name convention.
file_name_template: "{{ .ConventionalFileName }}"
# Umask to be used on files without explicit mode set. (overridable)
umask: 0o002
# Package version within this release version.
release: 1
# Section.
section: default
# Priority.
priority: optional
# GoReleaser will automatically add the binaries here
dependencies:
- zrok
# this allows users to satisfy the requirement for jq another way, not with the package manager, e.g.
# apt install --no-recommends zrok-share
recommends: []
overrides:
# yum and dnf do not automatically install "weak deps" aka "recommends", so we need to add them as a dependency
rpm:
dependencies:
- zrok
# Contents to add to the package.
contents:
- dst: /usr/lib/systemd/user/
src: ./nfpm/zrok-agent.service

View File

@ -27,7 +27,7 @@ nfpms:
license: Apache 2.0 license: Apache 2.0
# Build IDs for the builds you want to create NFPM packages for. # Build IDs for the builds you want to create NFPM packages for.
builds: ids:
- zrok-armv8 - zrok-armv8
# Formats to be generated. # Formats to be generated.
@ -141,3 +141,56 @@ nfpms:
- dst: /opt/openziti/etc/zrok/ - dst: /opt/openziti/etc/zrok/
src: ./etc/caddy/multiple_upstream.Caddyfile src: ./etc/caddy/multiple_upstream.Caddyfile
type: config|noreplace type: config|noreplace
- package_name: zrok-agent
id: zrok-agent
vendor: NetFoundry
homepage: https://zrok.io/
maintainer: support@zrok.io
description: |
This package provides zrok-agent.service. Enable your zrok account on this device with "zrok enable". Run
"systemctl enable --user --now zrok-agent.service" to enable the service for the current user and visit the agent
UI by running "zrok agent console".
license: Apache 2.0
# do not bundle the built binaries, only supporting files
meta: true
# Formats to be generated.
formats:
- deb
- rpm
# {{ .ConventionalFileName }} satisfies the RPM name convention.
file_name_template: "{{ .ConventionalFileName }}"
# Umask to be used on files without explicit mode set. (overridable)
umask: 0o002
# Package version within this release version.
release: 1
# Section.
section: default
# Priority.
priority: optional
# GoReleaser will automatically add the binaries here
dependencies:
- zrok
# this allows users to satisfy the requirement for jq another way, not with the package manager, e.g.
# apt install --no-recommends zrok-share
recommends: []
overrides:
# yum and dnf do not automatically install "weak deps" aka "recommends", so we need to add them as a dependency
rpm:
dependencies:
- zrok
# Contents to add to the package.
contents:
- dst: /usr/lib/systemd/user/
src: ./nfpm/zrok-agent.service

View File

@ -31,7 +31,7 @@ nfpms:
license: Apache 2.0 license: Apache 2.0
# Build IDs for the builds you want to create NFPM packages for. # Build IDs for the builds you want to create NFPM packages for.
builds: ids:
- zrok-armel - zrok-armel
# Formats to be generated. # Formats to be generated.
@ -145,3 +145,56 @@ nfpms:
- dst: /opt/openziti/etc/zrok/ - dst: /opt/openziti/etc/zrok/
src: ./etc/caddy/multiple_upstream.Caddyfile src: ./etc/caddy/multiple_upstream.Caddyfile
type: config|noreplace type: config|noreplace
- package_name: zrok-agent
id: zrok-agent
vendor: NetFoundry
homepage: https://zrok.io/
maintainer: support@zrok.io
description: |
This package provides zrok-agent.service. Enable your zrok account on this device with "zrok enable". Run
"systemctl enable --user --now zrok-agent.service" to enable the service for the current user and visit the agent
UI by running "zrok agent console".
license: Apache 2.0
# do not bundle the built binaries, only supporting files
meta: true
# Formats to be generated.
formats:
- deb
- rpm
# {{ .ConventionalFileName }} satisfies the RPM name convention.
file_name_template: "{{ .ConventionalFileName }}"
# Umask to be used on files without explicit mode set. (overridable)
umask: 0o002
# Package version within this release version.
release: 1
# Section.
section: default
# Priority.
priority: optional
# GoReleaser will automatically add the binaries here
dependencies:
- zrok
# this allows users to satisfy the requirement for jq another way, not with the package manager, e.g.
# apt install --no-recommends zrok-share
recommends: []
overrides:
# yum and dnf do not automatically install "weak deps" aka "recommends", so we need to add them as a dependency
rpm:
dependencies:
- zrok
# Contents to add to the package.
contents:
- dst: /usr/lib/systemd/user/
src: ./nfpm/zrok-agent.service

View File

@ -29,7 +29,7 @@ nfpms:
license: Apache 2.0 license: Apache 2.0
# Build IDs for the builds you want to create NFPM packages for. # Build IDs for the builds you want to create NFPM packages for.
builds: ids:
- zrok-armhf - zrok-armhf
# Formats to be generated. # Formats to be generated.
@ -143,3 +143,56 @@ nfpms:
- dst: /opt/openziti/etc/zrok/ - dst: /opt/openziti/etc/zrok/
src: ./etc/caddy/multiple_upstream.Caddyfile src: ./etc/caddy/multiple_upstream.Caddyfile
type: config|noreplace type: config|noreplace
- package_name: zrok-agent
id: zrok-agent
vendor: NetFoundry
homepage: https://zrok.io/
maintainer: support@zrok.io
description: |
This package provides zrok-agent.service. Enable your zrok account on this device with "zrok enable". Run
"systemctl enable --user --now zrok-agent.service" to enable the service for the current user and visit the agent
UI by running "zrok agent console".
license: Apache 2.0
# do not bundle the built binaries, only supporting files
meta: true
# Formats to be generated.
formats:
- deb
- rpm
# {{ .ConventionalFileName }} satisfies the RPM name convention.
file_name_template: "{{ .ConventionalFileName }}"
# Umask to be used on files without explicit mode set. (overridable)
umask: 0o002
# Package version within this release version.
release: 1
# Section.
section: default
# Priority.
priority: optional
# GoReleaser will automatically add the binaries here
dependencies:
- zrok
# this allows users to satisfy the requirement for jq another way, not with the package manager, e.g.
# apt install --no-recommends zrok-share
recommends: []
overrides:
# yum and dnf do not automatically install "weak deps" aka "recommends", so we need to add them as a dependency
rpm:
dependencies:
- zrok
# Contents to add to the package.
contents:
- dst: /usr/lib/systemd/user/
src: ./nfpm/zrok-agent.service

View File

@ -1,5 +1,17 @@
# CHANGELOG # CHANGELOG
## v1.0.1
FEATURE: The zrok Agent now persists private accesses and reserved shares between executions. Any `zrok access private` instances or `zrok share reserved` instances created using the agent are now persisted to a registry stored in `${HOME}/.zrok`. When restarting the agent these accesses and reserved shares are re-created from the data in this registry (https://github.com/openziti/zrok/pull/922)
FEATURE: zrok-agent Linux package runs the agent as a user service (https://github.com/openziti/zrok/issues/883)
CHANGE: Updated the "Getting Started" guide to be slightly more streamlined and reflect the `v1.0` changes (https://github.com/openziti/zrok/issues/877)
CHANGE: let the Docker instance set the Caddy HTTPS port (https://github.com/openziti/zrok/pull/920)
CHANGE: Add Traefik option for TLS termination in the Docker instance (https://github.com/openziti/zrok/issues/808)
## v1.0.0 ## v1.0.0
MAJOR RELEASE: zrok reaches version 1.0.0! MAJOR RELEASE: zrok reaches version 1.0.0!

View File

@ -1,12 +1,20 @@
![zrok](docs/images/zrok_cover.png) ![zrok logo](docs/images/zrok_cover.png)
**Note: If you upgrade to `v1.0.0` and you receive an error message like this:**
```
[ERROR]: unable to create share (error getting zrok client: client version error accessing api endpoint 'https://api.zrok.io': [POST /clientVersionCheck] clientVersionCheck (status 404): {}: [POST /clientVersionCheck] clientVersionCheck (status 404): {})
```
Use the command `zrok rebase apiEndpoint https://api-v1.zrok.io/` to update your environment for the `v1.0.0` release.
## Your Secure Internet Sharing Perimeter
`zrok` is a next-generation, peer-to-peer sharing platform built on top of [OpenZiti](https://docs.openziti.io/docs/learn/introduction/), a programmable zero-trust network overlay. `zrok` is a _Ziti Native Application_. `zrok` is a next-generation, peer-to-peer sharing platform built on top of [OpenZiti](https://docs.openziti.io/docs/learn/introduction/), a programmable zero-trust network overlay. `zrok` is a _Ziti Native Application_.
`zrok` facilitates sharing resources both publicly and privately. Public sharing allows you to share `zrok` resources with non-`zrok` users over the public internet. Private sharing allows you to directly share your resources peer-to-peer with other `zrok` users without changing your security or firewall settings. `zrok` facilitates both public and private sharing. Public sharing allows you to share securely with non-`zrok` users over the public internet. Private sharing allows you to directly share peer-to-peer with other `zrok` users. No security or firewall changes are required for either type of sharing. No inbound connectivity is required. The OpenZiti overlay provides peer-to-peer connectivity without IP addresses, and employs end-to-end encryption for world-class security.
Like other offerings in this space, `zrok` allows users to share tunnels for HTTP, TCP and UDP network resources. `zrok` additionally allows users to easily and rapidly share files, web content, and custom resources in a peer-to-peer manner. Like other offerings in this space, `zrok` allows users to create tunnels for HTTP, TCP and UDP network resources. `zrok` additionally allows users to easily and rapidly share files, web content, and custom resources in a peer-to-peer manner.
`zrok` is an extensible platform for sharing. Initially we're targeting technical users. Super-simple sharing for end users is planned and in the backlog.
![zrok Web Console](docs/images/zrok_web_console.png) ![zrok Web Console](docs/images/zrok_web_console.png)
@ -15,7 +23,7 @@ Like other offerings in this space, `zrok` allows users to share tunnels for HTT
You can be up and sharing using the `zrok.io` service in minutes. Here is a synopsis of what's involved: You can be up and sharing using the `zrok.io` service in minutes. Here is a synopsis of what's involved:
* [Install the package or download the binary for your platform](https://docs.zrok.io/docs/guides/install/). * [Install the package or download the binary for your platform](https://docs.zrok.io/docs/guides/install/).
* `zrok invite` to create an account with the service * `zrok invite` to create an account with the service (use the [NetFoundry hosted zrok.io service](https://docs.zrok.io/docs/getting-started/))
* `zrok enable` to enable your shell environment for sharing with the service * `zrok enable` to enable your shell environment for sharing with the service
### And then... sharing... ### And then... sharing...

View File

@ -35,3 +35,43 @@ Pre-release version strings must contain exactly one hyphen, and may not contain
## Rolling Back Downstreams ## Rolling Back Downstreams
The concepts, tools, and procedures for managing existing downstream artifacts in Artifactory and Docker Hub are identical for zrok and ziti. Here's the [RELEASING.md document for ziti](https://github.com/openziti/ziti/blob/main/RELEASING.md#rolling-back-downstreams). The concepts, tools, and procedures for managing existing downstream artifacts in Artifactory and Docker Hub are identical for zrok and ziti. Here's the [RELEASING.md document for ziti](https://github.com/openziti/ziti/blob/main/RELEASING.md#rolling-back-downstreams).
## Updating the Homebrew Formula
[`zrok.rb`](https://github.com/Homebrew/homebrew-core/blob/master/Formula/z/zrok.rb) is a Ruby script in `Homebrew/homebrew-core` that defines the build procedure for the `zrok` binary. The Homebrew workflow triggered by the "released" event in GitHub sends a pull request to update the zrok formula. Usually, the only differences are the HTTP URL of the release's source code archive and it's checksum. It's also necessary to send a PR for the Ruby script when the zrok build procedure changes ([example PR](https://github.com/Homebrew/homebrew-core/pull/210917)).
```bash
# Clone the Homebrew/homebrew-core repository
brew tap --force homebrew/core
cd $(brew --repo homebrew/core)
# if already cloned then fetch
git fetch origin master
# if you're patching a PR HEAD that failed to build, then branch from that PR branch's HEAD
git fetch origin pull/<pr-number>/head # e.g. `git fetch origin pull/1234/head`
git checkout -b fix-homebrew FETCH_HEAD
# Disable API-based installation to enable local build and testing
export HOMEBREW_NO_INSTALL_FROM_API=1
# Edit ./Formula/z/zrok.rb
brew edit zrok
# Build from source
brew install --verbose --formula --build-bottle zrok
# run the test section of the formula
brew test zrok
# Audit the the formula
brew audit --strict zrok
# Check formula styles
brew style zrok
```
Finally, if correcting a failed GitHub Actions check on a PR based on Homebrew/homebrew-core master branch, then push commits as the ziti-ci user with the "gh_ci_key" SSH key to update the PR. A valid commit message is just the formula name and new version string, e.g., "zrok 1.0.0".
[Homebrew Documentation](https://docs.brew.sh/FAQ#can-i-edit-formulae-myself)

View File

@ -6,6 +6,16 @@ import (
"github.com/openziti/zrok/cmd/zrok/subordinate" "github.com/openziti/zrok/cmd/zrok/subordinate"
) )
type AccessPrivateRequest struct {
Token string `json:"token"`
BindAddress string `json:"bind_address"`
AutoMode bool `json:"auto_mode"`
AutoAddress string `json:"auto_address"`
AutoStartPort uint16 `json:"auto_start_port"`
AutoEndPort uint16 `json:"auto_end_port"`
ResponseHeaders []string `json:"response_headers"`
}
type access struct { type access struct {
frontendToken string frontendToken string
token string token string
@ -16,6 +26,8 @@ type access struct {
autoEndPort uint16 autoEndPort uint16
responseHeaders []string responseHeaders []string
request *AccessPrivateRequest
process *proctree.Child process *proctree.Child
sub *subordinate.MessageHandler sub *subordinate.MessageHandler

View File

@ -12,14 +12,14 @@ import (
"os" "os"
) )
func (i *agentGrpcImpl) AccessPrivate(_ context.Context, req *agentGrpc.AccessPrivateRequest) (*agentGrpc.AccessPrivateResponse, error) { func (a *Agent) AccessPrivate(req *AccessPrivateRequest) (frontendToken string, err error) {
root, err := environment.LoadRoot() root, err := environment.LoadRoot()
if err != nil { if err != nil {
return nil, err return "", err
} }
if !root.IsEnabled() { if !root.IsEnabled() {
return nil, errors.New("unable to load environment; did you 'zrok enable'?") return "", errors.New("unable to load environment; did you 'zrok enable'?")
} }
accCmd := []string{os.Args[0], "access", "private", "--subordinate", "-b", req.BindAddress, req.Token} accCmd := []string{os.Args[0], "access", "private", "--subordinate", "-b", req.BindAddress, req.Token}
@ -34,11 +34,12 @@ func (i *agentGrpcImpl) AccessPrivate(_ context.Context, req *agentGrpc.AccessPr
bindAddress: req.BindAddress, bindAddress: req.BindAddress,
autoMode: req.AutoMode, autoMode: req.AutoMode,
autoAddress: req.AutoAddress, autoAddress: req.AutoAddress,
autoStartPort: uint16(req.AutoStartPort), autoStartPort: req.AutoStartPort,
autoEndPort: uint16(req.AutoEndPort), autoEndPort: req.AutoEndPort,
responseHeaders: req.ResponseHeaders, responseHeaders: req.ResponseHeaders,
request: req,
sub: subordinate.NewMessageHandler(), sub: subordinate.NewMessageHandler(),
agent: i.agent, agent: a,
} }
acc.sub.MessageHandler = func(msg subordinate.Message) { acc.sub.MessageHandler = func(msg subordinate.Message) {
logrus.Info(msg) logrus.Info(msg)
@ -74,20 +75,36 @@ func (i *agentGrpcImpl) AccessPrivate(_ context.Context, req *agentGrpc.AccessPr
acc.process, err = proctree.StartChild(acc.sub.Tail, accCmd...) acc.process, err = proctree.StartChild(acc.sub.Tail, accCmd...)
if err != nil { if err != nil {
return nil, err return "", err
} }
<-acc.sub.BootComplete <-acc.sub.BootComplete
if bootErr == nil { if bootErr == nil {
go acc.monitor() go acc.monitor()
i.agent.addAccess <- acc a.addAccess <- acc
return &agentGrpc.AccessPrivateResponse{FrontendToken: acc.frontendToken}, nil return acc.frontendToken, nil
} else { } else {
if err := proctree.WaitChild(acc.process); err != nil { if err := proctree.WaitChild(acc.process); err != nil {
logrus.Errorf("error joining: %v", err) logrus.Errorf("error joining: %v", err)
} }
return nil, fmt.Errorf("unable to start access: %v", bootErr) return "", fmt.Errorf("unable to start access: %v", bootErr)
}
}
func (i *agentGrpcImpl) AccessPrivate(_ context.Context, req *agentGrpc.AccessPrivateRequest) (*agentGrpc.AccessPrivateResponse, error) {
if frontendToken, err := i.agent.AccessPrivate(&AccessPrivateRequest{
Token: req.Token,
BindAddress: req.BindAddress,
AutoMode: req.AutoMode,
AutoAddress: req.AutoAddress,
AutoStartPort: uint16(req.AutoStartPort),
AutoEndPort: uint16(req.AutoEndPort),
ResponseHeaders: req.ResponseHeaders,
}); err == nil {
return &agentGrpc.AccessPrivateResponse{FrontendToken: frontendToken}, nil
} else {
return nil, err
} }
} }

View File

@ -19,16 +19,17 @@ import (
) )
type Agent struct { type Agent struct {
cfg *AgentConfig cfg *AgentConfig
httpEndpoint string httpEndpoint string
root env_core.Root root env_core.Root
agentSocket string agentSocket string
shares map[string]*share shares map[string]*share
addShare chan *share addShare chan *share
rmShare chan *share rmShare chan *share
accesses map[string]*access accesses map[string]*access
addAccess chan *access addAccess chan *access
rmAccess chan *access rmAccess chan *access
persistRegistry bool
} }
func NewAgent(cfg *AgentConfig, root env_core.Root) (*Agent, error) { func NewAgent(cfg *AgentConfig, root env_core.Root) (*Agent, error) {
@ -67,6 +68,12 @@ func (a *Agent) Run() error {
go a.manager() go a.manager()
go a.gateway(a.cfg) go a.gateway(a.cfg)
a.persistRegistry = false
if err := a.ReloadRegistry(); err != nil {
logrus.Errorf("error reloading registry '%v'", err)
}
a.persistRegistry = true
srv := grpc.NewServer() srv := grpc.NewServer()
agentGrpc.RegisterAgentServer(srv, &agentGrpcImpl{agent: a}) agentGrpc.RegisterAgentServer(srv, &agentGrpcImpl{agent: a})
if err := srv.Serve(l); err != nil { if err := srv.Serve(l); err != nil {
@ -79,6 +86,7 @@ func (a *Agent) Run() error {
func (a *Agent) Shutdown() { func (a *Agent) Shutdown() {
logrus.Infof("stopping") logrus.Infof("stopping")
a.persistRegistry = false
if err := os.Remove(a.agentSocket); err != nil { if err := os.Remove(a.agentSocket); err != nil {
logrus.Warnf("unable to remove agent socket: %v", err) logrus.Warnf("unable to remove agent socket: %v", err)
} }
@ -96,6 +104,60 @@ func (a *Agent) Config() *AgentConfig {
return a.cfg return a.cfg
} }
func (a *Agent) ReloadRegistry() error {
registryPath, err := a.root.AgentRegistry()
if err != nil {
return err
}
registry, err := LoadRegistry(registryPath)
if err != nil {
return err
}
logrus.Infof("loaded %d reserved shares, %d accesses", len(registry.ReservedShares), len(registry.PrivateAccesses))
for _, req := range registry.ReservedShares {
if resp, err := a.ShareReserved(req); err == nil {
logrus.Infof("restarted reserved share '%v' -> '%v'", req, resp)
} else {
logrus.Errorf("error restarting reserved share '%v': %v", req, err)
}
}
for _, req := range registry.PrivateAccesses {
if resp, err := a.AccessPrivate(req); err == nil {
logrus.Infof("restarted private access '%v' -> '%v'", req, resp)
} else {
logrus.Errorf("error restarting private access '%v': %v", req, err)
}
}
logrus.Infof("reload complete")
return nil
}
func (a *Agent) SaveRegistry() error {
r := &Registry{}
for _, shr := range a.shares {
if shr.request != nil {
switch shr.request.(type) {
case *ShareReservedRequest:
logrus.Infof("persisting reserved share '%v'", shr.token)
r.ReservedShares = append(r.ReservedShares, shr.request.(*ShareReservedRequest))
}
}
}
for _, acc := range a.accesses {
if acc.request != nil {
r.PrivateAccesses = append(r.PrivateAccesses, acc.request)
}
}
registryPath, err := a.root.AgentRegistry()
if err != nil {
return err
}
if err := r.Save(registryPath); err != nil {
return err
}
return nil
}
func (a *Agent) gateway(cfg *AgentConfig) { func (a *Agent) gateway(cfg *AgentConfig) {
logrus.Info("started") logrus.Info("started")
defer logrus.Warn("exited") defer logrus.Warn("exited")
@ -132,6 +194,12 @@ func (a *Agent) manager() {
logrus.Infof("adding new share '%v'", inShare.token) logrus.Infof("adding new share '%v'", inShare.token)
a.shares[inShare.token] = inShare a.shares[inShare.token] = inShare
if a.persistRegistry {
if err := a.SaveRegistry(); err != nil {
logrus.Errorf("unable to persist registry: %v", err)
}
}
case outShare := <-a.rmShare: case outShare := <-a.rmShare:
if shr, found := a.shares[outShare.token]; found { if shr, found := a.shares[outShare.token]; found {
logrus.Infof("removing share '%v'", shr.token) logrus.Infof("removing share '%v'", shr.token)
@ -147,6 +215,13 @@ func (a *Agent) manager() {
} }
} }
delete(a.shares, shr.token) delete(a.shares, shr.token)
if a.persistRegistry {
if err := a.SaveRegistry(); err != nil {
logrus.Errorf("unable to persist registry: %v", err)
}
}
} else { } else {
logrus.Debug("skipping unidentified (orphaned) share removal") logrus.Debug("skipping unidentified (orphaned) share removal")
} }
@ -155,6 +230,12 @@ func (a *Agent) manager() {
logrus.Infof("adding new access '%v'", inAccess.frontendToken) logrus.Infof("adding new access '%v'", inAccess.frontendToken)
a.accesses[inAccess.frontendToken] = inAccess a.accesses[inAccess.frontendToken] = inAccess
if a.persistRegistry {
if err := a.SaveRegistry(); err != nil {
logrus.Errorf("unable to persist registry: %v", err)
}
}
case outAccess := <-a.rmAccess: case outAccess := <-a.rmAccess:
if acc, found := a.accesses[outAccess.frontendToken]; found { if acc, found := a.accesses[outAccess.frontendToken]; found {
logrus.Infof("removing access '%v'", acc.frontendToken) logrus.Infof("removing access '%v'", acc.frontendToken)
@ -168,6 +249,13 @@ func (a *Agent) manager() {
logrus.Errorf("error deleting access '%v': %v", acc.frontendToken, err) logrus.Errorf("error deleting access '%v': %v", acc.frontendToken, err)
} }
delete(a.accesses, acc.frontendToken) delete(a.accesses, acc.frontendToken)
if a.persistRegistry {
if err := a.SaveRegistry(); err != nil {
logrus.Errorf("unable to persist registry: %v", err)
}
}
} else { } else {
logrus.Debug("skipping unidentified (orphaned) access removal") logrus.Debug("skipping unidentified (orphaned) access removal")
} }

View File

@ -201,26 +201,26 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.26.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.25.9", "@babel/template": "^7.27.0",
"@babel/types": "^7.26.0" "@babel/types": "^7.27.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.26.2", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.26.0" "@babel/types": "^7.27.0"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@ -262,9 +262,9 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.26.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
@ -274,14 +274,14 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.25.9", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.25.9", "@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.25.9", "@babel/parser": "^7.27.0",
"@babel/types": "^7.25.9" "@babel/types": "^7.27.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -315,9 +315,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.26.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.25.9", "@babel/helper-string-parser": "^7.25.9",
@ -3873,9 +3873,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.2.0", "version": "6.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz",
"integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", "integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

39
agent/registry.go Normal file
View File

@ -0,0 +1,39 @@
package agent
import (
"encoding/json"
"fmt"
"os"
)
const RegistryV = "1"
type Registry struct {
V string `json:"v"`
ReservedShares []*ShareReservedRequest `json:"reserved_shares"`
PrivateAccesses []*AccessPrivateRequest `json:"private_accesses"`
}
func LoadRegistry(path string) (*Registry, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
r := &Registry{}
if err := json.Unmarshal(data, r); err != nil {
return nil, err
}
if r.V != RegistryV {
return nil, fmt.Errorf("invalid registry version '%v'; expected '%v", r.V, RegistryV)
}
return r, nil
}
func (r *Registry) Save(path string) error {
r.V = RegistryV
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}

View File

@ -7,13 +7,16 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func (i *agentGrpcImpl) ReleaseAccess(_ context.Context, req *agentGrpc.ReleaseAccessRequest) (*agentGrpc.ReleaseAccessResponse, error) { func (a *Agent) ReleaseAccess(frontendToken string) error {
if acc, found := i.agent.accesses[req.FrontendToken]; found { if acc, found := a.accesses[frontendToken]; found {
i.agent.rmAccess <- acc a.rmAccess <- acc
logrus.Infof("released access '%v'", acc.frontendToken) logrus.Infof("released access '%v'", acc.frontendToken)
} else { } else {
return nil, errors.Errorf("agent has no access with frontend token '%v'", req.FrontendToken) return errors.Errorf("agent has no access with frontend token '%v'", frontendToken)
} }
return nil, nil return nil
}
func (i *agentGrpcImpl) ReleaseAccess(_ context.Context, req *agentGrpc.ReleaseAccessRequest) (*agentGrpc.ReleaseAccessResponse, error) {
return nil, i.agent.ReleaseAccess(req.FrontendToken)
} }

View File

@ -7,13 +7,16 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func (i *agentGrpcImpl) ReleaseShare(_ context.Context, req *agentGrpc.ReleaseShareRequest) (*agentGrpc.ReleaseShareResponse, error) { func (a *Agent) ReleaseShare(shareToken string) error {
if shr, found := i.agent.shares[req.Token]; found { if shr, found := a.shares[shareToken]; found {
i.agent.rmShare <- shr a.rmShare <- shr
logrus.Infof("released share '%v'", shr.token) logrus.Infof("released share '%v'", shr.token)
} else { } else {
return nil, errors.Errorf("agent has no share with token '%v'", req.Token) errors.Errorf("agent has no share with token '%v'", shareToken)
} }
return nil, nil return nil
}
func (i *agentGrpcImpl) ReleaseShare(_ context.Context, req *agentGrpc.ReleaseShareRequest) (*agentGrpc.ReleaseShareResponse, error) {
return nil, i.agent.ReleaseShare(req.Token)
} }

View File

@ -9,6 +9,41 @@ import (
"time" "time"
) )
type SharePrivateRequest struct {
Target string `json:"target"`
BackendMode string `json:"backend_mode"`
Insecure bool `json:"insecure"`
Closed bool `json:"closed"`
AccessGrants []string `json:"access_grants"`
}
type SharePublicRequest struct {
Target string `json:"target"`
BasicAuth []string `json:"basic_auth"`
FrontendSelection []string `json:"frontend_selection"`
BackendMode string `json:"backend_mode"`
Insecure bool `json:"insecure"`
OauthProvider string `json:"oauth_provider"`
OauthEmailAddressPatterns []string `json:"oauth_email_address_patterns"`
OauthCheckInterval string `json:"oauth_check_interval"`
Closed bool `json:"closed"`
AccessGrants []string `json:"access_grants"`
}
type ShareReservedRequest struct {
Token string `json:"token"`
OverrideEndpoint string `json:"override_endpoint"`
Insecure bool `json:"insecure"`
}
type ShareReservedResponse struct {
Token string
BackendMode string
ShareMode string
FrontendEndpoints []string
Target string
}
type share struct { type share struct {
token string token string
frontendEndpoints []string frontendEndpoints []string
@ -25,6 +60,8 @@ type share struct {
closed bool closed bool
accessGrants []string accessGrants []string
request interface{}
process *proctree.Child process *proctree.Child
sub *subordinate.MessageHandler sub *subordinate.MessageHandler

View File

@ -13,22 +13,23 @@ import (
"os" "os"
) )
func (i *agentGrpcImpl) SharePrivate(_ context.Context, req *agentGrpc.SharePrivateRequest) (*agentGrpc.SharePrivateResponse, error) { func (a *Agent) SharePrivate(req *SharePrivateRequest) (shareToken string, err error) {
root, err := environment.LoadRoot() root, err := environment.LoadRoot()
if err != nil { if err != nil {
return nil, err return "", err
} }
if !root.IsEnabled() { if !root.IsEnabled() {
return nil, errors.New("unable to load environment; did you 'zrok enable'?") return "", errors.New("unable to load environment; did you 'zrok enable'?")
} }
shrCmd := []string{os.Args[0], "share", "private", "--subordinate", "-b", req.BackendMode} shrCmd := []string{os.Args[0], "share", "private", "--subordinate", "-b", req.BackendMode}
shr := &share{ shr := &share{
shareMode: sdk.PrivateShareMode, shareMode: sdk.PrivateShareMode,
backendMode: sdk.BackendMode(req.BackendMode), backendMode: sdk.BackendMode(req.BackendMode),
request: req,
sub: subordinate.NewMessageHandler(), sub: subordinate.NewMessageHandler(),
agent: i.agent, agent: a,
} }
shr.sub.MessageHandler = func(msg subordinate.Message) { shr.sub.MessageHandler = func(msg subordinate.Message) {
logrus.Info(msg) logrus.Info(msg)
@ -63,20 +64,34 @@ func (i *agentGrpcImpl) SharePrivate(_ context.Context, req *agentGrpc.SharePriv
shr.process, err = proctree.StartChild(shr.sub.Tail, shrCmd...) shr.process, err = proctree.StartChild(shr.sub.Tail, shrCmd...)
if err != nil { if err != nil {
return nil, err return "", err
} }
<-shr.sub.BootComplete <-shr.sub.BootComplete
if bootErr == nil { if bootErr == nil {
go shr.monitor() go shr.monitor()
i.agent.addShare <- shr a.addShare <- shr
return &agentGrpc.SharePrivateResponse{Token: shr.token}, nil return shr.token, nil
} else { } else {
if err := proctree.WaitChild(shr.process); err != nil { if err := proctree.WaitChild(shr.process); err != nil {
logrus.Errorf("error joining: %v", err) logrus.Errorf("error joining: %v", err)
} }
return nil, fmt.Errorf("unable to start share: %v", bootErr) return "", fmt.Errorf("unable to start share: %v", bootErr)
}
}
func (i *agentGrpcImpl) SharePrivate(_ context.Context, req *agentGrpc.SharePrivateRequest) (*agentGrpc.SharePrivateResponse, error) {
if shareToken, err := i.agent.SharePrivate(&SharePrivateRequest{
Target: req.Target,
BackendMode: req.BackendMode,
Insecure: req.Insecure,
Closed: req.Closed,
AccessGrants: req.AccessGrants,
}); err == nil {
return &agentGrpc.SharePrivateResponse{Token: shareToken}, nil
} else {
return nil, err
} }
} }

View File

@ -13,22 +13,23 @@ import (
"os" "os"
) )
func (i *agentGrpcImpl) SharePublic(_ context.Context, req *agentGrpc.SharePublicRequest) (*agentGrpc.SharePublicResponse, error) { func (a *Agent) SharePublic(req *SharePublicRequest) (shareToken string, frontendEndpoint []string, err error) {
root, err := environment.LoadRoot() root, err := environment.LoadRoot()
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
if !root.IsEnabled() { if !root.IsEnabled() {
return nil, errors.New("unable to load environment; did you 'zrok enable'?") return "", nil, errors.New("unable to load environment; did you 'zrok enable'?")
} }
shrCmd := []string{os.Args[0], "share", "public", "--subordinate", "-b", req.BackendMode} shrCmd := []string{os.Args[0], "share", "public", "--subordinate", "-b", req.BackendMode}
shr := &share{ shr := &share{
shareMode: sdk.PublicShareMode, shareMode: sdk.PublicShareMode,
backendMode: sdk.BackendMode(req.BackendMode), backendMode: sdk.BackendMode(req.BackendMode),
request: req,
sub: subordinate.NewMessageHandler(), sub: subordinate.NewMessageHandler(),
agent: i.agent, agent: a,
} }
shr.sub.MessageHandler = func(msg subordinate.Message) { shr.sub.MessageHandler = func(msg subordinate.Message) {
logrus.Info(msg) logrus.Info(msg)
@ -87,23 +88,39 @@ func (i *agentGrpcImpl) SharePublic(_ context.Context, req *agentGrpc.SharePubli
shr.process, err = proctree.StartChild(shr.sub.Tail, shrCmd...) shr.process, err = proctree.StartChild(shr.sub.Tail, shrCmd...)
if err != nil { if err != nil {
return nil, err return "", nil, err
} }
<-shr.sub.BootComplete <-shr.sub.BootComplete
if bootErr == nil { if bootErr == nil {
go shr.monitor() go shr.monitor()
i.agent.addShare <- shr a.addShare <- shr
return &agentGrpc.SharePublicResponse{ return shr.token, shr.frontendEndpoints, nil
Token: shr.token,
FrontendEndpoints: shr.frontendEndpoints,
}, nil
} else { } else {
if err := proctree.WaitChild(shr.process); err != nil { if err := proctree.WaitChild(shr.process); err != nil {
logrus.Errorf("error joining: %v", err) logrus.Errorf("error joining: %v", err)
} }
return nil, fmt.Errorf("unable to start share: %v", bootErr) return "", nil, fmt.Errorf("unable to start share: %v", bootErr)
}
}
func (i *agentGrpcImpl) SharePublic(_ context.Context, req *agentGrpc.SharePublicRequest) (*agentGrpc.SharePublicResponse, error) {
if shareToken, frontendEndpoints, err := i.agent.SharePublic(&SharePublicRequest{
Target: req.Target,
BasicAuth: req.BasicAuth,
FrontendSelection: req.FrontendSelection,
BackendMode: req.BackendMode,
Insecure: req.Insecure,
OauthProvider: req.OauthProvider,
OauthEmailAddressPatterns: req.OauthEmailAddressPatterns,
OauthCheckInterval: req.OauthCheckInterval,
Closed: req.Closed,
AccessGrants: req.AccessGrants,
}); err == nil {
return &agentGrpc.SharePublicResponse{Token: shareToken, FrontendEndpoints: frontendEndpoints}, nil
} else {
return nil, err
} }
} }

View File

@ -12,7 +12,7 @@ import (
"os" "os"
) )
func (i *agentGrpcImpl) ShareReserved(_ context.Context, req *agentGrpc.ShareReservedRequest) (*agentGrpc.ShareReservedResponse, error) { func (a *Agent) ShareReserved(req *ShareReservedRequest) (*ShareReservedResponse, error) {
root, err := environment.LoadRoot() root, err := environment.LoadRoot()
if err != nil { if err != nil {
return nil, err return nil, err
@ -25,8 +25,9 @@ func (i *agentGrpcImpl) ShareReserved(_ context.Context, req *agentGrpc.ShareRes
shrCmd := []string{os.Args[0], "share", "reserved", "--subordinate"} shrCmd := []string{os.Args[0], "share", "reserved", "--subordinate"}
shr := &share{ shr := &share{
reserved: true, reserved: true,
request: req,
sub: subordinate.NewMessageHandler(), sub: subordinate.NewMessageHandler(),
agent: i.agent, agent: a,
} }
shr.sub.MessageHandler = func(msg subordinate.Message) { shr.sub.MessageHandler = func(msg subordinate.Message) {
logrus.Info(msg) logrus.Info(msg)
@ -60,8 +61,8 @@ func (i *agentGrpcImpl) ShareReserved(_ context.Context, req *agentGrpc.ShareRes
if bootErr == nil { if bootErr == nil {
go shr.monitor() go shr.monitor()
i.agent.addShare <- shr a.addShare <- shr
return &agentGrpc.ShareReservedResponse{ return &ShareReservedResponse{
Token: shr.token, Token: shr.token,
BackendMode: string(shr.backendMode), BackendMode: string(shr.backendMode),
ShareMode: string(shr.shareMode), ShareMode: string(shr.shareMode),
@ -76,3 +77,21 @@ func (i *agentGrpcImpl) ShareReserved(_ context.Context, req *agentGrpc.ShareRes
return nil, fmt.Errorf("unable to start share: %v", bootErr) return nil, fmt.Errorf("unable to start share: %v", bootErr)
} }
} }
func (i *agentGrpcImpl) ShareReserved(_ context.Context, req *agentGrpc.ShareReservedRequest) (*agentGrpc.ShareReservedResponse, error) {
if resp, err := i.agent.ShareReserved(&ShareReservedRequest{
Token: req.Token,
OverrideEndpoint: req.OverrideEndpoint,
Insecure: req.Insecure,
}); err == nil {
return &agentGrpc.ShareReservedResponse{
Token: resp.Token,
BackendMode: resp.BackendMode,
ShareMode: resp.ShareMode,
FrontendEndpoints: resp.FrontendEndpoints,
Target: resp.Target,
}, nil
} else {
return nil, err
}
}

View File

@ -25,8 +25,6 @@ func init() {
adminCmd.AddCommand(adminListCmd) adminCmd.AddCommand(adminListCmd)
adminCmd.AddCommand(adminUpdateCmd) adminCmd.AddCommand(adminUpdateCmd)
rootCmd.AddCommand(agentCmd) rootCmd.AddCommand(agentCmd)
agentCmd.AddCommand(agentAccessCmd)
agentCmd.AddCommand(agentShareCmd)
agentCmd.AddCommand(agentReleaseCmd) agentCmd.AddCommand(agentReleaseCmd)
rootCmd.AddCommand(adminCmd) rootCmd.AddCommand(adminCmd)
rootCmd.AddCommand(configCmd) rootCmd.AddCommand(configCmd)
@ -84,22 +82,12 @@ var adminUpdateCmd = &cobra.Command{
Short: "Update global resources", Short: "Update global resources",
} }
var agentAccessCmd = &cobra.Command{
Use: "access",
Short: "zrok Agent access commands",
}
var agentCmd = &cobra.Command{ var agentCmd = &cobra.Command{
Use: "agent", Use: "agent",
Short: "zrok Agent commands", Short: "zrok Agent commands",
Aliases: []string{"daemon"}, Aliases: []string{"daemon"},
} }
var agentShareCmd = &cobra.Command{
Use: "share",
Short: "zrok Agent sharing commands",
}
var agentReleaseCmd = &cobra.Command{ var agentReleaseCmd = &cobra.Command{
Use: "release", Use: "release",
Short: "zrok Agent release commands", Short: "zrok Agent release commands",

View File

@ -8,7 +8,7 @@
# redir https://{host}{uri} permanent # redir https://{host}{uri} permanent
# } # }
*.{$ZROK_DNS_ZONE} { *.{$ZROK_DNS_ZONE}:{$CADDY_HTTPS_PORT} {
tls { tls {
dns {$CADDY_DNS_PLUGIN} {$CADDY_DNS_PLUGIN_TOKEN} dns {$CADDY_DNS_PLUGIN} {$CADDY_DNS_PLUGIN_TOKEN}
propagation_timeout 60m propagation_timeout 60m

View File

@ -1,27 +1,18 @@
## Docker Instance ## Docker Instance
<iframe width="100%" height="315" src="https://www.youtube.com/embed/70zJ_h4uiD8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe> <iframe width="100%" height="315" src="https://www.youtube.com/embed/70zJ_h4uiD8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
This Docker Compose project creates a zrok instance and includes a ziti controller and router. An optional Caddy container is included to provide HTTPS and reverse proxy services for the zrok API and public shares. This Docker Compose project creates a zrok instance supported by a OpenZiti controller and router. It supports flexible deployment configurations:
### DNS Configuration 1. **Basic Configuration**: Services exposed on localhost only (no TLS)
2. **With Caddy**: Services published using Caddy (TLS)
1. A wildcard record exists for the IP address where the zrok instance will run, e.g. if your DNS zone is `share.example.com`, then your wildcard record is `*.share.example.com`. 3. **With Traefik**: Services published using Traefik (TLS)
#### Additional DNS Configuration for Caddy TLS
The included Caddy container can automatically manage a wildcard certificate for your zrok instance. You can enable Caddy in this compose project by renaming `compose.caddy.yml` as `compose.override.yml`.
1. Ensure A Caddy DNS plugin is available for your DNS provider (see [github.com/caddy-dns](https://github.com/orgs/caddy-dns/repositories?type=all&q=sort%3Aname-asc)).
1. Designate A DNS zone for zrok, e.g. `example.com` or `share.example.com` and create the zone on your DNS provider's platform.
1. Created an API token in your DNS provider that has permission to manage zrok's DNS zone.
### Create the Docker Compose Project ### Create the Docker Compose Project
Create a working directory on your Docker host and save these Docker Compose project files. Create a working directory on your Docker host and save these Docker Compose project files.
#### Shortcut Option #### YOLO
1. Run this script to download the files in the current directory. 1. Run this script to download the files in the current directory.
@ -35,7 +26,7 @@ Create a working directory on your Docker host and save these Docker Compose pro
curl https://get.openziti.io/zrok-instance/fetch.bash | bash -s /path/to/compose/project/dir curl https://get.openziti.io/zrok-instance/fetch.bash | bash -s /path/to/compose/project/dir
``` ```
#### Manual Option #### I'll Do it Myself
1. Get the zrok repo ZIP file. 1. Get the zrok repo ZIP file.
@ -49,81 +40,117 @@ Create a working directory on your Docker host and save these Docker Compose pro
unzip -j -d . main.zip '*/docker/compose/zrok-instance/*' unzip -j -d . main.zip '*/docker/compose/zrok-instance/*'
``` ```
### Configure the Docker Compose Project Environment ### Basic Configuration (No TLS, Localhost Only)
Create an `.env` file in the working directory. This is the simplest way to get started with zrok, exposing services on localhost only, without TLS.
```bash title=".env required" #### DNS Configuration (Optional for localhost-only setup)
1. If you plan to use this beyond localhost, set up a wildcard record for the IP address where the zrok instance will run
(e.g., if your DNS zone is `share.example.com`, then your wildcard record is `*.share.example.com`).
#### Configure the Docker Compose Project Environment
Create an `.env` file in the working directory with the minimal required configuration:
```bash title=".env minimal configuration"
# Required settings
ZROK_DNS_ZONE=share.example.com ZROK_DNS_ZONE=share.example.com
ZROK_USER_EMAIL=me@example.com ZROK_USER_EMAIL=me@example.com
ZROK_USER_PWD=zrokuserpw ZROK_USER_PWD=zrokuserpw
ZITI_PWD=zitiadminpw ZITI_PWD=zitiadminpw
ZROK_ADMIN_TOKEN=zroktoken ZROK_ADMIN_TOKEN=zroktoken
```
```bash title=".env options" # Expose services only on localhost (default)
# Caddy TLS option: rename compose.caddy.yml to compose.override.yml and set these vars; allow 80,443 in firewall ZROK_INSECURE_INTERFACE=127.0.0.1
# # Service ports
## set these in .env for providers other than Route53
#
# plugin name for your DNS provider
CADDY_DNS_PLUGIN=cloudflare
# API token from your DNS provider
CADDY_DNS_PLUGIN_TOKEN=abcd1234
# use the staging API until you're sure everything is working to avoid hitting the rate limit
CADDY_ACME_API=https://acme-staging-v02.api.letsencrypt.org/directory
#
## set these in .env for Route53
#
# AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
# AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
# AWS_REGION: ${AWS_REGION}
# AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN} # if temporary credential, e.g., from STS
#
## if not using Caddy for TLS, uncomment to publish the insecure ports to the internet
#
#ZROK_INSECURE_INTERFACE=0.0.0.0
# these insecure ports must be proxied with TLS for security
ZROK_CTRL_PORT=18080 ZROK_CTRL_PORT=18080
ZROK_FRONTEND_PORT=8080 ZROK_FRONTEND_PORT=8080
ZROK_OAUTH_PORT=8081 ZROK_OAUTH_PORT=8081
# these secure ziti ports must be published to the internet
ZITI_CTRL_ADVERTISED_PORT=80 ZITI_CTRL_ADVERTISED_PORT=80
ZITI_ROUTER_PORT=3022 ZITI_ROUTER_PORT=3022
# optionally configure oauth for public shares
#ZROK_OAUTH_HASH_KEY=oauthhashkeysecret
#ZROK_OAUTH_GITHUB_CLIENT_ID=abcd1234
#ZROK_OAUTH_GITHUB_CLIENT_SECRET=abcd1234
#ZROK_OAUTH_GOOGLE_CLIENT_ID=abcd1234
#ZROK_OAUTH_GOOGLE_CLIENT_SECRET=abcd1234
# zrok version, e.g., 1.0.0
ZROK_CLI_TAG=latest
# ziti version, e.g., 1.0.0
ZITI_CLI_TAG=latest
``` ```
### Start the Docker Compose Project #### Start the Docker Compose Project
1. Start the zrok instance. Start the zrok instance:
The container images for zrok (including caddy) are built in this step. This provides a simple configuration to get started. You can modify the templates named like `*.envsubst` or mount a customized configuration file to mask the one that was built in. ```bash
docker compose up --build --detach
```
```bash ### Expanded Configuration with TLS (Caddy or Traefik)
docker compose up --build --detach
``` For production deployments, you should use TLS. You can choose between Caddy or Traefik for TLS termination and reverse proxy to the zrok services. The ziti services are always published directly, not proxied, and they bring their own TLS.
#### DNS Configuration for TLS
1. Ensure a wildcard record exists for the IP address where the zrok instance will run
(e.g., if your DNS zone is `share.example.com`, then your wildcard record is `*.share.example.com`).
2. Choose a DNS provider that supports automatic DNS challenge for obtaining wildcard certificates and for which a plugin is available in Caddy or Traefik.
#### Configure the Docker Compose File
Add this setting to your `.env` file to select which TLS provider to use:
```bash
# Use one of the following:
COMPOSE_FILE=compose.yml:compose.caddy.yml # For Caddy
# OR
COMPOSE_FILE=compose.yml:compose.traefik.yml # For Traefik
```
#### Caddy Configuration
If using Caddy, add these settings to your `.env` file:
```bash title=".env for Caddy"
# Caddy TLS configuration
CADDY_DNS_PLUGIN=cloudflare # Plugin name for your DNS provider (see github.com/caddy-dns)
CADDY_DNS_PLUGIN_TOKEN=abcd1234 # API token from your DNS provider
CADDY_ACME_API=https://acme-v02.api.letsencrypt.org/directory # ACME API endpoint
CADDY_HTTPS_PORT=443 # HTTPS port (optional, defaults to 443)
CADDY_INTERFACE=0.0.0.0 # Interface to bind to (optional, defaults to all interfaces)
# For AWS Route53, uncomment and set these instead of CADDY_DNS_PLUGIN_TOKEN:
# AWS_ACCESS_KEY_ID=your-access-key
# AWS_SECRET_ACCESS_KEY=your-secret-key
# AWS_REGION=your-region
# AWS_SESSION_TOKEN=your-session-token # Only if using temporary credentials
```
#### Traefik Configuration
If using Traefik, add these settings to your `.env` file:
```bash title=".env for Traefik"
# Traefik TLS configuration
TRAEFIK_DNS_PROVIDER=digitalocean # DNS provider for Traefik
TRAEFIK_DNS_PROVIDER_TOKEN=abcd1234 # API token from your DNS provider
TRAEFIK_ACME_API=https://acme-v02.api.letsencrypt.org/directory # ACME API endpoint
TRAEFIK_HTTPS_PORT=443 # HTTPS port (optional, defaults to 443)
TRAEFIK_INTERFACE=0.0.0.0 # Interface to bind to (optional, defaults to all interfaces)
# For AWS Route53, uncomment and set these instead of TRAEFIK_DNS_PROVIDER_TOKEN:
# AWS_ACCESS_KEY_ID=your-access-key
# AWS_SECRET_ACCESS_KEY=your-secret-key
# AWS_REGION=your-region
# AWS_SESSION_TOKEN=your-session-token # Only if using temporary credentials
```
#### Start the Docker Compose Project
Start the zrok instance with TLS support:
```bash
docker compose up --build --detach
```
### Set up a User Account ### Set up a User Account
This step creates a user account. You will log in to the zrok web console with the account password created in this step. The ZROK_USER_EMAIL and ZROK_USER_PWD variables are set in the `.env` file. You can create more user accounts the same way by substituting a different email and password. This step creates a user account. You will log in to the zrok web console with the account password created in this step. The ZROK_USER_EMAIL and ZROK_USER_PWD variables are set in the `.env` file.
```bash title="Create the first user account" ```bash title="Create the first user account"
docker compose exec zrok-controller bash -xc 'zrok admin create account ${ZROK_USER_EMAIL} ${ZROK_USER_PWD}' docker compose exec zrok-controller bash -xc 'zrok admin create account ${ZROK_USER_EMAIL} ${ZROK_USER_PWD}'
@ -148,125 +175,74 @@ You must enable each device environment with the account token obtained when the
Follow [the getting started guide](/docs/getting-started#installing-the-zrok-command) to install the zrok CLI on some device and enable a zrok environment. Follow [the getting started guide](/docs/getting-started#installing-the-zrok-command) to install the zrok CLI on some device and enable a zrok environment.
1. Configure the environment with the zrok API. Substitute the API endpoint with the one you're using, e.g. `https://zrok.${ZROK_DNS_ZONE}`. 1. Configure the environment with the zrok API endpoint:
```bash ```bash
zrok config set apiEndpoint https://zrok.share.example.com # If using TLS (Caddy or Traefik)
``` zrok config set apiEndpoint https://zrok.share.example.com
or, if not using Caddy for TLS: # If using basic configuration (localhost, no TLS)
zrok config set apiEndpoint http://localhost:18080
```
```bash 2. Enable an environment on this device with the account token from the previous step.
zrok config set apiEndpoint http://zrok.share.example.com:18080
```
1. Enable an environment on this device with the account token from the previous step. ```bash
zrok enable heMqncCyxZcx
```bash ```
zrok enable heMqncCyxZcx
```
### Firewall Configuration ### Firewall Configuration
The `ziti-quickstart` and `caddy` containers publish ports to all devices that use zrok shares. The `zrok-controller` and `zrok-frontend` containers expose ports only to the `caddy` container and the Docker host's loopback interface. - `443/tcp` - HTTPS for all services (Caddy or Traefik)
- `80/tcp` - ziti ctrl plane
- `3022/tcp` - ziti data plane
#### Required ### Additional Configuration Options
1. `443/tcp` - reverse proxy handles HTTPS requests for zrok API, OAuth, and public shares (published by container `caddy`) You can add these additional settings to your `.env` file for more customization:
1. `80/tcp` - ziti ctrl plane (published by container `ziti-quickstart`)
1. `3022/tcp` - ziti data plane (published by container `ziti-quickstart`)
<!-- 1. 443/udp used by Caddy for HTTP/3 QUIC protocol (published by container `caddy`) --> ```bash
# OAuth configuration for public shares
See "My internet connection can only send traffic to common ports" below about changing the required ports. ZROK_OAUTH_HASH_KEY=oauthhashkeysecret
ZROK_OAUTH_GITHUB_CLIENT_ID=abcd1234
ZROK_OAUTH_GITHUB_CLIENT_SECRET=abcd1234
ZROK_OAUTH_GOOGLE_CLIENT_ID=abcd1234
ZROK_OAUTH_GOOGLE_CLIENT_SECRET=abcd1234
```
### Troubleshooting ### Troubleshooting
1. Check the ziti and zrok logs. 1. Check the service logs:
You can substitute the service container name of each to check their logs individually: `ziti-quickstart`, `zrok-controller`, `zrok-frontend`. ```bash
# View logs for a specific service
docker compose logs zrok-controller
docker compose logs zrok-frontend
docker compose logs ziti-quickstart
```bash # View logs for Caddy (if using)
docker compose logs zrok-controller docker compose logs caddy
```
1. Check the Caddy logs. # View logs for Traefik (if using)
docker compose logs traefik
```
It can take a few minutes for Caddy to obtain the wildcard certificate. You can check the logs to see if there were any errors completing the DNS challenge which involves using the Caddy DNS plugin to create a TXT record in your DNS zone. This leverages the API token you provided in the `.env` file, which must have permission to create DNS records in the zrok DNS zone. 2. Validate TLS configuration:
```bash ```bash
docker compose logs caddy # For Caddy
``` docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
1. Caddy keeps failing to obtain a wildcard certificate because it timed out waiting for DNS. # For Traefik
docker compose exec traefik traefik healthcheck
```
Symptom: the Caddy log contains "timed out waiting for record to fully propagate." This means that Caddy added a DNS record with your DNS provider's API to prove to the CA it controls the zrok DNS zone, but it wasn't able to verify the record was created successfully with a DNS query. 3. Check certificate status:
Solutions: ```bash
# For Caddy
docker compose exec caddy curl -s "http://localhost:2019/certificates"
- Add `propagation_delay` in your `Caddyfile` to delay the first DNS verification query. This avoids caching a verification query failure by waiting a few minutes for the record to become available so the verification query will succeed on the first attempt. Caddy will be unable to verify the DNS record if the failure remains in the cache too long. # For Traefik - view the ACME certificate file directly
- If the prior solution fails, you can override the default resolves/nameservers with `resolvers`, a space-separated list of DNS servers. This gives you more control over if and where the verification query result is cached. docker compose exec traefik cat /etc/traefik/acme/acme.json | grep -A 5 "Certificates"
```
```
tls {
dns {CADDY_DNS_PLUGIN} {CADDY_DNS_PLUGIN_TOKEN}
propagation_timeout 60m # default 2m
propagation_delay 5m # default 0m
}
```
1. `zrok enable` fails certificate verification: ensure you are not using the staging API for Let's Encrypt.
If you are using the staging API, you will see an error about the API certificate when you use the zrok CLI. You can switch to the production API by removing the overriding assignment of the `CADDY_ACME_API` variable.
```buttonless title="Example output"
there was a problem enabling your environment!
you are trying to use the zrok service at: https://zrok.share.example.com
you can change your zrok service endpoint using this command:
$ zrok config set apiEndpoint <newEndpoint>
(where newEndpoint is something like: https://some.zrok.io)
[ERROR]: error creating service client (error getting version from api endpoint 'https://zrok.share.example.com': Get "https://zrok.share.example.com/api/v1/version": tls: failed to verify certificate: x509: certificate signed by unknown authority: Get "https://zrok.share.example.com/api/v1/version": tls: failed to verify certificate: x509: certificate signed by unknown authority)
```
1. Validate the Caddyfile.
```bash
docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
```
1. Verify the correct DNS provider module was built-in to Caddy.
```bash
docker compose exec caddy caddy list-modules | grep dns.providers
```
```buttonless title="Example output"
dns.providers.cloudflare
```
1. Use the Caddy admin API.
You can use the Caddy admin API to check the status of the Caddy instance. The admin API is available on port `2019/tcp` inside the Docker Compose project. You can modify `compose.override.yml` to publish the port if you want to access the admin API from the Docker host or elsewhere.
```bash
docker compose exec caddy curl http://localhost:2019/config/ | jq
```
1. My DNS provider credential is composed of several values, not a single API token.
As long as your DNS provider is supported by Caddy then it will work. Here's a checklist for DNS providers like Route53 with credentials expressed as multiple values, e.g., `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`.
1. Define env vars in `.env` file.
1. Declare env vars in `compose.override.yml` file on `caddy`'s `environment`.
1. Modify `Caddyfile` according to the DNS plugin author's instructions ([link to Route53 README](https://github.com/caddy-dns/route53)). This means modifying the `Caddyfile` to reference the env vars. The provided file `route53.Caddyfile` serves as an example.
1. My internet connection can only send traffic to common ports like 80, 443, and 3389.
You can change the required ports in the `.env` file. Caddy will still use port 443 for zrok shares and API if you renamed `compose.caddy.yml` as `compose.override.yml` to enable Caddy.
```bash title=".env"
ZITI_CTRL_ADVERTISED_PORT=80
ZITI_ROUTER_PORT=3389
```

View File

@ -87,7 +87,7 @@ until [[ -n "${ZITI_PUBLIC_ID}" ]]; do
done done
echo "DEBUG: 'public' ZITI_PUBLIC_ID=$ZITI_PUBLIC_ID" echo "DEBUG: 'public' ZITI_PUBLIC_ID=$ZITI_PUBLIC_ID"
until curl -sSf "${ZROK_API_ENDPOINT}/api/v1/version"; do until curl -sSf "${ZROK_API_ENDPOINT}" &>/dev/null; do
echo "DEBUG: waiting for zrok controller API version endpoint to respond" echo "DEBUG: waiting for zrok controller API version endpoint to respond"
sleep 3 sleep 3
done done

View File

@ -8,6 +8,7 @@ services:
CADDY_DNS_PLUGIN: ${CADDY_DNS_PLUGIN} # e.g., "digitalocean" (see github.com/caddy-dns) CADDY_DNS_PLUGIN: ${CADDY_DNS_PLUGIN} # e.g., "digitalocean" (see github.com/caddy-dns)
restart: unless-stopped restart: unless-stopped
environment: environment:
CADDY_HTTPS_PORT: ${CADDY_HTTPS_PORT:-443}
# #
## set these in .env for providers other than Route53 ## set these in .env for providers other than Route53
# #
@ -31,12 +32,12 @@ services:
ZROK_OAUTH_PORT: ${ZROK_OAUTH_PORT:-8081} ZROK_OAUTH_PORT: ${ZROK_OAUTH_PORT:-8081}
expose: expose:
# - 80/tcp # - 80/tcp
- 443/tcp - ${CADDY_HTTPS_PORT:-443}/tcp
- 443/udp # Caddy's HTTP/3 (QUIC) (not published) - ${CADDY_HTTPS_PORT:-443}/udp # Caddy's HTTP/3 (QUIC) (not published)
- 2019/tcp # Caddy's admin API (not published) - 2019/tcp # Caddy's admin API (not published)
ports: ports:
# - ${CADDY_INTERFACE:-0.0.0.0}:80:80 # - ${CADDY_INTERFACE:-0.0.0.0}:80:80 # port occupied by ziti
- ${CADDY_INTERFACE:-0.0.0.0}:443:443 - ${CADDY_INTERFACE:-0.0.0.0}:${CADDY_HTTPS_PORT:-443}:${CADDY_HTTPS_PORT:-443}
# - ${CADDY_INTERFACE:-0.0.0.0}:443:443/udp" # future: HTTP/3 (QUIC) # - ${CADDY_INTERFACE:-0.0.0.0}:443:443/udp" # future: HTTP/3 (QUIC)
volumes: volumes:
- caddy_data:/data - caddy_data:/data
@ -47,7 +48,7 @@ services:
zrok-frontend: zrok-frontend:
environment: environment:
ZROK_FRONTEND_SCHEME: https ZROK_FRONTEND_SCHEME: https
ZROK_FRONTEND_PORT: 443 ZROK_FRONTEND_PORT: ${CADDY_HTTPS_PORT:-443}
volumes: volumes:
caddy_data: caddy_data:

View File

@ -0,0 +1,80 @@
# delete this file from your compose project if you do not want to use Traefik for TLS termination
services:
traefik:
build:
context: .
dockerfile: ./traefik.Dockerfile
restart: unless-stopped
environment:
# DNS provider configuration
TRAEFIK_CERTIFICATESRESOLVERS_default_ACME_EMAIL: ${ZROK_USER_EMAIL}
TRAEFIK_CERTIFICATESRESOLVERS_default_ACME_CASERVER: ${TRAEFIK_ACME_API:-https://acme-v02.api.letsencrypt.org/directory}
TRAEFIK_CERTIFICATESRESOLVERS_default_ACME_DNSCHALLENGE: "true"
TRAEFIK_CERTIFICATESRESOLVERS_default_ACME_DNSCHALLENGE_PROVIDER: ${TRAEFIK_DNS_PROVIDER}
TRAEFIK_CERTIFICATESRESOLVERS_default_ACME_DNSCHALLENGE_RESOLVERS: "1.1.1.1:53,8.8.8.8:53"
TRAEFIK_CERTIFICATESRESOLVERS_default_ACME_DNSCHALLENGE_DELAYBEFORECHECK: "60"
TRAEFIK_CERTIFICATESRESOLVERS_default_ACME_STORAGE: /etc/traefik/acme/acme.json
# Entrypoints configuration
TRAEFIK_ENTRYPOINTS_websecure_ADDRESS: ":${TRAEFIK_HTTPS_PORT:-443}"
# DNS provider credentials - these will be mapped to environment variables expected by the provider
# See: https://doc.traefik.io/traefik/https/acme/#providers
TRAEFIK_DNS_PROVIDER: ${TRAEFIK_DNS_PROVIDER} # e.g., "digitalocean"
# Provider-specific credentials - uncomment and set in .env as needed
# Digital Ocean
# DO_AUTH_TOKEN: ${TRAEFIK_DNS_PROVIDER_TOKEN:-}
# Cloudflare - Option 1: Using Email and API Key
# CLOUDFLARE_EMAIL: ${CLOUDFLARE_EMAIL:-}
# CLOUDFLARE_API_KEY: ${CLOUDFLARE_API_KEY:-}
# Cloudflare - Option 2: Using API Tokens (recommended)
CLOUDFLARE_DNS_API_TOKEN: ${TRAEFIK_DNS_PROVIDER_TOKEN:-}
# CLOUDFLARE_ZONE_API_TOKEN: ${TRAEFIK_DNS_PROVIDER_TOKEN:-}
# AWS Route53 - uncomment if using Route53
# AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID:-}
# AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY:-}
# AWS_REGION: ${AWS_REGION:-}
# AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN:-} # if temporary credential, e.g., from STS
# General configuration
ZROK_DNS_ZONE: ${ZROK_DNS_ZONE} # e.g., "example.com" or "127.0.0.1.sslip.io"
ZROK_CTRL_PORT: ${ZROK_CTRL_PORT:-18080}
ZROK_FRONTEND_PORT: ${ZROK_FRONTEND_PORT:-8080}
ZROK_OAUTH_PORT: ${ZROK_OAUTH_PORT:-8081}
ZITI_CTRL_ADVERTISED_PORT: ${ZITI_CTRL_ADVERTISED_PORT:-80}
# Traefik specific configurations
TRAEFIK_API_DASHBOARD: "true"
TRAEFIK_API_INSECURE: "false"
TRAEFIK_PROVIDERS_DOCKER: "false" # Disable Docker provider since we're not mounting the socket
TRAEFIK_PROVIDERS_DOCKER_EXPOSEDBYDEFAULT: "false"
TRAEFIK_PROVIDERS_FILE_DIRECTORY: "/etc/traefik/dynamic"
TRAEFIK_PROVIDERS_FILE_WATCH: "true"
TRAEFIK_LOG_LEVEL: "DEBUG"
TRAEFIK_ACCESSLOG: "true"
TRAEFIK_ACCESSLOG_FORMAT: "common"
expose:
- ${TRAEFIK_HTTPS_PORT:-443}/tcp
- ${TRAEFIK_HTTPS_PORT:-443}/udp # For HTTP/3 (QUIC) (not published yet)
- 8080/tcp # Traefik's admin API (not published)
ports:
- ${TRAEFIK_INTERFACE:-0.0.0.0}:${TRAEFIK_HTTPS_PORT:-443}:${TRAEFIK_HTTPS_PORT:-443}
# - ${TRAEFIK_INTERFACE:-0.0.0.0}:${TRAEFIK_HTTPS_PORT:-443}:${TRAEFIK_HTTPS_PORT:-443}/udp # future: HTTP/3 (QUIC)
volumes:
- traefik_data:/etc/traefik/acme
# - /var/run/docker.sock:/var/run/docker.sock:ro # Docker provider for detecting new routes by label
networks:
zrok-instance:
zrok-frontend:
environment:
ZROK_FRONTEND_SCHEME: https
ZROK_FRONTEND_PORT: ${TRAEFIK_HTTPS_PORT:-443}
volumes:
traefik_data:

View File

@ -8,7 +8,7 @@
# redir https://{host}{uri} permanent # redir https://{host}{uri} permanent
# } # }
*.{$ZROK_DNS_ZONE} { *.{$ZROK_DNS_ZONE}:{$CADDY_HTTPS_PORT} {
tls { tls {
dns route53 { dns route53 {
access_key_id {$AWS_ACCESS_KEY_ID} access_key_id {$AWS_ACCESS_KEY_ID}

View File

@ -0,0 +1,17 @@
# Use the official Traefik image
FROM traefik:v2.10
# Install curl for healthcheck
RUN apk add --no-cache curl
# Create necessary directories
RUN mkdir -p /etc/traefik/dynamic /etc/traefik/acme
# Add configuration file
COPY ./traefik.dynamic.toml /etc/traefik/dynamic/traefik.toml
# Create and set permissions for the ACME certificates storage
RUN touch /etc/traefik/acme/acme.json && chmod 600 /etc/traefik/acme/acme.json
HEALTHCHECK --interval=5s --timeout=3s --retries=3 \
CMD traefik healthcheck

View File

@ -0,0 +1,95 @@
# Dynamic configuration for Traefik
# Entrypoints configuration is handled in the main Traefik configuration through environment variables
# We don't define entryPoints here as they will be configured by the environment variables in compose.traefik.yml:
# TRAEFIK_ENTRYPOINTS_websecure_ADDRESS: ":${TRAEFIK_HTTPS_PORT:-443}"
# TLS wildcard certificate configuration
[tls]
[tls.options]
[tls.options.default]
minVersion = "VersionTLS12"
sniStrict = true
# HTTP to HTTPS redirect middleware
[http.middlewares]
[http.middlewares.https-redirect.redirectScheme]
scheme = "https"
permanent = true
# Note: We can't use template syntax here as it's not supported in static config
# Instead, we'll use passHostHeader in the loadBalancer configs
# Define servers transports
[http.serversTransports]
[http.serversTransports.ziti-transport]
insecureSkipVerify = true
# Routing configuration
[http.routers]
# Ziti router
[http.routers.ziti]
rule = "Host(`ziti.{{ env "ZROK_DNS_ZONE" }}`)"
service = "ziti"
entrypoints = ["websecure"]
[http.routers.ziti.tls]
certResolver = "default"
[[http.routers.ziti.tls.domains]]
main = "*.{{ env "ZROK_DNS_ZONE" }}"
# OAuth router
[http.routers.oauth]
rule = "Host(`oauth.{{ env "ZROK_DNS_ZONE" }}`)"
service = "oauth"
entrypoints = ["websecure"]
[http.routers.oauth.tls]
certResolver = "default"
[[http.routers.oauth.tls.domains]]
main = "*.{{ env "ZROK_DNS_ZONE" }}"
# Controller router
[http.routers.ctrl]
rule = "Host(`zrok.{{ env "ZROK_DNS_ZONE" }}`)"
service = "ctrl"
entrypoints = ["websecure"]
[http.routers.ctrl.tls]
certResolver = "default"
[[http.routers.ctrl.tls.domains]]
main = "*.{{ env "ZROK_DNS_ZONE" }}"
# Frontend router (default route)
[http.routers.frontend]
rule = "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.{{ env "ZROK_DNS_ZONE" }}`) && !Host(`ziti.{{ env "ZROK_DNS_ZONE" }}`) && !Host(`oauth.{{ env "ZROK_DNS_ZONE" }}`) && !Host(`zrok.{{ env "ZROK_DNS_ZONE" }}`)"
service = "frontend"
entrypoints = ["websecure"]
[http.routers.frontend.tls]
certResolver = "default"
[[http.routers.frontend.tls.domains]]
main = "*.{{ env "ZROK_DNS_ZONE" }}"
# Service configuration
[http.services]
# Ziti service
[http.services.ziti.loadBalancer]
passHostHeader = true
serversTransport = "ziti-transport"
[[http.services.ziti.loadBalancer.servers]]
url = "http://ziti-quickstart:{{ env "ZITI_CTRL_ADVERTISED_PORT" | default 80 }}"
# OAuth service
[http.services.oauth.loadBalancer]
passHostHeader = true
[[http.services.oauth.loadBalancer.servers]]
url = "http://zrok-frontend:{{ env "ZROK_OAUTH_PORT" | default 8081 }}"
# Controller service
[http.services.ctrl.loadBalancer]
passHostHeader = true
[[http.services.ctrl.loadBalancer.servers]]
url = "http://zrok-controller:{{ env "ZROK_CTRL_PORT" | default 18080 }}"
# Frontend service
[http.services.frontend.loadBalancer]
passHostHeader = true
[[http.services.frontend.loadBalancer.servers]]
url = "http://zrok-frontend:{{ env "ZROK_FRONTEND_PORT" | default 8080 }}"

View File

@ -36,7 +36,7 @@ services:
HOME: /mnt # zrok homedir in container HOME: /mnt # zrok homedir in container
# most relevant options # most relevant options
ZROK_UNIQUE_NAME: # name is used to construct frontend domain name, e.g. "myapp" in "myapp.share.zrok.io" ZROK_UNIQUE_NAME: # name is used to construct frontend domain name, e.g. "toaster" in "toaster.share.zrok.io"; lowercase alphanumeric, between 4 and 32 characters in length
ZROK_BACKEND_MODE: # web, caddy, drive, proxy ZROK_BACKEND_MODE: # web, caddy, drive, proxy
ZROK_TARGET: # backend target, is a path in container filesystem unless proxy mode ZROK_TARGET: # backend target, is a path in container filesystem unless proxy mode
ZROK_INSECURE: # "--insecure" if proxy target has unverifiable TLS server certificate ZROK_INSECURE: # "--insecure" if proxy target has unverifiable TLS server certificate

View File

@ -1,18 +0,0 @@
services:
my-other-zrok-share:
image: ${ZROK_CONTAINER_IMAGE:-docker.io/openziti/zrok}
restart: unless-stopped
entrypoint: zrok-share.bash
depends_on:
zrok-enable:
condition: service_completed_successfully
volumes:
- zrok_env:/mnt
- ./Caddyfile:/Caddyfile
environment:
# most relevant options
ZROK_UNIQUE_NAME: "my-other-zrok-share"
ZROK_BACKEND_MODE: caddy
ZROK_TARGET: /Caddyfile
# internal configuration
HOME: /mnt # zrok homedir in container

View File

@ -38,7 +38,7 @@ services:
HOME: /mnt # zrok homedir in container HOME: /mnt # zrok homedir in container
# most relevant options # most relevant options
ZROK_UNIQUE_NAME: # name is used to construct frontend domain name, e.g. "myapp" in "myapp.share.zrok.io" ZROK_UNIQUE_NAME: # name is used to construct frontend domain name, e.g. "toaster" in "toaster.share.zrok.io"; lowercase alphanumeric, between 4 and 32 characters in length
ZROK_BACKEND_MODE: # web, caddy, drive, proxy ZROK_BACKEND_MODE: # web, caddy, drive, proxy
ZROK_TARGET: # backend target, is a path in container filesystem unless proxy mode ZROK_TARGET: # backend target, is a path in container filesystem unless proxy mode
ZROK_INSECURE: # "--insecure" if proxy target has unverifiable TLS server certificate ZROK_INSECURE: # "--insecure" if proxy target has unverifiable TLS server certificate

View File

@ -48,6 +48,6 @@ The following illustration shows the possibilities available.
![Frontend Selection](../../images/zrok_frontends_v0.3.png) ![Frontend Selection](../../images/zrok_frontends_v0.3.png)
The `*.in.zrok.io` frontend is a "public" frontend, available to all `zrok` users. Most `zrok` installations will want to have at least one public, global frontend for all public, internet-facing ingress traffic for private backend instances. In the underlying data store, the public frontend will have a `name` set to `public` (or some other representative name), allowing users to reference that `frontend` using a friendly label. The `*.share.zrok.io` frontend is a "public" frontend, available to all `zrok` users. Most `zrok` installations will want to have at least one public, global frontend for all public, internet-facing ingress traffic for private backend instances. In the underlying data store, the public frontend will have a `name` set to `public` (or some other representative name), allowing users to reference that `frontend` using a friendly label.
The other two "private" frontends are configured with no `name` label (the lack of a `name` label signifies that these are "private" frontends). The ephemeral environment is allocated when a `zrok` frontend request is made without an account on behalf of a private share. The other two "private" frontends are configured with no `name` label (the lack of a `name` label signifies that these are "private" frontends). The ephemeral environment is allocated when a `zrok` frontend request is made without an account on behalf of a private share.

View File

@ -16,16 +16,16 @@ To delete your `reserved` share use the `zrok release` command or click the dele
## Unique Names ## Unique Names
The default is to generate a random _share token_ and you may specify a _unique name_. The default is to generate a random _share token_ and you may instead specify a _unique name_. The unique name must be lowercase alphanumeric, between 4 and 32 characters in length.
This reserves public share token "myshare." This reserves public share token "toaster".
```bash title="Reserve with the Command Line" ```bash title="Reserve with the Command Line"
zrok reserve public 80 --unique-name "myshare" zrok reserve public 80 --unique-name "toaster"
``` ```
This shares `127.0.0.1:80` as `https://myshare.zrok.example.com` where `https://{token}.zrok.example.com` is the frontend's template. This shares `127.0.0.1:80` as `https://toaster.zrok.example.com` where `https://{token}.zrok.example.com` is the frontend's template.
```bash title="Share a Reserved Token" ```bash title="Share a Reserved Token"
zrok share reserved "myshare" zrok share reserved "toaster"
``` ```

View File

@ -9,11 +9,32 @@ import DownloadCard from '@site/src/components/download-card';
import DownloadCardStyles from '@site/src/css/download-card.module.css'; import DownloadCardStyles from '@site/src/css/download-card.module.css';
import InstallCards from '/../docs/guides/install/_install_cards.mdx'; import InstallCards from '/../docs/guides/install/_install_cards.mdx';
:::note
If you've upgraded to `v1.0.0` from a previous version and you receive an error message like this:
```
[ERROR]: unable to create share (error getting zrok client: client version error accessing api endpoint 'https://api.zrok.io': [POST /clientVersionCheck] clientVersionCheck (status 404): {}: [POST /clientVersionCheck] clientVersionCheck (status 404): {})
```
Use the command `zrok rebase apiEndpoint https://api-v1.zrok.io/` to update your environment to the latest zrok endpoint.
:::
## Your Secure Internet Sharing Perimeter ## Your Secure Internet Sharing Perimeter
`zrok` (*/ziːɹɒk/ ZEE-rock*) is a secure, open-source, self-hostable sharing platform that simplifies shielding and sharing network services or files. `zrok` (*/ziːɹɒk/ ZEE-rock*) is a secure, open-source, self-hostable sharing platform that simplifies shielding and sharing network services or files.
There's a hardened zrok-as-a-service offering available at [myzrok.io](https://myzrok.io) with a generous free tier. There's a hardened zrok-as-a-service offering available at [myzrok.io](https://myzrok.io) with a generous free tier.
## What's it for?
Use `zrok` to share a running service, like a web server or a network socket, or to share a directory of static files. `zrok` goes beyond simple tunneling to provide sharing solutions for a variety of network and storage use cases.
When using `zrok` to [share publicly](./concepts/sharing-public.mdx), you can reserve a public hostname, enable authentication options, or both. Public shares proxy HTTPS to your service or files.
If [sharing privately](./concepts/sharing-private.mdx), only users with the share token (and the appropriate permission grants) can access your share. In addition to what you can share publicly, private shares can include TCP and UDP services.
Here's a quick overview of what's involved in getting started with `zrok`:
### Your First Share ### Your First Share
1. Get an account token 1. Get an account token
@ -36,7 +57,7 @@ There's a hardened zrok-as-a-service offering available at [myzrok.io](https://m
<Column style={{paddingBottom: 20}}> <Column style={{paddingBottom: 20}}>
<Card shadow='tl'> <Card shadow='tl'>
<CardHeader> <CardHeader>
<h3>Self-Hosted zrok</h3> <h3>Self-Hosted zrok</h3>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
Run a zrok instance on Linux, Docker, or Kubernetes. Run a zrok instance on Linux, Docker, or Kubernetes.
@ -51,7 +72,7 @@ There's a hardened zrok-as-a-service offering available at [myzrok.io](https://m
</Columns> </Columns>
2. [Download the zrok binary](#installing-the-zrok-command) 2. [Download the zrok binary](#installing-the-zrok-command)
3. Enable zrok for your [user environment](#enabling-your-zrok-environment) 3. Enable zrok for your [environment](#enabling-your-zrok-environment)
```bash ```bash
zrok enable <your_account_token> zrok enable <your_account_token>
@ -65,41 +86,11 @@ There's a hardened zrok-as-a-service offering available at [myzrok.io](https://m
5. Visit the public URL displayed in your terminal 5. Visit the public URL displayed in your terminal
![zrok share public](images/zrok_share_public.png) ![zrok share public](images/zrok-share-public.png)
## Share Backend Modes # A Deeper Look at Getting Started
zrok shares can be public or private, with different options for backend modes, including: Here's a deeper, more thorough look at getting started with `zrok`:
* [Public shares](./concepts/sharing-public.mdx) for [web services](./concepts/http.md) or [files](./concepts/files.md)
* [Private shares for web services or files](./concepts/sharing-private.mdx)
* [TCP Tunnels](./concepts/tunnels.md)
* [UDP Tunnels](./concepts/tunnels.md)
* [File Drives](./guides/drives.mdx)
* [VPN](./guides/vpn/vpn.md)
## Open Source
`zrok` is licensed under Apache 2.0.
Check [the roadmap](https://github.com/orgs/openziti/projects/16) if you're thinking about the future. We would love to hear your ideas for `zrok`!
The best ways to engage are [Discourse](https://openziti.discourse.group/) for questions and [GitHub Issues](https://github.com/openziti/zrok/issues) for documenting problems.
[Read more about zrok open source](/concepts/opensource.md).
### Ziti native
`zrok` is a _Ziti Native Application_, built on the [OpenZiti](https://openziti.io) platform, and supported by the OpenZiti community and NetFoundry team.
## What's it for?
Use `zrok` to share a running service, like a web server or a network socket, or to share a directory of static files.
If [sharing publicly](./concepts/sharing-public.mdx), you can reserve a subdomain, enable authentication options, or both. Public shares proxy HTTPS to your service or files.
If [sharing privately](./concepts/sharing-private.mdx), only users with the share token can access your share. In addition to what you can share publicly, private shares can include TCP and UDP services.
## Installing the zrok Command ## Installing the zrok Command
@ -111,56 +102,64 @@ After you have [an account](#your-first-share), you can enable your `zrok` envir
A zrok environment usually refers to an enabled device where shares and accesses can be created, .e.g., `~/.zrok` on a Unix machine. It can be a specific user's environment or a system-wide agent's environment owned by the administrator. A zrok environment usually refers to an enabled device where shares and accesses can be created, .e.g., `~/.zrok` on a Unix machine. It can be a specific user's environment or a system-wide agent's environment owned by the administrator.
When your `zrok` account was created, the service generated a _secret token_ that identifies and authenticates in a single step. Protect your secret token as if it were a password, or an important account number; it's a _secret_, protect it. When your `zrok` account was created, the service generated an _account token_ that identifies and authenticates in a single step. Protect your account token as if it were a password, or an important account number; it's a _secret_, protect it.
When we left off you had downloaded, extracted, and configured your `zrok` environment. In order to use that environment with your account, you'll need to `enable` it. Enabling an environment generates a secure identity and the necessary underlying security policies with the OpenZiti network hosting the `zrok` service. When we left off you had downloaded, extracted, and configured your `zrok` software. In order to use that environment with your account, you'll need to `enable` an _environment_ on your system. Enabling an environment generates a secure identity and the necessary underlying security policies with the OpenZiti network hosting the `zrok` service so that you can begin sharing.
From the web console, click on your email address in the upper right corner of the header. That drop down menu contains an `Enable Your Environment` link. Click that link and a modal dialog will be shown like this: Log into the API console at:
![Enable Modal Dialog](images/zrok_enable_modal.png) [https://api-v1.zrok.io/](https://api-v1.zrok.io/)
This dialog box shows you the `zrok enable` command that you can use to enable any shell to work with your `zrok` account with a single command. When you first log into your account on the API console, your interface will look like this:
Let's copy that command and paste it into your shell: ![zrok API console, empty](images/zrok-getting-started-button.png)
```buttonless title="Example" In the toolbar, there is a big green button that says "CLICK HERE TO GET STARTED!". If you click that button, you'll see the getting started wizard, which looks like this:
$ zrok enable klFEoIi0QAg7
![zrok getting started modal](images/zrok-getting-started-modal.png)
This wizard is broken into multiple steps. The first step we've already covered, which gets the zrok software installed onto your system.
Below "step 2" is a command: `zrok enable 7g3K6gVKikWb` (your account will have a different account token, other than `7g3K6gVKikWb`). You'll want to copy this command into your shell and execute it:
```txt
$ zrok enable 7g3K6gVKikWb
⣻ contacting the zrok service... ⣻ contacting the zrok service...
``` ```
After a few seconds, the message will change and indicate that the enable operation succeeded: After a few seconds, the message will change and indicate that the enable operation succeeded:
```buttonless title="Example" ```txt
$ zrok enable klFEoIi0QAg7 $ zrok enable 7g3K6gVKikWb
⣻ the zrok environment was successfully enabled... ⣻ the zrok environment was successfully enabled...
``` ```
Now, if we run a `zrok status` command, you will see the details of your environment: Now, if we run a `zrok status` command, you will see the details of your environment:
```txt ```txt
zrok status $ zrok status
```
```buttonless title="Output"
Config: Config:
CONFIG VALUE SOURCE CONFIG VALUE SOURCE
apiEndpoint https://api.staging.zrok.io env apiEndpoint https://api-v1.zrok.io env
defaultFrontend public binary
headless false binary
Environment: Environment:
PROPERTY VALUE PROPERTY VALUE
Secret Token <<SET>> Account Token <<SET>>
Ziti Identity <<SET>> Ziti Identity <<SET>>
``` ```
Excellent... our environment is now fully enabled. Excellent... our environment is now fully enabled.
If we return to the _web console_, we'll now see the new environment reflected in the explorer view: If we return to the _API console_, we'll now see the new environment reflected in the API console visualizer:
![New Environment in Web UI](images/zrok_web_ui_new_environment.png) ![New Environment in Web UI](images/zrok-visualizer-enabled.png)
In my case, the environment is named `michael@ziti-lx`, which is the username of my shell and the hostname of the system the shell is running on. In my case, the environment is named `michael@testing`, which is the username of my shell and the hostname of the system the shell is running on.
:::note :::note
Should you want to use a non-default name for your environment, you can pass the `-d` option to the `zrok enable` command. See `zrok enable --help` for details. Should you want to use a non-default name for your environment, you can pass the `-d` option to the `zrok enable` command. See `zrok enable --help` for details.
@ -168,16 +167,12 @@ Should you want to use a non-default name for your environment, you can pass the
If you click on the environment node in the explorer in the _web console_, the details panel shown at the bottom of the page will change: If you click on the environment node in the explorer in the _web console_, the details panel shown at the bottom of the page will change:
![Empty Environment](images/zrok_web_ui_empty_shares.png) ![Empty Environment](images/zrok-visualizer-environment.png)
The explorer supports clicking, dragging, mouse wheel zooming, and selecting the nodes in the graph for more information (and available actions) for the selected node. If you ever get lost in the explorer, click the ![Zoom to Fit](images/zrok_zoom_to_fit.png) _zoom to fit_ icon in the lower right corner of the explorer. The visualizer supports clicking, dragging, mouse wheel zooming, and selecting the nodes in the graph for more information (and available actions) for the selected node. If you ever get lost in the visualizer, click the ![Zoom to Fit](images/zrok-zoom-to-fit.png) _zoom to fit_ icon in the lower right corner of the explorer.
If we click on the `Detail` tab for our environment, we'll see something like:
![Environment Detail](images/zrok_web_ui_empty_environment_detail.png)
:::note :::note
With your `zrok` account you can `zrok enable` multiple environments. This will allow you to run `zrok share` in one environment, and `zrok access` in other environments. With your `zrok` account you can `zrok enable` multiple environments. This will allow you to share (and access your shares) from multiple environments simultaneously.
::: :::
Your environment is fully ready to go. Now we can move on to the fun stuff... Your environment is fully ready to go. Now we can move on to the fun stuff...
@ -196,63 +191,30 @@ Resources that are shared _publicly_ are exposed to any users on the internet wh
A frontend is an HTTPS listener exposed to the internet, that lets any user with your ephemeral share token access your publicly shared resources. A frontend is an HTTPS listener exposed to the internet, that lets any user with your ephemeral share token access your publicly shared resources.
For example, I might create a public share using the `zrok share public` command, which results in my `zrok` instance exposing a URL like `https://2ptgbr8tlfvk.share.zrok.io` to access my resources. For example, I might create a public share using the `zrok share public` command, which results in my `zrok` instance exposing a URL like `https://xxr2b7tzfx64.share.zrok.io` to access my resources.
In this case, my share was given the "share token" of `2ptgbr8tlfvk`. That URL can be given to any user, allowing them to immediately access the shared resources directly from my local environment, all without exposing any access to my private, secure environment. The physical network location of my environment is not exposed to anonymous consumers of my resources. ```
$ zrok share public --backend-mode web .
:::note
Here is the `--help` output from `zrok share public`:
```text
zrok share public
``` ```
```buttonless title="Output" In this case, my share was given the "share token" of `xxr2b7tzfx64`. That URL can be given to any user, allowing them to immediately access the shared resources directly from my local environment, all without exposing any access to my private, secure environment. The physical network location of my environment is not exposed to anonymous consumers of my resources.
Error: accepts 1 arg(s), received 0
Usage:
zrok share public <target> [flags]
Flags:
--backend-mode string The backend mode {proxy, web, caddy, drive} (default "proxy")
--basic-auth stringArray Basic authentication users (<username:password>,...)
--frontends stringArray Selected frontends to use for the share (default [public])
--headless Disable TUI and run headless
-h, --help help for public
--insecure Enable insecure TLS certificate validation for <target>
Global Flags:
-p, --panic Panic instead of showing pretty errors
-v, --verbose Enable verbose logging
[ERROR]: an error occurred (accepts 1 arg(s), received 0)
```
`<target>` defines the path to the local resource that you intend to share. The form of `<target>` depends on the `--backend-mode` that you're using.
In the case of `--backend-mode proxy`, `<target>` should be a URL to an HTTP endpoint.
In the case of `--backend-mode web`, `<target>` is the path to a file on disk that serves as the "root" of the file tree to be shared.
:::
If we return to the web console, we see our share in the explorer: If we return to the web console, we see our share in the explorer:
![Web Console Share](images/zrok_web_console_explorer_share.png) ![Web Console Share](images/zrok-visualizer-public-share.png)
If we click on our new share in the explorer, we can see the share details:
![Share Details](images/zrok_web_console_share_detail.png)
If we click on the _frontend endpoint_ a new browser tab opens and we see the content of our share: If we click on the _frontend endpoint_ a new browser tab opens and we see the content of our share:
![Share Frontend](images/zrok_web_console_share_frontend.png) ![Share Frontend](images/zrok_web_console_share_frontend.png)
If we click on the environment in the explorer, we're shown all of the shares for that environment (including our new share), along with a spark line that shows the activity: When we start accessing our share, notice the _sparkline_ graphs showing the activity:
![Environment Spark Line](images/zrok_web_console_environment_spark.png) ![Environment Spark Line](images/zrok-visualizer-sparklines.png)
And as soon as I terminate the `zrok share` client, the resources are removed from the `zrok` environment. And as soon as I terminate the `zrok share` client, the resources are removed from the `zrok` environment.
If we try to reload the frontend endpoint in our web browser, we'll see: If we try to reload the frontend endpoint in our web browser, we'll see:
![Not Found](images/zrok_not_found.png) ![Not Found](images/zrok-not-found.png)
[More about public shares](/concepts/sharing-public.mdx) [More about public shares](/concepts/sharing-public.mdx)
@ -260,7 +222,7 @@ If we try to reload the frontend endpoint in our web browser, we'll see:
`zrok` also provides a powerful _private_ sharing model. If I execute the following command: `zrok` also provides a powerful _private_ sharing model. If I execute the following command:
```buttonless title="Example" ```buttonless
$ zrok share private http://localhost:8080 $ zrok share private http://localhost:8080
``` ```
@ -294,7 +256,7 @@ A reserved share can be re-used multiple times; it will survive termination of t
The first step is to create the reserved share: The first step is to create the reserved share:
```txt title="Example" ```txt
$ zrok reserve public --backend-mode web v0.3_getting_started $ zrok reserve public --backend-mode web v0.3_getting_started
[ 0.275] INFO main.(*reserveCommand).run: your reserved share token is 'mltwsinym1s2' [ 0.275] INFO main.(*reserveCommand).run: your reserved share token is 'mltwsinym1s2'
[ 0.275] INFO main.(*reserveCommand).run: reserved frontend endpoint: https://mltwsinym1s2.share.zrok.io [ 0.275] INFO main.(*reserveCommand).run: reserved frontend endpoint: https://mltwsinym1s2.share.zrok.io
@ -306,13 +268,13 @@ You'll want to remember the share token (`mltwsinym1s2` in this case), and the f
If we do nothing else, and then point a web browser at the frontend endpoint, we get: If we do nothing else, and then point a web browser at the frontend endpoint, we get:
![Not Found](images/zrok_reserved_not_found.png) ![Not Found](images/zrok-reserved-not-found.png)
This is the `404` error message returned by the `zrok` frontend. We're getting this because we haven't yet started up a `zrok share` for the service. Let's do that: This is the `404` error message returned by the `zrok` frontend. We're getting this because we haven't yet started up a `zrok share` for the service. Let's do that:
This command: This command:
```txt title="Example" ```txt
$ zrok share reserved mltwsinym1s2 $ zrok share reserved mltwsinym1s2
``` ```
@ -328,7 +290,7 @@ With the reserved share, we're free to stop and restart the `zrok share reserved
When we're done with the reserved share, we can _release_ it using this command: When we're done with the reserved share, we can _release_ it using this command:
```txt title="Example" ```txt
$ zrok release mltwsinym1s2 $ zrok release mltwsinym1s2
[ 0.230] INFO main.(*releaseCommand).run: reserved share 'mltwsinym1s2' released [ 0.230] INFO main.(*releaseCommand).run: reserved share 'mltwsinym1s2' released
``` ```

View File

@ -59,10 +59,10 @@ ZROK_ENABLE_TOKEN="14cbfca9772f"
## Name your Share ## Name your Share
This unique name becomes part of the domain name of the share, e.g. `https://my-prod-app.in.zrok.io`. A random name is generated if you don't specify one. This unique name becomes part of the domain name of the share, e.g. `https://toaster.share.zrok.io`. A random name is generated if you don't specify one. The name must be lowercase alphanumeric, between 4 and 32 characters in length.
```bash title="/opt/openziti/etc/zrok/zrok-share.env" ```bash title="/opt/openziti/etc/zrok/zrok-share.env"
ZROK_UNIQUE_NAME="my-prod-app" ZROK_UNIQUE_NAME="toaster"
``` ```
## Use Cases ## Use Cases

View File

@ -0,0 +1,17 @@
1. Set up `zrok`'s Linux package repository by following [the Linux install guide](/guides/install/linux.mdx#install-zrok-from-the-repository), or run this one-liner to complete the repo setup and install packages.
```bash
curl -sSLf https://get.openziti.io/install.bash \
| sudo bash -s zrok-agent
```
1. If you set up the repository by following the guide, then also install the `zrok-agent` package. This package provides the systemd service.
```bash title="Ubuntu, Debian"
sudo apt install zrok-agent
```
```bash title="Fedora, Rocky"
sudo dnf install zrok-agent
```

View File

@ -0,0 +1,8 @@
{
"label": "The zrok Agent",
"position": 15,
"link": {
"type": "doc",
"id": "guides/agent/index"
}
}

109
docs/guides/agent/index.mdx Normal file
View File

@ -0,0 +1,109 @@
---
title: Agent
sidebar_position: 20
---
The zrok Agent centralizes management of your zrok shares and accesses. It provides both web-based and command-line interfaces, and changes how the `zrok share` and `zrok access` commands behave.
## Tutorial
Run the Agent in the foreground.
```bash
zrok agent
```
In another terminal, open the console.
```bash
zrok agent console
```
You should see the Agent UI in your default web browser.
Start sharing a public share with the Agent.
```bash
zrok share public 8080
```
```buttonless title="Output"
token:"zje5x8p0k9pi" frontendEndpoints:"https://zje5x8p0k9pi.share.zrok.io"
```
You will see the new public share in the Agent UI and you can access it at the public share URL.
Reserve a private share for the Agent to share.
```bash
zrok reserve private 8080 --closed --unique-name "myshare"
```
```buttonless title="Output"
[ 1.883] INFO main.(*reserveCommand).run: your reserved share token is 'myshare'
```
Start sharing the reserved share with the Agent.
```bash
zrok share reserved "myshare"
```
```buttonless title="Output"
[ 0.001] INFO main.(*shareReservedCommand).shareAgent: starting
token:"myshare" backendMode:"proxy" shareMode:"private" target:"http://127.0.0.1:8080"
```
You will see the new reserved share in the Agent UI and you can access it by running `zrok access "myshare"` on another device where you have enabled the same zrok account, since the share was reserved with closed permission mode.
Check the status of the Agent's shares and accesses.
```bash
zrok agent status
```
```buttonless title="Output"
FRONTEND TOKEN TOKEN BIND ADDRESS
0 accesses in agent
TOKEN RESERVED SHARE MODE BACKEND MODE TARGET
myshare true private tcpTunnel 127.0.0.1:8080
1 share in agent
```
### Running the Agent in the background
You can keep the Agent running reliably in the background by installing the Agent service in Windows or Linux.
- Windows - [set up the Windows system service](/guides/agent/windows-service/index.mdx)
- Linux - [install the Linux package `zrok-agent`](/guides/agent/linux-service.mdx)
## How the Agent Works
### Centralized Management
Without the Agent running, each time you execute a `zrok share` or `zrok access` command, a separate process is created to handle that specific share or access.
When the Agent is running:
- All shares and accesses are managed by a single Agent process.
- The Agent provides a web UI for monitoring and managing your shares and accesses.
- The `zrok share` and `zrok access` commands delegate their operations to the running Agent.
- You can stop and restart individual shares/accesses without terminating the Agent.
- The Agent will remember and automatically restart your shares started with `share reserved`, and any accesses started with `access private`.
- The Agent will not restart regular, ephemeral shares started with `share private` or `share public`.
### Agent Console
The Agent provides a web-based console interface that can be accessed with:
```bash
zrok agent console
```
This command opens your default web browser to the Agent UI, where you can:
- View the status of all your active shares and accesses
- Create new shares and accesses using simple UI widgets
- Stop or restart existing shares and accesses
- Monitor traffic and connection statistics

View File

@ -0,0 +1,42 @@
---
title: Linux Agent Service
sidebar_position: 40
---
import LinuxAgentInstall from '/../docs/guides/_linux-agent-install.mdx'
## Overview
Run the zrok agent as a `systemd --user` service under your Linux user account.
## Install the Package
The package provides the `zrok` executable and the `zrok-agent.service` unit.
<LinuxAgentInstall />
## Enable your Account
This creates a `~/.zrok` directory enabled for your zrok account.
```bash
zrok enable <your_account_token>
```
## Start the Service
```bash
systemctl --user enable --now zrok-agent.service
```
## Use the agent
Learn more about using the zrok agent in the [agent guide](/guides/agent/index.mdx).
## Troubleshooting
### Check the User Service Log
```bash
journalctl --user -lfu zrok-agent.service
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -0,0 +1,144 @@
---
title: Configuring a Windows Service
sidebar_label: Windows Agent Service
---
In Windows environments, it can be useful to run the zrok Agent as a service, allowing it to automatically restart with your system.
Support for running the zrok Agent as a Windows service is handled through a third party utility, `nssm`, which is available here:
[https://nssm.cc/download](https://nssm.cc/download)
Give the `nssm` documentation a quick review, here:
[https://nssm.cc/usage](https://nssm.cc/usage)
You'll want to install both the `zrok.exe` and `nssm.exe` in a convenient, protected location. In this example, we'll put them in `C:\Program Files\zrok`, as `C:\Program Files\zrok\zrok.exe` and `C:\Program Files\zrok\nssm.exe`.
![C:\\Program Files\\zrok](images/program-files.png)
We're going to use a command prompt to install and configure our Agent service. Open a new command prompt and `cd "\Program Files\zrok"`:
![Empty Command Prompt](images/empty-command-prompt.png)
## Enabling the Service Environment
On Windows, the `USERPROFILE` environment variable controls the "home" where zrok will look for the enabled environment. When running as a service on Windows, the process will be running as the `Local System` user, which uses the directory `C:\Windows\System32\config\systemprofile` directory as the user's "home" directory.
We're going to want to enable a zrok environment for our new Agent service in this directory. First, we'll run:
```cmd
C:\Program Files\zrok>set USERPROFILE=c:\Windows\System32\config\systemprofile
```
And with that environment variable set, we'll use the `zrok enable` command to enable an environment for our new Agent service:
```cmd
C:\Program Files\zrok>zrok enable <accountToken>
```
![Enabling the Service Environment](images/enable.png)
Now we've got a new, enabled environment to use with our Agent running as a Windows service.
## Installing the Service
In the `C:\Program Files\zrok` directory, execute this command to invoke `nssm` to create the new Windows service for our zrok Agent:
```cmd
C:\Program Files\zrok>nssm install zrokAgent
```
Windows might ask you for elevated Administrator privileges and will then show the main `nssm` installation dialog, which we'll use to configure the new Windows service:
![nssm install](images/nssm-install.png)
We'll set "Path" to `C:\Program Files\zrok\zrok.exe` (the zrok executable). We'll set the "Startup directory" to `C:\Windows\System32\config\systemprofile` (the "home" directory for the `Local System` user). The "Arguments" are the command-line parameters that will get added to the command-line when starting the service, in this case, we want the service to start with the command `zrok agent start`.
We'll set the "Service name" to `zrokAgent`.
Next, scroll over to the "I/O" tab in the `nssm` installer:
![I/O Tab](images/nssm-io.png)
Set the "Output (stdout)" to log the standard output from the `zrok agent start` process to the file `C:\Windows\System32\config\systemprofile\.zrok\agent-stdout.log`.
Set the "Error (stderr)" to log the standard error to the file `C:\Windows\System32\config\systemprofile\.zrok\agent-stderr.log`.
Setting the I/O redirection in this way will produce logs from the `zrok agent start` process that could be useful for future troubleshooting.
`nssm` also provides options for automatically rotating these log files:
![File rotation](images/nssm-file-rotation.png)
We'll leave these unset in this example, but if your configuration needs this kind of log file rotation `nssm` makes it available.
Finally, click the `Install service` button to create the service.
If we open the "Services" utility in Windows, we can see our new `zrokAgent` service:
![Service Manager](images/services.png)
If we click the start button in the toolbar, or right-click on the service and select "start", our new zrok Agent service will start:
![Service Running](images/services-running.png)
If we open a Windows Explorer in the folder `C:\Windows\System32\config\systemprofile\.zrok` we can see our logs, and the `agent.socket` which is used by the zrok command-line to interact with our Agent service:
![zrokdir](images/zrokdir.png)
This zrok Agent service is now available for use. Whenever a Windows Command Prompt wants to interact with this environment, it is important that the `USERPROFILE` environment variable is properly set to `C:\Windows\System32\config\systemprofile`, otherwise the zrok commands will attempt to interact with the environment that would be created in the user's default profile directory.
We can access the Agent console using this command:
```cmd
C:\Program Files\zrok>zrok agent console
```
And this will open a web interface which allows the Agent to be managed:
![zrok agent console](images/zrok-agent-console.png)
The Agent console can be used to directly create shares and accesses, and the zrok command-line can also be used.
## Non-interactive Service Installation
`nssm` provides a command-line that can do this configuration without interacting with the `nssm` GUI. We would create our service like this:
```cmd
C:\Program Files\zrok>nssm install zrokAgent "C:\Program Files\zrok\zrok.exe" agent start
```
```cmd
C:\Program Files\zrok>nssm set zrokAgent AppDirectory C:\Windows\System32\config\systemprofile
```
```cmd
C:\Program Files\zrok>nssm set zrokAgent AppStdout C:\Windows\System32\config\systemprofile\.zrok\agent-stdout.log
```
```cmd
C:\Program Files\zrok>nssm set zrokAgent AppStderr C:\Windows\System32\config\systemprofile\.zrok\agent-stderr.log
```
And we can start our new service using the standard Windows service control utility, `sc`:
```cmd
C:\Program Files\zrok>sc start zrokAgent
```
![nssm CLI installation](images/nssm-cli-installation.png)
## Removing the zrok Agent Service
The following commands (in a Command Prompt running as Administrator) can be used to remove the service from your system:
```cmd
C:\>sc stop zrokAgent
```
```cmd
C:\>sc delete zrokAgent
```
And if you have your `USERPROFILE` environment variable properly set to `C:\Windows\System32\config\systemprofile`, you can use this command to remove the environment from your system and from the zrok service:
```cmd
C:\Program Files\zrok>zrok disable
```

View File

@ -41,10 +41,10 @@ When the project runs it will:
1. Name the Share 1. Name the Share
This unique name becomes part of the domain name of the share, e.g. `https://my-prod-app.in.zrok.io`. A random name is generated if you don't specify one. This unique name becomes part of the domain name of the share, e.g. `https://toaster.share.zrok.io`. A random name is generated if you don't specify one.
```bash title=".env" ```bash title=".env"
ZROK_UNIQUE_NAME="my-prod-app" ZROK_UNIQUE_NAME="toaster"
``` ```
1. Run the Compose project to start sharing the built-in demo web server. Be sure to `--detach` so the project runs in the background if you want it to auto-restart when your computer reboots. 1. Run the Compose project to start sharing the built-in demo web server. Be sure to `--detach` so the project runs in the background if you want it to auto-restart when your computer reboots.
@ -60,7 +60,7 @@ When the project runs it will:
``` ```
```buttonless title="Output" ```buttonless title="Output"
zrok-public-share-1 | https://w6r1vesearkj.in.zrok.io/ zrok-public-share-1 | https://w6r1vesearkj.share.zrok.io/
``` ```
This concludes the minimum steps to begin sharing the demo web server. Read on to learn how to pivot to sharing any website or web service by leveraging additional zrok backend modes. This concludes the minimum steps to begin sharing the demo web server. Read on to learn how to pivot to sharing any website or web service by leveraging additional zrok backend modes.
@ -149,5 +149,5 @@ With Caddy, you can balance the workload for websites or web services or share s
``` ```
```buttonless title="Output" ```buttonless title="Output"
INFO: zrok public URL: https://88s803f2qvao.in.zrok.io/ INFO: zrok public URL: https://88s803f2qvao.share.zrok.io/
``` ```

View File

@ -14,9 +14,9 @@ This page provides `docker` and `docker compose` examples of mounting the host's
## Permanent Public Share ## Permanent Public Share
Let's say you have a `compose.yml` file that defines a web app known within the project's bridge network as `https://myapp:8080` and you want to publish it as a reliable, public site. Let's say you have a `compose.yml` file that defines a web app known within the project's bridge network as `https://toaster:8080` and you want to publish it as a reliable, public site.
1. Reserve a subdomain by running `zrok reserve public --unique-name "myapp" https://myapp:8080` on the Docker host. 1. Reserve a subdomain by running `zrok reserve public --unique-name "toaster" https:toasterpp:8080` on the Docker host.
1. Merge this YAML with `compose.yml` or save it in the same directory as `compose.override.yml` to let `docker compose up` merge it for you. 1. Merge this YAML with `compose.yml` or save it in the same directory as `compose.override.yml` to let `docker compose up` merge it for you.
```yaml ```yaml
@ -29,10 +29,10 @@ Let's say you have a `compose.yml` file that defines a web app known within the
- ${HOME}/.zrok:/home/ziggy/.zrok - ${HOME}/.zrok:/home/ziggy/.zrok
environment: environment:
PFXLOG_NO_JSON: "true" PFXLOG_NO_JSON: "true"
command: share reserved "myapp" --headless command: share reserved "toaster" --headless
``` ```
The reserved share will be available at `https://myapp.share.zrok.io` each time the `zrok` container starts up. The reserved share will be available at `https://toaster.share.zrok.io` each time the `zrok` container starts up.
## Temporary Public Share ## Temporary Public Share

View File

@ -1,8 +0,0 @@
{
"label": "Linux User Share",
"position": 40,
"link": {
"type": "doc",
"id": "guides/linux-user-share/index"
}
}

View File

@ -1,44 +0,0 @@
---
title: Linux User Share
---
import LinuxShareInstall from '/../docs/guides/_linux-share-install.mdx'
## Overview
You can run any number of zrok share services as `systemd --user` units with your Linux user's zrok environment in `~/.zrok`. This is like [zrok frontdoor](/guides/frontdoor.mdx) except that frontdoor is a system service managed by root separately from your user's login. Linux user shares, Linux system services, and Docker shares all use the same configuration environment variables.
## Install the Linux Package
The package provides the `zrok` executable and service unit template.
<LinuxShareInstall />
## Create a User Share Configuration File
Substitute a name for your instance in place of `my-instance` in the following example. To avoid character escaping problems, use only letters, numbers, hyphens, and underscores in the instance name, not spaces or other special characters.
```bash
ZROK_INSTANCE="my-instance"
cp /opt/openziti/etc/zrok/zrok-share.env ~/.zrok/zrok-share@${ZROK_INSTANCE}.env
```
## Edit the User Share Configuration File
Edit the configuration file in `~/.zrok/zrok-share@${ZROK_INSTANCE}.env` as you would for [zrok frontdoor](/guides/frontdoor.mdx), except ignore the first section "ZROK ENVIRONMENT" because user shares re-use `~/.zrok` and do not need a separate zrok environment.
## Start the User Share Service
```bash
systemctl --user enable --now zrok-share@${ZROK_INSTANCE}.service
```
## Check the User Share Journal
```bash
journalctl --user -lfu zrok-share@${ZROK_INSTANCE}.service
```
## Add Another User Share
To create another user share, choose another instance name, copy the `zrok-share.env` file, edit the configuration file, and start the service.

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

BIN
docs/images/zrok-not-found.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

BIN
docs/images/zrok-share-public.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/images/zrok_web_console.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -6,7 +6,7 @@ title: Custom Domains
[myzrok.io](https://myzrok.io) is a hosted zrok-as-a-service offering that provides a way for you bring a custom DNS name for zrok shares. [myzrok.io](https://myzrok.io) is a hosted zrok-as-a-service offering that provides a way for you bring a custom DNS name for zrok shares.
For example, let's say you own the domain `foo.example.io`, you can leverage zrok custom domains to For example, let's say you own the domain `foo.example.io`, you can leverage zrok custom domains to
create ephemeral shares such as: `https://vw8jbg4ijz5g.foo.example.io` create ephemeral shares such as: `https://vw8jbg4ijz5g.foo.example.io`
or [reserved shares](/concepts/sharing-reserved.md) such as `https://myshare.foo.example.io`. or [reserved shares](/concepts/sharing-reserved.md) such as `https://toaster.foo.example.io`.
Custom domains require a Pro subscription with [myzrok.io](https://myzrok.io). Custom domains require a Pro subscription with [myzrok.io](https://myzrok.io).
If you don't already have an account, you can sign up for one [here](https://myzrok.io). If you don't already have an account, you can sign up for one [here](https://myzrok.io).

View File

@ -29,6 +29,7 @@ type Root interface {
DeleteZitiIdentityNamed(name string) error DeleteZitiIdentityNamed(name string) error
AgentSocket() (string, error) AgentSocket() (string, error)
AgentRegistry() (string, error)
} }
type Environment struct { type Environment struct {

View File

@ -194,7 +194,11 @@ func (r *Root) DeleteZitiIdentityNamed(name string) error {
} }
func (r *Root) AgentSocket() (string, error) { func (r *Root) AgentSocket() (string, error) {
return "", errors.Errorf("this environment version does not support agent sockets; please 'zrok update' this environment") return "", errors.Errorf("this environment version does not support the zrok Agent; please 'zrok update' this environment")
}
func (r *Root) AgentRegistry() (string, error) {
return "", errors.Errorf("this environment version does not support the zrok Agent; please 'zrok update' this environment")
} }
func (r *Root) Obliterate() error { func (r *Root) Obliterate() error {

View File

@ -196,6 +196,10 @@ func (r *Root) AgentSocket() (string, error) {
return agentSocket() return agentSocket()
} }
func (r *Root) AgentRegistry() (string, error) {
return agentRegistry()
}
func (r *Root) Obliterate() error { func (r *Root) Obliterate() error {
zrd, err := rootDir() zrd, err := rootDir()
if err != nil { if err != nil {

View File

@ -61,3 +61,11 @@ func agentSocket() (string, error) {
} }
return filepath.Join(zrd, "agent.socket"), nil return filepath.Join(zrd, "agent.socket"), nil
} }
func agentRegistry() (string, error) {
zrd, err := rootDir()
if err != nil {
return "", err
}
return filepath.Join(zrd, "agent-registry.json"), nil
}

16
nfpm/zrok-agent.service Normal file
View File

@ -0,0 +1,16 @@
# /usr/lib/systemd/user/zrok-agent.service
[Unit]
Description=zrok agent user service unit
After=network-online.target
[Service]
Type=simple
UMask=0007
ExecStartPre=/usr/bin/env rm --force %h/.zrok/agent.socket
ExecStart=/opt/openziti/bin/zrok agent start
Restart=always
RestartSec=3
[Install]
WantedBy=default.target

View File

@ -84,7 +84,7 @@ ZROK_TARGET="" # e.g., http://127.0.0.1:3000
# #
# you MAY customize the share token that is used to construct the reserved subdomain; if not set a random # you MAY customize the share token that is used to construct the reserved subdomain; if not set a random
# subdomain is reserved # subdomain is reserved; lowercase alphanumeric, between 4 and 32 characters in length
# WARNING: changes take effect the next time the frontend URL is reserved # WARNING: changes take effect the next time the frontend URL is reserved
#ZROK_UNIQUE_NAME="" #ZROK_UNIQUE_NAME=""

View File

@ -14,4 +14,4 @@ Restart=always
RestartSec=3 RestartSec=3
[Install] [Install]
WantedBy=multi-user.target WantedBy=default.target

48
ui/package-lock.json generated
View File

@ -210,26 +210,26 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.26.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.25.9", "@babel/template": "^7.27.0",
"@babel/types": "^7.26.0" "@babel/types": "^7.27.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.26.2", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.26.0" "@babel/types": "^7.27.0"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@ -271,9 +271,9 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.26.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
@ -283,14 +283,14 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.25.9", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.25.9", "@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.25.9", "@babel/parser": "^7.27.0",
"@babel/types": "^7.25.9" "@babel/types": "^7.27.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -324,9 +324,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.26.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.25.9", "@babel/helper-string-parser": "^7.25.9",
@ -4739,9 +4739,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.2.0", "version": "6.2.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz",
"integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", "integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {