Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
aab4b7a
feat(proxy): enhance domain management and TLS configuration
marcomc May 25, 2026
5e585dd
feat(proxy): add documentation for custom global proxy domains
marcomc May 25, 2026
9735ea2
feat(proxy): enhance domain management with conflict resolution for T…
marcomc May 25, 2026
e2c18e8
refactor: simplify conditional checks and improve error message forma…
marcomc May 26, 2026
f44a784
refactor(proxy): streamline domain command executions by reducing red…
marcomc May 26, 2026
1bd146b
feat(proxy): update documentation for custom proxy domains and improv…
marcomc Jun 15, 2026
077d98c
feat(proxy): add output option for TLS configuration command and impl…
marcomc Jun 15, 2026
b1c0cd0
fix(proxy): correct command syntax for adding and updating Global Pro…
marcomc Jun 15, 2026
fed1ba8
fix(proxy): update command syntax for adding and updating Global Prox…
marcomc Jun 15, 2026
8b233c2
feat(proxy): enhance documentation for custom proxy domains and updat…
marcomc Jun 16, 2026
498730c
fix(Collection): update source handling to use the last defined attri…
marcomc Jun 16, 2026
5be6146
test(Attribute): add test for attribute metadata source precedence
marcomc Jun 16, 2026
2ef1a66
feat(Workspace): add attributeMetadata method to retrieve attribute m…
marcomc Jun 16, 2026
20aaf8d
feat(proxy): enhance proxy domain handling with validation and update…
marcomc Jun 16, 2026
c8288fd
feat(proxy): update proxy configuration documentation with internal U…
marcomc Jun 16, 2026
a15989b
Fix proxy domain registry shadow checks
marcomc Jun 16, 2026
8e50383
Check proxy domain updates against registry
marcomc Jun 16, 2026
97f2dfb
Check proxy domain imports against registry
marcomc Jun 16, 2026
750b99f
feat(proxy): update command for Traefik host rule to use host-prefix …
marcomc Jun 17, 2026
84d0402
feat(proxy): add validation for DNS hostname length in Traefik host rule
marcomc Jun 17, 2026
e598829
feat(proxy): enhance error handling for temporary file writes in Cert…
marcomc Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions config/workspace/global.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

command('global service logger (enable|disable)', 'global service logger'): |
command('global service logger (enable|disable|restart)', 'global service logger'): |
#!bash|=
ws-service logger ={input.command(4)}

command('global service mail (enable|disable)', 'global service mail'):
command('global service mail (enable|disable|restart)', 'global service mail'):
description: Actions for mailhog (mail.my127.site)
exec: |
#!bash|=
Expand All @@ -15,6 +15,94 @@ command('global service proxy (enable|disable|restart)', 'global service proxy')
#!bash|=
ws-service proxy ={input.command(4)}

command('global service proxy config domain list', 'global service proxy config domain list'):
description: List configured Global Proxy domains
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyDomainCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyDomainCommand::domainList($domains);

command('global service proxy config domain add <id> --name=<name> --crt=<certificate-url> --key=<key-url> [--crt-file=<filename>] [--key-file=<filename>]', 'global service proxy config domain add'):
description: Register a Global Proxy domain
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyDomainCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyDomainCommand::domainAdd($domains, $input, home());

command('global service proxy config domain update <id> --name=<name> --crt=<certificate-url> --key=<key-url> [--crt-file=<filename>] [--key-file=<filename>]', 'global service proxy config domain update'):
description: Replace a registered Global Proxy domain
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyDomainCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyDomainCommand::domainUpdate($domains, $input, home(), $ws);

command('global service proxy config domain remove <id>', 'global service proxy config domain remove'):
description: Remove a registered Global Proxy domain
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyDomainCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyDomainCommand::domainRemove($domains, $input, home(), $ws);

command('global service proxy config domain import <source>', 'global service proxy config domain import'):
description: Import registered Global Proxy domains
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyDomainCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyDomainCommand::domainImport($domains, $input, home());

command('global service proxy config rule <service>', 'global service proxy config rule'):
description: Print a Traefik Host rule for a Global Proxy service
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyRuntimeCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyRuntimeCommand::rule($domains, $input);

command('global service proxy config tls [--output=<file>]', 'global service proxy config tls'):
description: Print Traefik TLS configuration
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyRuntimeCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyRuntimeCommand::tls($domains, $input);

command('global service proxy config certificates download <dir>', 'global service proxy config certificates download'):
description: Download configured Global Proxy certificates
exec: |
#!php(cwd:/)
use my127\Workspace\GlobalService\Proxy\ProxyDomainConfiguration;
use my127\Workspace\GlobalService\Proxy\ProxyRuntimeCommand;

$domains = ProxyDomainConfiguration::assertDomainMap($ws['global.service.proxy.domains'] ?? []);

ProxyRuntimeCommand::downloadCertificates($domains, $input, home());

command('global service tracing (start|stop|restart)', 'global service tracing'): |
#!bash|=
ws-service tracing ={input.command(4)}
Expand Down
15 changes: 14 additions & 1 deletion docs/cheatsheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ You need only to restart the Traefik proxy service:
ws global service proxy restart
```

## Custom proxy domains

```bash
ws global service proxy config domain add mydomain \
--name=mydomain.site \
--crt=<certificate-url> \
--key=<key-url>
ws global service proxy restart
```

See [Custom Global Proxy domains](custom-proxy-domains.md) for import, update,
remove, and project configuration examples.

## How-to receive email

The email service is not running by default. It can be started with:
Expand All @@ -20,4 +33,4 @@ ws global service mail enable

This will allow email to be viewed at `https://mail.my127.site/`

This will collect email sent from any server through native `sendmail`.
This will collect email sent from any server through native `sendmail`.
146 changes: 146 additions & 0 deletions docs/custom-proxy-domains.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Custom Global Proxy domains

Workspace uses `my127.site` by default for local HTTPS hostnames. You can also
register extra Global Proxy domains on your machine, so different projects can
use different DNS suffixes while sharing the same Traefik proxy.

## Contents

- [Register a domain](#register-a-domain)
- [Host the proxy configuration files](#host-the-proxy-configuration-files)
- [Use a domain in a project](#use-a-domain-in-a-project)

Registered domains are stored in:

```text
~/.config/my127/workspace/proxy-domains.yml
```

The built-in `my127.site` domain remains available by default. Do not add it to
`proxy-domains.yml`; that file is for extra domains registered on the machine.

## Register a domain

A proxy domain must be registered before the Global Proxy can serve it. The
certificate and key must be reachable `http://` or `https://` URLs.

Certificate URLs may point at private locations that are already reachable from
the machine, such as an internal HTTP(S) endpoint.

The certificate must cover the bare domain and the subdomains used by projects
or global services. For example, a certificate for `mydomain.site` should also
cover `*.mydomain.site`.

## Host the proxy configuration files

Before developers import a custom proxy domain, the organisation needs stable
HTTP(S) URLs for:

- `domains.yml`: the Workspace proxy domain configuration to import.
- `fullchain.pem`: the certificate served by the Global Proxy.
- `privkey.pem`: the private key used by that certificate.

The files do not need to live in the same repository, website, or directory.
The certificate and key URLs are read from the attributes in `domains.yml`; the
layout below keeps them together only to make the example easy to follow.

GitHub raw URLs work only for public repositories because Workspace does not
authenticate to GitHub. Use them for `domains.yml`, or for disposable test
certificates. For private keys, use organisation-only HTTP(S) URLs reachable
from developer machines.

Suggested structure for one or more domains:

```text
proxy-config/
├── domains.yml
└── certs/
├── mydomain.site/
│ ├── fullchain.pem
│ └── privkey.pem
└── otherdomain.site/
├── fullchain.pem
└── privkey.pem
```

`domains.yml` can reference any number of domains. Each entry should point to
the hosted certificate and key URLs for that domain. When certificates are
renewed, update the hosted certificate files and keep the URLs stable, then
developers only need to restart the Global Proxy. Re-import `domains.yml` only
when registering a new machine or adding a domain; use `domain update` when an
existing domain definition changes.

The following commands are alternatives for common tasks. Run the one that
matches the change you want to make.

```bash
# List registered proxy domains.
ws global service proxy config domain list

# Register one domain manually.
ws global service proxy config domain add mydomain \
--name=mydomain.site \
--crt=https://proxy-config.example.internal/certs/mydomain.site/fullchain.pem \
--key=https://proxy-config.example.internal/certs/mydomain.site/privkey.pem

# Import domains from a local file.
ws global service proxy config domain import domains.yml

# Import domains from a public GitHub repository.
ws global service proxy config domain import https://raw.githubusercontent.com/my-org/public-proxy-config/main/domains.yml

# Import domains from an internal website.
ws global service proxy config domain import https://proxy-config.example.internal/domains.yml

# Replace one registered domain.
ws global service proxy config domain update mydomain \
--name=mydomain.site \
--crt=https://proxy-config.example.internal/certs/mydomain.site/fullchain.pem \
--key=https://proxy-config.example.internal/certs/mydomain.site/privkey.pem

# Remove one registered domain.
ws global service proxy config domain remove mydomain
```

Example `domains.yml`:

```yaml
attributes:
global:
service:
proxy:
domains:
mydomain:
name: mydomain.site
https:
crt: https://proxy-config.example.internal/certs/mydomain.site/fullchain.pem
key: https://proxy-config.example.internal/certs/mydomain.site/privkey.pem
crt_file: mydomain.site.crt
key_file: mydomain.site.key
otherdomain:
name: otherdomain.site
https:
crt: https://proxy-config.example.internal/certs/otherdomain.site/fullchain.pem
key: https://proxy-config.example.internal/certs/otherdomain.site/privkey.pem
crt_file: otherdomain.site.crt
key_file: otherdomain.site.key
```

After changing registered domains, restart the proxy:

```bash
ws global service proxy restart
```

Enabled global services such as mail, logger, or tracing may also need a
restart before they pick up the new host rules.

## Use a domain in a project

A project uses the normal `domain` attribute. Register the proxy domain first,
then set the project domain:

```yaml
attributes:
domain: mydomain.site
```
35 changes: 31 additions & 4 deletions home/lib/sidekick.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ INDICATOR_PASSTHRU="37m"

prompt()
{
if [ "${RUN_CWD}" != "$(pwd)" ]; then
RUN_CWD="$(pwd)"
echo -e "\\033[1m[\\033[0m$(pwd)\\033[1m]:\\033[0m" >&2
local CWD

CWD="$(pwd)"

if [ "${RUN_CWD}" != "$CWD" ]; then
RUN_CWD="$CWD"
echo -e "\\033[1m[\\033[0m$CWD\\033[1m]:\\033[0m" >&2
fi
}

Expand Down Expand Up @@ -82,9 +86,32 @@ passthru()

setCommandIndicator()
{
echo -ne "\\033[1A" >&2
echo -ne "\\033[1A" >&2
echo -ne "\\033[$1" >&2
echo -n "■" >&2
echo -ne "\\033[0m" >&2
echo -ne "\\033[1E" >&2
}

updateEnvGeneratedKey()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updates one generated key in a .env file.

It rewrites the file by removing any existing line that starts with the same KEY=, preserves all other lines, then appends the new KEY=value at the end.

{
local -r FILE="$1"
local -r KEY="$2"
local -r VALUE="$3"
local TEMP_FILE
local LINE

TEMP_FILE="$(mktemp "$FILE.XXXXXX")"

if [ -f "$FILE" ]; then
while IFS= read -r LINE || [ -n "$LINE" ]; do
case "$LINE" in
"$KEY="*) ;;
*) printf '%s\n' "$LINE" >> "$TEMP_FILE" ;;
esac
done < "$FILE"
fi

printf '%s=%s\n' "$KEY" "$VALUE" >> "$TEMP_FILE"
mv "$TEMP_FILE" "$FILE"
}
1 change: 1 addition & 0 deletions home/service/logger/.env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
TRAEFIK_NETWORK=my127ws
TRAEFIK_KIBANA_RULE=Host(`kibana.my127.site`)
2 changes: 1 addition & 1 deletion home/service/logger/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
- elasticsearch
labels:
- traefik.enable=true
- traefik.http.routers.kibana.rule=Host(`kibana.my127.site`)
- traefik.http.routers.kibana.rule=${TRAEFIK_KIBANA_RULE}
- traefik.http.services.kibana.loadbalancer.server.port=5601
- traefik.docker.network=${TRAEFIK_NETWORK}
networks:
Expand Down
18 changes: 17 additions & 1 deletion home/service/logger/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,25 @@ main()
disable
exit
fi

if [ "$1" = "restart" ]; then
restart
exit
fi
}

enable()
{
local TRAEFIK_KIBANA_RULE

TRAEFIK_KIBANA_RULE="$(ws global service proxy config rule logger)"
updateEnvGeneratedKey ".env" "TRAEFIK_KIBANA_RULE" "$TRAEFIK_KIBANA_RULE"

if [ ! -f .flag-built ]; then
run docker-compose -p my127ws-logger up -d --build
touch .flag-built
else
run docker-compose -p my127ws-logger start
run docker-compose -p my127ws-logger up -d

@marcomc marcomc Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start would not pick up the new generated Traefik rule.
Logger now writes TRAEFIK_KIBANA_RULE into .env before enabling the service. That value is used inside the Compose labels:

    labels:
      - traefik.enable=true
      - traefik.http.routers.kibana.rule=${TRAEFIK_KIBANA_RULE}

docker-compose up -d reconciles the Compose project: if the interpolated label changed, it recreates the affected container; if nothing changed, it is effectively a start/no-op.

fi
}

Expand All @@ -34,6 +44,12 @@ disable()
run docker-compose -p my127ws-logger stop
}

restart()
{
disable
enable
}

bootstrap()
{
DIR="$(cd "$(dirname "$0")" && pwd)"
Expand Down
1 change: 1 addition & 0 deletions home/service/mail/.env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
TRAEFIK_NETWORK=my127ws
TRAEFIK_MAIL_RULE=Host(`mail.my127.site`)
2 changes: 1 addition & 1 deletion home/service/mail/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ services:
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.http.routers.mail.rule=Host(`mail.my127.site`)
- traefik.http.routers.mail.rule=${TRAEFIK_MAIL_RULE}
- traefik.http.services.mail.loadbalancer.server.port=8025
networks:
- private
Expand Down
Loading
Loading