Remotes
Named remotes for the pocketone command: the apps a project deploys to, each signed in to once. Give an app a name
like prod, log in, and commands such as pocketone deploy prod know where to go and who you are.
pocketone remote add prod https://myapp.example.com
pocketone login prod
pocketone deploy prod # Deploy
Install · Configure · Use · Integrations · Example
Install
Remotes is built into pocketone and on: the remote, login, logout and link commands are always there, and
every app it serves answers GET /_pocker/whoami so other machines can sign in to it. There's nothing to install.
In your own Go build, register it before the app starts:
app := pocketbase.New()
remotes.MustRegister(app, remotes.Config{}) // whoami route; on *pocketbase.PocketBase also the commands
app.Start()
The module is github.com/pockethost/pocketone/ext/remotes. Add Tokens so a password login
can create a revocable API token, and OAuth Server for browser login.
Configure
There is no pocketone.yaml section, flag or settings screen. Remotes keeps two files and reads a few environment
variables.
The project's remotes
.pocketbase/remotes.json in the project holds names and URLs only, so commit it:
{"default": "prod", "remotes": {"prod": {"url": "https://myapp.example.com"}}}
Commands find it by walking up from the current folder. It isn't a pb_* name, since pb_* folders are deployed.
A project linked with npx pockethost link has a .pockethostconfig ({"instanceName": "myapp"}). It reads as the
remote pockethost → https://myapp.pockethost.io (a name with a dot is taken as a custom domain).
Saved logins
Logins are saved per user, keyed by URL, in ~/.config/pocketone/credentials.json (mode 0600). Set
POCKETONE_CONFIG_DIR to use another folder; with XDG_CONFIG_HOME set, it's $XDG_CONFIG_HOME/pocketone/.
Environment variables
| Variable | What it does |
|---|---|
POCKETONE_TOKEN_<REMOTE> |
A token for one remote, for CI. The name is upper case, other characters as _ (POCKETONE_TOKEN_PROD). |
POCKETONE_TOKEN |
A token for any remote. |
POCKETHOST_TOKEN |
A token for the pockethost remote. |
POCKETONE_PASSWORD |
The password for login --email, so it isn't asked for. |
A token in the environment wins over a saved login. The POCKETONE in these names and the pocketone folder come
from the command's name, so a build shipped under another name uses its own (POCKETHOST_TOKEN, ~/.config/pockethost).
Use
Manage remotes
pocketone remote add prod https://myapp.example.com --default
pocketone remote list # names, URLs and who you're logged in as; * marks the default
pocketone remote default prod
pocketone remote rm staging # the saved login stays; see logout
pocketone remote whoami prod # who you are there, your token's scopes, and what the app offers
A URL without a scheme gets https://. The first remote you add becomes the default. A command that names no remote
uses the default, else the only remote, else the one called default. Any command also takes a URL in place of a
name: pocketone deploy https://other.example.com.
Log in
pocketone login prod
login tries, in order:
- The browser, when the remote runs OAuth Server. Your browser opens, you sign in
and approve, and the command finishes. Over SSH or without a display it shows a code to enter on another device
instead;
--deviceasks for that, and--no-browserprints the URL without opening it. - A pasted token (
pbt_..., a PocketHost token, or a PocketBase auth token). At the prompt, paste it, or pass--token <token>or--token-stdin. - Email and password. Type a superuser email at the prompt, or pass
--email(or--passwordto skip the browser).--collectionpicks another auth collection (default_superusers). If the account has MFA, a one-time code is sent and asked for.
A password login creates an API token named "pocketone CLI on
You'll see Logged in to https://myapp.example.com as [email protected] (oauth).
Link in one step
pocketone link https://myapp.example.com
pocketone link https://myapp.pockethost.io ph_pat_...
link adds the remote (named default, or --name), makes it the default if there's none yet, and logs in. It takes
the same login flags.
Log out
pocketone logout prod
Forgets the saved login. It also revokes it on the remote: the API token a password login created, or the browser login's refresh token. If the remote can't be reached, it tells you to revoke it in the dashboard.
Integrations
Works with
| Extension | How they fit |
|---|---|
| Deploy | deploy, dev, pull and logs resolve their remote and login through Remotes, and find the deploy API through /_pocker/whoami. |
| OAuth Server | Advertises itself as the oauth feature in /_pocker/whoami; login then signs in through the browser (loopback redirect on 127.0.0.1 with PKCE) or a device code. |
| Tokens | Password logins create an API token, and logout deletes it. Whoami reports a token's id and scopes. |
| PocketHost | .pockethostconfig and POCKETHOST_TOKEN work as the pockethost remote. A remote without /_pocker/whoami takes a pasted token as is. |
GET /_pocker/whoami
Every app with Remotes answers who the request's credential is and what the app offers, so clients can discover features:
{"auth": {"collection": "_superusers", "id": "…", "email": "[email protected]"}, "via": "token",
"tokenId": "…", "scopes": ["deploy"], "features": {"deploy": "/_pocker/deploy/v1", "tokens": "/api/tokens"},
"app": {"name": "Acme", "url": "https://myapp.example.com"}}
via is token for an API token, jwt for a PocketBase auth token, and empty with no credential (auth is then
null). Every API token may call it, whatever its scopes.
Building on Remotes
- Advertise a feature.
remotes.Advertise(app, "deploy", "/_pocker/deploy/v1")lists it in whoami. Deploy and OAuth Server do this; Tokens is detected without importing it. - Go client.
remotes.Open(dir, name)resolves a remote and its credential (environment first, then the saved login, renewing a browser login when due) and returns a*remotes.Client: base URL, bearer token, an HTTP client with short connect timeouts, andJSON,NewRequest,DoandWhoami. Deploy's commands build on it. - Commands.
remotes.Commands()returns theremote,login,logoutandlinkcommands, for a binary that isn't a*pocketbase.PocketBase. - Login.
remotes.BrowserLoginis the hooklogintries first; this package fills it with its OAuth client.
Example
example/main.go is an app with remotes and API tokens. Run two copies and add one as a remote of the other; its comment has the commands.
// Command example is a PocketOne app with remotes and API tokens: run two copies, add one as a remote of the other's
// project and sign in.
//
// go run ./example serve --http=127.0.0.1:8090 # the "remote"
// go run ./example remote add local http://127.0.0.1:8090 # in a project folder
// go run ./example login local --email [email protected] # password: password; mints a pbt_ token
// go run ./example remote whoami
//
// Logins (ext/devusers, local testing only): dashboard [email protected] / password, app user [email protected] / password.
package main
import (
"log"
"ganigeorgiev.com/pocketbase/pocketbase"
"github.com/pockethost/pocketone/ext/devusers"
"github.com/pockethost/pocketone/ext/remotes"
"github.com/pockethost/pocketone/ext/tokens"
)
func main() {
app := pocketbase.New()
devusers.Register(app) // local testing: superuser [email protected] and user [email protected], password "password"
tokens.MustRegister(app, tokens.Config{}) // so login can mint a revocable API token
remotes.MustRegister(app, remotes.Config{}) // GET /_pocker/whoami + remote/login/logout/link commands
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
License
MIT
Questions and comments
Comments are a GitHub Discussions thread; sign in with GitHub to post.