@atomiton-dev/gap-cli 0.0.0-stage → 0.9.0-mvp
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -2
- package/bin/gap.js +47 -0
- package/installer/CHANNEL +1 -0
- package/installer/VERSION +1 -0
- package/installer/docker-compose.yml +86 -0
- package/installer/env.template +13 -0
- package/installer/gap +365 -0
- package/installer/gap-lib.ps1 +1248 -0
- package/installer/gap-lib.sh +1203 -0
- package/installer/gap.ps1 +361 -0
- package/installer/install.ps1 +262 -0
- package/installer/install.sh +252 -0
- package/lib/launcher.js +31 -0
- package/lib/postinstall.js +137 -0
- package/package.json +31 -4
package/README.md
CHANGED
|
@@ -1,3 +1,133 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @atomiton-dev/gap-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Installs and runs the GAP platform on your own machine with Docker. Your data is kept when you run
|
|
4
|
+
the installer again.
|
|
5
|
+
|
|
6
|
+
## What you need
|
|
7
|
+
|
|
8
|
+
- Node.js 18 or newer.
|
|
9
|
+
- Docker, installed **and running**: Docker Desktop on macOS and Windows; Docker Engine with the Compose
|
|
10
|
+
plugin (v2) on Linux. On Linux your user must be able to run `docker` without `sudo`, and `curl` must be
|
|
11
|
+
installed.
|
|
12
|
+
- On macOS and Linux, `bash`. On Windows, Windows PowerShell 5.1.
|
|
13
|
+
- Access to GAP's images (see "Get access to the images" below). They are private.
|
|
14
|
+
|
|
15
|
+
## Install the command
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npm install -g @atomiton-dev/gap-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This puts the `gap` command on your computer. It does not download GAP yet.
|
|
22
|
+
|
|
23
|
+
If you use oh-my-zsh, the install also adds one small file, `~/.oh-my-zsh/custom/atomiton-gap.zsh`, so that a new
|
|
24
|
+
terminal runs `gap` and not oh-my-zsh's `alias gap='git apply'` (which would answer "can't open patch"). It turns
|
|
25
|
+
off only that one alias, only while this package is installed (with several Node versions it follows every copy
|
|
26
|
+
that is still installed), and changes nothing else; delete the file to undo it. It holds a path on this machine, so do
|
|
27
|
+
not copy it to another one. Open a new terminal (or run `exec zsh`) after the install. This step writes nothing else
|
|
28
|
+
outside the package.
|
|
29
|
+
|
|
30
|
+
## Get access to the images
|
|
31
|
+
|
|
32
|
+
GAP's images are stored in a private registry (ghcr.io). You need a GitHub account that has been given access.
|
|
33
|
+
|
|
34
|
+
1. Ask your contact at Atomiton to add your GitHub account to the GAP customers group. GitHub emails you an
|
|
35
|
+
invitation; accept it.
|
|
36
|
+
2. Create a personal access token (classic) that has the `read:packages` permission. This link selects the
|
|
37
|
+
permission for you: <https://github.com/settings/tokens/new?scopes=read:packages>. Nobody else can create the
|
|
38
|
+
token for you.
|
|
39
|
+
3. Log in to the registry:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
gap login
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It asks for your GitHub username and the token (the token is hidden as you type), logs Docker in to ghcr.io, and then
|
|
46
|
+
checks that your account can read GAP's images. It needs a terminal, and it exits with status 0 only when you are logged
|
|
47
|
+
in and can read the images. `gap` does not store the token anywhere: Docker keeps the login, in the system's credential
|
|
48
|
+
store when it has one (for example the macOS keychain). Where it has none, as on many Linux
|
|
49
|
+
machines, Docker stores it in `~/.docker/config.json` and says so. If you prefer Docker's own command,
|
|
50
|
+
`docker login ghcr.io -u <your GitHub username>` does the same login (paste the token when it asks for the password);
|
|
51
|
+
`gap doctor` then checks the access. (If `gap` still runs another program, see "If something goes wrong"; `command gap login` works meanwhile.)
|
|
52
|
+
|
|
53
|
+
You do this once per machine. When the token expires, create a new one and run `gap login` again.
|
|
54
|
+
|
|
55
|
+
## Install GAP
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
gap install
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`gap install` pulls the images, starts GAP and prints the address of the Admin UI
|
|
62
|
+
(`http://127.0.0.1:<port>`, a port between 8061 and 8070) and the sign-in details. It is safe to run again at
|
|
63
|
+
any time: your data and password are kept. If it cannot pull the images it stops before changing anything on
|
|
64
|
+
your machine and tells you what to do.
|
|
65
|
+
|
|
66
|
+
## Update
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
npm install -g @atomiton-dev/gap-cli@latest
|
|
70
|
+
gap update
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`gap update` moves GAP to the version of the package you just installed. In this
|
|
74
|
+
order: it pulls the new images, makes a backup of your database and your app files in
|
|
75
|
+
`~/.gap/backups/<date and time>` (readable by you only; on Windows it has the permissions of your user folder), and only then starts the new version. If pulling or the backup fails,
|
|
76
|
+
it stops and nothing has changed. It does not go back to an older version, and it does nothing when you are already on
|
|
77
|
+
the version of the package. If the new version does not start, it undoes nothing by itself: it prints where the backup
|
|
78
|
+
is and the commands to go back to your old version with your data. It never deletes a backup.
|
|
79
|
+
`gap install` does not go back to an older version either: it stops and says so. `gap uninstall` keeps the `backups` folder
|
|
80
|
+
(`gap uninstall --purge` erases it with everything else).
|
|
81
|
+
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
| Command | What it does |
|
|
85
|
+
|---|---|
|
|
86
|
+
| `gap login` | asks for your GitHub username and token, logs in to the image registry, and checks that you can read the images |
|
|
87
|
+
| `gap install` | installs, or installs again |
|
|
88
|
+
| `gap update` | moves GAP to the version of this package, after a backup |
|
|
89
|
+
| `gap status` | shows whether GAP is running and healthy |
|
|
90
|
+
| `gap start` | starts GAP again (after `gap stop` or a reboot) |
|
|
91
|
+
| `gap stop` | stops GAP; your data is kept |
|
|
92
|
+
| `gap logs [service] [-f]` | shows logs (`kernel`, `postgres` or `admin-ui`) |
|
|
93
|
+
| `gap version` | shows which GAP version is installed, and the version of this package |
|
|
94
|
+
| `gap doctor` | checks this machine and the install, and says what to do about each problem; it changes nothing |
|
|
95
|
+
| `gap uninstall [--purge] [--yes]` | removes GAP from this machine; your data is kept unless you add `--purge` (it asks you to type `delete`) |
|
|
96
|
+
|
|
97
|
+
`gap help` prints the list.
|
|
98
|
+
|
|
99
|
+
## One way of installing per machine
|
|
100
|
+
|
|
101
|
+
Use either this package or the ZIP package of GAP on a machine, not both. They share the same data. If you
|
|
102
|
+
used the ZIP before, run `gap install`. On macOS and Linux it reports a leftover `~/.local/bin/gap` launcher
|
|
103
|
+
from the ZIP, which may run instead of the npm command; remove it with `rm ~/.local/bin/gap`.
|
|
104
|
+
|
|
105
|
+
## Your data
|
|
106
|
+
|
|
107
|
+
Your data lives in two Docker volumes, `gap_pgdata` and `gap_data`. Do not run `docker volume prune` or
|
|
108
|
+
`docker system prune --volumes`: they can erase it.
|
|
109
|
+
|
|
110
|
+
## If something goes wrong
|
|
111
|
+
|
|
112
|
+
Run `gap doctor` first. It checks Node.js, Docker, the Admin UI port, the `gap` command, your access to the images and
|
|
113
|
+
the state of GAP, and says what to do about each problem. It exits with status 1 when it finds one, and it changes
|
|
114
|
+
nothing.
|
|
115
|
+
|
|
116
|
+
- **`gap: command not found`** after installing: npm's global command folder is not on your `PATH`. Run
|
|
117
|
+
`npm prefix -g`; add that folder's `bin` subfolder (on Windows, the folder itself) to your `PATH`, then open a
|
|
118
|
+
new terminal.
|
|
119
|
+
- **`gap` fails with "can't open patch 'login'" (or another word), or runs another program:** your terminal has
|
|
120
|
+
its own command named `gap`. With oh-my-zsh it is `alias gap='git apply'`; the install turns it off for new
|
|
121
|
+
terminals, so open a new terminal (or run `exec zsh`). If it still happens, the install could not do it (for
|
|
122
|
+
example it ran with `--ignore-scripts`, under `sudo`, or with yarn or pnpm, which may not run it; or your
|
|
123
|
+
`ZSH_CUSTOM` is set only in `~/.zshrc`; or you use another framework such as prezto). Add
|
|
124
|
+
`unalias gap 2>/dev/null` at the end of `~/.zshrc`, or run `command gap login` each time.
|
|
125
|
+
`gap install` also finds this and offers to turn it off.
|
|
126
|
+
- **"GAP's images are private and this machine is not logged in to ghcr.io"** (`unauthorized`): follow steps 2
|
|
127
|
+
and 3 of "Get access to the images": create a token, then run `gap login`.
|
|
128
|
+
- **"ghcr.io refused access to ..."** (`denied`): either your token has expired or been revoked, or your GitHub
|
|
129
|
+
account has not been given access yet. Create a new token and run `gap login` again; if it still fails, accept the
|
|
130
|
+
invitation GitHub emailed you and ask your contact at Atomiton to check that you are in the GAP customers group.
|
|
131
|
+
- **"Docker daemon stopped":** start Docker Desktop (or the Docker service on Linux), wait until it is running,
|
|
132
|
+
then run `gap install` again.
|
|
133
|
+
- **Something does not start:** `gap logs kernel` (or `postgres`, `admin-ui`) shows why.
|
package/bin/gap.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// `gap`: a thin Node shell over the installer scripts (#1986, #2010, D2). It runs bash (macOS, Linux) or
|
|
4
|
+
// PowerShell (Windows) with the same arguments and exits with the script's exit code. ES5 only: on an old
|
|
5
|
+
// Node this file must still get as far as printing why it cannot go on.
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var childProcess = require('child_process');
|
|
8
|
+
var launcher = require('../lib/launcher.js');
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
function die(message) {
|
|
12
|
+
process.stderr.write(message + '\n');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!launcher.nodeSupported(process.versions.node)) {
|
|
17
|
+
die('Node ' + launcher.MIN_NODE_MAJOR + ' or newer is required (found ' + process.versions.node + '). ' +
|
|
18
|
+
'Install a current Node.js from https://nodejs.org and run the command again.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var SIGNALS = require('os').constants.signals;
|
|
22
|
+
var win = process.platform === 'win32';
|
|
23
|
+
var command = launcher.buildCommand({
|
|
24
|
+
platform: process.platform,
|
|
25
|
+
pkgRoot: path.resolve(__dirname, '..'),
|
|
26
|
+
args: process.argv.slice(2)
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
var child = childProcess.spawn(command.cmd, command.args, { stdio: 'inherit' });
|
|
30
|
+
|
|
31
|
+
child.on('error', function (err) {
|
|
32
|
+
if (err && err.code === 'ENOENT') {
|
|
33
|
+
die(win
|
|
34
|
+
? 'powershell is required but was not found on PATH. Use Windows PowerShell 5.1 or newer.'
|
|
35
|
+
: 'bash is required but was not found on PATH. Install bash and run the command again.');
|
|
36
|
+
}
|
|
37
|
+
die('Could not start ' + command.cmd + ': ' + (err && err.message ? err.message : err));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach(function (sig) {
|
|
41
|
+
process.on(sig, function () { child.kill(sig); });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on('exit', function (code, signal) {
|
|
45
|
+
if (signal) process.exit(128 + (SIGNALS[signal] || 1));
|
|
46
|
+
process.exit(code === null ? 1 : code);
|
|
47
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.0-mvp
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# GAP local installer stack: postgres + kernel + admin-ui.
|
|
2
|
+
#
|
|
3
|
+
# Ships in the installer zip next to env.template, images.tar.gz and install.sh / install.ps1; the
|
|
4
|
+
# installer copies it to ~/.gap, where .env is written and where it runs from (D28)
|
|
5
|
+
# (#1750, plan .claude/plan/issue-1750/03-compose-and-config.md). Always run with `-p gap`:
|
|
6
|
+
# COMPOSE_PROJECT_NAME in the user's shell overrides the `name:` key below.
|
|
7
|
+
#
|
|
8
|
+
# Two channels share this file, told apart only by two keys of .env (#1986):
|
|
9
|
+
# ZIP (default, offline): the images are loaded from images.tar.gz (`docker load`) under their
|
|
10
|
+
# local names and never pulled: GAP_IMAGE_PREFIX is empty and GAP_PULL_POLICY is `never`, so a
|
|
11
|
+
# missing image fails with a clear error instead of reaching for Docker Hub.
|
|
12
|
+
# npm (online): the images are pulled from a registry: GAP_IMAGE_PREFIX is
|
|
13
|
+
# `ghcr.io/atomiton-gap/` and GAP_PULL_POLICY is `missing`.
|
|
14
|
+
# An .env without either key (every ZIP installed before #1986) behaves exactly as the ZIP channel.
|
|
15
|
+
# Required variables use `${VAR:?...}` so Compose stops with a message instead of running with an
|
|
16
|
+
# empty value.
|
|
17
|
+
name: gap
|
|
18
|
+
|
|
19
|
+
services:
|
|
20
|
+
postgres:
|
|
21
|
+
# Hard-coded on purpose: changing the major version breaks an existing volume. The official
|
|
22
|
+
# postgres:16, packaged under a GAP name so that `docker load` never moves the user's own
|
|
23
|
+
# postgres:16 tag (build-package.sh).
|
|
24
|
+
image: ${GAP_IMAGE_PREFIX:-}gap-postgres:16
|
|
25
|
+
pull_policy: ${GAP_PULL_POLICY:-never}
|
|
26
|
+
environment:
|
|
27
|
+
POSTGRES_USER: postgres
|
|
28
|
+
# Only read when the data volume is still empty (first start).
|
|
29
|
+
POSTGRES_PASSWORD: ${GAP_DB_PASSWORD:?GAP_DB_PASSWORD missing, re-run the installer}
|
|
30
|
+
volumes:
|
|
31
|
+
- pgdata:/var/lib/postgresql/data
|
|
32
|
+
healthcheck:
|
|
33
|
+
# -h 127.0.0.1: without it pg_isready reaches the temporary socket-only server that the
|
|
34
|
+
# image runs during first-time initialisation and reports ready too early.
|
|
35
|
+
test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-U", "postgres"]
|
|
36
|
+
interval: 5s
|
|
37
|
+
timeout: 3s
|
|
38
|
+
retries: 30
|
|
39
|
+
restart: unless-stopped
|
|
40
|
+
# No `ports`: the database is reachable only from the other services.
|
|
41
|
+
|
|
42
|
+
kernel:
|
|
43
|
+
image: ${GAP_IMAGE_PREFIX:-}gap-kernel:${GAP_VERSION:?GAP_VERSION missing, re-run the installer}
|
|
44
|
+
pull_policy: ${GAP_PULL_POLICY:-never}
|
|
45
|
+
depends_on:
|
|
46
|
+
postgres:
|
|
47
|
+
condition: service_healthy
|
|
48
|
+
environment:
|
|
49
|
+
ENVIRONMENT: dev
|
|
50
|
+
DEBUG: "false"
|
|
51
|
+
# Required: without it the App UI endpoints answer 500 (main.py warns at start).
|
|
52
|
+
GAP_DATA_ROOT: /data/gap-data
|
|
53
|
+
DB_HOST: postgres
|
|
54
|
+
DB_PORT: "5432"
|
|
55
|
+
DB_USER: postgres
|
|
56
|
+
DB_PASSWORD: ${GAP_DB_PASSWORD:?GAP_DB_PASSWORD missing, re-run the installer}
|
|
57
|
+
DB_ADMIN_USER: postgres
|
|
58
|
+
DB_ADMIN_PASSWORD: ${GAP_DB_PASSWORD:?GAP_DB_PASSWORD missing, re-run the installer}
|
|
59
|
+
# Explicit: the Kernel default follows the CPU count of the machine.
|
|
60
|
+
OFFLOAD_MAX_WORKERS: "2"
|
|
61
|
+
# JWT_SECRET and APP_CREDENTIAL_ENCRYPTION_KEY are left to the Kernel defaults (D7); that is
|
|
62
|
+
# only acceptable while nothing but 127.0.0.1 can reach the Admin UI (D9).
|
|
63
|
+
volumes:
|
|
64
|
+
- data:/data
|
|
65
|
+
# The default 10 s equals worker_drain_timeout_seconds, which leaves no time for the rest
|
|
66
|
+
# of the shutdown.
|
|
67
|
+
stop_grace_period: 30s
|
|
68
|
+
restart: unless-stopped
|
|
69
|
+
# No `ports`: every API call goes through the Admin UI (nginx).
|
|
70
|
+
|
|
71
|
+
admin-ui:
|
|
72
|
+
image: ${GAP_IMAGE_PREFIX:-}gap-admin-ui:${GAP_VERSION:?GAP_VERSION missing, re-run the installer}
|
|
73
|
+
pull_policy: ${GAP_PULL_POLICY:-never}
|
|
74
|
+
depends_on:
|
|
75
|
+
kernel:
|
|
76
|
+
condition: service_healthy
|
|
77
|
+
ports:
|
|
78
|
+
# Loopback only: the seeded admin account and the default JWT secret are acceptable only
|
|
79
|
+
# because of this binding (D6, D7, D9). Never bind 0.0.0.0 without revisiting those.
|
|
80
|
+
- "127.0.0.1:${GAP_UI_PORT:?GAP_UI_PORT missing, re-run the installer}:8080"
|
|
81
|
+
restart: unless-stopped
|
|
82
|
+
|
|
83
|
+
# Real names on Docker are gap_pgdata and gap_data (project name + key).
|
|
84
|
+
volumes:
|
|
85
|
+
pgdata:
|
|
86
|
+
data:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Written by the installer on first install, as .env in ~/.gap (not in the unzipped folder, D28);
|
|
2
|
+
# do not commit a filled-in copy. The installer owns these values and reads them back on later runs.
|
|
3
|
+
GAP_VERSION=@GAP_VERSION@
|
|
4
|
+
GAP_UI_PORT=@GAP_UI_PORT@
|
|
5
|
+
# Random, generated once. Postgres keeps the password it was first started with, so editing it by
|
|
6
|
+
# hand makes the Kernel fail to authenticate. If this file is lost, install.sh / install.ps1
|
|
7
|
+
# generates a new password and sets it inside Postgres, so the data is kept (D28).
|
|
8
|
+
GAP_DB_PASSWORD=@GAP_DB_PASSWORD@
|
|
9
|
+
# Which channel the images come from (#1986). The ZIP installer keeps these two as they are: images
|
|
10
|
+
# loaded from the zip under their local names, never pulled. The npm installer sets the registry
|
|
11
|
+
# prefix (ghcr.io/atomiton-gap/) and `missing`. docker-compose.yml reads them.
|
|
12
|
+
GAP_IMAGE_PREFIX=
|
|
13
|
+
GAP_PULL_POLICY=never
|
package/installer/gap
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gap: status / start / stop / logs / uninstall / version / doctor (and update, npm channel) for the local GAP stack (#1750, D27, D28). In the npm package
|
|
3
|
+
# (#1986) `gap install` is the other script, install.sh, which the Node launcher (bin/gap.js) runs.
|
|
4
|
+
# install.sh copies this file to ~/.gap and puts a launcher for it on the PATH, so `gap status`
|
|
5
|
+
# works in any terminal. It always works on the instance in ~/.gap (GAP_HOME, from gap-lib.sh),
|
|
6
|
+
# also when run from the unzipped installer folder.
|
|
7
|
+
set -u
|
|
8
|
+
|
|
9
|
+
GAP_SRC=$(cd "$(dirname "$0")" && pwd -P)
|
|
10
|
+
# shellcheck source=gap-lib.sh
|
|
11
|
+
. "$GAP_SRC/gap-lib.sh"
|
|
12
|
+
|
|
13
|
+
usage() {
|
|
14
|
+
printf 'Usage: gap <command>\n\n'
|
|
15
|
+
# `install` and `update` are commands of the npm package only: the ZIP's gap has none, its installer is install.sh /
|
|
16
|
+
# install.ps1 (and a ZIP updates by running the new one).
|
|
17
|
+
if [ "$(channel)" = npm ]; then
|
|
18
|
+
printf ' install install GAP on this machine (pulls the images; run it again any time)\n'
|
|
19
|
+
printf ' login log in to the registry that holds the images (asks for your GitHub username and token)\n'
|
|
20
|
+
printf ' update move GAP to the version of the installed npm package, after a backup\n'
|
|
21
|
+
fi
|
|
22
|
+
cat <<'EOF'
|
|
23
|
+
status show whether GAP is running and healthy
|
|
24
|
+
version show the installed GAP version
|
|
25
|
+
doctor check this machine and the install for problems; changes nothing
|
|
26
|
+
start start GAP again (after `gap stop` or a reboot)
|
|
27
|
+
stop stop GAP; your data is kept
|
|
28
|
+
logs [service] [-f]
|
|
29
|
+
show logs (service: kernel, postgres or admin-ui)
|
|
30
|
+
uninstall [--purge] [--yes]
|
|
31
|
+
remove GAP from this machine; your data is kept
|
|
32
|
+
unless you add --purge (it asks you to type "delete")
|
|
33
|
+
EOF
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
need_install() {
|
|
37
|
+
[ -f "$GAP_HOME/.env" ] && return 0
|
|
38
|
+
warn "GAP is not installed in $GAP_HOME"
|
|
39
|
+
if [ "$(channel)" = npm ]; then printf 'Run: gap install\n'
|
|
40
|
+
elif [ -f "$GAP_SRC/install.sh" ]; then printf 'Run: %s/install.sh\n' "$GAP_SRC"
|
|
41
|
+
else printf 'Run install.sh from the GAP installer folder.\n'; fi
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
cmd_status() {
|
|
46
|
+
local pg kernel ui runtime overall=READY port
|
|
47
|
+
check_prereqs "gap status" || return 1
|
|
48
|
+
pg=$(svc_state postgres); kernel=$(svc_state kernel); ui=$(svc_state admin-ui)
|
|
49
|
+
# D19: the Runtime lives inside the Kernel; /system/health answering 200 proves the database
|
|
50
|
+
# session and the app manager are up. The `status` field is not read: it says "error" on a
|
|
51
|
+
# fresh install that has no app worker yet.
|
|
52
|
+
if [ "$kernel" = Healthy ] && [ "$ui" = Healthy ] && http_ok /system/health; then runtime=Healthy
|
|
53
|
+
elif [ "$kernel" = Stopped ]; then runtime=Stopped
|
|
54
|
+
else runtime=Unreachable; fi
|
|
55
|
+
for s in "$pg" "$kernel" "$runtime" "$ui"; do [ "$s" = Healthy ] || overall="NOT READY"; done
|
|
56
|
+
port=$(env_get GAP_UI_PORT)
|
|
57
|
+
printf '%-15s %s\n' "PostgreSQL" "$pg" "GAP Kernel" "$kernel" "GAP Runtime" "$runtime" "GAP Admin UI" "$ui"
|
|
58
|
+
printf 'Version: %s\n' "$(env_get GAP_VERSION)"
|
|
59
|
+
newer_package_note
|
|
60
|
+
[ "$overall" = READY ] && printf 'URL: http://127.0.0.1:%s\n' "$port"
|
|
61
|
+
printf 'Overall: %s\n' "$overall"
|
|
62
|
+
[ "$overall" = READY ]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# version and doctor work before GAP is installed: doctor is what to run when the install did not work.
|
|
66
|
+
cmd_version() {
|
|
67
|
+
local installed pkg
|
|
68
|
+
installed=$(installed_version); pkg=$(cli_version)
|
|
69
|
+
printf 'Installed: %s\n' "${installed:-not installed}"
|
|
70
|
+
[ -z "$pkg" ] || printf 'CLI package: %s\n' "$pkg"
|
|
71
|
+
if [ -n "$pkg" ] && [ -n "$installed" ] && [ "$installed" != unknown ] && [ "$installed" != "$pkg" ]; then
|
|
72
|
+
if package_is_newer; then printf 'The installed version differs from this package. Run: gap update\n'
|
|
73
|
+
elif package_is_older; then
|
|
74
|
+
printf 'The installed version is newer than this package. Install the newer package: npm install -g @atomiton-dev/gap-cli@latest\n'
|
|
75
|
+
else printf 'The installed version differs from this package. Run: gap install\n'; fi
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# doctor_* are in gap-lib.sh (where the tests reach them). Exits 0 when no check found a problem, 1 otherwise.
|
|
80
|
+
cmd_doctor() {
|
|
81
|
+
DOCTOR_PROBLEMS=0
|
|
82
|
+
doctor_docker
|
|
83
|
+
doctor_node
|
|
84
|
+
doctor_ports
|
|
85
|
+
doctor_gap_command
|
|
86
|
+
doctor_registry
|
|
87
|
+
doctor_install
|
|
88
|
+
case "$DOCTOR_PROBLEMS" in
|
|
89
|
+
0) printf 'Overall: OK\n' ;;
|
|
90
|
+
1) printf 'Overall: 1 problem\n' ;;
|
|
91
|
+
*) printf 'Overall: %s problems\n' "$DOCTOR_PROBLEMS" ;;
|
|
92
|
+
esac
|
|
93
|
+
[ "$DOCTOR_PROBLEMS" = 0 ]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# cannot_hide_token: what `gap login` says when the terminal cannot be told to stop echoing (see cmd_login). Uses `host` of the caller.
|
|
97
|
+
cannot_hide_token() {
|
|
98
|
+
warn "Cannot hide the token on this terminal, so gap login did not ask for it"
|
|
99
|
+
printf 'Log in with Docker instead (it asks for the token itself): docker login %s -u <your GitHub username>\n' "${host:-ghcr.io}"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# `gap login` (#1986): npm channel only (the ZIP carries its images). The way in to the private registry that holds them, in
|
|
103
|
+
# GAP's own words: it asks for a GitHub username and a token, hands the token to `docker login --password-stdin` (on stdin, so it
|
|
104
|
+
# is in no argument list and no log), and then reads the three images the way `gap doctor` does, so that "logged in" and "can
|
|
105
|
+
# read GAP's images" are not mistaken for each other: a valid token of an account that was never given access logs in fine.
|
|
106
|
+
# Needs a terminal (the token is typed hidden); without one it says how to do it by hand. Docker keeps the credential, in its
|
|
107
|
+
# own store (the macOS keychain, Windows' credential manager, ...); gap writes it nowhere.
|
|
108
|
+
cmd_login() {
|
|
109
|
+
local host user token out rc saved traced=0
|
|
110
|
+
if [ "$(channel)" != npm ]; then
|
|
111
|
+
warn "The ZIP has no \`gap login\`"
|
|
112
|
+
printf 'Its images are inside the ZIP, so there is no registry to log in to.\n'
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
host=$(channel_prefix); host=${host%%/*}
|
|
116
|
+
if [ ! -t 0 ]; then
|
|
117
|
+
warn "gap login asks for your GitHub username and token, so it needs a terminal"
|
|
118
|
+
printf 'Without one, log in with Docker and pipe the token into it:\n'
|
|
119
|
+
printf ' docker login %s -u <your GitHub username> --password-stdin\n' "$host"
|
|
120
|
+
return 1
|
|
121
|
+
fi
|
|
122
|
+
check_prereqs "gap login" || return 1
|
|
123
|
+
# The questions go to stderr, as `read -p` does: with `gap login > log` they are still on the screen.
|
|
124
|
+
{
|
|
125
|
+
printf 'GAP'"'"'s images are in %s, a private registry. You need a GitHub personal access token (classic)\n' "$host"
|
|
126
|
+
printf 'with the read:packages permission. Create one here if you have none:\n'
|
|
127
|
+
printf ' https://github.com/settings/tokens/new?scopes=read:packages\n\n'
|
|
128
|
+
printf 'GitHub username: '
|
|
129
|
+
} >&2
|
|
130
|
+
# `read` (IFS at its default) drops the blanks a paste brings in front of and behind the text.
|
|
131
|
+
read -r user || { printf '\n' >&2; user=""; }
|
|
132
|
+
if [ -z "$user" ]; then warn "No username was given"; return 1; fi
|
|
133
|
+
# The echo is turned off BEFORE the prompt is printed, not by `read -s` after it: a token pasted right after the prompt
|
|
134
|
+
# appears (or typed ahead together with the username) would be echoed in the meantime. A Ctrl-C used to leave the echo off
|
|
135
|
+
# (measured on bash 3.2), so that what the user typed next was invisible: put the terminal back as it was, whichever way this
|
|
136
|
+
# ends.
|
|
137
|
+
# If the terminal cannot be asked how it is set, or cannot be told to stop echoing, the token would be shown while the prompt says
|
|
138
|
+
# it is hidden (a missing or broken stty): then nothing is asked, rather than a lie.
|
|
139
|
+
saved=$(stty -g 2>/dev/null)
|
|
140
|
+
if [ -z "$saved" ]; then cannot_hide_token; return 1; fi
|
|
141
|
+
trap 'stty "$saved" 2>/dev/null; printf "\n" >&2; exit 130' INT TERM HUP QUIT
|
|
142
|
+
# A job that was stopped (Ctrl-Z) and resumed (fg) gets the terminal back as the shell saved it, echo on: hide the token again.
|
|
143
|
+
trap 'stty -echo 2>/dev/null' CONT
|
|
144
|
+
if ! stty -echo 2>/dev/null; then
|
|
145
|
+
trap - INT TERM HUP QUIT CONT; stty "$saved" 2>/dev/null
|
|
146
|
+
cannot_hide_token; return 1
|
|
147
|
+
fi
|
|
148
|
+
printf 'Token (hidden as you type): ' >&2
|
|
149
|
+
# From here the token is in a variable: no shell trace (`bash -x`, SHELLOPTS=xtrace) may print it, so it is switched off
|
|
150
|
+
# until the token has gone to Docker, and back on afterwards if it was on.
|
|
151
|
+
case $- in *x*) traced=1 ;; esac
|
|
152
|
+
{ set +x; } 2>/dev/null
|
|
153
|
+
read -r token || token=""
|
|
154
|
+
trap - INT TERM HUP QUIT CONT
|
|
155
|
+
stty "$saved" 2>/dev/null
|
|
156
|
+
printf '\n' >&2
|
|
157
|
+
if [ -z "$token" ]; then [ "$traced" = 0 ] || set -x; warn "No token was given"; return 1; fi
|
|
158
|
+
out=$(printf '%s' "$token" | docker login "$host" -u "$user" --password-stdin 2>&1); rc=$?
|
|
159
|
+
out=${out//"$token"/***} # Docker does not print the password; if a text ever carried it, it would not reach the screen
|
|
160
|
+
token=""
|
|
161
|
+
[ "$traced" = 0 ] || set -x
|
|
162
|
+
if [ "$rc" != 0 ]; then
|
|
163
|
+
case "$(classify_login_error "$out")" in
|
|
164
|
+
rejected)
|
|
165
|
+
# ghcr.io ignores the username of a valid token (measured: a made-up name logs in), so only the token is blamed.
|
|
166
|
+
warn "$host did not accept that token"
|
|
167
|
+
printf ' - Paste the personal access token (classic) itself, not your GitHub password.\n'
|
|
168
|
+
printf ' - It needs the read:packages permission and must not be expired or revoked.\n'
|
|
169
|
+
printf ' Nothing was changed. Run gap login to try again.\n' ;;
|
|
170
|
+
network)
|
|
171
|
+
warn "Could not reach $host"
|
|
172
|
+
printf ' Check your internet connection (and proxy settings), then run gap login again.\n' ;;
|
|
173
|
+
*)
|
|
174
|
+
warn "docker login failed"
|
|
175
|
+
printf '%s\n' "$out" | tail -n 3 | sed 's/^/ /' ;;
|
|
176
|
+
esac
|
|
177
|
+
return 1
|
|
178
|
+
fi
|
|
179
|
+
ok "Logged in to $host as $user"
|
|
180
|
+
# What Docker said besides "Login Succeeded" is worth reading (it warns when the credential is kept unencrypted).
|
|
181
|
+
printf '%s\n' "$out" | grep -v '^Login Succeeded$' | sed -e '/^$/d' -e 's/^/ /'
|
|
182
|
+
LOGIN_JUST_DONE=1; DOCTOR_PROBLEMS=0; DOCTOR_DOCKER=1
|
|
183
|
+
doctor_registry
|
|
184
|
+
[ "$DOCTOR_PROBLEMS" = 0 ] || return 1
|
|
185
|
+
# Exit 0 means logged in AND able to read the images. When the check could not be made (no network for it, no VERSION file, a
|
|
186
|
+
# text Docker gave that is not known) neither is claimed: it is said, the status is 1, and no "Next:" is offered.
|
|
187
|
+
if [ "$REGISTRY_READABLE" != 1 ]; then
|
|
188
|
+
printf 'Logged in, but whether your account can read the images could not be checked. Run gap doctor when that is possible.\n'
|
|
189
|
+
return 1
|
|
190
|
+
fi
|
|
191
|
+
is_installed || printf 'Next: gap install\n'
|
|
192
|
+
return 0
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
# update_failed OLD NEW: the new version did not come up. Nothing is rolled back (the Kernel may already have migrated the
|
|
196
|
+
# database, and a migration is not undone): say where the backup is, what .env says now, and how to go back (print_restore_help).
|
|
197
|
+
update_failed() {
|
|
198
|
+
warn "The update to GAP $2 did not finish, and GAP $1 was not restored automatically"
|
|
199
|
+
printf ' The backup made before the update: %s\n' "$BACKUP_DIR"
|
|
200
|
+
printf ' .env now says GAP_VERSION=%s. Nothing was rolled back and nothing was deleted.\n' "$(env_get GAP_VERSION)"
|
|
201
|
+
printf ' First look at what the new version says: gap logs kernel\n'
|
|
202
|
+
print_restore_help "$BACKUP_DIR" "$1"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# update_not_applied OLD NEW: the new .env or compose file could not be written. The stack was not touched (the old version still
|
|
206
|
+
# runs as before); .env and the compose file may be half moved, and the backup holds the old ones.
|
|
207
|
+
update_not_applied() {
|
|
208
|
+
warn "The update to GAP $2 could not be applied: the files in $GAP_HOME could not be written"
|
|
209
|
+
printf ' The running stack was not touched; GAP %s runs as before. The backup made before: %s\n' "$1" "$BACKUP_DIR"
|
|
210
|
+
printf ' .env now says GAP_VERSION=%s. To put the old .env and compose file back:\n' "$(env_get GAP_VERSION)"
|
|
211
|
+
print_files_back_help "$BACKUP_DIR"
|
|
212
|
+
printf ' Check the permissions of %s, then run: gap update\n' "$GAP_HOME"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# D-update (#2014): npm channel only. Order matters: nothing of the old stack is touched until the images are here AND the
|
|
216
|
+
# backup was made and checked. Equal versions are "nothing to do"; an older package or a version it cannot compare is refused.
|
|
217
|
+
cmd_update() {
|
|
218
|
+
local installed pkg cmp
|
|
219
|
+
if [ "$(channel)" != npm ]; then
|
|
220
|
+
warn "The ZIP has no \`gap update\`"
|
|
221
|
+
printf 'To update: unzip the new installer and run its install.sh. It keeps your data and your secrets.\n'
|
|
222
|
+
return 1
|
|
223
|
+
fi
|
|
224
|
+
need_install || return 1
|
|
225
|
+
installed=$(installed_version); pkg=$(cli_version)
|
|
226
|
+
if [ -z "$pkg" ]; then
|
|
227
|
+
warn "This package has no VERSION file, so it cannot tell which version it brings"
|
|
228
|
+
return 1
|
|
229
|
+
fi
|
|
230
|
+
if ! cmp=$(version_cmp "$installed" "$pkg"); then
|
|
231
|
+
warn "Cannot compare the installed version ($installed) with the version of this package ($pkg)"
|
|
232
|
+
printf ' gap update only moves between versions it can compare (major.minor.patch, with the same -suffix).\n'
|
|
233
|
+
printf ' Nothing was changed.\n'
|
|
234
|
+
return 1
|
|
235
|
+
fi
|
|
236
|
+
if [ "$cmp" = 0 ]; then
|
|
237
|
+
printf 'GAP %s is already the version of this package. Nothing to do.\n' "$installed"
|
|
238
|
+
return 0
|
|
239
|
+
fi
|
|
240
|
+
if [ "$cmp" = 1 ]; then
|
|
241
|
+
warn "This package ($pkg) is older than the installed GAP ($installed)"
|
|
242
|
+
older_package_help
|
|
243
|
+
printf ' Nothing was changed.\n'
|
|
244
|
+
return 1
|
|
245
|
+
fi
|
|
246
|
+
printf 'Updating GAP %s -> %s\n\n' "$installed" "$pkg"
|
|
247
|
+
check_prereqs "gap update" up || return 1
|
|
248
|
+
pull_images "$pkg" "gap update" || return 1 # has said what to do; the old stack is untouched
|
|
249
|
+
ok "Images ready"
|
|
250
|
+
make_backup || return 1 # has said what is wrong; nothing was changed
|
|
251
|
+
ok "Backup saved in $BACKUP_DIR"
|
|
252
|
+
# The files first: if they cannot be written the running stack has not been touched, and the way back is two `cp` lines, not
|
|
253
|
+
# the restore (which starts with `down -v` and would erase a healthy stack).
|
|
254
|
+
if ! { put_file "$GAP_SRC/docker-compose.yml" "$GAP_HOME/docker-compose.yml" 644 \
|
|
255
|
+
&& env_set GAP_VERSION "$pkg" && env_set GAP_IMAGE_PREFIX "$(channel_prefix)" \
|
|
256
|
+
&& env_set GAP_PULL_POLICY "$(channel_pull_policy)"; }; then
|
|
257
|
+
update_not_applied "$installed" "$pkg"
|
|
258
|
+
return 1
|
|
259
|
+
fi
|
|
260
|
+
if ! { bring_up && health_checks; }; then
|
|
261
|
+
update_failed "$installed" "$pkg"
|
|
262
|
+
return 1
|
|
263
|
+
fi
|
|
264
|
+
printf '\nGAP updated: %s -> %s\n' "$installed" "$pkg"
|
|
265
|
+
printf ' Backup of GAP %s: %s\n' "$installed" "$BACKUP_DIR"
|
|
266
|
+
printf ' It is kept; delete it when you no longer need it. Check GAP with: gap status\n'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
cmd_start() {
|
|
270
|
+
local stored port
|
|
271
|
+
check_prereqs "gap start" up || return 1
|
|
272
|
+
stored=$(env_get GAP_UI_PORT)
|
|
273
|
+
port=$(pick_port "$stored") || { warn "No free port for the Admin UI"; printf 'Action: free a port between %s and %s\n' "$GAP_PORT_FIRST" "$GAP_PORT_LAST"; return 1; }
|
|
274
|
+
if [ "$port" != "$stored" ]; then
|
|
275
|
+
warn "Port $stored is in use, using $port instead"
|
|
276
|
+
env_set GAP_UI_PORT "$port"
|
|
277
|
+
fi
|
|
278
|
+
bring_up || return 1
|
|
279
|
+
ok "GAP is running"
|
|
280
|
+
printf 'Admin UI: %s\n' "$(ui_url)"
|
|
281
|
+
newer_package_note
|
|
282
|
+
open_url "$(ui_url)"
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
cmd_stop() {
|
|
286
|
+
check_prereqs "gap stop" || return 1
|
|
287
|
+
dc stop || return 1
|
|
288
|
+
printf 'GAP stopped. Your data is kept. Run: gap start\n'
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
cmd_logs() {
|
|
292
|
+
check_prereqs "gap logs" || return 1
|
|
293
|
+
dc logs "$@"
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# D28: stop the stack and remove everything the installer put on this machine. Without --purge the
|
|
297
|
+
# Docker volumes (gap_pgdata, gap_data) stay, and a later install finds and reuses them. With
|
|
298
|
+
# --purge the volumes and this version's GAP images go too (gap-kernel, gap-admin-ui and gap-postgres:16,
|
|
299
|
+
# under the names the install gave them: the registry prefix of .env, empty for a ZIP; the user's own
|
|
300
|
+
# postgres:16, if any, is a different name and is never touched). Nothing is removed until the stack has
|
|
301
|
+
# been taken down. Public since #1986 (#2015): it was hidden in #1750 (Scott, 21/10) and is now in usage()
|
|
302
|
+
# and the READMEs. It removes ~/.gap, the launcher and the rc lines the installer added, and nothing else: the
|
|
303
|
+
# unzipped installer folder (ZIP) and the package folder (npm) are never touched, and `npm uninstall -g` is the
|
|
304
|
+
# user's separate, later step.
|
|
305
|
+
cmd_uninstall() {
|
|
306
|
+
local purge=0 yes=0 arg answer version prefix removed had_backups=0
|
|
307
|
+
for arg in "$@"; do
|
|
308
|
+
case "$arg" in
|
|
309
|
+
--purge) purge=1 ;;
|
|
310
|
+
--yes) yes=1 ;;
|
|
311
|
+
*) usage >&2; return 2 ;;
|
|
312
|
+
esac
|
|
313
|
+
done
|
|
314
|
+
check_prereqs "gap uninstall" || return 1
|
|
315
|
+
version=$(env_get GAP_VERSION)
|
|
316
|
+
[ ! -d "$GAP_HOME/backups" ] || had_backups=1
|
|
317
|
+
if [ "$purge" = 1 ] && [ "$yes" = 0 ]; then
|
|
318
|
+
if [ ! -t 0 ]; then
|
|
319
|
+
warn "--purge erases all GAP data; run it in a terminal, or add --yes to confirm"
|
|
320
|
+
return 1
|
|
321
|
+
fi
|
|
322
|
+
if [ "$had_backups" = 1 ]; then
|
|
323
|
+
printf 'This erases ALL GAP data (database and files), the GAP images and the backups in %s/backups. Type "delete" to continue: ' "$GAP_HOME"
|
|
324
|
+
else
|
|
325
|
+
printf 'This erases ALL GAP data (database and files) and the GAP images. Type "delete" to continue: '
|
|
326
|
+
fi
|
|
327
|
+
read -r answer
|
|
328
|
+
[ "$answer" = delete ] || { printf 'Cancelled. Nothing was changed.\n'; return 1; }
|
|
329
|
+
fi
|
|
330
|
+
if [ "$purge" = 1 ]; then dc down -v || return 1; else dc down || return 1; fi
|
|
331
|
+
if [ "$purge" = 1 ]; then
|
|
332
|
+
prefix=$(installed_prefix)
|
|
333
|
+
docker rmi "$(image_ref "$prefix" gap-kernel "$version")" "$(image_ref "$prefix" gap-admin-ui "$version")" \
|
|
334
|
+
"$(image_ref "$prefix" gap-postgres 16)" >/dev/null 2>&1 || true
|
|
335
|
+
fi
|
|
336
|
+
# The launcher only if it is ours: a `gap` of another program, or a symlink, stays.
|
|
337
|
+
if launcher_is_ours "$LAUNCHER_DIR/gap"; then rm -f "$LAUNCHER_DIR/gap"; fi
|
|
338
|
+
removed=$(remove_rc_lines)
|
|
339
|
+
# The backups of `gap update` are data: without --purge they stay, everything else of ~/.gap goes (they hold the database
|
|
340
|
+
# and the old password, so nothing here makes them readable by others: the folder is 700).
|
|
341
|
+
if [ "$purge" = 0 ] && [ "$had_backups" = 1 ]; then
|
|
342
|
+
find "$GAP_HOME" -mindepth 1 -maxdepth 1 ! -name backups -exec rm -rf {} +
|
|
343
|
+
else
|
|
344
|
+
rm -rf "$GAP_HOME"
|
|
345
|
+
fi
|
|
346
|
+
ok "GAP removed"
|
|
347
|
+
[ -z "$removed" ] || printf 'Removed the lines the installer added to:%s\n' "$removed"
|
|
348
|
+
if [ "$purge" = 1 ]; then
|
|
349
|
+
printf 'All GAP data was erased.\n'
|
|
350
|
+
[ "$had_backups" = 0 ] || printf 'The backups in %s/backups were erased too.\n' "$GAP_HOME"
|
|
351
|
+
else
|
|
352
|
+
[ "$had_backups" = 0 ] || printf 'The backups of gap update are kept in %s/backups (delete that folder when you no longer need them).\n' "$GAP_HOME"
|
|
353
|
+
printf 'Your data is kept in the Docker volumes %s_pgdata and %s_data; installing again brings it back.\n' "$GAP_PROJECT" "$GAP_PROJECT"
|
|
354
|
+
printf 'To erase it for good, install again and run: gap uninstall --purge\n'
|
|
355
|
+
fi
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
cmd=${1:-}
|
|
359
|
+
[ $# -gt 0 ] && shift
|
|
360
|
+
case "$cmd" in
|
|
361
|
+
version|doctor|update|login) [ $# -eq 0 ] || { usage >&2; exit 2; }; "cmd_$cmd" ;;
|
|
362
|
+
status|start|stop|logs|uninstall) need_install || exit 1; "cmd_$cmd" "$@" ;;
|
|
363
|
+
help|-h|--help) usage ;;
|
|
364
|
+
*) usage >&2; exit 2 ;;
|
|
365
|
+
esac
|