Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 6 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

## What this is
Expose any local service on an Omarchy machine (model API, web app, desktop) with one command and a chosen auth mode — no containers/K8s/user system.
Core: `omarchy-remote expose <name> <port>` (private, Tailscale — the default) or `... --auth login|token|password|none [--via pangolin|cloudflare]` (public).
Core: `omarchy-remote expose <name> <port>` (private, Tailscale — the default) or `... --auth login|token|password|bearer|none [--via cloudflare|pangolin]` (public; default provider Cloudflare, or Pangolin if only it is set up).
Private path: `tailscale serve --https=<port>`, tracked by name in `~/.config/omarchy-remote/tailscale.map`.
Public path: Pangolin resource `<name>.<NS-delegated domain>` → site tunnel → 127.0.0.1:<port> (login/token/password/none),
or Cloudflare: proxied CNAME `<name>.<zone>` → tunnel `omarchy-remote-<host>` (user unit `omarchy-remote-tunnel`) → 127.0.0.1:<port> (bearer/none; bearer = WAF custom rule).
or Cloudflare: proxied CNAME `<name>.<zone>` → tunnel `omarchy-remote-<host>` (user unit `omarchy-remote-tunnel`) → 127.0.0.1:<port> (login/token = Cloudflare Access, bearer = WAF custom rule, none).
Built-in service: browser remote desktop (noVNC :6080 → wayvnc :5900 → Hyprland).
Long-term goal: a lightweight personal cloud on top of Omarchy (no Kubernetes, containers, or heavyweight user system):
"your own computer, managed by AI, reachable anywhere". Planned: zero-config access via a shared domain + self-hosted
Expand Down Expand Up @@ -55,7 +55,10 @@ per-user desktops, and distribution through the Omarchy plugin marketplace.
- Cloudflare: only `<name>.<zone>` (one level) — the free Universal SSL cert doesn't cover deeper names. Never overwrite existing DNS records.
- Cloudflare negative answers are cached up to 30 min (SOA minimum 1800); probe new hostnames via DoH (`cf_probe`), not the local resolver.
- WAF custom rules take ~10–30 s to reach the edge; `expose --auth bearer` must see a keyless 403 before reporting success. Free plan: 5 custom rules per zone.
- Cloudflare API token needs: Account Cloudflare Tunnel Edit; Zone DNS Edit, Zone WAF Edit, Zone Read. Access (login/token on Cloudflare) is not implemented.
- Cloudflare API token needs: Account Cloudflare Tunnel Edit; Zone DNS Edit, Zone WAF Edit, Zone Read. `login`/`token` on Cloudflare also need Zero Trust on the account plus Access: Apps and Policies, Service Tokens, and Organizations/IdPs/Groups (Edit).
- Cloudflare `login`/`token` = Access app `omarchy-remote:<name>:<auth>` + reusable policy `omarchy-remote:<name>` (emails / service token, decision `non_identity`) + service token `omarchy-remote:<name>`. Inline policies in the app body can't carry `include`; create the policy first, delete the app before its policy. Never verified against a live Zero Trust org: the test account's org belongs to Eros (don't use it); mock tests only.
- Cloudflare `password` is refused: a WAF block can't send `WWW-Authenticate`, so browsers would never get a prompt.
- Rollback traps run after errexit has unwound the function, so locals are gone: bake values into the trap string (`'"$fqdn"'`), don't reference `$fqdn` inside it.
- noVNC settings live in `$NOVNC_DIR/defaults.json`; `index.html` symlinks to `vnc.html` so `/` opens the desktop.

## Roadmap
Expand Down
45 changes: 27 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,25 @@ model API, a web app you are building, or the desktop itself — with one comman
of your choice. No containers, no Kubernetes, no public IP, no open ports.

```
┌─► 127.0.0.1:11434 (e.g. a model API)
browser / app ─► Pangolin ┼─► 127.0.0.1:3000 (e.g. your web app)
(public + login) └─► 127.0.0.1:6080 (the desktop, via noVNC)
your own devices ─► Tailscale (private, peer-to-peer)
your own devices ── Tailscale (private, peer-to-peer) ──┐ ┌─► 127.0.0.1:11434 (e.g. a model API)
├────┼─► 127.0.0.1:3000 (e.g. your web app)
anyone, any app ─── Cloudflare or Pangolin (public) ────┘ └─► 127.0.0.1:6080 (the desktop, via noVNC)
```

```bash
omarchy-remote expose app 3000
# -> https://<machine>.<tailnet>.ts.net:3000 (default: Tailscale, only your own devices)
omarchy-remote expose app 3000 --auth login --allow friend@example.com
# -> https://app.home.example.com (public; browser login, your friend gets an email code)
# -> https://app.example.com (public; browser login, your friend gets an email code)
omarchy-remote expose ai 11434 --auth bearer
# -> https://ai.example.com (public; OpenAI-style clients send "Authorization: Bearer <key>")
```

Private URLs go through **Tailscale** (free for personal use, no domain needed). Public URLs
go through **Pangolin** (browser login, tokens, passwords) or **Cloudflare** (API keys checked
at Cloudflare's edge — works with OpenAI-compatible clients and SDKs, including streaming).
go through the provider you pick: **Cloudflare** (browser login and service tokens via
Cloudflare Access, plus API keys checked at Cloudflare's edge — works with OpenAI-compatible
clients and SDKs, including streaming) or **Pangolin** (browser login, tokens, passwords).
Set up one or both.

How your service runs (a binary, a script, a container) is up to you — `expose` only needs a
port on `127.0.0.1`. The tool also ships a browser remote desktop as a ready-made service.
Expand Down Expand Up @@ -76,14 +77,19 @@ Optional, so `expose` doesn't need sudo: `sudo tailscale set --operator=$USER`.
```
The key is stored in `~/.config/omarchy-remote/pangolin.key` (mode 600).

## One-time setup: Cloudflare (public URLs, API keys)
## One-time setup: Cloudflare (public URLs)

Needs a domain on Cloudflare (the free plan is enough). Services get `https://<name>.<domain>`
(one level below the domain, so Cloudflare's free certificate covers them).

1. Create an API token: My Profile → API Tokens → Create Token → Custom token, with
**Account: Cloudflare Tunnel (Edit)** and **Zone: DNS (Edit), Zone WAF (Edit), Zone (Read)**,
limited to your account and that zone.
For `--auth login` / `--auth token`, also enable **Zero Trust** on the account (the free
plan covers 50 users) and add **Account: Access: Apps and Policies (Edit), Access: Service
Tokens (Edit), Access: Organizations, Identity Providers, and Groups (Edit)**. The first
`login` service turns on the email one-time-code login method for your Zero Trust
organization if it isn't on yet.
2. On the Omarchy machine:
```bash
omarchy-remote cloudflare login # asks for the token (hidden) and the domain
Expand All @@ -103,25 +109,28 @@ omarchy-remote unexpose <name>
```

Without `--auth` the URL is private (Tailscale). With `--auth` it is public, through the
provider in `--via pangolin|cloudflare`, or the default set with
`omarchy-remote config set public <provider>` (initially `pangolin`; `--auth bearer` always
uses Cloudflare).
provider in `--via cloudflare|pangolin`, or the default set with
`omarchy-remote config set public <provider>`. Unset, the default is Cloudflare (Pangolin if
only Pangolin is set up on this machine); `--auth bearer` always uses Cloudflare and
`--auth password` always uses Pangolin.

| `--auth` | For | How to get in | Share with someone | Providers |
|---|---|---|---|---|
| `login` | people, in a browser | login page: members of your Pangolin org sign in with their account; others get an email code | `--allow a@x.com,b@y.com` | Pangolin |
| `token` | programs / APIs | headers `P-Access-Token-Id` + `P-Access-Token`, or `?p_token=<id>.<token>` | give them the token | Pangolin |
| `login` | people, in a browser | login page with an email code. Pangolin: members of your Pangolin org can also sign in with their account | `--allow a@x.com,b@y.com` (required on Cloudflare) | Cloudflare, Pangolin |
| `token` | programs / APIs | Cloudflare: headers `CF-Access-Client-Id` + `CF-Access-Client-Secret`. Pangolin: headers `P-Access-Token-Id` + `P-Access-Token`, or `?p_token=<id>.<token>` | give them the token | Cloudflare, Pangolin |
| `password` | programs and browsers | HTTP Basic: `curl -u user:pass …` or `https://user:pass@host/` | give them the password | Pangolin |
| `bearer` | programs, OpenAI-style clients | `Authorization: Bearer <key>` (e.g. `base_url=https://ai.example.com/v1`, `api_key=<key>`) | give them the key | Cloudflare |
| `none` | everyone | no login — requires `--yes-public` | share the URL | Pangolin, Cloudflare |
| `none` | everyone | no login — requires `--yes-public` | share the URL | Cloudflare, Pangolin |

Passwords, tokens and keys are generated for you and **printed once** — save them.

A new Pangolin address needs about 30 seconds for its certificate (404 / connection errors until
then). `expose --auth bearer` waits until Cloudflare refuses keyless requests before it reports
success (usually 10–30 s). After `unexpose`, an address may keep answering for a few seconds.
then). On Cloudflare, `expose` with `bearer`, `login` or `token` waits until requests without
credentials are refused before it reports success (usually 10–30 s). After `unexpose`, an address may keep answering for a few seconds.

> `bearer` uses one Cloudflare WAF custom rule per service; the free plan allows 5 custom rules
> On Cloudflare, `login` and `token` create one Access application (plus a policy, and for
> `token` a service token) per service, all named `omarchy-remote:<name>`; `unexpose` removes
> them. `bearer` uses one Cloudflare WAF custom rule per service; the free plan allows 5 custom rules
> per domain (shared with any rules you made yourself). Anyone who can open your Cloudflare
> dashboard can read the keys in those rules.

Expand All @@ -148,8 +157,8 @@ Shows what this machine exposes, with copy/open buttons for each URL

```
omarchy-remote expose <name> <port> private URL via Tailscale
omarchy-remote expose <name> <port> --auth login|token|bearer|none [--allow emails] [--via cloudflare]
omarchy-remote expose <name> <port> --auth login|token|password|none [--allow emails] [--user name] [--via pangolin]
omarchy-remote expose <name> <port> --auth bearer|none [--via cloudflare]
omarchy-remote unexpose <name>
omarchy-remote config set public pangolin|cloudflare
omarchy-remote list show exposed services
Expand Down
28 changes: 14 additions & 14 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@
在任何地方访问你 [Omarchy](https://omarchy.org) 电脑上的服务:本地模型的 API、你正在开发的网页,或者桌面本身。一条命令暴露出去,再选一种登录方式。不需要容器、不需要 Kubernetes、不需要公网 IP,也不用开端口。

```
┌─► 127.0.0.1:11434 (比如模型 API)
浏览器 / 程序 ─► Pangolin ┼─► 127.0.0.1:3000 (比如你的网页)
(公网 + 登录) └─► 127.0.0.1:6080 (桌面,通过 noVNC)
你自己的设备 ─► Tailscale(私有,点对点直连)
你自己的设备 ── Tailscale(私有,点对点直连)───┐ ┌─► 127.0.0.1:11434 (比如模型 API)
├────┼─► 127.0.0.1:3000 (比如你的网页)
任何人、任何程序 ── Cloudflare 或 Pangolin(公网)┘ └─► 127.0.0.1:6080 (桌面,通过 noVNC)
```

```bash
omarchy-remote expose app 3000
# -> https://<机器名>.<tailnet>.ts.net:3000(默认走 Tailscale,只有你自己的设备能访问)
omarchy-remote expose app 3000 --auth login --allow friend@example.com
# -> https://app.home.example.com(公网;浏览器登录,朋友用邮箱验证码进入)
# -> https://app.example.com (公网;浏览器登录,朋友用邮箱验证码进入)
omarchy-remote expose ai 11434 --auth bearer
# -> https://ai.example.com (公网;OpenAI 风格的客户端用 "Authorization: Bearer <key>" 调用)
```

私有地址走 **Tailscale**(个人使用免费,不需要域名)。公网地址走 **Pangolin**(浏览器登录、访问令牌、密码),或者走 **Cloudflare**(在 Cloudflare 边缘校验 API key,兼容 OpenAI 风格的客户端和 SDK,包括流式输出)。
私有地址走 **Tailscale**(个人使用免费,不需要域名)。公网地址走你选的提供方:**Cloudflare**(通过 Cloudflare Access 做浏览器登录和服务令牌,还能在 Cloudflare 边缘校验 API key,兼容 OpenAI 风格的客户端和 SDK,包括流式输出),或者 **Pangolin**(浏览器登录、访问令牌、密码)。两个都配或只配一个都行。

服务怎么跑(二进制、脚本还是容器)由你决定,`expose` 只要求它在 `127.0.0.1` 上监听一个端口。工具还自带一个浏览器远程桌面,作为现成的服务。

Expand Down Expand Up @@ -56,11 +55,12 @@ curl -fsSL https://raw.githubusercontent.com/second-state/omarchy-remote/main/in
```
密钥保存在 `~/.config/omarchy-remote/pangolin.key`(权限 600)。

## 一次性配置:Cloudflare(公网地址、API key)
## 一次性配置:Cloudflare(公网地址)

需要一个托管在 Cloudflare 上的域名,免费版就够。服务地址是 `https://<名字>.<域名>`:只能在域名下一层,这样 Cloudflare 免费证书才能覆盖。

1. 创建 API 令牌:我的个人资料 → API 令牌 → 创建令牌 → 自定义令牌。权限选 **帐户:Cloudflare Tunnel(编辑)**,以及 **区域:DNS(编辑)、区域 WAF(编辑)、区域(读取)**,作用范围限定为你的帐户和这个域名。
要用 `--auth login` 或 `--auth token`,还需要在这个帐户上开通 **Zero Trust**(免费版支持 50 个用户),并给令牌加上 **帐户:Access: Apps and Policies(编辑)、Access: Service Tokens(编辑)、Access: Organizations, Identity Providers, and Groups(编辑)**。第一个 `login` 服务会为你的 Zero Trust 组织开启邮箱验证码登录(如果还没开)。
2. 在 Omarchy 机器上执行:
```bash
omarchy-remote cloudflare login # 输入令牌(不会显示)并选择域名
Expand All @@ -78,21 +78,21 @@ omarchy-remote list
omarchy-remote unexpose <名字>
```

不带 `--auth` 时是私有地址(Tailscale)。带了 `--auth` 就是公网地址,走 `--via pangolin|cloudflare` 指定的提供方;不写 `--via` 时用 `omarchy-remote config set public <提供方>` 设置的默认值(初始为 `pangolin`;`--auth bearer` 总是走 Cloudflare)。
不带 `--auth` 时是私有地址(Tailscale)。带了 `--auth` 就是公网地址,走 `--via cloudflare|pangolin` 指定的提供方;不写 `--via` 时用 `omarchy-remote config set public <提供方>` 设置的默认值。没设置时默认走 Cloudflare(如果这台机器只配了 Pangolin,就走 Pangolin);`--auth bearer` 总是走 Cloudflare,`--auth password` 总是走 Pangolin。

| `--auth` | 给谁用 | 怎么进入 | 分享给别人 | 提供方 |
|---|---|---|---|---|
| `login` | 人,用浏览器 | 登录页:Pangolin 组织成员用账号直接登录,其他人用邮箱验证码 | `--allow a@x.com,b@y.com` | Pangolin |
| `token` | 程序 / API | 请求头 `P-Access-Token-Id` + `P-Access-Token`,或 `?p_token=<id>.<token>` | 把令牌发给对方 | Pangolin |
| `login` | 人,用浏览器 | 登录页,用邮箱验证码进入。Pangolin 上,组织成员也可以用账号直接登录 | `--allow a@x.com,b@y.com`(Cloudflare 上必填) | Cloudflare、Pangolin |
| `token` | 程序 / API | Cloudflare:请求头 `CF-Access-Client-Id` + `CF-Access-Client-Secret`。Pangolin:请求头 `P-Access-Token-Id` + `P-Access-Token`,或 `?p_token=<id>.<token>` | 把令牌发给对方 | Cloudflare、Pangolin |
| `password` | 程序和浏览器都行 | HTTP Basic:`curl -u 用户:密码 …` 或 `https://用户:密码@域名/` | 把密码发给对方 | Pangolin |
| `bearer` | 程序、OpenAI 风格的客户端 | `Authorization: Bearer <key>`(比如 `base_url=https://ai.example.com/v1`,`api_key=<key>`) | 把 key 发给对方 | Cloudflare |
| `none` | 所有人 | 不需要登录,必须加 `--yes-public` 确认 | 直接发地址 | Pangolin、Cloudflare |
| `none` | 所有人 | 不需要登录,必须加 `--yes-public` 确认 | 直接发地址 | Cloudflare、Pangolin |

密码、令牌和 key 都由工具自动生成,**只显示一次**,请自己保存好。

Pangolin 的新地址需要大约 30 秒签发证书,在此之前出现 404 或连不上是正常的。`expose --auth bearer` 会等到 Cloudflare 确实拒绝不带 key 的请求后才报告成功(通常 10~30 秒)。`unexpose` 之后,地址可能还会响应几秒。
Pangolin 的新地址需要大约 30 秒签发证书,在此之前出现 404 或连不上是正常的。在 Cloudflare 上,`bearer`、`login`、`token` 三种方式的 `expose` 都会等到不带凭据的请求确实被拒绝后才报告成功(通常 10~30 秒)。`unexpose` 之后,地址可能还会响应几秒。

> 每个 `bearer` 服务占用一条 Cloudflare WAF 自定义规则。免费版每个域名最多 5 条,跟你自己建的规则共用。能打开你 Cloudflare 后台的人,都能看到这些规则里的 key。
> 在 Cloudflare 上,每个 `login` 或 `token` 服务会建一个 Access 应用和一条策略(`token` 还会建一个服务令牌),名字都是 `omarchy-remote:<名字>`,`unexpose` 时一并删除。每个 `bearer` 服务占用一条 Cloudflare WAF 自定义规则。免费版每个域名最多 5 条,跟你自己建的规则共用。能打开你 Cloudflare 后台的人,都能看到这些规则里的 key。

### 远程桌面

Expand All @@ -116,8 +116,8 @@ omarchy plugin add https://github.com/second-state/omarchy-remote-plugin --enabl

```
omarchy-remote expose <名字> <端口> 通过 Tailscale 生成私有地址
omarchy-remote expose <名字> <端口> --auth login|token|bearer|none [--allow 邮箱] [--via cloudflare]
omarchy-remote expose <名字> <端口> --auth login|token|password|none [--allow 邮箱] [--user 用户名] [--via pangolin]
omarchy-remote expose <名字> <端口> --auth bearer|none [--via cloudflare]
omarchy-remote unexpose <名字>
omarchy-remote config set public pangolin|cloudflare
omarchy-remote list 查看已暴露的服务
Expand Down
Loading
Loading