feat: one-command crew coordinator installer
This commit is contained in:
parent
ae1d7275e6
commit
f7316060ae
5 changed files with 123 additions and 4 deletions
14
AGENTS.md
14
AGENTS.md
|
|
@ -1418,7 +1418,9 @@ session with **no other memory of this project**. So:
|
|||
it in when known.
|
||||
**Profile discovery (built 2026-09-29):** the carrier is the existing kind-0
|
||||
**`cordn`** field (app convention, documented in NIP.md — not a new kind).
|
||||
**Crew-owned coordinator kit (2026-10-31):** `deploy/cordn/` is the
|
||||
**Crew-owned coordinator kit (2026-10-31, install helper 2026-11-01):**
|
||||
`deploy/cordn/install.sh` is the one-command installer (generates `.env`,
|
||||
starts SQLite, prints nprofile/add link). `deploy/cordn/` remains the
|
||||
production hand-off for crews: Docker Compose, SQLite volume, `.env` template,
|
||||
backup/restore scripts and a runbook. Cordn connects **outbound** to ContextVM
|
||||
relays; no inbound port, domain or TLS terminator is required for the
|
||||
|
|
@ -3489,6 +3491,16 @@ These components follow a consistent pattern using React's `forwardRef` and use
|
|||
|
||||
The project includes a **`skills/`** directory containing specialized implementation guides for specific features. Each skill has a comprehensive `SKILL.md` file with complete patterns, code examples, and best practices. **Always reference the appropriate SKILL.md file when implementing related functionality.**
|
||||
|
||||
### Deferred: NIP-05 as a wallet address
|
||||
**Do not set `lud16` to `name@skate-spots.org` until an LNURL-pay service exists.**
|
||||
NIP-05 is a Nostr identity record; a Lightning address is a separate
|
||||
`/.well-known/lnurlp/<name>` service. A Minibits-style wallet address needs the
|
||||
NIP-05 server **and** an LNURL/mint/claim service (plus rate limits, backups,
|
||||
privacy copy and a clear mint-trust model). Optional future paths:
|
||||
self-hosted LNURL + Cashu mint, or a deliberate npub.cash/Minibits-style
|
||||
integration. Until then keep NIP-05 as identity and use NWC/built-in Cashu;
|
||||
never overwrite a user's custom `lud16`.
|
||||
|
||||
### Wallet Skills
|
||||
|
||||
- **`skills/bitcoin-wallet/SKILL.md`**: Use when implementing Bitcoin wallet features - provides complete architecture for on-chain Bitcoin transactions, UTXO management, address derivation from Nostr keys, and transaction handling with proper security patterns. Read when building Bitcoin wallet functionality, adding on-chain transaction support, implementing UTXO management, or integrating Bitcoin with Nostr-based applications.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,21 @@ coordinator in the APK.
|
|||
|
||||
## 1. Install
|
||||
|
||||
### Easiest: one command
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.skate-spots.org/skatespots/SkateSpots/raw/branch/main/deploy/cordn/install.sh | bash
|
||||
```
|
||||
|
||||
It checks Docker, generates the coordinator key, writes a private `.env`, starts
|
||||
SQLite persistence, waits for the coordinator to announce, and prints the
|
||||
`nprofile`/add link.
|
||||
|
||||
No inbound port, domain or TLS setup is required. The coordinator connects
|
||||
outbound to the ContextVM relays in `CORDN_RELAY_URLS`.
|
||||
|
||||
### Manual: from this directory
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
chmod 600 .env
|
||||
|
|
@ -40,6 +55,17 @@ If your build does not print an add link, derive the public key from the private
|
|||
key using the Cordn CLI or `nostr-tools`, then encode an `nprofile` with the same
|
||||
relay hints. Keep the private key on the server.
|
||||
|
||||
### Can I run it on a phone?
|
||||
|
||||
Not with this Docker kit. A phone can **administer** a coordinator, and Cordn's
|
||||
own phone app is the hand-off path, but an always-on coordinator needs a machine
|
||||
that stays awake and reachable. Android may suspend a foreground coordinator;
|
||||
do not promise crew members that a phone is a reliable coordinator host.
|
||||
|
||||
For a crew that wants ownership without a VPS, an old Raspberry Pi or a
|
||||
small always-on home server is the simplest reliable option. The coordinator
|
||||
still needs no inbound port.
|
||||
|
||||
## 2. Adopt it in SkateSpots
|
||||
|
||||
Either:
|
||||
|
|
|
|||
77
deploy/cordn/install.sh
Executable file
77
deploy/cordn/install.sh
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env bash
|
||||
# One-command crew coordinator installer.
|
||||
#
|
||||
# curl -fsSL https://git.skate-spots.org/skatespots/SkateSpots/raw/branch/main/deploy/cordn/install.sh | bash
|
||||
#
|
||||
# Creates a persistent Cordn coordinator with SQLite storage, starts it, and
|
||||
# prints the nprofile/add link when the server announces itself. The coordinator
|
||||
# connects OUT to ContextVM relays; it needs no inbound port or TLS domain.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")" 2>/dev/null || true
|
||||
umask 077
|
||||
|
||||
need() { command -v "$1" >/dev/null || { echo "missing: $1" >&2; exit 1; }; }
|
||||
need docker
|
||||
need openssl
|
||||
docker compose version >/dev/null 2>&1 || { echo "Docker Compose v2 is required (docker compose)." >&2; exit 1; }
|
||||
|
||||
if [ ! -f docker-compose.yml ]; then
|
||||
cat > docker-compose.yml <<'YAML'
|
||||
name: cordn
|
||||
services:
|
||||
cordn:
|
||||
image: ghcr.io/cordn-msg/cordn:latest
|
||||
container_name: cordn
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
CORDN_STORAGE_BACKEND: sqlite
|
||||
CORDN_SQLITE_PATH: /data/cordn.sqlite
|
||||
volumes:
|
||||
- cordn-data:/data
|
||||
volumes:
|
||||
cordn-data:
|
||||
YAML
|
||||
fi
|
||||
|
||||
if [ ! -f .env ]; then
|
||||
NAME="${CORDN_SERVER_NAME:-skatespots-crew}"
|
||||
RELAYS="${CORDN_RELAY_URLS:-wss://relay.contextvm.org,wss://relay2.contextvm.org}"
|
||||
KEY="$(openssl rand -hex 32)"
|
||||
cat > .env <<EOF
|
||||
CORDN_SERVER_PRIVATE_KEY=$KEY
|
||||
CORDN_RELAY_URLS=$RELAYS
|
||||
CORDN_ANNOUNCED=true
|
||||
CORDN_SERVER_NAME=$NAME
|
||||
CORDN_STORAGE_BACKEND=sqlite
|
||||
CORDN_SQLITE_PATH=/data/cordn.sqlite
|
||||
CORDN_RATE_LIMIT_ENABLED=true
|
||||
CORDN_RATE_LIMIT_REFILL_PER_MINUTE=250
|
||||
CORDN_RATE_LIMIT_BURST=80
|
||||
CORDN_MAX_KEY_PACKAGES_PER_IDENTITY=50
|
||||
CORDN_MAX_LAST_RESORT_KEY_PACKAGES_PER_IDENTITY=1
|
||||
EOF
|
||||
chmod 600 .env
|
||||
echo "Generated .env (keep it private; it contains the coordinator key)."
|
||||
fi
|
||||
|
||||
docker compose up -d
|
||||
echo "Waiting for the coordinator to announce itself…"
|
||||
|
||||
for _ in $(seq 1 30); do
|
||||
logs="$(docker compose logs --no-color 2>&1 || true)"
|
||||
if printf '%s\n' "$logs" | grep -q 'nprofile'; then
|
||||
printf '\n%s\n' "$logs" | grep -A12 -B2 'CORDN COORDINATOR'
|
||||
echo
|
||||
echo "Back up .env and the cordn-data volume before inviting the crew."
|
||||
echo "Adopt it in SkateSpots: Settings → Coordinator, or Crew edit → Coordinator."
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "The container started but no announcement appeared within 60s." >&2
|
||||
echo "Inspect: docker compose logs --no-color" >&2
|
||||
exit 1
|
||||
|
|
@ -20,7 +20,7 @@ import { useTranslation } from '@/contexts/language';
|
|||
import { toast } from '@/hooks/useToast';
|
||||
import {
|
||||
CORDN_APP_URL,
|
||||
CORDN_DOCKER_COMMAND,
|
||||
CORDN_INSTALL_COMMAND,
|
||||
CORDN_DEPLOY_KIT_URL,
|
||||
CORDN_RELAY_URL,
|
||||
coordinatorAddUrl,
|
||||
|
|
@ -315,11 +315,11 @@ export function CoordinatorSettingsCard() {
|
|||
</p>
|
||||
<div className="flex items-center gap-2 rounded-xl bg-black/40 border border-white/5 px-3 py-2">
|
||||
<code className="flex-1 min-w-0 truncate text-[11px] font-mono text-gray-300">
|
||||
{CORDN_DOCKER_COMMAND}
|
||||
{CORDN_INSTALL_COMMAND}
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void copyText(CORDN_DOCKER_COMMAND, t('socialUi.coordinator.commandCopied'), t('socialUi.coordinator.copyFailed'), t('socialUi.coordinator.copyManually'))}
|
||||
onClick={() => void copyText(CORDN_INSTALL_COMMAND, t('socialUi.coordinator.commandCopied'), t('socialUi.coordinator.copyFailed'), t('socialUi.coordinator.copyManually'))}
|
||||
className="shrink-0 text-muted-foreground hover:text-white transition-colors"
|
||||
aria-label={t('socialUi.coordinator.copyCommand')}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ export const CORDN_HOME = 'https://cordn.net';
|
|||
export const CORDN_DOCKER_COMMAND =
|
||||
'docker run -d --name cordn --restart unless-stopped -v cordn-data:/data -e CORDN_STORAGE_BACKEND=sqlite -e CORDN_SQLITE_PATH=/data/cordn.sqlite -e CORDN_RELAY_URLS=wss://relay.contextvm.org -e CORDN_ANNOUNCED=true -e CORDN_SERVER_PRIVATE_KEY=YOUR_64_HEX_KEY ghcr.io/cordn-msg/cordn:latest';
|
||||
|
||||
/** Self-contained server installer: creates .env, starts SQLite, prints nprofile. */
|
||||
export const CORDN_INSTALL_COMMAND =
|
||||
'curl -fsSL https://git.skate-spots.org/skatespots/SkateSpots/raw/branch/main/deploy/cordn/install.sh | bash';
|
||||
|
||||
/** Production deployment kit (persistent SQLite volume + backup/restore). */
|
||||
export const CORDN_DEPLOY_KIT_URL =
|
||||
'https://git.skate-spots.org/skatespots/SkateSpots/tree/main/deploy/cordn';
|
||||
|
|
|
|||
Loading…
Reference in a new issue