docs: new root as prior history lost (orphan + GC); ~215 commits not recoverable
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
*.bak
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
.tmp/
|
||||||
4
2026/2026-06-29.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# 2026-06-29
|
||||||
|
|
||||||
|
- koopa-admin-log started
|
||||||
|
- merchant start/health; container TLS :8081 + certbot :8082
|
||||||
4
2026/2026-06-30.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# 2026-06-30
|
||||||
|
|
||||||
|
- separate exchange container reverted → conf.d drop-in
|
||||||
|
- exchange start/health + GOA zz- drop-in
|
||||||
3
2026/2026-07-01.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# 2026-07-01
|
||||||
|
|
||||||
|
- bank start scripts + GOA bank-overrides
|
||||||
3
2026/2026-07-02.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# 2026-07-02
|
||||||
|
|
||||||
|
- exchange ensure-helpers; bank demo withdraw/credit helpers
|
||||||
3
2026/2026-07-07.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# 2026-07-07
|
||||||
|
|
||||||
|
- taler.hacktivism.ch LE (certbot), notBefore ~20:44 CEST / notAfter ~2026-10-05
|
||||||
6
2026/2026-07-08.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# 2026-07-08
|
||||||
|
|
||||||
|
- Caddy live: taler LE certs (from 07.07.) → /etc/caddy/certs; :443 then 8085/8443 then 9000/9001 + ACME webroot
|
||||||
|
- firewalld 9000/9001/80; drop obsolete 443/8082
|
||||||
|
- exchange GOA overrides (no KUDOS)
|
||||||
|
- exchange.hacktivism.ch LE via Caddy ACME ~21:36 CEST
|
||||||
47
2026/2026-07-09--amount-ladder-partial-results.md
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Amount ladder bench — partial results (interrupted 2026-07-09)
|
||||||
|
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
| Item | Value |
|
||||||
|
|------|-------|
|
||||||
|
| Bank user | `bench-fat` |
|
||||||
|
| Password file (koopa host) | `/root/bank-bench-fat-password.txt` |
|
||||||
|
| Admin credit | GOA:100000 |
|
||||||
|
| Withdraw | GOA:25000 → wallet available GOA:25000 |
|
||||||
|
| Merchant instance | `goa-demo-cp4zqk` |
|
||||||
|
| Free-amount template | `goa-free` |
|
||||||
|
| Fixed template (legacy) | `goa` amount GOA:5 fixed-order |
|
||||||
|
| Withdraw wall time | ~393 s (confirm path issues) |
|
||||||
|
|
||||||
|
## Payments (all failed — template URL 404)
|
||||||
|
|
||||||
|
| # | Amount | handle (s) | rud (s) | total (s) | status | loadavg |
|
||||||
|
|--:|--------|----------:|--------:|----------:|--------|--------:|
|
||||||
|
| 1 | `GOA:0.000001` | 0.209 | 0.168 | **0.377** | error | 2.17 |
|
||||||
|
| 2 | `GOA:0.001` | 0.234 | 0.17 | **0.404** | error | 2.06 |
|
||||||
|
| 3 | `GOA:0.05` | 0.203 | 0.168 | **0.371** | error | 2.21 |
|
||||||
|
| 4 | `GOA:0.5` | 0.199 | 0.169 | **0.369** | error | 2.17 |
|
||||||
|
| 5 | `GOA:1` | 0.204 | 0.167 | **0.37** | error | 2.07 |
|
||||||
|
| 6 | `GOA:3` | 0.201 | 0.168 | **0.37** | error | 2.22 |
|
||||||
|
| 7 | `GOA:7` | 0.198 | 0.168 | **0.366** | error | 2.18 |
|
||||||
|
| 8 | `GOA:10` | 0.286 | 0.17 | **0.456** | error | 2.23 |
|
||||||
|
| 9 | `GOA:25` | 0.204 | 0.167 | **0.372** | error | 2.21 |
|
||||||
|
| 10 | `GOA:50` | 0.204 | 0.167 | **0.372** | error | 2.41 |
|
||||||
|
| 11 | `GOA:100` | 0.279 | 0.166 | **0.445** | error | 2.27 |
|
||||||
|
| 12 | `GOA:250` | 0.294 | 0.168 | **0.462** | error | 2.23 |
|
||||||
|
| 13 | `GOA:500` | 0.21 | 0.165 | **0.375** | error | 2.12 |
|
||||||
|
| 14 | `GOA:1000` | 0.202 | 0.169 | **0.37** | error | 2.33 |
|
||||||
|
| 15 | `GOA:2500` | 0.198 | 0.164 | **0.362** | error | 2.31 |
|
||||||
|
| 16 | `GOA:5000` | 0.201 | 0.167 | **0.368** | error | 2.43 |
|
||||||
|
|
||||||
|
### Failure
|
||||||
|
Wallet requested:
|
||||||
|
```
|
||||||
|
GET https://taler.hacktivism.ch/instances/goa-demo-cp4zqk/goa-free/templates/
|
||||||
|
→ 404
|
||||||
|
```
|
||||||
|
URI used: `taler://pay-template/taler.hacktivism.ch/instances/goa-demo-cp4zqk/goa-free/?amount=…&summary=…`
|
||||||
|
Fix next: correct pay-template path / merchant public template endpoint.
|
||||||
|
|
||||||
|
## Planned amount ladder (not all run)
|
||||||
|
`GOA:0.000001 0.001 0.05 0.5 1 3 7 10 25 50 100 250 500 1000 2500 5000`
|
||||||
46
2026/2026-07-09--bonfire-ground-zero.md
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Bonfire ground zero + gitbot (2026-07-09)
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Path | `/home/hernani/koopa-bonfire/` |
|
||||||
|
| Containers | `koopa-bonfire`, `koopa-bonfire-db` |
|
||||||
|
| Port | **9021** → Caddy `bonfire.hacktivism.ch` |
|
||||||
|
| Image | `bonfirenetworks/bonfire:1.0.5-social-amd64` |
|
||||||
|
| Env | secrets **64** chars; `DB_MIGRATE_INDEXES_CONCURRENTLY=false` |
|
||||||
|
|
||||||
|
Ground zero: wiped volume + dir, clean migrate (import_me `20200828094944` applied, ~109 migrations, identity tables present).
|
||||||
|
|
||||||
|
## Accounts
|
||||||
|
|
||||||
|
| Login | Role |
|
||||||
|
|-------|------|
|
||||||
|
| `foss` / `foss@bonfire.hacktivism.ch` | operator account |
|
||||||
|
| character `gitbot` | same account (multi-profile) |
|
||||||
|
|
||||||
|
Passwords: `/home/hernani/koopa-bonfire/users.env` (mode 600).
|
||||||
|
|
||||||
|
Public: https://bonfire.hacktivism.ch/
|
||||||
|
|
||||||
|
## Gitbot (commit mirror)
|
||||||
|
|
||||||
|
- Live: `~/koopa-bonfire/bin/gitbot-mirror.py`
|
||||||
|
- Mirror in repo: `scripts/bonfire/gitbot-mirror.py`
|
||||||
|
- State: `~/koopa-bonfire/gitbot-state.json`
|
||||||
|
- Poll Forgejo every **300s** (`gitbot-mirror.service`); log `~/koopa-bonfire/gitbot.log`
|
||||||
|
- No webhook — API only
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user status gitbot-mirror.service
|
||||||
|
tail -20 ~/koopa-bonfire/gitbot.log
|
||||||
|
python3 ~/koopa-bonfire/bin/gitbot-mirror.py
|
||||||
|
```
|
||||||
|
|
||||||
|
More: `2026/2026-07-11--bonfire-gitbot-systemd.md`.
|
||||||
|
|
||||||
|
## Lessons
|
||||||
|
|
||||||
|
- First boot needs **≥64-byte** `SECRET_KEY_BASE`.
|
||||||
|
- Migrations: **`DB_MIGRATE_INDEXES_CONCURRENTLY=false`** on empty DB (avoids concurrent index in txn).
|
||||||
|
- Partial DB → wipe volume and ground zero rather than patch half-migrated state.
|
||||||
34
2026/2026-07-09--castopod-content.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Castopod — admin note (2026-07-09)
|
||||||
|
|
||||||
|
Site: **https://castopod.hacktivism.ch/**
|
||||||
|
Admin: https://castopod.hacktivism.ch/cp-admin
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Live root | `~/koopa-castopod/` |
|
||||||
|
| Compose | `podman-compose` (mirror: `configs/castopod/compose.yml`) |
|
||||||
|
| Containers | `koopa-castopod`, `koopa-castopod-mariadb`, `koopa-castopod-redis` |
|
||||||
|
| Host port | **9020** → Caddy |
|
||||||
|
|
||||||
|
Secrets (not in this repo): `~/koopa-castopod/.env`, `~/koopa-castopod/users.env` (mode 600).
|
||||||
|
Also: `koopa-admin-secrets/…/koopa-castopod/`.
|
||||||
|
|
||||||
|
## Accounts
|
||||||
|
|
||||||
|
| User | Role |
|
||||||
|
|------|------|
|
||||||
|
| `admin` | instance superadmin |
|
||||||
|
| `ngi` | podcaster |
|
||||||
|
|
||||||
|
## Ops
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/koopa-castopod && set -a && source .env && set +a && podman-compose up -d
|
||||||
|
# scripts: scripts/castopod/{up,status,lib}.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Podcast content is managed in Castopod, not in admin-log.
|
||||||
|
|
||||||
|
Boot/branding: `2026/2026-07-13--castopod-boot-branding.md`, `configs/castopod/`, `scripts/castopod/`.
|
||||||
201
2026/2026-07-09--exchange-snapshot-and-restore.md
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
# Exchange snapshot + restore (GOA) — 2026-07-09
|
||||||
|
|
||||||
|
**Scope:** local on host **koopa** only. No registry, no upload, no public internet publish.
|
||||||
|
|
||||||
|
Container was **stopped** then **committed/saved**. Live name: `taler-hacktivism-exchange-ansible`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What was stored (local files)
|
||||||
|
|
||||||
|
Directory (hernani on koopa):
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/hernani/images/taler-stack-20260709-snapshot/
|
||||||
|
```
|
||||||
|
|
||||||
|
| File | Role | Size (approx.) |
|
||||||
|
|------|------|----------------|
|
||||||
|
| `taler-hacktivism-exchange-ansible-live-20260709-snapshot.tar` | full exchange image archive | **~1.6 G** |
|
||||||
|
| `taler-hacktivism-live-20260709-snapshot.tar` | merchant archive (partial earlier run) | ~2.4 G |
|
||||||
|
| `SHA256SUMS` | checksums for completed saves | |
|
||||||
|
|
||||||
|
**Podman images (local store only):**
|
||||||
|
|
||||||
|
| Image | Tag | Notes |
|
||||||
|
|-------|-----|--------|
|
||||||
|
| `localhost/taler-hacktivism-exchange-ansible-live` | `20260709-snapshot` | committed from stopped container |
|
||||||
|
| `localhost/taler-hacktivism-exchange-ansible-live` | `9011` | previous live tag (still present) |
|
||||||
|
| `localhost/taler-hacktivism-live` | `20260709-snapshot` | merchant (if commit kept) |
|
||||||
|
| `localhost/taler-bank-hacktivism-live` | `20260709-snapshot` | bank (if commit kept) |
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# as hernani@koopa
|
||||||
|
ls -lh /home/hernani/images/taler-stack-20260709-snapshot/
|
||||||
|
sha256sum -c /home/hernani/images/taler-stack-20260709-snapshot/SHA256SUMS
|
||||||
|
podman images | grep exchange
|
||||||
|
podman ps -a --filter name=taler-hacktivism-exchange-ansible
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected container state after stop:
|
||||||
|
|
||||||
|
```text
|
||||||
|
taler-hacktivism-exchange-ansible Exited (…) localhost/taler-hacktivism-exchange-ansible-live:9011 0.0.0.0:9011->9011/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. How the snapshot was made
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# as hernani@koopa — local only
|
||||||
|
podman stop taler-hacktivism-exchange-ansible
|
||||||
|
|
||||||
|
TAG=20260709-snapshot
|
||||||
|
OUT=/home/hernani/images/taler-stack-$TAG
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
podman commit taler-hacktivism-exchange-ansible \
|
||||||
|
localhost/taler-hacktivism-exchange-ansible-live:$TAG
|
||||||
|
|
||||||
|
podman save -o "$OUT/taler-hacktivism-exchange-ansible-live-$TAG.tar" \
|
||||||
|
localhost/taler-hacktivism-exchange-ansible-live:$TAG
|
||||||
|
|
||||||
|
sha256sum "$OUT/taler-hacktivism-exchange-ansible-live-$TAG.tar" | tee -a "$OUT/SHA256SUMS"
|
||||||
|
```
|
||||||
|
|
||||||
|
`-p` on commit pauses the container if still running; after `stop` it is optional.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Restore from image (same host)
|
||||||
|
|
||||||
|
### A. From local image tag (fast)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman rm -f taler-hacktivism-exchange-ansible # only if replacing dead/exited
|
||||||
|
|
||||||
|
podman run -d --name taler-hacktivism-exchange-ansible \
|
||||||
|
--network pasta \
|
||||||
|
-p 9011:9011 \
|
||||||
|
--label org.hacktivism.service=taler-exchange \
|
||||||
|
--label org.hacktivism.host_port=9011 \
|
||||||
|
--label org.hacktivism.site=exchange.hacktivism.ch \
|
||||||
|
--label org.hacktivism.currency=GOA \
|
||||||
|
--label org.hacktivism.managed_by=koopa-admin \
|
||||||
|
localhost/taler-hacktivism-exchange-ansible-live:20260709-snapshot \
|
||||||
|
sleep infinity
|
||||||
|
```
|
||||||
|
|
||||||
|
### B. From tar (if image store was wiped)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman load -i /home/hernani/images/taler-stack-20260709-snapshot/taler-hacktivism-exchange-ansible-live-20260709-snapshot.tar
|
||||||
|
# then same podman run as above
|
||||||
|
```
|
||||||
|
|
||||||
|
### C. Start services (manual model, no systemd)
|
||||||
|
|
||||||
|
Inside container as **root**, then app user:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman exec -u root -it taler-hacktivism-exchange-ansible bash
|
||||||
|
# 1) base: postgres, runtime dirs, secmods, wire helpers
|
||||||
|
/root/start_base_services_for_taler_exchange.sh --no-shell
|
||||||
|
# 2) httpd
|
||||||
|
runuser -u taler-exchange-httpd -- /usr/local/bin/start_exchange.sh --restart
|
||||||
|
# 3) health
|
||||||
|
/usr/local/bin/check_exchange-health.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Host / public checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:9011/config
|
||||||
|
curl -sS -o /dev/null -w "%{http_code}\n" https://exchange.hacktivism.ch/config
|
||||||
|
curl -sS -o /dev/null -w "%{http_code}\n" https://exchange.hacktivism.ch/keys
|
||||||
|
```
|
||||||
|
|
||||||
|
Caddy already proxies `exchange.hacktivism.ch` → host `:9011` (see `configs/caddy/`, `configs/ports.md`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. GOA configuration (authoritative tree)
|
||||||
|
|
||||||
|
**Do not** edit package `taler-exchange.conf` defaults by hand. Site config lives in overrides + coins (merchant pattern).
|
||||||
|
|
||||||
|
| In container | In admin-log repo |
|
||||||
|
|--------------|-------------------|
|
||||||
|
| `/etc/taler-exchange/exchange-overrides.conf` | `configs/taler-exchange/exchange-overrides.conf` |
|
||||||
|
| `/etc/taler-exchange/conf.d/exchange-coins.conf` | `configs/taler-exchange/conf.d-exchange-coins.conf` |
|
||||||
|
| secrets (wire gateway) | **not** in admin-log → `koopa-admin-secrets` |
|
||||||
|
| start scripts | `scripts/taler-exchange/` |
|
||||||
|
|
||||||
|
GOA essentials in overrides:
|
||||||
|
|
||||||
|
- `CURRENCY = GOA`, `BASE_URL = https://exchange.hacktivism.ch/`
|
||||||
|
- `SERVE = tcp`, `PORT = 9011`
|
||||||
|
- `[currency-goa]` ENABLED + unit names (Yotta…Atomic)
|
||||||
|
- `[exchange-account-1]` payto `x-taler-bank/bank.hacktivism.ch/exchange`
|
||||||
|
- Terms stubs: `TERMS_ETAG` / `PRIVACY_ETAG` = `no-terms-v0` / `no-privacy-v0`
|
||||||
|
|
||||||
|
Deploy overrides from the local machine (example):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from laptop, secrets never committed to admin-log
|
||||||
|
scp configs/taler-exchange/exchange-overrides.conf \
|
||||||
|
hernani@koopa:/tmp/exchange-overrides.conf
|
||||||
|
scp configs/taler-exchange/conf.d-exchange-coins.conf \
|
||||||
|
hernani@koopa:/tmp/exchange-coins.conf
|
||||||
|
ssh hernani@koopa 'podman cp /tmp/exchange-overrides.conf taler-hacktivism-exchange-ansible:/etc/taler-exchange/exchange-overrides.conf
|
||||||
|
podman cp /tmp/exchange-coins.conf taler-hacktivism-exchange-ansible:/etc/taler-exchange/conf.d/exchange-coins.conf'
|
||||||
|
```
|
||||||
|
|
||||||
|
Greenfield (empty Debian + packages) only:
|
||||||
|
`scripts/taler-exchange/archive/exchange-bootstrap.sh` **as root inside** container — generates a **new** master key; not the same as restoring this snapshot.
|
||||||
|
|
||||||
|
After config change:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
runuser -u taler-exchange-httpd -- /usr/local/bin/start_exchange.sh --restart
|
||||||
|
# optional offline keys / wire:
|
||||||
|
# scripts/taler-exchange/wire-enable-and-upload.sh
|
||||||
|
# scripts/taler-exchange/offline-sign-upload-keys.sh
|
||||||
|
# /usr/local/bin/install_no_terms.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Ansible
|
||||||
|
|
||||||
|
There is **no** Ansible playbook in `koopa-admin-log` / this monorepo path for the exchange.
|
||||||
|
Operational model is: **podman** + **manual start scripts** + **GOA overrides** as above.
|
||||||
|
|
||||||
|
If an external Ansible tree is added later, it should only:
|
||||||
|
|
||||||
|
1. `podman load` / ensure image tag
|
||||||
|
2. `podman run` with pasta + `9011:9011` + labels above
|
||||||
|
3. copy GOA configs from `configs/taler-exchange/`
|
||||||
|
4. run `start_base` → `start_exchange`
|
||||||
|
5. never push images to a public registry without explicit request
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Related docs
|
||||||
|
|
||||||
|
| Doc | Content |
|
||||||
|
|-----|---------|
|
||||||
|
| `configs/taler-exchange/README.md` | GOA site notes, terms, ports |
|
||||||
|
| `scripts/taler-exchange/archive/README.md` | bootstrap vs daily start |
|
||||||
|
| `configs/ports.md` | 9011 pasta |
|
||||||
|
| `ops/ROOT_HYGIENE.md` | secrets layout |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Session note (2026-07-09)
|
||||||
|
|
||||||
|
- Exchange **stopped** on request (`Exited 137`).
|
||||||
|
- Snapshot **local only** under `/home/hernani/images/…`.
|
||||||
|
- Known pre-stop issue: wirewatch had died after Postgres ownership/outage → bank confirms OK but wallet “Withdraw money from bank…” could hang until wirewatch imports credits. After restore, always verify **wirewatch** is live (`check_exchange-health.sh`).
|
||||||
34
2026/2026-07-09--ipv6-15d6.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# 2026-07-09 — IPv6 `:15d6` reachability
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
`https://exchange.hacktivism.ch/config` failed with `ERR_ADDRESS_UNREACHABLE`
|
||||||
|
when clients preferred AAAA `2a02:168:53a8:0:d584:9e34:13cf:15d6`.
|
||||||
|
IPv4 `212.51.151.254` worked (HTTP 200).
|
||||||
|
|
||||||
|
## Path comparison
|
||||||
|
|
||||||
|
| | IPv4 | IPv6 |
|
||||||
|
|--|------|------|
|
||||||
|
| Public endpoint | WAN IP DNAT | Host global `:15d6` |
|
||||||
|
| VeciGate | dstnat 443→9001 | forward 443 (no NAT) |
|
||||||
|
| Host listener | Caddy `:9001` | `https-proxy.socket` `:443` → 9001 |
|
||||||
|
| firewalld needed | 9001 | **443** (+ 80 for ACME) |
|
||||||
|
|
||||||
|
## Fix applied
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# on koopa (root)
|
||||||
|
firewall-cmd --permanent --zone=public --add-port=443/tcp
|
||||||
|
firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
VeciGate: confirmed IPv6 filter allow 443; added explicit allow **80** for ACME.
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -6 -sS -o /dev/null -w '%{http_code} %{remote_ip}\n' \
|
||||||
|
https://exchange.hacktivism.ch/config
|
||||||
|
# 200 2a02:168:53a8:0:d584:9e34:13cf:15d6
|
||||||
|
```
|
||||||
17
2026/2026-07-09--keys-wire.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 2026-07-09 — `/keys` + Wire + Merchant GOA
|
||||||
|
|
||||||
|
## Done
|
||||||
|
|
||||||
|
1. **Exchange `/keys`:** was suspended (no wire accounts → then no online signing keys).
|
||||||
|
- `enable-account` + `wire-fee` + `global-fee` → offline **upload** (local `BASE_URL=http://127.0.0.1:9011/`)
|
||||||
|
- `download` → `sign` → `upload` for denoms + signkeys
|
||||||
|
- Result: `GET /keys` **200**, ~1 MB, currency GOA, master `TW6K5FXF…`
|
||||||
|
2. **Merchant health:** `check_merchant-health.sh` includes exchange `/keys` + MASTER_KEY match → **ALL CRITICAL PASSED** (GOA via `host.containers.internal:9011` fallback).
|
||||||
|
3. **Wire helpers:** secret `exchange-accountcredentials-1.secret.conf` needs group `taler-exchange-db` mode 640; then aggregator/closer/wirewatch/transfer OK.
|
||||||
|
4. **Smoke:** local+public `/keys` 200; bank wire-gateway config as `exchange` → 200 on `:9012`; bank SPA public earlier (LE cert). Full phone Withdraw is manual (wallet app).
|
||||||
|
5. **Admin-log:** docs diagram keys/wire; scripts `wire-enable-and-upload.sh`, `offline-sign-upload-keys.sh`, `start_wire_helpers.sh`; merchant overrides GOA; ports 9012 noted.
|
||||||
|
|
||||||
|
## Manual withdraw (phone)
|
||||||
|
|
||||||
|
1. https://bank.hacktivism.ch/ → login `explorer` (pw host `/root/bank-explorer-password.txt`)
|
||||||
|
2. Withdraw GOA → open in Taler Wallet (exchange.hacktivism.ch `/keys` must stay 200)
|
||||||
43
2026/2026-07-09--koopa-prime-jellyfin.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# koopa-prime — Jellyfin + qBittorrent (2026-07-09)
|
||||||
|
|
||||||
|
## Containers (podman-compose)
|
||||||
|
|
||||||
|
Path: `/home/hernani/koopa-prime/`
|
||||||
|
|
||||||
|
| Container | Role | Host port |
|
||||||
|
|-----------|------|-----------|
|
||||||
|
| `koopa-prime-jellyfin` | Video library (Jellyfin) | **9022** |
|
||||||
|
| `koopa-prime-qbittorrent` | BitTorrent client | **9023** WebUI, **6881** BT |
|
||||||
|
|
||||||
|
## Media wiring
|
||||||
|
|
||||||
|
| Host path | Jellyfin | qBittorrent |
|
||||||
|
|-----------|----------|-------------|
|
||||||
|
| `/home/hernani/Downloads` | `/media/downloads` **ro** (library) | `/downloads` **rw** (save path) |
|
||||||
|
|
||||||
|
Downloads land in the same folder Jellyfin scans.
|
||||||
|
|
||||||
|
## Auth (login required)
|
||||||
|
|
||||||
|
- **Jellyfin:** admin account created via Startup API; library access needs login.
|
||||||
|
- **qBittorrent:** WebUI password required (401 without session).
|
||||||
|
|
||||||
|
Credentials: `~/koopa-prime/users.env` (mode 600).
|
||||||
|
|
||||||
|
## Public / Caddy
|
||||||
|
|
||||||
|
| Host | Backend |
|
||||||
|
|------|---------|
|
||||||
|
| `prime.hacktivism.ch` | `127.0.0.1:9022` |
|
||||||
|
| `torrent.prime.hacktivism.ch` | `127.0.0.1:9023` (needs DNS CNAME) |
|
||||||
|
|
||||||
|
See session notes for Caddy snippets. BT port **6881** may need firewall/VeciGate if peers should connect.
|
||||||
|
|
||||||
|
## Ops
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/koopa-prime
|
||||||
|
podman-compose ps
|
||||||
|
podman-compose logs -f jellyfin
|
||||||
|
podman-compose up -d
|
||||||
|
```
|
||||||
8
2026/2026-07-09--merchant-bank-link.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# 2026-07-09 — Merchant demo + bank link
|
||||||
|
|
||||||
|
1. Disabled mandatory SMS/email TAN on merchant (`MANDATORY_TAN_CHANNELS` empty).
|
||||||
|
2. Self-provisioned instance `goa-demo-cp4zqk` via `POST /instances` (HTTP 200, no challenge).
|
||||||
|
3. Created bank user `goa-demo-cp4zqk`, credited **GOA:5000**.
|
||||||
|
4. Linked payto to merchant: `POST .../private/accounts` → active wire account.
|
||||||
|
|
||||||
|
See `configs/taler-hacktivism/demo-instance-goa-demo-cp4zqk.md`.
|
||||||
6
2026/2026-07-09.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# 2026-07-09
|
||||||
|
|
||||||
|
- bank.hacktivism.ch LE ~05:43 CEST
|
||||||
|
- public intros :9013–9015 + landing-stats; merchant stats fail-closed
|
||||||
|
- dual-currency terms + Swiss FADP privacy (merchant/exchange)
|
||||||
|
- taler-monitoring (+ versions vs deb.taler.net)
|
||||||
40
2026/2026-07-10--admin-log-origin-forgejo.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# koopa-admin-log origin on git.hacktivism.ch (2026-07-10)
|
||||||
|
|
||||||
|
## Remote
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Host | **Forgejo rootless** `https://git.hacktivism.ch/` |
|
||||||
|
| Repo | **https://git.hacktivism.ch/hernani/koopa-admin-log** |
|
||||||
|
| Visibility | **public** (ops log only; **no secret values**) |
|
||||||
|
| Default branch | `main` |
|
||||||
|
| HTTPS clone | `https://git.hacktivism.ch/hernani/koopa-admin-log.git` |
|
||||||
|
| SSH clone | `ssh://git@git.hacktivism.ch:9200/hernani/koopa-admin-log.git` |
|
||||||
|
|
||||||
|
## Local setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/koopa-admin-log
|
||||||
|
git remote add origin https://git.hacktivism.ch/hernani/koopa-admin-log.git
|
||||||
|
# or SSH (port 9200):
|
||||||
|
# git remote add origin ssh://git@git.hacktivism.ch:9200/hernani/koopa-admin-log.git
|
||||||
|
|
||||||
|
git push -u origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
Auth: Forgejo user **`hernani`** (admin; registration disabled site-wide).
|
||||||
|
Credentials: `koopa-admin-secrets/…/koopa-forgejo/users.env` — not in this repo.
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- Forgejo install: `2026-07-10--forgejo-rootless.md`
|
||||||
|
- Secrets sibling: **not** hosted here (private material stays in `koopa-admin-secrets` only)
|
||||||
|
|
||||||
|
## Network (SSH :9200)
|
||||||
|
|
||||||
|
Public git-SSH requires:
|
||||||
|
|
||||||
|
1. VeciGate WAN/hairpin + IPv6 forward **9200** → koopa
|
||||||
|
2. koopa firewalld **9200/tcp**
|
||||||
|
|
||||||
|
See `2026-07-10--forgejo-rootless.md` (Network exposure) and `../vecigate-admin-log/docs/CHANGELOG.md`.
|
||||||
60
2026/2026-07-10--bonfire-federate.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Bonfire — FEDERATE + public guest access (2026-07-10)
|
||||||
|
|
||||||
|
Follow-up to ground zero: `2026-07-09--bonfire-ground-zero.md`.
|
||||||
|
|
||||||
|
## Change
|
||||||
|
|
||||||
|
| Item | Before | After |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `.env` `FEDERATE` | unset | **`true`** |
|
||||||
|
| Container | recreated | `podman-compose up -d --force-recreate web` |
|
||||||
|
| `printenv FEDERATE` (web) | — | `true` |
|
||||||
|
| `HOSTNAME` / compose | `bonfire.hacktivism.ch` | unchanged |
|
||||||
|
| Port | **9021** → Caddy | unchanged |
|
||||||
|
|
||||||
|
Only new env key vs ground-zero `.env`: **`FEDERATE`**.
|
||||||
|
|
||||||
|
Live: `/home/hernani/koopa-bonfire/.env` (mode 600).
|
||||||
|
Mirror: `koopa-admin-secrets/koopa/home-hernani/koopa-bonfire/.env`.
|
||||||
|
Compose mirror: `configs/bonfire/compose.yml`.
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
- Allow federation-related runtime paths (`FEDERATE` consumed in Bonfire `runtime.exs` as `true|yes|1`).
|
||||||
|
- Keep **guest-readable** public surfaces (no login required for explore / profiles that are already public).
|
||||||
|
- Operator stance: **federation enablement is accepted as OK** even if the UI banner lags.
|
||||||
|
|
||||||
|
## Observed behaviour (after recreate)
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
|-------|--------|
|
||||||
|
| Local `http://127.0.0.1:9021/` | 200; guest UI (Explore / Log in) |
|
||||||
|
| Guest UI banner | still may show **“Federation disabled”** |
|
||||||
|
| App RPC (earlier probe) | `activity_pub` instance sometimes still `federating: false`, `hostname: "localhost"` despite env |
|
||||||
|
| Public profiles | guest-readable when already public |
|
||||||
|
| Front / local feed | can be sparse depending on boundaries / feed window |
|
||||||
|
|
||||||
|
So: **env is set and container healthy**; full ActivityPub “federating=true” in Application config may still need admin UI / deeper config if outbound federation is required later.
|
||||||
|
|
||||||
|
## Ops
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/koopa-bonfire
|
||||||
|
grep ^FEDERATE= .env
|
||||||
|
podman exec koopa-bonfire printenv FEDERATE HOSTNAME
|
||||||
|
podman ps --filter name=koopa-bonfire
|
||||||
|
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:9021/
|
||||||
|
```
|
||||||
|
|
||||||
|
Recreate web after env change:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/koopa-bonfire
|
||||||
|
set -a && source .env && set +a
|
||||||
|
podman-compose up -d --force-recreate web
|
||||||
|
```
|
||||||
|
|
||||||
|
## Secrets / log rules
|
||||||
|
|
||||||
|
- Values only in **`koopa-admin-secrets`**, not in this repo.
|
||||||
|
- Daylog major bullet only (see `2026-07-10.md`).
|
||||||
90
2026/2026-07-10--forgejo-rootless.md
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Forgejo rootless — git.hacktivism.ch (2026-07-10)
|
||||||
|
|
||||||
|
## Emphasis: rootless
|
||||||
|
|
||||||
|
Forgejo is installed **rootless end-to-end**:
|
||||||
|
|
||||||
|
1. **Image:** `codeberg.org/forgejo/forgejo:11-rootless` (process runs as UID/GID **1000**, not container root).
|
||||||
|
2. **Podman:** started as user **hernani** (rootless podman), **not** rootful.
|
||||||
|
3. **Userns:** `userns_mode: keep-id` so container 1000 maps to host hernani.
|
||||||
|
4. **Bind mounts:** `./data` → `/var/lib/gitea`, `./config` → `/etc/gitea` under `~/koopa-forgejo/`.
|
||||||
|
|
||||||
|
Do **not** switch to the non-rootless `forgejo:11` image under rootless podman: that image refuses to run as mapped root (`Forgejo is not supposed to be run as root`).
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Path | `/home/hernani/koopa-forgejo/` |
|
||||||
|
| Containers | `koopa-forgejo` (**rootless**), `koopa-forgejo-db` (postgres:16-alpine) |
|
||||||
|
| HTTP | host **9024** → Caddy `git.hacktivism.ch` |
|
||||||
|
| SSH (git) | host **9200** → container 2222 (direct; not via Caddy) |
|
||||||
|
| ROOT_URL | `https://git.hacktivism.ch/` |
|
||||||
|
| Landing | explore (public browse OK) |
|
||||||
|
| LE cert | automatic via Caddy (`CN=git.hacktivism.ch`) |
|
||||||
|
|
||||||
|
Compose + redacted `app.ini`: `configs/forgejo/`.
|
||||||
|
Caddy site block: `configs/caddy/git.hacktivism.ch.caddy` (+ full `configs/caddy/Caddyfile`).
|
||||||
|
|
||||||
|
## Access policy
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|---------|--------|
|
||||||
|
| `DISABLE_REGISTRATION` | **true** — no free self-signup |
|
||||||
|
| `SHOW_REGISTRATION_BUTTON` | **false** |
|
||||||
|
| OpenID signup/signin | **false** |
|
||||||
|
| Public site | readable without login (`REQUIRE_SIGNIN_VIEW=false`) |
|
||||||
|
| Accounts | admin-created only (`forgejo admin user create`) |
|
||||||
|
|
||||||
|
Reserved username `admin` cannot be used; first admin: **`hernani`**.
|
||||||
|
|
||||||
|
Passwords: `~/koopa-forgejo/users.env` + DB/secrets in `.env` (mode 600).
|
||||||
|
Mirror: `koopa-admin-secrets/koopa/home-hernani/koopa-forgejo/`.
|
||||||
|
|
||||||
|
## Start / status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/koopa-forgejo
|
||||||
|
set -a && source .env && set +a
|
||||||
|
podman-compose up -d
|
||||||
|
podman ps --filter name=koopa-forgejo
|
||||||
|
curl -sS http://127.0.0.1:9024/api/healthz
|
||||||
|
curl -sS https://git.hacktivism.ch/api/healthz
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caddy (host root)
|
||||||
|
|
||||||
|
Live `/etc/caddy/Caddyfile` matches `configs/caddy/Caddyfile` (applied 2026-07-10; `http://git.` in ACME list; SSH note **9200**).
|
||||||
|
|
||||||
|
Site block live in `/etc/caddy/Caddyfile` (mirror under `configs/caddy/`).
|
||||||
|
Prefer including `http://git.hacktivism.ch` in the shared ACME/redirect host list.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo caddy validate --config /etc/caddy/Caddyfile
|
||||||
|
sudo systemctl reload caddy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network exposure (2026-07-10)
|
||||||
|
|
||||||
|
| Layer | Rule |
|
||||||
|
|-------|------|
|
||||||
|
| **VeciGate IPv4** | WAN **9200** → `192.168.100.95:9200`; hairpin LAN/LAN2 → public A:9200 |
|
||||||
|
| **VeciGate IPv6** | WAN→LAN forward **tcp/9200** |
|
||||||
|
| **koopa firewalld** | `public` **9200/tcp** (verified 2026-07-10: LAN + hairpin + `ssh -T`) |
|
||||||
|
|
||||||
|
Docs: `../vecigate-admin-log` (`ip/firewall/nat/`, `ipv6/firewall/filter/`, `docs/CHANGELOG.md`);
|
||||||
|
host ports: `configs/firewalld/public-ports.md`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# host
|
||||||
|
sudo firewall-cmd --permanent --add-port=9200/tcp && sudo firewall-cmd --reload
|
||||||
|
# client
|
||||||
|
ssh -p 9200 -T git@git.hacktivism.ch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- DNS: `git.hacktivism.ch` → koopa (same family as other `*.hacktivism.ch`).
|
||||||
|
- Git over SSH: `ssh://git@git.hacktivism.ch:9200/…`
|
||||||
|
- Clone URLs advertise `SSH_PORT=9200`; container still listens on internal **2222**.
|
||||||
|
- Admin-log origin: `hernani/koopa-admin-log` on this Forgejo — see `2026-07-10--admin-log-origin-forgejo.md`.
|
||||||
27
2026/2026-07-10--forgejo-theme-exchange-dark.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# 2026-07-10 — Forgejo global theme (exchange-dark)
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Make https://git.hacktivism.ch look closer to https://exchange.hacktivism.ch: warm dark layout, gold + teal accents, readable text.
|
||||||
|
|
||||||
|
## Done on koopa
|
||||||
|
|
||||||
|
- Custom theme file `theme-hacktivism.css` (full variable set from forgejo-dark + exchange palette).
|
||||||
|
- Installed under `~/koopa-forgejo/data/custom/public/assets/css/` (and gitea/custom mirror).
|
||||||
|
- Logo/favicon under `…/assets/img/`.
|
||||||
|
- `DEFAULT_THEME = hacktivism` in app.ini + compose `FORGEJO__ui__*`.
|
||||||
|
- Forced DB: `UPDATE "user" SET theme = 'hacktivism'` (logged-in preference was still `forgejo-auto`).
|
||||||
|
|
||||||
|
## Readability
|
||||||
|
|
||||||
|
- Body/text cream on `#1a1410`; muted secondary not below ~`#c9b8a0`.
|
||||||
|
- Gold primary buttons with dark label text.
|
||||||
|
- Explicit overrides for menus, forms, code, labels, messages, footer.
|
||||||
|
|
||||||
|
## Admin-log
|
||||||
|
|
||||||
|
Source of truth: `configs/forgejo/assets/` + `configs/forgejo/README.md`.
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
Browser cache may hold old CSS (`Cache-Control: max-age=21600`). Hard-reload or private window if UI looks stale.
|
||||||
21
2026/2026-07-10.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# 2026-07-10
|
||||||
|
|
||||||
|
- landing mem snapshot fix (pipeline → 0 B)
|
||||||
|
- Caddy redir / → /intro/ (three sites)
|
||||||
|
- SECRETS.md paths only; no secret material in admin-log
|
||||||
|
- **Bonfire** `FEDERATE=true` (+ web recreate); guest public profiles OK; UI may still say federation disabled
|
||||||
|
→ topic: `2026-07-10--bonfire-federate.md`; secrets: `koopa-admin-secrets/…/koopa-bonfire/`
|
||||||
|
- **Forgejo rootless** on `git.hacktivism.ch` (HTTP **9024**, git-SSH **9200**); no free registration; admin `hernani`
|
||||||
|
→ topic: `2026-07-10--forgejo-rootless.md`; secrets: `koopa-admin-secrets/…/koopa-forgejo/`
|
||||||
|
- **git-SSH :9200** exposed + **verified**: VeciGate DNAT/hairpin + IPv6; firewalld **9200/tcp** (LAN/hairpin/`ssh -T` OK)
|
||||||
|
→ vecigate-admin-log CHANGELOG 2026-07-10; `configs/firewalld/public-ports.md`
|
||||||
|
- **admin-log origin** → Forgejo `hernani/koopa-admin-log` on git.hacktivism.ch (public; SSH :9200)
|
||||||
|
→ topic: `2026-07-10--admin-log-origin-forgejo.md`
|
||||||
|
- admin-log: castopod/prime compose mirrors; firewalld list-all +9200; Caddyfile.taler-host marked legacy
|
||||||
|
- admin-log: Tor relay mirror documented (`configs/tor/`, host/tor)
|
||||||
|
- Caddyfile applied on host: git. in ACME/redirect list, SSH note :9200; live matches configs/caddy mirror (verified)
|
||||||
|
- Bonfire: republish foss outbox → Local users + public internet feeds; guest `/feed/local` shows posts
|
||||||
|
- Reflect live koopa state in admin-log: `host/overview/LIVE.md`, exchange name **taler-hacktivism-exchange-ansible**, ansible mirror
|
||||||
|
- configs/: rename mirrors to live container names (`taler-hacktivism`, `taler-hacktivism-bank`; image banking)
|
||||||
|
- **Forgejo branding**: theme `hacktivism` (exchange-dark) + Kamek-inspired logo (blue robe, flying on wand); compose DEFAULT_THEME
|
||||||
|
→ `configs/forgejo/`
|
||||||
25
2026/2026-07-11--bonfire-branding-feeds.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Bonfire branding + public feeds (2026-07-11)
|
||||||
|
|
||||||
|
After `2026/2026-07-11--bonfire-gitbot-systemd.md`. Logo: `2026/2026-07-11--bonfire-logo-not-served.md`.
|
||||||
|
|
||||||
|
## Branding
|
||||||
|
|
||||||
|
- hacktivism magician logo (same as git.hacktivism.ch).
|
||||||
|
- Deploy: `scripts/bonfire/apply-branding.sh`
|
||||||
|
- Logo URL: `/images/hacktivism-logo.svg` (compose bind-mount).
|
||||||
|
|
||||||
|
**Official:** [Admin Tools — Instance Settings](https://docs.bonfirenetworks.org/admin-tools.html) (name/icon in UI).
|
||||||
|
Instance theme via `bonfire remote` matches [Settings System](https://docs.bonfirenetworks.org/settings_system.html) (`scope: :instance`).
|
||||||
|
No official doc for custom static files in podman — bind-mount workaround only.
|
||||||
|
|
||||||
|
## Public feed (ops)
|
||||||
|
|
||||||
|
Gitbot activity can stay in the user outbox; guests on `/` may see a stale feed until republished.
|
||||||
|
|
||||||
|
**Official:** [Feed structure](https://docs.bonfirenetworks.org/feed_structure.html) (`FeedPublish`).
|
||||||
|
No official “republish outbox to local feed” procedure — ops SQL + gitbot workaround.
|
||||||
|
|
||||||
|
- Continuous: `gitbot-mirror.py` republishes outbox → local + internet every cycle.
|
||||||
|
- One-off: `scripts/bonfire/publish-outbox-to-public.sql`.
|
||||||
|
|
||||||
|
Check: https://bonfire.hacktivism.ch/feed/local
|
||||||
26
2026/2026-07-11--bonfire-gitbot-systemd.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Bonfire gitbot + boot (2026-07-11)
|
||||||
|
|
||||||
|
After `2026/2026-07-09--bonfire-ground-zero.md`.
|
||||||
|
|
||||||
|
## Problems
|
||||||
|
|
||||||
|
- Bare hostnames in commit text crash `createPost` (`URI.parse/1`).
|
||||||
|
- No webhook; bot polls git.ngi-0.eu every 300s.
|
||||||
|
- Stack did not start on boot.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
- `gitbot-mirror.py`: prefix bare hosts, fallback post, skip after 3 fails.
|
||||||
|
- User systemd: `gitbot-mirror.service`, `container-koopa-bonfire-*.service` (`podman-compose up -d`).
|
||||||
|
- Install: `scripts/bonfire/install-systemd.sh`
|
||||||
|
|
||||||
|
**Official:** [Hosting guide](https://docs.bonfirenetworks.org/deploy.html) recommends Co-op Cloud; we run podman-compose on koopa (ops choice, not upstream path).
|
||||||
|
GraphQL `createPost`: [API reference](https://docs.bonfirenetworks.org/api-reference.html) — no official gitbot mirror doc; custom script.
|
||||||
|
|
||||||
|
## Ops
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user status container-koopa-bonfire-db.service container-koopa-bonfire.service gitbot-mirror.service
|
||||||
|
tail -30 ~/koopa-bonfire/gitbot.log
|
||||||
|
python3 ~/koopa-bonfire/bin/gitbot-mirror.py
|
||||||
|
```
|
||||||
19
2026/2026-07-11--bonfire-logo-not-served.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Bonfire logo not served (2026-07-11)
|
||||||
|
|
||||||
|
After `scripts/bonfire/apply-branding.sh` the sidebar showed a broken image.
|
||||||
|
|
||||||
|
## Symptom
|
||||||
|
|
||||||
|
- `instance_icon` was `/images/hacktivism-logo.svg` but the file returned 404
|
||||||
|
- `data/branding/` on disk is not a public URL; `/branding/` is not served by Bonfire
|
||||||
|
|
||||||
|
## Official docs
|
||||||
|
|
||||||
|
[Admin Tools — Instance Settings](https://docs.bonfirenetworks.org/admin-tools.html) covers instance name and icon in the admin UI only.
|
||||||
|
|
||||||
|
No official guide for custom static files in Docker/podman. We use a bind-mount workaround.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
`compose.yml` mounts `data/branding/logo.svg` into `priv/static/images/hacktivism-logo.svg`.
|
||||||
|
Recreate web: `podman-compose up -d --no-deps --force-recreate web`
|
||||||
5
2026/2026-07-11.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# 2026-07-11
|
||||||
|
|
||||||
|
- Bonfire gitbot + boot systemd → `2026/2026-07-11--bonfire-gitbot-systemd.md`
|
||||||
|
- Bonfire logo not served → `2026/2026-07-11--bonfire-logo-not-served.md`
|
||||||
|
- Bonfire branding + public feeds → `2026/2026-07-11--bonfire-branding-feeds.md`
|
||||||
31
2026/2026-07-13--castopod-boot-branding.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Castopod — boot + branding (2026-07-13)
|
||||||
|
|
||||||
|
Site: **https://castopod.hacktivism.ch/** · `~/koopa-castopod/` · Caddy **:9020**
|
||||||
|
|
||||||
|
## Boot
|
||||||
|
|
||||||
|
User systemd + linger (like Bonfire):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/castopod/install-systemd.sh
|
||||||
|
systemctl --user status container-koopa-castopod{,-mariadb,-redis}.service
|
||||||
|
```
|
||||||
|
|
||||||
|
Units: `configs/castopod/container-koopa-castopod*.service`
|
||||||
|
|
||||||
|
## Branding
|
||||||
|
|
||||||
|
Official settings: https://docs.castopod.org/main/en/user-guide/instance/settings/
|
||||||
|
Docker: https://docs.castopod.org/main/en/getting-started/docker/
|
||||||
|
|
||||||
|
| Item | Host choice |
|
||||||
|
|------|-------------|
|
||||||
|
| Site icon | Magician logo (`configs/castopod/assets/img/`) |
|
||||||
|
| Theme | Castopod accent **amber** |
|
||||||
|
| Optional CSS | `assets/css/theme-hacktivism-overlay.css` (not upstream) |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/castopod/apply-branding.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Stack/accounts: `2026/2026-07-09--castopod-content.md`.
|
||||||
3
2026/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Day logs 2026
|
||||||
|
|
||||||
|
Major ops only. One file per work day; written the same day (not backfilled).
|
||||||
86
README.md
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# koopa-admin-log
|
||||||
|
|
||||||
|
Ops log and config mirror for host **koopa** (openSUSE Tumbleweed).
|
||||||
|
|
||||||
|
**Day logs:** `2026/` (major only, written same day). Topic notes: `YYYY-MM-DD--topic.md`.
|
||||||
|
|
||||||
|
**Secrets:** not in this repo — see **`SECRETS.md`** and sibling **`koopa-admin-secrets`**.
|
||||||
|
|
||||||
|
## Git origin
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|--|--|
|
||||||
|
| **origin** | `https://git.hacktivism.ch/hernani/koopa-admin-log.git` |
|
||||||
|
| Web | https://git.hacktivism.ch/hernani/koopa-admin-log |
|
||||||
|
| SSH | `ssh://git@git.hacktivism.ch:9200/hernani/koopa-admin-log.git` |
|
||||||
|
|
||||||
|
Hosted on **Forgejo rootless** (this host). See `2026/2026-07-10--admin-log-origin-forgejo.md`.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
host/ # openSUSE host-level config + overview
|
||||||
|
overview/services.md # graphical service map
|
||||||
|
systemd/ firewalld/ caddy/ network/
|
||||||
|
configs/ # mirrored app configs (caddy, taler, forgejo, …)
|
||||||
|
taler-hacktivism/ # merchant container taler-hacktivism
|
||||||
|
taler-hacktivism-bank/ # bank container (image …-banking)
|
||||||
|
taler-exchange/ # exchange conf (inside ansible container)
|
||||||
|
taler-exchange-ansible/ # container taler-hacktivism-exchange-ansible
|
||||||
|
forgejo/ # rootless git.hacktivism.ch
|
||||||
|
castopod/ bonfire/ prime/ # compose mirrors (no secret values)
|
||||||
|
tops/ # koopa-tops-ng1…ng3 + autostart unit
|
||||||
|
tor/ # KoopaRelay ORPort 8080
|
||||||
|
scripts/ # merchant, exchange, monitoring helpers
|
||||||
|
taler-merchant/ # merchant-model start scripts
|
||||||
|
taler-exchange/ # same model; archive/ = greenfield bootstrap
|
||||||
|
2026/ # day logs + dated topic notes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Live containers (podman)
|
||||||
|
|
||||||
|
**Authoritative snapshot of host koopa:** [`host/overview/LIVE.md`](host/overview/LIVE.md) (refreshed from live).
|
||||||
|
|
||||||
|
| Name (podman) | Role | Host port |
|
||||||
|
|---------------|------|-----------|
|
||||||
|
| `taler-hacktivism` | merchant | **9010** (+ landing **9015**) |
|
||||||
|
| **`taler-hacktivism-exchange-ansible`** | exchange (Ansible/systemd) | **9011** (+ landing **9014**) |
|
||||||
|
| `taler-hacktivism-bank` | libeufin-bank (GOA) | **9012** (+ landing **9013**) |
|
||||||
|
| `koopa-castopod` (+ mariadb/redis) | Castopod | **9020** |
|
||||||
|
| `koopa-bonfire` (+ postgres) | Bonfire | **9021** |
|
||||||
|
| `koopa-prime-jellyfin` | Jellyfin | **9022** |
|
||||||
|
| `koopa-prime-qbittorrent` | qBittorrent | **9023** |
|
||||||
|
| `koopa-forgejo` (+ postgres) | Forgejo rootless | **9024** + SSH **9200** |
|
||||||
|
| `koopa-tops-ng1` … `ng3` | static nginx | **9090**–**9092** |
|
||||||
|
|
||||||
|
Start model (Taler merchant/bank): **root** `start_base_services_*` → `/usr/local/bin/start_*.sh`.
|
||||||
|
Start model (exchange): `~/ansible-taler-exchange/` → `run-container-koopa.sh` + `deploy-hacktivism-goa.sh`.
|
||||||
|
Start model (user apps): `hernani` → `cd ~/koopa-* && podman-compose up -d`.
|
||||||
|
Start model (tops): user unit `container-koopa-tops.service` (linger).
|
||||||
|
|
||||||
|
Public hosts: `taler.` / `exchange.` / `bank.` / `castopod.` / `bonfire.` / `prime.` / `bt.` / **`git.hacktivism.ch`** / `tops.ng1`–`ng3`.hacktivism.ch
|
||||||
|
(Caddy → 9010–9015 / 9020–**9024** / **9090–9092**; git-SSH host **9200** direct).
|
||||||
|
|
||||||
|
Service **details**: `configs/taler-hacktivism*`, `configs/taler-exchange*`, `configs/forgejo/`, `configs/tops/`, `scripts/*`.
|
||||||
|
Host-wide picture: **`host/overview/services.md`**.
|
||||||
|
Port table: **`configs/ports.md`**.
|
||||||
|
Router: **`../vecigate-admin-log`**.
|
||||||
|
|
||||||
|
**Benchmarks** (drive from the local machine, measure on koopa): **`benchmarks/`**
|
||||||
|
→ amount ladder: `benchmarks/amount-ladder/`.
|
||||||
|
|
||||||
|
**Exchange snapshot/restore (GOA, local images only):**
|
||||||
|
→ `2026/2026-07-09--exchange-snapshot-and-restore.md`
|
||||||
|
|
||||||
|
**Exchange `/keys` + Wire + bank account (diagram):**
|
||||||
|
`configs/taler-exchange/README.md` (section “/keys, Wire and Account”).
|
||||||
|
|
||||||
|
**Castopod (stack/accounts/boot — not podcast content):**
|
||||||
|
`2026/2026-07-09--castopod-content.md` → https://castopod.hacktivism.ch/
|
||||||
|
|
||||||
|
**Bonfire FEDERATE + guest public (2026-07-10):**
|
||||||
|
`2026/2026-07-10--bonfire-federate.md` → https://bonfire.hacktivism.ch/
|
||||||
|
(ground zero: `2026/2026-07-09--bonfire-ground-zero.md`)
|
||||||
|
|
||||||
|
**Forgejo rootless (git.hacktivism.ch, no free registration, SSH :9200):**
|
||||||
|
`2026/2026-07-10--forgejo-rootless.md` → https://git.hacktivism.ch/
|
||||||
34
SECRETS.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Secrets — not stored in this repo
|
||||||
|
|
||||||
|
Live passwords/keys live in sibling **`koopa-admin-secrets`**.
|
||||||
|
|
||||||
|
## Map
|
||||||
|
|
||||||
|
### Taler — host `/root`
|
||||||
|
|
||||||
|
| What | Mirror (`koopa-admin-secrets/…`) | Live |
|
||||||
|
|------|----------------------------------|------|
|
||||||
|
| Bank admin/exchange/explorer/demo/bench | `koopa/host-root/taler-bank/*.txt` | `/root/bank-*-password.txt` |
|
||||||
|
| Merchant instance goa-demo | `koopa/host-root/taler-merchant/…` | `/root/merchant-*-password.txt` |
|
||||||
|
| Exchange ATTRIBUTE_ENCRYPTION_KEY | `containers/taler-exchange/secrets/exchange-attribute-encryption.secret.conf` | container `/etc/taler-exchange/secrets/` |
|
||||||
|
| Exchange wire / DB | `…/*.secret.conf` (+ `.example` templates) | same under container |
|
||||||
|
|
||||||
|
### Apps — `/home/hernani/`
|
||||||
|
|
||||||
|
| What | Mirror | Live |
|
||||||
|
|------|--------|------|
|
||||||
|
| Castopod | `koopa/home-hernani/koopa-castopod/{.env,users.env}` (+ compose mirror in admin-log `configs/castopod/`) | `~/koopa-castopod/` |
|
||||||
|
| Bonfire (`FEDERATE=true`) | `koopa/home-hernani/koopa-bonfire/{.env,users.env,compose.yml}` | `~/koopa-bonfire/` |
|
||||||
|
| Prime (Jellyfin + qBittorrent) | `koopa/home-hernani/koopa-prime/{.env,users.env}` | `~/koopa-prime/` |
|
||||||
|
| Forgejo (**rootless**, git.hacktivism.ch) | `koopa/home-hernani/koopa-forgejo/{.env,users.env,compose.yml}` | `~/koopa-forgejo/` |
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- **koopa-admin-log**: paths only; `@inline-secret@` in configs, never password/key material.
|
||||||
|
- Pull/deploy: `koopa-admin-secrets/scripts/{pull,deploy}-from-koopa.sh`
|
||||||
|
- Full inventory: `koopa-admin-secrets/MAP.md`
|
||||||
|
|
||||||
|
## Public (OK here)
|
||||||
|
|
||||||
|
- `MASTER_PUBLIC_KEY` / merchant `MASTER_KEY` (exchange public master key)
|
||||||
|
- Port maps, unit ladders, non-secret overrides
|
||||||
10
benchmarks/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Benchmarks (koopa Taler stack)
|
||||||
|
|
||||||
|
Run **from the local machine** against public endpoints.
|
||||||
|
**Measure load inside** the three Podman containers on koopa via `ssh hernani@koopa` + `podman exec` (no root/screen required for metrics).
|
||||||
|
|
||||||
|
| Benchmark | What it does |
|
||||||
|
|-----------|----------------|
|
||||||
|
| [`amount-ladder/`](amount-ladder/) | Free-amount template payments across a wide GOA ladder; wall times + host/container CPU/RSS samples |
|
||||||
|
|
||||||
|
Secrets: sibling repo **`koopa-admin-secrets`** (never committed here).
|
||||||
90
benchmarks/amount-ladder/README.md
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
# Amount-ladder payment benchmark
|
||||||
|
|
||||||
|
## Model
|
||||||
|
|
||||||
|
| Side | Machine | Role |
|
||||||
|
|------|---------|------|
|
||||||
|
| **Drive (external)** | Local machine (this tree) | `taler-wallet-cli`, orchestration, CSV/RESULTS |
|
||||||
|
| **SUT (internal)** | koopa containers | merchant / exchange / bank under load |
|
||||||
|
| **Measure (internal)** | `ssh hernani@koopa` | `loadavg`, `ps` in each container |
|
||||||
|
|
||||||
|
```
|
||||||
|
[ Local-machine wallet-cli ] --HTTPS--> taler.hacktivism.ch / exchange / bank
|
||||||
|
| ^
|
||||||
|
| ssh sample_load | pasta :9010/:9011/:9012
|
||||||
|
v |
|
||||||
|
[ hernani@koopa ] --podman exec--> process CPU/RSS snapshots
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Stack healthy (`check_*-health.sh` all green; public `/config` + exchange `/keys` 200).
|
||||||
|
2. Local wallet CLI (default path in script, override `MONO=`).
|
||||||
|
3. Secrets in **`../koopa-admin-secrets/koopa/host-root/`**:
|
||||||
|
- `taler-bank/bank-admin-password.txt`
|
||||||
|
- `taler-bank/bank-bench-fat-password.txt` (created if missing)
|
||||||
|
- `taler-merchant/merchant-goa-demo-cp4zqk-password.txt`
|
||||||
|
4. SSH: `hernani@koopa` BatchMode, `podman` without su.
|
||||||
|
|
||||||
|
## Amount ladder (~20 payments: small / medium / large)
|
||||||
|
|
||||||
|
| Band | Amounts (GOA) |
|
||||||
|
|------|----------------|
|
||||||
|
| **small** | `0.000001` `0.00001` `0.0001` `0.001` `0.01` `0.05` `0.1` `0.5` |
|
||||||
|
| **medium** | `1` `2` `5` `10` `25` `50` `100` |
|
||||||
|
| **large** | `250` `500` `1000` `2500` `5000` |
|
||||||
|
|
||||||
|
Default list (20 steps):
|
||||||
|
|
||||||
|
```text
|
||||||
|
GOA:0.000001 GOA:0.00001 GOA:0.0001 GOA:0.001 GOA:0.01 GOA:0.05 GOA:0.1 GOA:0.5
|
||||||
|
GOA:1 GOA:2 GOA:5 GOA:10 GOA:25 GOA:50 GOA:100
|
||||||
|
GOA:250 GOA:500 GOA:1000 GOA:2500 GOA:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
Σ ladder ≈ **GOA:9433** → default withdraw **GOA:50000**, admin credit **GOA:200000**.
|
||||||
|
|
||||||
|
Override: `AMOUNTS='GOA:1 GOA:10' ./run.sh`
|
||||||
|
|
||||||
|
## Pay-template URI (important)
|
||||||
|
|
||||||
|
Do **not** put a trailing slash after the template id (wallet then 404s).
|
||||||
|
|
||||||
|
```text
|
||||||
|
taler://pay-template/taler.hacktivism.ch/instances/goa-demo-cp4zqk/goa-free?amount=GOA:1
|
||||||
|
```
|
||||||
|
|
||||||
|
- Template `goa-free` is free-amount with fixed summary `"pay"` → pass **only** `amount=` (no `summary=` or merchant 409).
|
||||||
|
- HTTP GET probe:
|
||||||
|
`https://taler.hacktivism.ch/instances/goa-demo-cp4zqk/templates/goa-free` → 200.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd koopa-admin-log/benchmarks/amount-ladder
|
||||||
|
./run.sh
|
||||||
|
# short:
|
||||||
|
AMOUNTS='GOA:0.05 GOA:1 GOA:10' WITHDRAW_AMT=GOA:100 ./run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs under `../../.tmp/amount-bench-<UTC>/`:
|
||||||
|
|
||||||
|
| File | Content |
|
||||||
|
|------|---------|
|
||||||
|
| `payments.csv` | per-amount timings + status |
|
||||||
|
| `RESULTS.md` | table summary |
|
||||||
|
| `koopa-load-*.out` | **inside** process samples |
|
||||||
|
| `run.log` | timeline |
|
||||||
|
|
||||||
|
## Columns (RESULTS)
|
||||||
|
|
||||||
|
- **handle**: wall time of `wallet handle-uri`
|
||||||
|
- **run-until-done**: wallet background after handle
|
||||||
|
- **total**: sum
|
||||||
|
- **loadavg**: koopa 1‑min load after payment
|
||||||
|
- **status**: `paid` / `error` / `insufficient` / …
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- Partial earlier run (wrong URI): `2026/2026-07-09--amount-ladder-partial-results.md`
|
||||||
|
- Instance notes: `configs/taler-hacktivism/demo-instance-goa-demo-cp4zqk.md`
|
||||||
375
benchmarks/amount-ladder/run.sh
Executable file
|
|
@ -0,0 +1,375 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Amount-ladder benchmark — drive from the local machine, measure on koopa (inside containers).
|
||||||
|
# See README.md in this directory.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
BENCH_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
ROOT=$(cd "$BENCH_DIR/../.." && pwd)
|
||||||
|
SECRETS="${SECRETS_DIR:-}"
|
||||||
|
if [ -z "$SECRETS" ]; then
|
||||||
|
if [ -d "$ROOT/../koopa-admin-secrets" ]; then
|
||||||
|
SECRETS=$(cd "$ROOT/../koopa-admin-secrets" && pwd)
|
||||||
|
else
|
||||||
|
SECRETS="$ROOT/../koopa-admin-secrets"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
SCRATCH="${ROOT}/.tmp/amount-bench-$(date -u +%Y%m%d-%H%M%S)"
|
||||||
|
mkdir -p "$SCRATCH"
|
||||||
|
chmod 700 "$SCRATCH"
|
||||||
|
|
||||||
|
MONO="${MONO:-/Users/newkamek/src/taler/taler-typescript-core/packages/taler-wallet-cli/bin/taler-wallet-cli.mjs}"
|
||||||
|
WDB="$SCRATCH/wallet.sqlite3"
|
||||||
|
EX="${EXCHANGE_URL:-https://exchange.hacktivism.ch/}"
|
||||||
|
KOOPA="${KOOPA_HOST:-koopa}"
|
||||||
|
INST="${MERCHANT_INSTANCE:-goa-demo-cp4zqk}"
|
||||||
|
TPL="${TEMPLATE_ID:-goa-free}"
|
||||||
|
MER_HOST="${MER_HOST:-taler.hacktivism.ch}"
|
||||||
|
FAT_USER="${FAT_USER:-bench-fat}"
|
||||||
|
# ~20 payments: small → medium → large (atomic/tiny through multi-kilo)
|
||||||
|
# Override: AMOUNTS='GOA:1 GOA:10' ./run.sh
|
||||||
|
AMOUNTS="${AMOUNTS:-GOA:0.000001 GOA:0.00001 GOA:0.0001 GOA:0.001 GOA:0.01 GOA:0.05 GOA:0.1 GOA:0.5 GOA:1 GOA:2 GOA:5 GOA:10 GOA:25 GOA:50 GOA:100 GOA:250 GOA:500 GOA:1000 GOA:2500 GOA:5000}"
|
||||||
|
# sum of default ladder ≈ GOA:9433 → withdraw headroom for multi-coin + fees
|
||||||
|
WITHDRAW_AMT="${WITHDRAW_AMT:-GOA:50000}"
|
||||||
|
CREDIT_AMT="${CREDIT_AMT:-GOA:200000}"
|
||||||
|
|
||||||
|
BANK_ADMIN_PW_FILE="${BANK_ADMIN_PW_FILE:-$SECRETS/koopa/host-root/taler-bank/bank-admin-password.txt}"
|
||||||
|
BANK_FAT_PW_FILE="${BANK_FAT_PW_FILE:-$SECRETS/koopa/host-root/taler-bank/bank-bench-fat-password.txt}"
|
||||||
|
MER_PW_FILE="${MER_PW_FILE:-$SECRETS/koopa/host-root/taler-merchant/merchant-${INST}-password.txt}"
|
||||||
|
|
||||||
|
wcli() { node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults "$@"; }
|
||||||
|
log() { echo "$(date -u +%H:%M:%S) $*" | tee -a "$SCRATCH/run.log"; }
|
||||||
|
now() { python3 -c 'import time;print(time.time())'; }
|
||||||
|
die() { log "FAIL $*"; exit 1; }
|
||||||
|
|
||||||
|
[ -f "$MONO" ] || die "wallet cli missing: $MONO"
|
||||||
|
[ -f "$BANK_ADMIN_PW_FILE" ] || die "missing $BANK_ADMIN_PW_FILE"
|
||||||
|
[ -f "$MER_PW_FILE" ] || die "missing $MER_PW_FILE"
|
||||||
|
|
||||||
|
ADMIN_PW=$(tr -d '\n' <"$BANK_ADMIN_PW_FILE")
|
||||||
|
MER_PW=$(tr -d '\n' <"$MER_PW_FILE")
|
||||||
|
if [ -f "$BANK_FAT_PW_FILE" ]; then
|
||||||
|
FAT_PW=$(tr -d '\n' <"$BANK_FAT_PW_FILE")
|
||||||
|
else
|
||||||
|
FAT_PW=$(openssl rand -hex 12)
|
||||||
|
mkdir -p "$(dirname "$BANK_FAT_PW_FILE")"
|
||||||
|
printf '%s\n' "$FAT_PW" >"$BANK_FAT_PW_FILE"
|
||||||
|
chmod 600 "$BANK_FAT_PW_FILE"
|
||||||
|
log "generated fat password -> $BANK_FAT_PW_FILE (secrets repo)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- remote: run short script as hernani, capture stdout ---
|
||||||
|
run_koopa() {
|
||||||
|
local id="$1"
|
||||||
|
shift
|
||||||
|
local script_local="$SCRATCH/remote-$id.sh"
|
||||||
|
local mark="BENCH_${id}_OK"
|
||||||
|
{
|
||||||
|
echo '#!/bin/bash'
|
||||||
|
echo 'set +e'
|
||||||
|
printf '%s\n' "$@"
|
||||||
|
echo "echo $mark"
|
||||||
|
} >"$script_local"
|
||||||
|
scp -o BatchMode=yes -q "$script_local" "${KOOPA}:/tmp/bench-run-$id.sh"
|
||||||
|
ssh -o BatchMode=yes "$KOOPA" "bash /tmp/bench-run-$id.sh; rm -f /tmp/bench-run-$id.sh" \
|
||||||
|
>"$SCRATCH/koopa-$id.out" 2>&1 || true
|
||||||
|
if ! grep -q "$mark" "$SCRATCH/koopa-$id.out" 2>/dev/null; then
|
||||||
|
log "WARN run_koopa $id incomplete (see koopa-$id.out)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Measure **inside** containers (and host loadavg)
|
||||||
|
sample_load() {
|
||||||
|
local tag="$1"
|
||||||
|
scp -o BatchMode=yes -q "$BENCH_DIR/sample-load-inside.sh" "${KOOPA}:/tmp/sample-load-inside.sh"
|
||||||
|
ssh -o BatchMode=yes "$KOOPA" "bash /tmp/sample-load-inside.sh $(printf %q "$tag"); rm -f /tmp/sample-load-inside.sh" \
|
||||||
|
>"$SCRATCH/koopa-load-$tag.out" 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
log "scratch=$SCRATCH"
|
||||||
|
log "AMOUNTS=$AMOUNTS"
|
||||||
|
|
||||||
|
# --- preflight (external, public HTTPS) ---
|
||||||
|
for url in \
|
||||||
|
"https://taler.hacktivism.ch/config" \
|
||||||
|
"https://exchange.hacktivism.ch/keys" \
|
||||||
|
"https://taler.hacktivism.ch/instances/${INST}/templates/${TPL}"
|
||||||
|
do
|
||||||
|
code=$(curl -sk -o /dev/null -w '%{http_code}' -m 10 "$url" || echo err)
|
||||||
|
log "preflight $url -> $code"
|
||||||
|
[ "$code" = "200" ] || die "preflight failed $url"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- bank setup on koopa (API localhost, orchestrated from the local machine) ---
|
||||||
|
log "=== bank setup $FAT_USER credit=$CREDIT_AMT withdraw=$WITHDRAW_AMT ==="
|
||||||
|
{
|
||||||
|
echo '#!/bin/bash'
|
||||||
|
echo 'set -euo pipefail'
|
||||||
|
echo "export ADMIN_PW=$(printf %q "$ADMIN_PW")"
|
||||||
|
echo "export FAT_PW=$(printf %q "$FAT_PW")"
|
||||||
|
echo "export FAT_USER=$(printf %q "$FAT_USER")"
|
||||||
|
echo "export CREDIT_AMT=$(printf %q "$CREDIT_AMT")"
|
||||||
|
echo "export WITHDRAW_AMT=$(printf %q "$WITHDRAW_AMT")"
|
||||||
|
cat <<'EOS'
|
||||||
|
BANK=http://127.0.0.1:9012
|
||||||
|
AT=$(curl -sS -u "admin:${ADMIN_PW}" -H "Content-Type: application/json" -d '{"scope":"readwrite"}' \
|
||||||
|
"$BANK/accounts/admin/token" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
|
||||||
|
echo admin_tok_len=${#AT}
|
||||||
|
curl -sS -o /tmp/acc.json -w "create:%{http_code}\n" -X POST -H "Authorization: Bearer $AT" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"username\":\"${FAT_USER}\",\"password\":\"${FAT_PW}\",\"name\":\"Bench Fat\",\"is_public\":false,\"is_taler_exchange\":false,\"debit_threshold\":\"GOA:0\"}" \
|
||||||
|
"$BANK/accounts" || true
|
||||||
|
echo create_body=$(head -c 160 /tmp/acc.json)
|
||||||
|
python3 - <<'PY'
|
||||||
|
import json, os
|
||||||
|
A="0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
||||||
|
def c32(b):
|
||||||
|
v=bits=0; o=[]
|
||||||
|
for x in b:
|
||||||
|
v=(v<<8)|x; bits+=8
|
||||||
|
while bits>=5:
|
||||||
|
bits-=5; o.append(A[(v>>bits)&31])
|
||||||
|
if bits: o.append(A[(v<<(5-bits))&31])
|
||||||
|
return "".join(o)
|
||||||
|
uid=c32(os.urandom(32))
|
||||||
|
user=os.environ["FAT_USER"]
|
||||||
|
amt=os.environ["CREDIT_AMT"]
|
||||||
|
payto=f"payto://x-taler-bank/bank.hacktivism.ch/{user}?receiver-name={user}&message=bench-credit"
|
||||||
|
json.dump({"payto_uri":payto,"amount":amt,"request_uid":uid}, open("/tmp/tx.json","w"))
|
||||||
|
print("credit", amt, "->", user, "uid", uid)
|
||||||
|
PY
|
||||||
|
curl -sS -o /tmp/cred.json -w "credit:%{http_code}\n" -X POST -H "Authorization: Bearer $AT" \
|
||||||
|
-H "Content-Type: application/json" -d @/tmp/tx.json "$BANK/accounts/admin/transactions"
|
||||||
|
echo credit_body=$(head -c 200 /tmp/cred.json)
|
||||||
|
BT=$(curl -sS -u "${FAT_USER}:${FAT_PW}" -H "Content-Type: application/json" -d '{"scope":"readwrite"}' \
|
||||||
|
"$BANK/accounts/${FAT_USER}/token" | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
|
||||||
|
echo fat_tok_len=${#BT}
|
||||||
|
curl -sS -H "Authorization: Bearer $BT" "$BANK/accounts/${FAT_USER}" -o /tmp/fatbal.json
|
||||||
|
echo fat_account=$(head -c 280 /tmp/fatbal.json)
|
||||||
|
curl -sS -o /tmp/wd.json -w "wd:%{http_code}\n" -X POST -H "Authorization: Bearer $BT" \
|
||||||
|
-H "Content-Type: application/json" -d "{\"amount\":\"${WITHDRAW_AMT}\"}" \
|
||||||
|
"$BANK/accounts/${FAT_USER}/withdrawals"
|
||||||
|
python3 - <<'PY'
|
||||||
|
import json
|
||||||
|
d=json.load(open("/tmp/wd.json"))
|
||||||
|
print("wd_keys", list(d.keys())[:20])
|
||||||
|
uri=d.get("taler_withdraw_uri") or d.get("withdrawal_uri") or ""
|
||||||
|
wid=d.get("withdrawal_id") or d.get("id") or ""
|
||||||
|
if not uri:
|
||||||
|
for v in d.values():
|
||||||
|
if isinstance(v, str) and v.startswith("taler"):
|
||||||
|
uri=v
|
||||||
|
open("/tmp/bench-wuri.txt","w").write(uri)
|
||||||
|
open("/tmp/bench-wid.txt","w").write(wid)
|
||||||
|
print("WURI", uri)
|
||||||
|
print("WID", wid)
|
||||||
|
assert uri and wid, d
|
||||||
|
PY
|
||||||
|
# cleanup setup temps on host (not durable)
|
||||||
|
rm -f /tmp/acc.json /tmp/tx.json /tmp/cred.json /tmp/fatbal.json /tmp/wd.json
|
||||||
|
echo BENCH_banksetup_OK
|
||||||
|
EOS
|
||||||
|
} >"$SCRATCH/remote-banksetup.sh"
|
||||||
|
scp -o BatchMode=yes -q "$SCRATCH/remote-banksetup.sh" "${KOOPA}:/tmp/bench-run-banksetup.sh"
|
||||||
|
ssh -o BatchMode=yes "$KOOPA" 'bash /tmp/bench-run-banksetup.sh; rm -f /tmp/bench-run-banksetup.sh' \
|
||||||
|
| tee "$SCRATCH/koopa-banksetup.out"
|
||||||
|
grep -q BENCH_banksetup_OK "$SCRATCH/koopa-banksetup.out" || die "bank setup failed"
|
||||||
|
scp -o BatchMode=yes -q "${KOOPA}:/tmp/bench-wuri.txt" "${KOOPA}:/tmp/bench-wid.txt" "$SCRATCH/"
|
||||||
|
ssh -o BatchMode=yes "$KOOPA" 'rm -f /tmp/bench-wuri.txt /tmp/bench-wid.txt' || true
|
||||||
|
WURI=$(tr -d '\n' <"$SCRATCH/bench-wuri.txt")
|
||||||
|
WID=$(tr -d '\n' <"$SCRATCH/bench-wid.txt")
|
||||||
|
[ -n "$WURI" ] || die "missing withdraw URI"
|
||||||
|
log "WURI=$WURI WID=$WID"
|
||||||
|
|
||||||
|
log "=== load baseline (internal) ==="
|
||||||
|
sample_load baseline
|
||||||
|
|
||||||
|
log "=== wallet: exchange + withdraw (external) ==="
|
||||||
|
wcli exchanges add "$EX" 2>&1 | tee "$SCRATCH/ex-add.out" || true
|
||||||
|
wcli exchanges update "$EX" 2>&1 | tee "$SCRATCH/ex-upd.out" || true
|
||||||
|
wcli exchanges tos "$EX" 2>&1 | tee "$SCRATCH/ex-tos.out" || true
|
||||||
|
wcli exchanges accept-tos "$EX" 2>&1 | tee "$SCRATCH/ex-accept.out" || true
|
||||||
|
|
||||||
|
TW0=$(now)
|
||||||
|
# 1) Wallet selects exchange (status → selected). Must happen BEFORE bank confirm.
|
||||||
|
wcli withdraw accept-uri --exchange "$EX" "$WURI" 2>&1 | tee "$SCRATCH/wd-accept.out" || true
|
||||||
|
|
||||||
|
# 2) Wait until bank sees selection, then confirm once (libeufin rejects confirm if still unselected).
|
||||||
|
log "=== bank: wait selected → confirm ==="
|
||||||
|
run_koopa wdconfirm \
|
||||||
|
"export FAT_USER=$(printf %q "$FAT_USER")" \
|
||||||
|
"export FAT_PW=$(printf %q "$FAT_PW")" \
|
||||||
|
"export WID=$(printf %q "$WID")" \
|
||||||
|
'BANK=http://127.0.0.1:9012' \
|
||||||
|
'BT=$(curl -sS -u "${FAT_USER}:${FAT_PW}" -H "Content-Type: application/json" -d "{\"scope\":\"readwrite\"}" "$BANK/accounts/${FAT_USER}/token" | python3 -c "import sys,json;print(json.load(sys.stdin).get(\"access_token\",\"\"))")' \
|
||||||
|
'echo WID=$WID tok_len=${#BT}' \
|
||||||
|
'st=""' \
|
||||||
|
'for i in $(seq 1 60); do
|
||||||
|
curl -sS "$BANK/taler-integration/withdrawal-operation/${WID}" -o /tmp/wdst.json || true
|
||||||
|
st=$(python3 -c "import json;print(json.load(open(\"/tmp/wdst.json\")).get(\"status\",\"\"))" 2>/dev/null || true)
|
||||||
|
echo "wait_select #$i status=$st"
|
||||||
|
case "$st" in selected|confirmed) break ;; esac
|
||||||
|
sleep 1
|
||||||
|
done' \
|
||||||
|
'case "$st" in
|
||||||
|
selected)
|
||||||
|
echo confirming...
|
||||||
|
curl -sS -o /tmp/wdconf.out -w "confirm_http:%{http_code}\n" -X POST \
|
||||||
|
-H "Authorization: Bearer $BT" -H "Content-Type: application/json" -d "{}" \
|
||||||
|
"$BANK/accounts/${FAT_USER}/withdrawals/${WID}/confirm"
|
||||||
|
head -c 120 /tmp/wdconf.out; echo
|
||||||
|
;;
|
||||||
|
confirmed) echo already_confirmed ;;
|
||||||
|
*) echo "WARN never selected (last=$st)" ;;
|
||||||
|
esac' \
|
||||||
|
'for i in $(seq 1 30); do
|
||||||
|
curl -sS "$BANK/taler-integration/withdrawal-operation/${WID}" -o /tmp/wdst.json || true
|
||||||
|
python3 -c "import json;d=json.load(open(\"/tmp/wdst.json\"));print(\"post\",d.get(\"status\"),\"transfer\",d.get(\"transfer_done\"))"
|
||||||
|
st=$(python3 -c "import json;print(json.load(open(\"/tmp/wdst.json\")).get(\"status\",\"\"))" 2>/dev/null || true)
|
||||||
|
td=$(python3 -c "import json;print(json.load(open(\"/tmp/wdst.json\")).get(\"transfer_done\"))" 2>/dev/null || true)
|
||||||
|
case "$st" in confirmed) break ;; esac
|
||||||
|
[ "$td" = "True" ] || [ "$td" = "true" ] && break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
rm -f /tmp/wdst.json /tmp/wdconf.out' || true
|
||||||
|
|
||||||
|
# 3) Wallet finishes planchets (may take minutes for large withdraws)
|
||||||
|
log "=== wallet: run-until-done after confirm ==="
|
||||||
|
for round in $(seq 1 30); do
|
||||||
|
log "withdraw run-until-done round $round"
|
||||||
|
# timeout each round so we do not hang forever on long-poll
|
||||||
|
set +e
|
||||||
|
# 120s max per round
|
||||||
|
perl -e 'alarm shift; exec @ARGV' 120 \
|
||||||
|
node "$MONO" --wallet-db="$WDB" --no-throttle --skip-defaults run-until-done \
|
||||||
|
2>&1 | tee -a "$SCRATCH/wd-rud.out"
|
||||||
|
set -e
|
||||||
|
wcli balance 2>&1 | tee "$SCRATCH/bal-wd-$round.out"
|
||||||
|
if python3 - "$SCRATCH/bal-wd-$round.out" <<'PY'
|
||||||
|
import re,sys
|
||||||
|
t=open(sys.argv[1]).read()
|
||||||
|
vals=[float(x) for x in re.findall(r"GOA:([0-9.]+)", t)]
|
||||||
|
sys.exit(0 if vals and max(vals) >= 100 else 1)
|
||||||
|
PY
|
||||||
|
then
|
||||||
|
log "withdraw OK"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
TW1=$(now)
|
||||||
|
python3 -c "print('withdraw_wall_s', round(float('$TW1')-float('$TW0'), 3))" | tee "$SCRATCH/withdraw-time.txt"
|
||||||
|
wcli balance 2>&1 | tee "$SCRATCH/balance-before-pays.out"
|
||||||
|
sample_load after-withdraw
|
||||||
|
|
||||||
|
CSV="$SCRATCH/payments.csv"
|
||||||
|
echo "idx,amount,handle_s,rud_s,total_s,status,order_id,loadavg,notes" >"$CSV"
|
||||||
|
|
||||||
|
idx=0
|
||||||
|
for AMT in $AMOUNTS; do
|
||||||
|
idx=$((idx + 1))
|
||||||
|
# no trailing slash; amount only (template has fixed summary)
|
||||||
|
URI="taler://pay-template/${MER_HOST}/instances/${INST}/${TPL}?amount=${AMT}"
|
||||||
|
echo "$URI" >"$SCRATCH/uri-$idx.txt"
|
||||||
|
log "=== pay #$idx $AMT ==="
|
||||||
|
log "uri=$URI"
|
||||||
|
|
||||||
|
load_before=$(ssh -o BatchMode=yes "$KOOPA" 'cut -d" " -f1 /proc/loadavg' 2>/dev/null || echo "?")
|
||||||
|
sample_load "pay$idx" &
|
||||||
|
SPID=$!
|
||||||
|
|
||||||
|
T0=$(now)
|
||||||
|
set +e
|
||||||
|
wcli handle-uri --yes "$URI" 2>&1 | tee "$SCRATCH/pay-$idx-handle.out"
|
||||||
|
T1=$(now)
|
||||||
|
wcli run-until-done 2>&1 | tee "$SCRATCH/pay-$idx-rud.out"
|
||||||
|
T2=$(now)
|
||||||
|
set -e
|
||||||
|
|
||||||
|
handle_s=$(python3 -c "print(round(float('$T1')-float('$T0'),3))")
|
||||||
|
rud_s=$(python3 -c "print(round(float('$T2')-float('$T1'),3))")
|
||||||
|
total_s=$(python3 -c "print(round(float('$T2')-float('$T0'),3))")
|
||||||
|
wait "$SPID" 2>/dev/null || true
|
||||||
|
load_after=$(ssh -o BatchMode=yes "$KOOPA" 'cut -d" " -f1 /proc/loadavg' 2>/dev/null || echo "?")
|
||||||
|
|
||||||
|
run_koopa "ord$idx" \
|
||||||
|
"export MPW=$(printf %q "$MER_PW")" \
|
||||||
|
"export INST=$(printf %q "$INST")" \
|
||||||
|
"export AMT=$(printf %q "$AMT")" \
|
||||||
|
'AUTH="Authorization: Bearer secret-token:${MPW}"' \
|
||||||
|
'curl -sk -H "$AUTH" "https://127.0.0.1:9010/instances/${INST}/private/orders?paid=YES&delta=-30" -o /tmp/ol.json' \
|
||||||
|
'python3 - <<PY
|
||||||
|
import json, os
|
||||||
|
d=json.load(open("/tmp/ol.json"))
|
||||||
|
want=os.environ["AMT"]
|
||||||
|
hit=None
|
||||||
|
for o in d.get("orders") or []:
|
||||||
|
if o.get("amount")==want:
|
||||||
|
hit=o; break
|
||||||
|
if hit:
|
||||||
|
print("ORDER", hit.get("order_id"), "amount", hit.get("amount"), "summary", hit.get("summary"))
|
||||||
|
open("/tmp/oid.txt","w").write(hit.get("order_id") or "")
|
||||||
|
else:
|
||||||
|
print("NO_ORDER")
|
||||||
|
open("/tmp/oid.txt","w").write("")
|
||||||
|
PY' \
|
||||||
|
'OID=$(tr -d "\n" </tmp/oid.txt 2>/dev/null || true)' \
|
||||||
|
'if [ -n "$OID" ]; then
|
||||||
|
curl -sk -H "$AUTH" "https://127.0.0.1:9010/instances/${INST}/private/orders/${OID}" -o /tmp/od.json
|
||||||
|
python3 -c "import json;d=json.load(open(\"/tmp/od.json\"));print(\"DETAIL paid\",d.get(\"paid\"),\"status\",d.get(\"order_status\"),\"amount\",(d.get(\"contract_terms\")or{}).get(\"amount\"))"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/ol.json /tmp/od.json /tmp/oid.txt' || true
|
||||||
|
|
||||||
|
status=unknown
|
||||||
|
if grep -qE 'DETAIL paid True|DETAIL paid true' "$SCRATCH/koopa-ord$idx.out" 2>/dev/null; then
|
||||||
|
status=paid
|
||||||
|
elif grep -qiE 'insufficient balance' "$SCRATCH/pay-$idx-handle.out" 2>/dev/null; then
|
||||||
|
status=insufficient
|
||||||
|
elif grep -qiE 'error|fail|invalid|404|409|Unexpected HTTP' "$SCRATCH/pay-$idx-handle.out" 2>/dev/null; then
|
||||||
|
status=error
|
||||||
|
elif grep -qi 'paid' "$SCRATCH/pay-$idx-rud.out" 2>/dev/null; then
|
||||||
|
status=maybe
|
||||||
|
fi
|
||||||
|
oid=$(awk '/^ORDER /{print $2; exit}' "$SCRATCH/koopa-ord$idx.out" 2>/dev/null || true)
|
||||||
|
notes="load ${load_before}->${load_after}"
|
||||||
|
echo "$idx,$AMT,$handle_s,$rud_s,$total_s,$status,$oid,$load_after,$notes" >>"$CSV"
|
||||||
|
log "pay #$idx $AMT total=${total_s}s status=$status oid=$oid load=$load_after"
|
||||||
|
wcli balance 2>&1 | tee "$SCRATCH/bal-after-$idx.out" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
sample_load final
|
||||||
|
wcli balance 2>&1 | tee "$SCRATCH/balance-final.out" || true
|
||||||
|
|
||||||
|
python3 - "$CSV" "$SCRATCH" <<'PY' | tee "$SCRATCH/RESULTS.md"
|
||||||
|
import csv, sys
|
||||||
|
csv_path, scratch = sys.argv[1], sys.argv[2]
|
||||||
|
rows = list(csv.DictReader(open(csv_path)))
|
||||||
|
print("# Amount ladder payment benchmark")
|
||||||
|
print()
|
||||||
|
print(f"- Scratch: `{scratch}`")
|
||||||
|
print(f"- Drive: Local-machine wallet-cli (external) · Measure: koopa podman (internal)")
|
||||||
|
print(f"- Template: `goa-free` on `goa-demo-cp4zqk`")
|
||||||
|
print(f"- URI: `taler://pay-template/…/instances/…/goa-free?amount=…` (no trailing slash, no summary)")
|
||||||
|
print()
|
||||||
|
print("| # | Amount | handle (s) | run-until-done (s) | **total (s)** | status | order | loadavg |")
|
||||||
|
print("|--:|--------|----------:|-------------------:|--------------:|--------|-------|--------:|")
|
||||||
|
for r in rows:
|
||||||
|
print(f"| {r['idx']} | `{r['amount']}` | {r['handle_s']} | {r['rud_s']} | **{r['total_s']}** | {r['status']} | `{r.get('order_id','')}` | {r.get('loadavg','')} |")
|
||||||
|
print()
|
||||||
|
paid = [r for r in rows if r["status"] == "paid"]
|
||||||
|
print(f"Paid **{len(paid)}/{len(rows)}**.")
|
||||||
|
if paid:
|
||||||
|
totals = [float(r["total_s"]) for r in paid if r["total_s"]]
|
||||||
|
if totals:
|
||||||
|
print(f"**Min** {min(totals):.3f}s · **Max** {max(totals):.3f}s · **Avg** {sum(totals)/len(totals):.3f}s")
|
||||||
|
print()
|
||||||
|
print("Inside samples: `koopa-load-*.out` in scratch.")
|
||||||
|
PY
|
||||||
|
|
||||||
|
# durable copy of last RESULTS into benchmarks tree (no secrets)
|
||||||
|
cp "$SCRATCH/RESULTS.md" "$BENCH_DIR/LAST_RESULTS.md"
|
||||||
|
cp "$CSV" "$BENCH_DIR/LAST_payments.csv"
|
||||||
|
log "DONE -> $SCRATCH/RESULTS.md (also benchmarks/amount-ladder/LAST_*)"
|
||||||
|
cat "$SCRATCH/RESULTS.md"
|
||||||
26
benchmarks/amount-ladder/sample-load-inside.sh
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Sample host + three-container CPU/RSS from koopa (run as hernani via ssh).
|
||||||
|
# Usage (local): ssh hernani@koopa 'bash -s' < sample-load-inside.sh
|
||||||
|
# Or: ./sample-load-inside.sh # if executed ON koopa as hernani
|
||||||
|
set -eu
|
||||||
|
tag="${1:-manual}"
|
||||||
|
echo "=== sample_load tag=$tag $(date -u +%FT%TZ) ==="
|
||||||
|
echo "=== loadavg ==="
|
||||||
|
cat /proc/loadavg
|
||||||
|
echo "=== mem ==="
|
||||||
|
free -h 2>/dev/null || true
|
||||||
|
echo "=== top host cpu ==="
|
||||||
|
ps -eo pid,pcpu,pmem,rss,etime,comm --sort=-pcpu 2>/dev/null | head -15
|
||||||
|
echo "=== exchange (taler-hacktivism-exchange-ansible) ==="
|
||||||
|
podman exec taler-hacktivism-exchange-ansible \
|
||||||
|
ps -eo pid,pcpu,pmem,rss,etime,args 2>/dev/null \
|
||||||
|
| grep -E 'taler-exchange|postgres -D|PID' | head -25 || true
|
||||||
|
echo "=== merchant (taler-hacktivism) ==="
|
||||||
|
podman exec taler-hacktivism \
|
||||||
|
ps -eo pid,pcpu,pmem,rss,etime,args 2>/dev/null \
|
||||||
|
| grep -E 'taler-merchant|nginx|postgres -D' | head -25 || true
|
||||||
|
echo "=== bank (taler-bank-hacktivism) ==="
|
||||||
|
podman exec taler-bank-hacktivism \
|
||||||
|
ps -eo pid,pcpu,pmem,rss,etime,args 2>/dev/null \
|
||||||
|
| grep -E 'java|libeufin|postgres -D' | head -15 || true
|
||||||
|
echo "=== sample_load done ==="
|
||||||
19
configs/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Config mirrors (from host koopa)
|
||||||
|
|
||||||
|
Directories are named to match **live podman container names** where possible.
|
||||||
|
|
||||||
|
| Directory | Live container | Image (typical) |
|
||||||
|
|-----------|----------------|-----------------|
|
||||||
|
| `taler-hacktivism/` | `taler-hacktivism` | `taler-hacktivism-live:landing` |
|
||||||
|
| `taler-hacktivism-bank/` | `taler-hacktivism-bank` | **`taler-hacktivism-banking:live`** |
|
||||||
|
| `taler-exchange/` | conf inside exchange container | (see exchange-ansible) |
|
||||||
|
| `taler-exchange-ansible/` | **`taler-hacktivism-exchange-ansible`** | `taler-hacktivism-exchange-ansible:landing` |
|
||||||
|
| `bank-landing/` `exchange-landing/` `merchant-landing/` | nginx landing snippets | ports 9013–9015 |
|
||||||
|
| `koopa-*` apps | `koopa-castopod`, `koopa-bonfire`, … | compose mirrors |
|
||||||
|
| `tops/` | `koopa-tops-ng1` … `ng3` | `nginx:1.27-alpine` |
|
||||||
|
| `caddy/` `firewalld/` `systemd/` | host services | |
|
||||||
|
| `tor/` | **`koopa-tor-relay`** (podman host net) | `localhost/koopa-tor-relay:latest` |
|
||||||
|
|
||||||
|
**Authoritative running inventory:** `host/overview/LIVE.md`.
|
||||||
|
|
||||||
|
Secrets never live here — `SECRETS.md` / `koopa-admin-secrets`.
|
||||||
250
configs/bank-landing/README.md
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
# Bank landing (`bank.hacktivism.ch`)
|
||||||
|
|
||||||
|
English intro for **GOA exploration** (badge: **Intergalactic · GOA · no IBAN**):
|
||||||
|
put coins in a GNU Taler wallet, optionally open your own bank account, and
|
||||||
|
point merchants at `taler.hacktivism.ch`.
|
||||||
|
|
||||||
|
| Public URL | What |
|
||||||
|
|------------|------|
|
||||||
|
| `https://bank.hacktivism.ch/` · `/intro/` | Landing (nginx in bank container → host **9013**, Caddy) |
|
||||||
|
| `https://bank.hacktivism.ch/intro/stats.json` | **Live stats** (written *inside* bank container) |
|
||||||
|
| `https://bank.hacktivism.ch/intro/demo-withdraw.json` | Mint **shared-pool** (`explorer`) withdraw URI |
|
||||||
|
| `https://bank.hacktivism.ch/intro/auto-account.json` | Auto-create **personal** bank user (credentials once; balance **GOA:0**) |
|
||||||
|
| `https://bank.hacktivism.ch/webui/` | libeufin-bank SPA (register · login · withdraw) when libeufin is up on **9012** |
|
||||||
|
| `https://exchange.hacktivism.ch/` | Exchange (ToS `/terms`, keys `/keys`) |
|
||||||
|
| `https://taler.hacktivism.ch/` | Merchant backend |
|
||||||
|
|
||||||
|
## Page layout (top → bottom)
|
||||||
|
|
||||||
|
1. Hero (badge, short shared-account line)
|
||||||
|
2. **Get the wallet** — Play / F-Droid / iOS / web hub + **Debian/Ubuntu · Command line** (apt) + **Unix / POSIX · from source** (build monorepo or run `.mjs`)
|
||||||
|
3. **Withdraw GOA** — compact QR_Taler, open CTA, live pool balance note, generate-own-account link
|
||||||
|
4. **GOA shop · samples** — always visible (2 products + merchant link)
|
||||||
|
5. **How the shared account works** — collapsed fold (click to expand)
|
||||||
|
6. **Optional · own bank account** — collapsed fold: auto-create account + credentials + QR_Taler for bank UI
|
||||||
|
7. **GOA flow** stats
|
||||||
|
8. **Performance**
|
||||||
|
9. Footer
|
||||||
|
|
||||||
|
## User flow (documented on the page)
|
||||||
|
|
||||||
|
### Primary path (works without your own bank account)
|
||||||
|
|
||||||
|
1. **Install wallet** — Android (Play / F-Droid), iOS, Ubuntu Touch (via Waydroid + Android wallet), web (Chrome/Firefox/Opera on taler.net).
|
||||||
|
2. **One QR / link** — live `taler://withdraw/…` from the **community-shared** bank pool (`explorer`).
|
||||||
|
Minted via `GET /intro/demo-withdraw.json` (or fallback `withdraw.uri`).
|
||||||
|
Auto-confirm finishes the bank side so coins reach the wallet.
|
||||||
|
3. **Surprising balance note** — shows live `balance_explorer` from `stats.json`
|
||||||
|
(“pool holds GOA:… (shared, but also yours)”).
|
||||||
|
|
||||||
|
QR encoding matches merchant shop popup (**QR_Taler**): animated `#0042B3` conic
|
||||||
|
ring, off-DOM PNG, center `qr-logo.svg` (`shop-pay.css` classes). Label text:
|
||||||
|
**Scan in wallet**.
|
||||||
|
|
||||||
|
### Optional: personal bank account (auto-created)
|
||||||
|
|
||||||
|
In the optional fold:
|
||||||
|
|
||||||
|
1. User clicks **Create my bank account**.
|
||||||
|
2. Browser calls `GET /intro/auto-account.json` (no form fields).
|
||||||
|
3. Server registers a new libeufin user via public `POST /accounts`.
|
||||||
|
4. UI shows **username / password** once (“created for you”;
|
||||||
|
not stored for recovery).
|
||||||
|
5. Username shape: **`goa-account-<random>`** (shown to the user).
|
||||||
|
Password embeds **`pleasechangeme`** with random chars around it.
|
||||||
|
6. QR_Taler + link open `/webui/` so the user can log in with those credentials.
|
||||||
|
|
||||||
|
Balance **starts at zero** — not the shared `explorer` pool.
|
||||||
|
|
||||||
|
#### Deploy status — own bank account (**missing** until verified)
|
||||||
|
|
||||||
|
The **own bank account** path is **not complete** unless all of the following
|
||||||
|
are live on `taler-hacktivism-bank`. Treat this as **missing** until checked:
|
||||||
|
|
||||||
|
| Check | Expected |
|
||||||
|
|-------|----------|
|
||||||
|
| Landing HTML | Optional fold has **Create my bank account** |
|
||||||
|
| API process | `python3 /usr/local/bin/demo-withdraw-api.py` on **127.0.0.1:19096** |
|
||||||
|
| Nginx | `location = /intro/auto-account.json` → `:19096` |
|
||||||
|
| Public | `curl -sS https://bank.hacktivism.ch/intro/auto-account.json` returns JSON with `username` / `password` / `balance: "GOA:0"` |
|
||||||
|
| WebUI login | Credentials work at `https://bank.hacktivism.ch/webui/` |
|
||||||
|
|
||||||
|
Install helper: `scripts/taler-bank/install-demo-withdraw-api.sh`
|
||||||
|
(or manual copy of `demo-withdraw-api.py` + `nginx-landing.conf` + restart API).
|
||||||
|
|
||||||
|
### How the shared pool works
|
||||||
|
|
||||||
|
1. Shared account `explorer` is pre-funded with GOA.
|
||||||
|
2. Each demo withdraw is a **one-shot** op from that pool (not a personal login).
|
||||||
|
3. Wallet accepts the URI; **auto-confirm only for `explorer`** (see
|
||||||
|
`auto-confirm-withdrawals.sh`).
|
||||||
|
4. Pool balance is communal; amounts you withdraw are yours in the wallet.
|
||||||
|
|
||||||
|
### Helpers
|
||||||
|
|
||||||
|
| Script | Role |
|
||||||
|
|--------|------|
|
||||||
|
| `scripts/taler-bank/demo-withdraw-api.py` | Loopback HTTP **:19096** — `/demo-withdraw.json` + `/auto-account.json` |
|
||||||
|
| `scripts/taler-bank/install-demo-withdraw-api.sh` | Copy into bank container, nginx locations, start API + auto-confirm loop |
|
||||||
|
| `scripts/taler-bank/auto-confirm-withdrawals.sh` | Confirm **explorer-only** withdrawals when status=`selected` |
|
||||||
|
| `scripts/taler-bank/refresh-demo-withdraw.sh` | Refresh static `withdraw.uri` (no python) |
|
||||||
|
| `configs/bank-landing/nginx-landing.conf` | `/intro/demo-withdraw.json`, `/intro/auto-account.json` proxies |
|
||||||
|
|
||||||
|
Needs **python3** in the bank container for `demo-withdraw-api.py`.
|
||||||
|
|
||||||
|
### CLI sketch (fresh wallet DB)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# equivalent one-step in CLI terms:
|
||||||
|
taler-wallet-cli exchanges add https://exchange.hacktivism.ch/
|
||||||
|
taler-wallet-cli exchanges accept-tos https://exchange.hacktivism.ch/
|
||||||
|
# then withdraw from the exchange (amount chosen in wallet UI)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Live stats (in bank container)
|
||||||
|
|
||||||
|
Stats are **not** computed on the laptop or host browser. A small script runs
|
||||||
|
**inside the bank container**, queries libeufin for the demo funding account
|
||||||
|
(`explorer`), and writes a public JSON file next to the landing assets.
|
||||||
|
|
||||||
|
| Piece | Path / role |
|
||||||
|
|-------|-------------|
|
||||||
|
| Generator (in-container) | `/usr/local/bin/landing-stats.sh` |
|
||||||
|
| Source in admin-log | `scripts/taler-bank/landing-stats.sh` |
|
||||||
|
| Host installer | `scripts/taler-bank/landing-stats-install.sh` |
|
||||||
|
| Output | `/var/www/bank-landing/stats.json` → `https://bank.hacktivism.ch/intro/stats.json` |
|
||||||
|
| Landing UI | `index.html` section **GOA flow · live** |
|
||||||
|
|
||||||
|
### What the page shows
|
||||||
|
|
||||||
|
- **Bank accounts** — registered accounts (libeufin `GET /accounts`, admin)
|
||||||
|
- **Wallets involved** — unique reserve pubs from Taler withdrawal debits
|
||||||
|
- **Withdraws / bank credits** — flow amounts (exchange mirror skipped where noted)
|
||||||
|
- **Last withdraws** — recent list with amount, time (**CEST** / `Europe/Zurich`)
|
||||||
|
- **Last 24h / 7d** — amount
|
||||||
|
- **`balance_explorer`** — live shared-pool balance (shown on withdraw step)
|
||||||
|
- Performance probes + container RSS (bottom of page)
|
||||||
|
|
||||||
|
### Scan depth (must stay deep enough)
|
||||||
|
|
||||||
|
| Env | Default | Meaning |
|
||||||
|
|-----|---------|---------|
|
||||||
|
| `TX_DELTA` | `-50000` | per-account ledger window (`GET …/transactions?delta=`) |
|
||||||
|
| `ACCOUNTS_DELTA` | `-500` | account-list window (`GET /accounts?delta=`) |
|
||||||
|
| `MAX_SCAN_ACCOUNTS` | `500` | max usernames to scan |
|
||||||
|
|
||||||
|
Older defaults (`TX_DELTA=-100`, `MAX_SCAN_ACCOUNTS=80`) **undercounted** credits/withdraws
|
||||||
|
and account totals on this stack. Empty accounts often return **HTTP 204** (no body) —
|
||||||
|
that is normal (e.g. fresh auto-accounts), not a stats failure.
|
||||||
|
|
||||||
|
### `stats.json` shape (abridged)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"currency": "GOA",
|
||||||
|
"generated_at": "2026-07-10T20:35+02:00",
|
||||||
|
"balance_explorer": "GOA:960",
|
||||||
|
"bank_accounts": { "total": 56, "users": 55 },
|
||||||
|
"wallets": { "unique_reserves": 51 },
|
||||||
|
"withdraws": {
|
||||||
|
"count": 51,
|
||||||
|
"last_24h": { "amount": "GOA:…" },
|
||||||
|
"last_7d": { "amount": "GOA:…" }
|
||||||
|
},
|
||||||
|
"flow": {
|
||||||
|
"incoming": { "amount": "GOA:…", "count": 68 },
|
||||||
|
"withdraw": { "amount": "GOA:…", "count": 51 }
|
||||||
|
},
|
||||||
|
"source": "in-container landing-stats.sh"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install + refresh (on koopa host)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from laptop: copy scripts, then on koopa:
|
||||||
|
cd /path/to/koopa-admin-log/scripts/taler-bank
|
||||||
|
|
||||||
|
# copy into container + one-shot run
|
||||||
|
sudo ./landing-stats-install.sh
|
||||||
|
|
||||||
|
# every minute inside the container (* * * * *)
|
||||||
|
sudo ./landing-stats-install.sh --cron
|
||||||
|
# optional: LANDING_STATS_CRON='*/5 * * * *' ./landing-stats-install.sh --cron
|
||||||
|
|
||||||
|
# later: only re-run
|
||||||
|
sudo ./landing-stats-install.sh --run-only
|
||||||
|
```
|
||||||
|
|
||||||
|
Requirements **inside** the container:
|
||||||
|
|
||||||
|
- `curl`, `awk` (mawk OK), `sed`, `date` — **no python**
|
||||||
|
- libeufin-bank listening on loopback (script auto-detects `PORT` / 9012 / 8080)
|
||||||
|
- `/root/bank-explorer-password.txt` (or `BANK_PASS=…` on the `podman exec`)
|
||||||
|
- writable `/var/www/bank-landing/` (same tree nginx uses for `/intro/`)
|
||||||
|
|
||||||
|
`make-demo-withdraw-qr.sh` also calls `landing-stats.sh` after refreshing the demo URI,
|
||||||
|
when the binary is already installed at `/usr/local/bin/landing-stats.sh`.
|
||||||
|
|
||||||
|
### Manual one-liner (debug)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman exec -e LANDING_DIR=/var/www/bank-landing taler-hacktivism-bank \
|
||||||
|
/usr/local/bin/landing-stats.sh
|
||||||
|
curl -sS https://bank.hacktivism.ch/intro/stats.json | python3 -m json.tool
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Host / container files
|
||||||
|
|
||||||
|
Landing root (nginx in bank container), typically under something like
|
||||||
|
`/var/www/bank-landing/` or the container path mapped for **9013**:
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `index.html` | landing (wallet, shared withdraw, folds, stats) + Open Graph meta |
|
||||||
|
| `og-goa-shop.png` | **link preview** image (1200×630) for chats / social (OG + Twitter) |
|
||||||
|
| `shop-pay.css` / `shop-pay.js` | GOA shop popup + **QR_Taler** frame styles |
|
||||||
|
| `qr-logo.svg` | center logo on QR_Taler frames |
|
||||||
|
| `qrcode.min.js` | QR encode helper |
|
||||||
|
| `stats.json` | **live stats** (from in-container script) |
|
||||||
|
| `nginx-landing.conf` | bank container nginx (**9013**) |
|
||||||
|
| `withdraw.uri` | current demo `taler://withdraw/…` (optional) |
|
||||||
|
| `withdraw.amount` | e.g. `GOA:10` (optional) |
|
||||||
|
| `withdraw-watch.ids` | ids for auto-confirm helper (optional) |
|
||||||
|
|
||||||
|
## Deploy landing HTML + helper API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# landing assets
|
||||||
|
podman cp configs/bank-landing/index.html \
|
||||||
|
taler-hacktivism-bank:/var/www/bank-landing/index.html
|
||||||
|
podman cp configs/bank-landing/og-goa-shop.png \
|
||||||
|
taler-hacktivism-bank:/var/www/bank-landing/og-goa-shop.png
|
||||||
|
podman cp configs/bank-landing/shop-pay.js \
|
||||||
|
taler-hacktivism-bank:/var/www/bank-landing/shop-pay.js
|
||||||
|
# … shop-pay.css, qr-logo.svg, qrcode.min.js as needed
|
||||||
|
|
||||||
|
# demo-withdraw + auto-account API (python3 in container)
|
||||||
|
./scripts/taler-bank/install-demo-withdraw-api.sh
|
||||||
|
# or manually:
|
||||||
|
podman cp scripts/taler-bank/demo-withdraw-api.py \
|
||||||
|
taler-hacktivism-bank:/usr/local/bin/demo-withdraw-api.py
|
||||||
|
podman cp configs/bank-landing/nginx-landing.conf \
|
||||||
|
taler-hacktivism-bank:/etc/nginx/sites-available/bank-landing
|
||||||
|
# restart: python3 /usr/local/bin/demo-withdraw-api.py on 127.0.0.1:19096
|
||||||
|
# nginx -s reload; auto-confirm-withdrawals.sh --loop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Demo QR refresh (in container, no python)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman exec taler-hacktivism-bank /usr/local/bin/refresh-demo-withdraw.sh
|
||||||
|
# source: scripts/taler-bank/refresh-demo-withdraw.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Older host script (needs python3): `scripts/taler-bank/make-demo-withdraw-qr.sh`
|
||||||
2055
configs/bank-landing/index.html
Normal file
6
configs/bank-landing/nginx-cors-stats.conf
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Snippet: CORS so exchange/merchant landings can fetch stats.json
|
||||||
|
# Include inside bank landing server { } or merge into bank-landing site.
|
||||||
|
# location for stats only:
|
||||||
|
# include /etc/nginx/snippets/goa-stats-cors.conf; (if placed as snippet)
|
||||||
|
|
||||||
|
# Applied as extra location on :9013 bank-landing:
|
||||||
71
configs/bank-landing/nginx-landing.conf
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Bank landing — :9013 (behind Caddy :443 — never put 9013 in redirects)
|
||||||
|
server {
|
||||||
|
listen 9013;
|
||||||
|
listen [::]:9013;
|
||||||
|
server_name bank.hacktivism.ch _;
|
||||||
|
root /var/www/bank-landing;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Critical behind reverse_proxy: no :9013 in Location headers
|
||||||
|
absolute_redirect off;
|
||||||
|
port_in_redirect off;
|
||||||
|
|
||||||
|
location = / {
|
||||||
|
return 302 /intro/;
|
||||||
|
}
|
||||||
|
location = /intro {
|
||||||
|
return 302 /intro/;
|
||||||
|
}
|
||||||
|
# Terms (same dark palette as merchant/exchange terms pages)
|
||||||
|
location = /terms {
|
||||||
|
alias /var/www/bank-landing/terms.html;
|
||||||
|
default_type text/html;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
location = /terms/ {
|
||||||
|
return 302 /terms;
|
||||||
|
}
|
||||||
|
location = /privacy {
|
||||||
|
alias /var/www/bank-landing/privacy.html;
|
||||||
|
default_type text/html;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
location = /privacy/ {
|
||||||
|
return 302 /privacy;
|
||||||
|
}
|
||||||
|
# Fresh community-pool withdraw for one-click wallet funding
|
||||||
|
location = /intro/demo-withdraw.json {
|
||||||
|
proxy_pass http://127.0.0.1:19096/demo-withdraw.json;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
add_header Access-Control-Allow-Origin * always;
|
||||||
|
}
|
||||||
|
# Auto-create personal bank account (credentials returned once; balance GOA:0)
|
||||||
|
location = /intro/auto-account.json {
|
||||||
|
proxy_pass http://127.0.0.1:19096/auto-account.json;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
add_header Access-Control-Allow-Origin * always;
|
||||||
|
}
|
||||||
|
location /intro/ {
|
||||||
|
alias /var/www/bank-landing/;
|
||||||
|
}
|
||||||
|
location = /intro/stats.json {
|
||||||
|
alias /var/www/bank-landing/stats.json;
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin * always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
location = /stats.json {
|
||||||
|
alias /var/www/bank-landing/stats.json;
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin * always;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
configs/bank-landing/og-goa-shop.png
Normal file
|
After Width: | Height: | Size: 544 KiB |
137
configs/bank-landing/privacy.html
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>Privacy · GOA Bank · Swiss FADP</title>
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: dark light; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 42rem; margin: 2rem auto; padding: 0 1.1rem 3rem;
|
||||||
|
line-height: 1.5; color: #e8e6e3; background: #1a1520;
|
||||||
|
}
|
||||||
|
h1 { font-size: 1.35rem; font-weight: 800; margin: 0 0 1rem; color: #f5f0ea; }
|
||||||
|
h2 { font-size: 1.05rem; margin: 1.5rem 0 0.5rem; color: #e8c878; }
|
||||||
|
p, li, td, th { font-size: 0.95rem; }
|
||||||
|
ul { padding-left: 1.2rem; }
|
||||||
|
code, a { color: #5eead4; }
|
||||||
|
a { text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block; font-size: 0.72rem; font-weight: 700;
|
||||||
|
letter-spacing: 0.06em; text-transform: uppercase;
|
||||||
|
color: #c4b5fd; border: 1px solid rgba(196,181,253,0.35);
|
||||||
|
border-radius: 999px; padding: 0.2rem 0.65rem; margin-bottom: 0.85rem;
|
||||||
|
}
|
||||||
|
.note {
|
||||||
|
border-radius: 12px; padding: 0.75rem 0.9rem; margin: 0.85rem 0 1rem;
|
||||||
|
border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.25);
|
||||||
|
font-size: 0.9rem; color: #c8c4bf;
|
||||||
|
}
|
||||||
|
table { width: 100%; border-collapse: collapse; margin: 0.6rem 0 1rem; font-size: 0.88rem; }
|
||||||
|
th, td { border: 1px solid rgba(255,255,255,0.12); padding: 0.45rem 0.55rem; text-align: left; vertical-align: top; }
|
||||||
|
th { background: rgba(0,0,0,0.35); color: #e8c878; font-weight: 700; }
|
||||||
|
.muted { color: #a39e98; font-size: 0.88rem; }
|
||||||
|
footer { margin-top: 2rem; font-size: 0.85rem; color: #a39e98; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="badge">bank.hacktivism.ch · privacy · CH</div>
|
||||||
|
<h1>Privacy notice · GOA Exploration Bank</h1>
|
||||||
|
<p class="note">
|
||||||
|
This notice describes personal data processing for the self-hosted bank at
|
||||||
|
<code>bank.hacktivism.ch</code> under the Swiss Federal Act on Data Protection
|
||||||
|
(<strong>FADP / revDSG</strong>, in force since 1 Sep 2023). It is an
|
||||||
|
explorational service, not a licensed Swiss bank.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>1. Controller</h2>
|
||||||
|
<p>Operators of the hacktivism.ch GNU Taler stack (GOA exploration deployment).
|
||||||
|
Contact via the operational channels published for this host. No separate DPO
|
||||||
|
is appointed for this experimental service.</p>
|
||||||
|
|
||||||
|
<h2>2. Categories of data retained</h2>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Data</th><th>Examples</th><th>Typical retention</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Account identifiers</td>
|
||||||
|
<td>Username, account serial, registration timestamp</td>
|
||||||
|
<td>For account lifetime + up to 12 months after deletion or archive wipe</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Authentication secrets</td>
|
||||||
|
<td>Password hashes / tokens (not plaintext passwords)</td>
|
||||||
|
<td>While account exists; tokens until expiry or revoke</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Ledger / transactions</td>
|
||||||
|
<td>Credits, debits, amounts (GOA), subjects, counterparty account names, timestamps</td>
|
||||||
|
<td>Operational retention for the service lifetime; may be wiped on stack reset</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Balances & limits</td>
|
||||||
|
<td>Account balance, debt limit, conversion flags</td>
|
||||||
|
<td>While account exists</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Withdrawal operations</td>
|
||||||
|
<td>Withdrawal IDs, amounts, status (pending/selected/confirmed), reserve pub when known</td>
|
||||||
|
<td>Until completed/aborted + short operational logs (days–weeks)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Technical logs</td>
|
||||||
|
<td>HTTP access logs (IP, User-Agent, path, status), application logs</td>
|
||||||
|
<td>Typically days to a few weeks (rotation); not used for marketing</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Landing / demo artefacts</td>
|
||||||
|
<td>Public <code>stats.json</code> aggregates, demo withdraw URI state</td>
|
||||||
|
<td>Stats overwritten continuously; demo URI until used or rotated</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p><strong>Not retained as payment content:</strong> wallet coin private keys (never sent to the bank).
|
||||||
|
Full browser history or device contacts are not collected by this bank service.</p>
|
||||||
|
|
||||||
|
<h2>3. Purposes</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Operating bank accounts and GOA transfers for exploration of GNU Taler</li>
|
||||||
|
<li>Authenticating users and preventing abuse</li>
|
||||||
|
<li>Enabling withdrawals to the GOA exchange</li>
|
||||||
|
<li>Security, debugging, and capacity monitoring</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>4. Legal basis (Swiss FADP)</h2>
|
||||||
|
<p>Processing is necessary to provide the service requested by the user (account / withdraw)
|
||||||
|
and for overriding private interests of secure operation of an experimental public stack
|
||||||
|
(Art. 6 and 31 FADP principles: lawfulness, proportionality, purpose limitation).</p>
|
||||||
|
|
||||||
|
<h2>5. Recipients & transfers</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>GOA exchange</strong> (<code>exchange.hacktivism.ch</code>): reserve/wire-related data required by the protocol</li>
|
||||||
|
<li><strong>Infrastructure operators</strong> of this host (hosting, backups) under operational control</li>
|
||||||
|
<li>No sale of personal data. No intentional transfer outside Switzerland/EEA for this service;
|
||||||
|
infrastructure may use standard CDN/DNS resolvers</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>6. Your rights (FADP)</h2>
|
||||||
|
<p>Subject to legal limits: right to information/access, rectification, deletion, and to object
|
||||||
|
to processing. Contact the operators. You may lodge a complaint with the Swiss Federal Data
|
||||||
|
Protection and Information Commissioner (<strong>FDPIC / EDÖB</strong>).</p>
|
||||||
|
|
||||||
|
<h2>7. Security</h2>
|
||||||
|
<p>TLS in transit; access control on bank API; experimental service — no certified ISMS.
|
||||||
|
Do not store sensitive personal data in transaction subjects.</p>
|
||||||
|
|
||||||
|
<h2>Related</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://bank.hacktivism.ch/terms">Bank terms</a></li>
|
||||||
|
<li><a href="https://exchange.hacktivism.ch/privacy">Exchange privacy</a></li>
|
||||||
|
<li><a href="https://taler.hacktivism.ch/privacy">Merchant privacy</a></li>
|
||||||
|
<li><a href="https://bank.hacktivism.ch/intro/">Bank intro</a></li>
|
||||||
|
</ul>
|
||||||
|
<footer class="muted">bank-pp-swiss-v0 · Swiss FADP (revDSG)</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
configs/bank-landing/qr-logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
1
configs/bank-landing/qr-logo.svg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
configs/bank-landing/qr-payto.png
Normal file
|
After Width: | Height: | Size: 602 B |
BIN
configs/bank-landing/qr-product-beacon-badge.png
Normal file
|
After Width: | Height: | Size: 608 B |
BIN
configs/bank-landing/qr-product-blue-or-red-pill.png
Normal file
|
After Width: | Height: | Size: 617 B |
BIN
configs/bank-landing/qr-product-comet-cap.png
Normal file
|
After Width: | Height: | Size: 524 B |
BIN
configs/bank-landing/qr-product-eclipse-shades.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
configs/bank-landing/qr-product-nebula-coffee.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
configs/bank-landing/qr-product-orbit-sticker.png
Normal file
|
After Width: | Height: | Size: 597 B |
BIN
configs/bank-landing/qr-product-rainbow-pill.png
Normal file
|
After Width: | Height: | Size: 599 B |
BIN
configs/bank-landing/qr-product-relay-pin.png
Normal file
|
After Width: | Height: | Size: 528 B |
BIN
configs/bank-landing/qr-product-shuttle-pass.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
configs/bank-landing/qr-product-star-chart.png
Normal file
|
After Width: | Height: | Size: 527 B |
BIN
configs/bank-landing/qr-product-voidwave-playlist.png
Normal file
|
After Width: | Height: | Size: 602 B |
1
configs/bank-landing/qrcode.min.js
vendored
Normal file
348
configs/bank-landing/shop-pay.css
Normal file
|
|
@ -0,0 +1,348 @@
|
||||||
|
/* GOA shop pay popup — QR_Taler style from taler-merchant-webui / @gnu-taler/web-util */
|
||||||
|
|
||||||
|
/* Animated blue ring (same as webui QR.tsx) */
|
||||||
|
@property --angle {
|
||||||
|
syntax: "<angle>";
|
||||||
|
initial-value: 0deg;
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@keyframes goa-pay-qr-rotate {
|
||||||
|
to {
|
||||||
|
--angle: 360deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-item[data-product] {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s, transform 0.12s, background 0.15s;
|
||||||
|
}
|
||||||
|
.shop-item[data-product]:hover,
|
||||||
|
.shop-item[data-product]:focus-visible {
|
||||||
|
border-color: rgba(196, 181, 253, 0.75);
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
button.shop-item {
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
width: 100%;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goa-pay-modal {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 80;
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
background: rgba(8, 6, 14, 0.75);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
.goa-pay-modal.open {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.goa-pay-card {
|
||||||
|
position: relative;
|
||||||
|
background: rgba(36, 28, 48, 0.98);
|
||||||
|
border: 1px solid rgba(167, 139, 250, 0.45);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 1.25rem 1.2rem 1.2rem;
|
||||||
|
max-width: 24rem;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
||||||
|
max-height: 92vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.goa-pay-x {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.45rem;
|
||||||
|
right: 0.55rem;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #c4b5fd;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.2rem 0.45rem;
|
||||||
|
}
|
||||||
|
.goa-pay-card h3 {
|
||||||
|
margin: 0 0 0.2rem;
|
||||||
|
font-size: 1.08rem;
|
||||||
|
color: #faf5ff;
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
}
|
||||||
|
.goa-pay-amount {
|
||||||
|
margin: 0 0 0.85rem;
|
||||||
|
font-weight: 750;
|
||||||
|
color: #c4b5fd;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === QR_Taler frame (webui QR.tsx) === */
|
||||||
|
.goa-pay-taler-qr {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 280px;
|
||||||
|
margin: 0 auto 0.75rem;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
background: conic-gradient(
|
||||||
|
from var(--angle),
|
||||||
|
#0042b3 0deg,
|
||||||
|
#f1f1f4 20deg,
|
||||||
|
#f1f1f4 150deg,
|
||||||
|
#f1f1f4 160deg,
|
||||||
|
#0042b3 180deg,
|
||||||
|
#f1f1f4 200deg,
|
||||||
|
#f1f1f4 330deg,
|
||||||
|
#f1f1f4 340deg,
|
||||||
|
#0042b3
|
||||||
|
);
|
||||||
|
animation: goa-pay-qr-rotate 10s linear infinite;
|
||||||
|
}
|
||||||
|
.goa-pay-taler-qr__inner {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #fff;
|
||||||
|
line-height: 0;
|
||||||
|
min-height: 180px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
/* Always an <img data-url> with explicit pixel size from JS */
|
||||||
|
.goa-pay-taler-qr__inner img {
|
||||||
|
display: block !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
border: 0 !important;
|
||||||
|
background: #fff;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
/* Center logo plate — official qr-logo.png (webui ~100×50; PNG for Android) */
|
||||||
|
.goa-pay-taler-qr__logo {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 28%;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100px;
|
||||||
|
aspect-ratio: 200 / 95;
|
||||||
|
object-fit: contain;
|
||||||
|
pointer-events: none;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
/* Full encoded payload under QR — clickable (wallet / webextension) */
|
||||||
|
.goa-pay-payload {
|
||||||
|
display: block;
|
||||||
|
margin: 0.45rem 0 0.55rem;
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #7eb6ff;
|
||||||
|
word-break: break-all;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid rgba(126, 182, 255, 0.4);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.goa-pay-payload:hover {
|
||||||
|
color: #b8d6ff;
|
||||||
|
border-bottom-color: #7eb6ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settlement: collapsed by default, expand on click */
|
||||||
|
.goa-pay-settle {
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(0, 0, 0, 0.22);
|
||||||
|
}
|
||||||
|
.goa-pay-settle-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.65rem 0.75rem;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 650;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
.goa-pay-settle-toggle:hover {
|
||||||
|
background: rgba(148, 163, 184, 0.1);
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
.goa-pay-settle-toggle-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.goa-pay-settle-chevron {
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
.goa-pay-modal.settle-open .goa-pay-settle-chevron {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
.goa-pay-settle-body {
|
||||||
|
padding: 0 0.75rem 0.75rem;
|
||||||
|
border-top: 1px solid rgba(148, 163, 184, 0.25);
|
||||||
|
}
|
||||||
|
.goa-pay-settle-note {
|
||||||
|
margin-top: 0.55rem;
|
||||||
|
}
|
||||||
|
/* Settlement payto QR — plain box (not wallet pay / no logo / no blue ring) */
|
||||||
|
.goa-pay-payto-qr {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 160px;
|
||||||
|
margin: 0.35rem auto 0.55rem;
|
||||||
|
padding: 0.45rem 0.5rem 0.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px dashed rgba(100, 116, 139, 0.75);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.goa-pay-payto-qr__caption {
|
||||||
|
font-size: 0.62rem;
|
||||||
|
font-weight: 750;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #64748b;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 0 0.35rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.goa-pay-payto-qr__inner {
|
||||||
|
line-height: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.goa-pay-payto-qr__inner img {
|
||||||
|
display: block !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
background: #fff;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
.goa-pay-sub {
|
||||||
|
margin: 0 0 0.55rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #b8a8c9;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goa-pay-hint {
|
||||||
|
margin: 0 0 0.75rem;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #b8a8c9;
|
||||||
|
}
|
||||||
|
.goa-pay-hint.err {
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
.goa-pay-cta {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 750;
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
border-radius: 11px;
|
||||||
|
background: linear-gradient(135deg, #a78bfa, #6366f1);
|
||||||
|
color: #0f0a1a;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
.goa-pay-cta:hover {
|
||||||
|
filter: brightness(1.06);
|
||||||
|
}
|
||||||
|
.goa-pay-cta.disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.goa-pay-label {
|
||||||
|
margin: 0.55rem 0 0.2rem;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #b8a8c9;
|
||||||
|
}
|
||||||
|
.goa-pay-line {
|
||||||
|
margin: 0 0 0.35rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
.goa-pay-link {
|
||||||
|
color: #93c5fd;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.goa-pay-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.goa-pay-link.disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.goa-pay-sep {
|
||||||
|
margin: 0 0.4rem;
|
||||||
|
color: #b8a8c9;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.goa-pay-close {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.85rem;
|
||||||
|
border: 1px solid rgba(167, 139, 250, 0.45);
|
||||||
|
background: transparent;
|
||||||
|
color: #c4b5fd;
|
||||||
|
font-weight: 650;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bank (gold/teal) overrides */
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-card {
|
||||||
|
background: rgba(42, 32, 24, 0.98);
|
||||||
|
border-color: rgba(232, 168, 56, 0.4);
|
||||||
|
}
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-amount {
|
||||||
|
color: #5eead4;
|
||||||
|
}
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-cta {
|
||||||
|
background: linear-gradient(135deg, #5eead4, #0d9488);
|
||||||
|
color: #042f2e;
|
||||||
|
}
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-link {
|
||||||
|
color: #5eead4;
|
||||||
|
}
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-x,
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-close {
|
||||||
|
color: #e8c878;
|
||||||
|
border-color: rgba(232, 168, 56, 0.35);
|
||||||
|
}
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-settle {
|
||||||
|
border-color: rgba(232, 168, 56, 0.3);
|
||||||
|
background: rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-settle-toggle {
|
||||||
|
color: #e8c878;
|
||||||
|
}
|
||||||
|
.goa-pay-modal.bank-theme .goa-pay-settle-body {
|
||||||
|
border-top-color: rgba(232, 168, 56, 0.25);
|
||||||
|
}
|
||||||
410
configs/bank-landing/shop-pay.js
Normal file
|
|
@ -0,0 +1,410 @@
|
||||||
|
/**
|
||||||
|
* GOA shop pay popup — public only (no merchant secrets).
|
||||||
|
* Flow: POST templates/{id} → taler://pay/… + payto links.
|
||||||
|
*
|
||||||
|
* QR display matches taler-merchant-webui QR_Taler
|
||||||
|
* (@gnu-taler/web-util QR.tsx): animated #0042B3 conic ring + qr-logo.png.
|
||||||
|
* Uses qrcode-generator (same lib as webui) via global QRCode if present,
|
||||||
|
* else falls back to canvas from qrcode.min.js (davidshimjs).
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
var MERCHANT_HOST = "taler.hacktivism.ch";
|
||||||
|
/* goa-shop: dedicated instance with fixed-order product templates */
|
||||||
|
var INSTANCE = "goa-shop";
|
||||||
|
var SHOP_PAYTO =
|
||||||
|
"payto://x-taler-bank/bank.hacktivism.ch/goa-shop?receiver-name=GOA%20Shop";
|
||||||
|
var SHOP_PAYTO_HTTPS = "https://bank.hacktivism.ch/webui/";
|
||||||
|
|
||||||
|
function introBase() {
|
||||||
|
var b = document.querySelector("base");
|
||||||
|
return b && b.href ? b.href : "/intro/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function logoSrc() {
|
||||||
|
/* PNG: Android often drops SVG wordmark fill inheritance (missing "taler") */
|
||||||
|
return introBase() + "qr-logo.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
function templateHttps(productId) {
|
||||||
|
return (
|
||||||
|
"https://" +
|
||||||
|
MERCHANT_HOST +
|
||||||
|
"/instances/" +
|
||||||
|
INSTANCE +
|
||||||
|
"/templates/" +
|
||||||
|
encodeURIComponent(productId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function payTemplateUri(productId) {
|
||||||
|
return (
|
||||||
|
"taler://pay-template/" +
|
||||||
|
MERCHANT_HOST +
|
||||||
|
"/instances/" +
|
||||||
|
INSTANCE +
|
||||||
|
"/" +
|
||||||
|
encodeURIComponent(productId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureModal() {
|
||||||
|
var m = document.getElementById("goa-pay-modal");
|
||||||
|
if (m) return m;
|
||||||
|
m = document.createElement("div");
|
||||||
|
m.id = "goa-pay-modal";
|
||||||
|
m.className = "goa-pay-modal";
|
||||||
|
m.setAttribute("role", "dialog");
|
||||||
|
m.setAttribute("aria-modal", "true");
|
||||||
|
m.setAttribute("aria-labelledby", "goa-pay-title");
|
||||||
|
m.hidden = true;
|
||||||
|
m.innerHTML =
|
||||||
|
'<div class="goa-pay-card">' +
|
||||||
|
' <button type="button" class="goa-pay-x" id="goa-pay-x" aria-label="Close">×</button>' +
|
||||||
|
' <h3 id="goa-pay-title">Pay with Taler</h3>' +
|
||||||
|
' <p class="goa-pay-amount" id="goa-pay-amount"></p>' +
|
||||||
|
' <p class="goa-pay-hint" id="goa-pay-status">Preparing payment…</p>' +
|
||||||
|
' <p class="goa-pay-label">Wallet payment</p>' +
|
||||||
|
' <p class="goa-pay-sub">Scan with the GNU Taler Wallet to pay this product</p>' +
|
||||||
|
' <div class="goa-pay-taler-qr" id="goa-pay-qr-pay-wrap">' +
|
||||||
|
' <div class="goa-pay-taler-qr__inner" id="goa-pay-qr-pay"></div>' +
|
||||||
|
' <img class="goa-pay-taler-qr__logo" id="goa-pay-qr-pay-logo" alt="" />' +
|
||||||
|
" </div>" +
|
||||||
|
' <a class="goa-pay-payload" id="goa-pay-qr-payload" href="#">…</a>' +
|
||||||
|
' <a class="goa-pay-cta" id="goa-pay-open" href="#">Open payment link →</a>' +
|
||||||
|
' <p class="goa-pay-line">' +
|
||||||
|
' <a class="goa-pay-link" id="goa-pay-uri" href="#">taler:// URI</a>' +
|
||||||
|
' <span class="goa-pay-sep">·</span>' +
|
||||||
|
' <a class="goa-pay-link" id="goa-pay-uri-https" href="#" target="_blank" rel="noopener">HTTPS</a>' +
|
||||||
|
" </p>" +
|
||||||
|
' <div class="goa-pay-settle" id="goa-pay-settle">' +
|
||||||
|
' <button type="button" class="goa-pay-settle-toggle" id="goa-pay-settle-toggle" aria-expanded="false" aria-controls="goa-pay-settle-body">' +
|
||||||
|
' <span class="goa-pay-settle-toggle-text">Settlement account (payto) · not a wallet pay QR</span>' +
|
||||||
|
' <span class="goa-pay-settle-chevron" aria-hidden="true">▸</span>' +
|
||||||
|
" </button>" +
|
||||||
|
' <div class="goa-pay-settle-body" id="goa-pay-settle-body" hidden>' +
|
||||||
|
' <p class="goa-pay-sub goa-pay-settle-note">' +
|
||||||
|
" This is the merchant’s bank account address (payto://). " +
|
||||||
|
" The exchange wires funds here after a wallet payment. " +
|
||||||
|
" Do not scan this to buy the product — use the wallet QR above." +
|
||||||
|
" </p>" +
|
||||||
|
' <div class="goa-pay-payto-qr" id="goa-pay-qr-payto-wrap">' +
|
||||||
|
' <div class="goa-pay-payto-qr__caption">Settlement only</div>' +
|
||||||
|
' <div class="goa-pay-payto-qr__inner" id="goa-pay-qr-payto"></div>' +
|
||||||
|
" </div>" +
|
||||||
|
' <a class="goa-pay-payload" id="goa-pay-payto-payload" href="#">…</a>' +
|
||||||
|
' <p class="goa-pay-line">' +
|
||||||
|
' <a class="goa-pay-link" id="goa-pay-payto" href="#">payto:// URI</a>' +
|
||||||
|
' <span class="goa-pay-sep">·</span>' +
|
||||||
|
' <a class="goa-pay-link" id="goa-pay-payto-https" href="#" target="_blank" rel="noopener">HTTPS</a>' +
|
||||||
|
" </p>" +
|
||||||
|
" </div>" +
|
||||||
|
" </div>" +
|
||||||
|
' <button type="button" class="goa-pay-close" id="goa-pay-close">Close</button>' +
|
||||||
|
"</div>";
|
||||||
|
document.body.appendChild(m);
|
||||||
|
function close() {
|
||||||
|
m.classList.remove("open");
|
||||||
|
m.hidden = true;
|
||||||
|
// collapse settlement on close
|
||||||
|
var body = document.getElementById("goa-pay-settle-body");
|
||||||
|
var btn = document.getElementById("goa-pay-settle-toggle");
|
||||||
|
if (body) body.hidden = true;
|
||||||
|
if (btn) {
|
||||||
|
btn.setAttribute("aria-expanded", "false");
|
||||||
|
m.classList.remove("settle-open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.addEventListener("click", function (e) {
|
||||||
|
if (e.target === m) close();
|
||||||
|
});
|
||||||
|
document.getElementById("goa-pay-x").onclick = close;
|
||||||
|
document.getElementById("goa-pay-close").onclick = close;
|
||||||
|
document.getElementById("goa-pay-settle-toggle").onclick = function () {
|
||||||
|
var body = document.getElementById("goa-pay-settle-body");
|
||||||
|
var btn = document.getElementById("goa-pay-settle-toggle");
|
||||||
|
var open = body.hidden;
|
||||||
|
body.hidden = !open;
|
||||||
|
btn.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
|
m.classList.toggle("settle-open", open);
|
||||||
|
if (open) {
|
||||||
|
// paint payto QR when first expanded
|
||||||
|
renderQr(document.getElementById("goa-pay-qr-payto"), SHOP_PAYTO, 140);
|
||||||
|
var pp = document.getElementById("goa-pay-payto-payload");
|
||||||
|
if (pp) {
|
||||||
|
pp.href = SHOP_PAYTO;
|
||||||
|
pp.textContent = SHOP_PAYTO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape" && m.classList.contains("open")) close();
|
||||||
|
});
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render URI as a real <img> PNG (not a live canvas).
|
||||||
|
* davidshimjs paints canvas then often hides it for an img; if the modal is
|
||||||
|
* still display:none, layout collapses and you only see the blue ring.
|
||||||
|
* We always encode off-DOM and inject a fixed-size image.
|
||||||
|
*/
|
||||||
|
function renderQr(hostEl, text, size) {
|
||||||
|
if (!hostEl) return;
|
||||||
|
hostEl.innerHTML = "";
|
||||||
|
size = size || 220;
|
||||||
|
if (!text) {
|
||||||
|
hostEl.innerHTML = '<p class="goa-pay-hint">…</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof QRCode === "undefined") {
|
||||||
|
hostEl.innerHTML =
|
||||||
|
'<p class="goa-pay-hint err">QR library missing (qrcode.min.js)</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var level =
|
||||||
|
QRCode.CorrectLevel && QRCode.CorrectLevel.M != null
|
||||||
|
? QRCode.CorrectLevel.M
|
||||||
|
: QRCode.CorrectLevel && QRCode.CorrectLevel.L != null
|
||||||
|
? QRCode.CorrectLevel.L
|
||||||
|
: 1;
|
||||||
|
var scratch = document.createElement("div");
|
||||||
|
scratch.setAttribute("aria-hidden", "true");
|
||||||
|
scratch.style.cssText =
|
||||||
|
"position:fixed;left:-9999px;top:0;width:" +
|
||||||
|
size +
|
||||||
|
"px;height:" +
|
||||||
|
size +
|
||||||
|
"px;overflow:hidden;opacity:0;pointer-events:none";
|
||||||
|
document.body.appendChild(scratch);
|
||||||
|
var dataUrl = "";
|
||||||
|
try {
|
||||||
|
new QRCode(scratch, {
|
||||||
|
text: String(text),
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
colorDark: "#000000",
|
||||||
|
colorLight: "#ffffff",
|
||||||
|
correctLevel: level,
|
||||||
|
});
|
||||||
|
var canvas = scratch.querySelector("canvas");
|
||||||
|
var libImg = scratch.querySelector("img");
|
||||||
|
if (canvas && canvas.width > 0) {
|
||||||
|
try {
|
||||||
|
dataUrl = canvas.toDataURL("image/png");
|
||||||
|
} catch (e1) {}
|
||||||
|
}
|
||||||
|
if (!dataUrl && libImg && libImg.src && libImg.src.indexOf("data:") === 0) {
|
||||||
|
dataUrl = libImg.src;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
dataUrl = "";
|
||||||
|
}
|
||||||
|
if (scratch.parentNode) scratch.parentNode.removeChild(scratch);
|
||||||
|
|
||||||
|
if (!dataUrl) {
|
||||||
|
hostEl.innerHTML =
|
||||||
|
'<p class="goa-pay-hint err">QR encode failed</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var img = document.createElement("img");
|
||||||
|
img.alt = "QR code";
|
||||||
|
img.width = size;
|
||||||
|
img.height = size;
|
||||||
|
img.src = dataUrl;
|
||||||
|
img.style.display = "block";
|
||||||
|
img.style.width = size + "px";
|
||||||
|
img.style.height = size + "px";
|
||||||
|
img.style.maxWidth = "100%";
|
||||||
|
img.style.margin = "0 auto";
|
||||||
|
img.style.background = "#fff";
|
||||||
|
hostEl.appendChild(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLogo(imgEl) {
|
||||||
|
if (!imgEl) return;
|
||||||
|
imgEl.alt = "Taler";
|
||||||
|
imgEl.width = 100;
|
||||||
|
imgEl.height = 50;
|
||||||
|
imgEl.decoding = "async";
|
||||||
|
imgEl.src = logoSrc();
|
||||||
|
imgEl.onerror = function () {
|
||||||
|
imgEl.src = introBase() + "qr-logo.svg";
|
||||||
|
};
|
||||||
|
imgEl.style.display = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePayUri(uri) {
|
||||||
|
if (!uri) return "";
|
||||||
|
return String(uri)
|
||||||
|
.replace(/taler\.hacktivism\.ch:443/g, "taler.hacktivism.ch")
|
||||||
|
.replace(/:443\//g, "/")
|
||||||
|
.replace(/:443\?/g, "?");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Public: template → order → taler_pay_uri (no secrets). */
|
||||||
|
function createPayUri(productId) {
|
||||||
|
var url = templateHttps(productId);
|
||||||
|
return fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: "{}",
|
||||||
|
cache: "no-store",
|
||||||
|
})
|
||||||
|
.then(function (r) {
|
||||||
|
if (!r.ok) throw new Error("template POST HTTP " + r.status);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then(function (created) {
|
||||||
|
var oid = created.order_id;
|
||||||
|
var tok = created.token;
|
||||||
|
if (!oid || !tok) throw new Error("no order_id/token");
|
||||||
|
var statusUrl =
|
||||||
|
"https://" +
|
||||||
|
MERCHANT_HOST +
|
||||||
|
"/instances/" +
|
||||||
|
INSTANCE +
|
||||||
|
"/orders/" +
|
||||||
|
encodeURIComponent(oid) +
|
||||||
|
"?token=" +
|
||||||
|
encodeURIComponent(tok);
|
||||||
|
return fetch(statusUrl, { cache: "no-store" }).then(function (r) {
|
||||||
|
return r.text().then(function (t) {
|
||||||
|
var pay = "";
|
||||||
|
try {
|
||||||
|
var d = JSON.parse(t);
|
||||||
|
pay = d.taler_pay_uri || "";
|
||||||
|
} catch (e) {}
|
||||||
|
if (!pay) {
|
||||||
|
pay =
|
||||||
|
"taler://pay/" +
|
||||||
|
MERCHANT_HOST +
|
||||||
|
"/instances/" +
|
||||||
|
INSTANCE +
|
||||||
|
"/" +
|
||||||
|
oid +
|
||||||
|
"/?c=" +
|
||||||
|
tok;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
taler_pay_uri: normalizePayUri(pay),
|
||||||
|
order_id: oid,
|
||||||
|
token: tok,
|
||||||
|
status_https: statusUrl.replace(
|
||||||
|
"taler.hacktivism.ch:443",
|
||||||
|
"taler.hacktivism.ch"
|
||||||
|
),
|
||||||
|
template_https: url,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPay(productId, name, amount) {
|
||||||
|
var m = ensureModal();
|
||||||
|
if (document.body && document.body.getAttribute("data-shop-theme") === "bank") {
|
||||||
|
m.classList.add("bank-theme");
|
||||||
|
} else {
|
||||||
|
m.classList.remove("bank-theme");
|
||||||
|
}
|
||||||
|
document.getElementById("goa-pay-title").textContent = name || productId;
|
||||||
|
document.getElementById("goa-pay-amount").textContent = amount || "";
|
||||||
|
var status = document.getElementById("goa-pay-status");
|
||||||
|
status.textContent = "Preparing payment…";
|
||||||
|
status.className = "goa-pay-hint";
|
||||||
|
var open = document.getElementById("goa-pay-open");
|
||||||
|
open.removeAttribute("href");
|
||||||
|
open.classList.add("disabled");
|
||||||
|
open.onclick = null;
|
||||||
|
var payLink = document.getElementById("goa-pay-uri");
|
||||||
|
payLink.removeAttribute("href");
|
||||||
|
payLink.classList.add("disabled");
|
||||||
|
payLink.textContent = "taler:// URI";
|
||||||
|
document.getElementById("goa-pay-uri-https").href = templateHttps(productId);
|
||||||
|
document.getElementById("goa-pay-uri-https").textContent = "HTTPS";
|
||||||
|
var paytoLink = document.getElementById("goa-pay-payto");
|
||||||
|
paytoLink.href = SHOP_PAYTO;
|
||||||
|
paytoLink.textContent = "payto:// URI";
|
||||||
|
document.getElementById("goa-pay-payto-https").href = SHOP_PAYTO_HTTPS;
|
||||||
|
document.getElementById("goa-pay-payto-https").textContent = "HTTPS";
|
||||||
|
|
||||||
|
setLogo(document.getElementById("goa-pay-qr-pay-logo"));
|
||||||
|
|
||||||
|
// Reset settlement panel (collapsed until user expands)
|
||||||
|
var settleBody = document.getElementById("goa-pay-settle-body");
|
||||||
|
var settleBtn = document.getElementById("goa-pay-settle-toggle");
|
||||||
|
if (settleBody) settleBody.hidden = true;
|
||||||
|
if (settleBtn) settleBtn.setAttribute("aria-expanded", "false");
|
||||||
|
m.classList.remove("settle-open");
|
||||||
|
var paytoHost = document.getElementById("goa-pay-qr-payto");
|
||||||
|
if (paytoHost) paytoHost.innerHTML = "";
|
||||||
|
|
||||||
|
// Open first so layout exists, then paint wallet QR only
|
||||||
|
m.hidden = false;
|
||||||
|
m.classList.add("open");
|
||||||
|
var tpl = payTemplateUri(productId);
|
||||||
|
renderQr(
|
||||||
|
document.getElementById("goa-pay-qr-pay"),
|
||||||
|
tpl,
|
||||||
|
220
|
||||||
|
);
|
||||||
|
var payload = document.getElementById("goa-pay-qr-payload");
|
||||||
|
if (payload) {
|
||||||
|
payload.href = tpl;
|
||||||
|
payload.textContent = tpl;
|
||||||
|
}
|
||||||
|
|
||||||
|
createPayUri(productId)
|
||||||
|
.then(function (info) {
|
||||||
|
var pay = info.taler_pay_uri;
|
||||||
|
open.href = pay;
|
||||||
|
open.classList.remove("disabled");
|
||||||
|
open.textContent = "Open payment link →";
|
||||||
|
payLink.href = pay;
|
||||||
|
payLink.classList.remove("disabled");
|
||||||
|
payLink.textContent = "taler:// URI";
|
||||||
|
var uh = document.getElementById("goa-pay-uri-https");
|
||||||
|
uh.href = info.status_https || info.template_https;
|
||||||
|
uh.textContent = "HTTPS";
|
||||||
|
// Live unpaid taler://pay (fresh order each open)
|
||||||
|
renderQr(document.getElementById("goa-pay-qr-pay"), pay, 220);
|
||||||
|
if (payload) {
|
||||||
|
payload.href = pay;
|
||||||
|
payload.textContent = pay;
|
||||||
|
}
|
||||||
|
status.textContent = "Ready — scan in wallet";
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
status.textContent =
|
||||||
|
"Payment setup failed: " + (err && err.message ? err.message : err);
|
||||||
|
status.className = "goa-pay-hint err";
|
||||||
|
open.textContent = "Retry";
|
||||||
|
open.onclick = function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
showPay(productId, name, amount);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bind() {
|
||||||
|
document.querySelectorAll("[data-product]").forEach(function (el) {
|
||||||
|
el.addEventListener("click", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var id = el.getAttribute("data-product");
|
||||||
|
var nameEl = el.querySelector(".name");
|
||||||
|
var priceEl = el.querySelector(".price");
|
||||||
|
showPay(
|
||||||
|
id,
|
||||||
|
nameEl ? nameEl.textContent.trim() : id,
|
||||||
|
priceEl ? priceEl.textContent.trim() : ""
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", bind);
|
||||||
|
} else {
|
||||||
|
bind();
|
||||||
|
}
|
||||||
|
})();
|
||||||
75
configs/bank-landing/terms.html
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<title>No Formal Terms · GOA Bank</title>
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: dark light; }
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 40rem; margin: 2rem auto; padding: 0 1.1rem 3rem;
|
||||||
|
line-height: 1.5; color: #e8e6e3; background: #1a1520;
|
||||||
|
}
|
||||||
|
h1 { font-size: 1.35rem; font-weight: 800; margin: 0 0 1rem; color: #f5f0ea; }
|
||||||
|
h2 { font-size: 1.05rem; margin: 1.4rem 0 0.5rem; color: #e8c878; }
|
||||||
|
p, li { font-size: 0.98rem; }
|
||||||
|
ul { padding-left: 1.2rem; }
|
||||||
|
code, a { color: #5eead4; }
|
||||||
|
a { text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block; font-size: 0.72rem; font-weight: 700;
|
||||||
|
letter-spacing: 0.06em; text-transform: uppercase;
|
||||||
|
color: #c4b5fd; border: 1px solid rgba(196,181,253,0.35);
|
||||||
|
border-radius: 999px; padding: 0.2rem 0.65rem; margin-bottom: 0.85rem;
|
||||||
|
}
|
||||||
|
.cur {
|
||||||
|
border-radius: 12px; padding: 0.75rem 0.9rem; margin: 0.85rem 0 1rem;
|
||||||
|
border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.25);
|
||||||
|
}
|
||||||
|
.cur strong { display: block; font-size: 1.05rem; margin-bottom: 0.25rem; color: #5eead4; }
|
||||||
|
.muted { color: #a39e98; font-size: 0.88rem; }
|
||||||
|
footer { margin-top: 2rem; font-size: 0.85rem; color: #a39e98; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="badge">bank.hacktivism.ch · GOA</div>
|
||||||
|
<h1>No Formal Terms · GOA Exploration Bank</h1>
|
||||||
|
<p>This is a <strong>self-hosted GNU Taler regional bank</strong> at
|
||||||
|
<code>bank.hacktivism.ch</code> (hacktivism.ch) for the explorational currency
|
||||||
|
<strong>GOA</strong>.</p>
|
||||||
|
<p><strong>No formal terms of service</strong> apply beyond this short notice.</p>
|
||||||
|
|
||||||
|
<div class="cur">
|
||||||
|
<strong>GOA · explorational</strong>
|
||||||
|
Not legal tender. No guaranteed real-world value, redemption, or convertibility.
|
||||||
|
Accounts and demo withdraws are for exploration of the GNU Taler stack only.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>By using this bank you acknowledge</h2>
|
||||||
|
<ul>
|
||||||
|
<li>GOA is for exploration and testing only.</li>
|
||||||
|
<li>There is no guaranteed availability, support, or uptime.</li>
|
||||||
|
<li>Operators may reset accounts, balances, or configuration without notice.</li>
|
||||||
|
<li>Registration data and wire-style transfers may identify account holders to operators.</li>
|
||||||
|
<li>Software is provided as-is, without warranty.</li>
|
||||||
|
</ul>
|
||||||
|
<p>If you do not agree, do not use this bank.</p>
|
||||||
|
|
||||||
|
<h2>Related</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://bank.hacktivism.ch/intro/">Bank intro</a></li>
|
||||||
|
<li><a href="https://exchange.hacktivism.ch/terms">Exchange terms (GOA)</a></li>
|
||||||
|
<li><a href="https://taler.hacktivism.ch/terms">Merchant terms</a></li>
|
||||||
|
<li><a href="https://bank.hacktivism.ch/privacy">Bank privacy</a></li>
|
||||||
|
<li><a href="https://bank.hacktivism.ch/webui/">Bank UI</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Privacy</h2>
|
||||||
|
<p class="muted">Processing under Swiss FADP (revDSG). What data is retained
|
||||||
|
(accounts, ledger, logs, …) is listed on
|
||||||
|
<a href="https://bank.hacktivism.ch/privacy">/privacy</a>.</p>
|
||||||
|
<footer class="muted">bank.hacktivism.ch · landing terms</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
28
configs/bonfire/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Bonfire — bonfire.hacktivism.ch
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Live | `/home/hernani/koopa-bonfire/` |
|
||||||
|
| Image | `bonfirenetworks/bonfire:1.0.5-social-amd64` |
|
||||||
|
| Port | **9021** → Caddy |
|
||||||
|
| Ground zero | `2026/2026-07-09--bonfire-ground-zero.md` |
|
||||||
|
|
||||||
|
Secrets: `koopa-admin-secrets/…/koopa-bonfire/{.env,users.env}`.
|
||||||
|
|
||||||
|
**Upstream:** [Running your own](https://docs.bonfirenetworks.org/running-your-own.html), [Hosting guide](https://docs.bonfirenetworks.org/deploy.html) (we use podman-compose, not Co-op Cloud).
|
||||||
|
|
||||||
|
## Branding
|
||||||
|
|
||||||
|
Hacktivism theme + hacktivism magician logo. `scripts/bonfire/apply-branding.sh`.
|
||||||
|
Logo bind-mount: `2026/2026-07-11--bonfire-logo-not-served.md`.
|
||||||
|
|
||||||
|
## Boot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/bonfire/install-systemd.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Units: `container-koopa-bonfire-db`, `container-koopa-bonfire`, `gitbot-mirror`.
|
||||||
|
Needs `loginctl enable-linger hernani`. Run `podman-compose up -d` once before first boot.
|
||||||
|
|
||||||
|
Public feeds: `public-feeds.md`.
|
||||||
90
configs/bonfire/assets/img/favicon.svg
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Wizard flying on magic wand, blue robe">
|
||||||
|
<!-- Original Kamek-inspired mark; not Nintendo art. No filters/transforms for clean raster. -->
|
||||||
|
|
||||||
|
<!-- motion streaks -->
|
||||||
|
<line x1="4" y1="64" x2="22" y2="64" stroke="#5eead4" stroke-width="2.5" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<line x1="6" y1="74" x2="20" y2="74" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
<line x1="8" y1="54" x2="20" y2="54" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
|
||||||
|
<!-- ground shadow -->
|
||||||
|
<ellipse cx="64" cy="118" rx="30" ry="5" fill="#000" opacity="0.2"/>
|
||||||
|
|
||||||
|
<!-- ===== MAGIC WAND (mount) — diagonal, thick gold shaft ===== -->
|
||||||
|
<!-- sits under the rider from lower-left to upper-right -->
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#92400e" stroke-width="11" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#e8a838" stroke-width="7" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- wand jewel (front tip) -->
|
||||||
|
<circle cx="112" cy="70" r="13" fill="#f59e0b" stroke="#fff7ed" stroke-width="3"/>
|
||||||
|
<circle cx="108" cy="66" r="4.5" fill="#fef9c3"/>
|
||||||
|
<!-- sparkles at tip -->
|
||||||
|
<polygon points="120,54 121.6,58.5 126,60 121.6,61.5 120,66 118.4,61.5 114,60 118.4,58.5" fill="#fef08a"/>
|
||||||
|
<polygon points="102,56 103,59 106,60 103,61 102,64 101,61 98,60 101,59" fill="#5eead4"/>
|
||||||
|
<circle cx="118" cy="78" r="2" fill="#bae6fd"/>
|
||||||
|
|
||||||
|
<!-- ===== RIDER on top of wand ===== -->
|
||||||
|
<!-- blue cape fluttering left -->
|
||||||
|
<path d="M38 78 C18 82, 10 98, 8 110 C22 98, 34 96, 40 92 Z" fill="#1e3a8a"/>
|
||||||
|
<path d="M40 80 C22 88, 14 104, 12 112 C28 100, 36 98, 42 94 Z" fill="#1d4ed8"/>
|
||||||
|
|
||||||
|
<!-- BLUE ROBE (body sitting ON shaft) -->
|
||||||
|
<ellipse cx="54" cy="86" rx="28" ry="22" fill="#1d4ed8"/>
|
||||||
|
<ellipse cx="54" cy="84" rx="24" ry="18" fill="#2563eb"/>
|
||||||
|
<!-- white trim -->
|
||||||
|
<path d="M32 74 Q54 94 76 74" fill="none" stroke="#f8fafc" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M36 82 Q54 98 72 82" fill="none" stroke="#e2e8f0" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- green shell -->
|
||||||
|
<ellipse cx="66" cy="92" rx="13" ry="11" fill="#16a34a"/>
|
||||||
|
<ellipse cx="66" cy="92" rx="6" ry="5" fill="#86efac" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- legs over the wand -->
|
||||||
|
<ellipse cx="42" cy="100" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<ellipse cx="60" cy="102" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<!-- yellow feet hanging past shaft -->
|
||||||
|
<ellipse cx="38" cy="106" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="64" cy="108" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
|
||||||
|
<!-- left arm forward -->
|
||||||
|
<ellipse cx="30" cy="82" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="24" cy="78" r="5.5" fill="#fbbf24"/>
|
||||||
|
<!-- right arm up -->
|
||||||
|
<ellipse cx="74" cy="74" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="80" cy="68" r="5.5" fill="#fbbf24"/>
|
||||||
|
<circle cx="82" cy="60" r="5" fill="#38bdf8"/>
|
||||||
|
<circle cx="81" cy="58" r="1.8" fill="#e0f2fe"/>
|
||||||
|
|
||||||
|
<!-- ===== HEAD ===== -->
|
||||||
|
<ellipse cx="54" cy="50" rx="26" ry="24" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="54" cy="53" rx="21" ry="17" fill="#fcd34d"/>
|
||||||
|
<ellipse cx="34" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
<ellipse cx="74" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- glasses -->
|
||||||
|
<circle cx="42" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<circle cx="66" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="52" y1="50" x2="56" y2="50" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="32" y1="47" x2="24" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<line x1="76" y1="47" x2="84" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="44" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="68" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="45.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
<circle cx="69.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
|
||||||
|
<!-- snout + mustache -->
|
||||||
|
<ellipse cx="54" cy="62" rx="8" ry="5" fill="#f59e0b"/>
|
||||||
|
<path d="M36 64 Q46 74 54 70 Q62 74 72 64 Q64 72 54 74 Q44 72 36 64 Z" fill="#f8fafc"/>
|
||||||
|
<path d="M44 66 Q54 74 64 66" fill="none" stroke="#92400e" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- BLUE pointed hat -->
|
||||||
|
<path d="M28 44 L54 2 L80 44 Z" fill="#1d4ed8"/>
|
||||||
|
<path d="M36 44 L54 10 L72 44 Z" fill="#3b82f6" opacity="0.5"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="#1e40af"/>
|
||||||
|
<ellipse cx="54" cy="42.5" rx="26" ry="5" fill="#2563eb"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="none" stroke="#f8fafc" stroke-width="2.2"/>
|
||||||
|
<polygon points="54,12 55.6,16.8 60.5,18.4 55.6,20 54,24.8 52.4,20 47.5,18.4 52.4,16.8" fill="#f8fafc"/>
|
||||||
|
<polygon points="42,22 43.1,25.2 46.2,26.4 43.1,27.6 42,30.8 40.9,27.6 37.8,26.4 40.9,25.2" fill="#fef08a"/>
|
||||||
|
<circle cx="54" cy="4" r="6" fill="#f8fafc"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
BIN
configs/bonfire/assets/img/logo.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
90
configs/bonfire/assets/img/logo.svg
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Wizard flying on magic wand, blue robe">
|
||||||
|
<!-- Original Kamek-inspired mark; not Nintendo art. No filters/transforms for clean raster. -->
|
||||||
|
|
||||||
|
<!-- motion streaks -->
|
||||||
|
<line x1="4" y1="64" x2="22" y2="64" stroke="#5eead4" stroke-width="2.5" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<line x1="6" y1="74" x2="20" y2="74" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
<line x1="8" y1="54" x2="20" y2="54" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
|
||||||
|
<!-- ground shadow -->
|
||||||
|
<ellipse cx="64" cy="118" rx="30" ry="5" fill="#000" opacity="0.2"/>
|
||||||
|
|
||||||
|
<!-- ===== MAGIC WAND (mount) — diagonal, thick gold shaft ===== -->
|
||||||
|
<!-- sits under the rider from lower-left to upper-right -->
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#92400e" stroke-width="11" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#e8a838" stroke-width="7" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- wand jewel (front tip) -->
|
||||||
|
<circle cx="112" cy="70" r="13" fill="#f59e0b" stroke="#fff7ed" stroke-width="3"/>
|
||||||
|
<circle cx="108" cy="66" r="4.5" fill="#fef9c3"/>
|
||||||
|
<!-- sparkles at tip -->
|
||||||
|
<polygon points="120,54 121.6,58.5 126,60 121.6,61.5 120,66 118.4,61.5 114,60 118.4,58.5" fill="#fef08a"/>
|
||||||
|
<polygon points="102,56 103,59 106,60 103,61 102,64 101,61 98,60 101,59" fill="#5eead4"/>
|
||||||
|
<circle cx="118" cy="78" r="2" fill="#bae6fd"/>
|
||||||
|
|
||||||
|
<!-- ===== RIDER on top of wand ===== -->
|
||||||
|
<!-- blue cape fluttering left -->
|
||||||
|
<path d="M38 78 C18 82, 10 98, 8 110 C22 98, 34 96, 40 92 Z" fill="#1e3a8a"/>
|
||||||
|
<path d="M40 80 C22 88, 14 104, 12 112 C28 100, 36 98, 42 94 Z" fill="#1d4ed8"/>
|
||||||
|
|
||||||
|
<!-- BLUE ROBE (body sitting ON shaft) -->
|
||||||
|
<ellipse cx="54" cy="86" rx="28" ry="22" fill="#1d4ed8"/>
|
||||||
|
<ellipse cx="54" cy="84" rx="24" ry="18" fill="#2563eb"/>
|
||||||
|
<!-- white trim -->
|
||||||
|
<path d="M32 74 Q54 94 76 74" fill="none" stroke="#f8fafc" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M36 82 Q54 98 72 82" fill="none" stroke="#e2e8f0" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- green shell -->
|
||||||
|
<ellipse cx="66" cy="92" rx="13" ry="11" fill="#16a34a"/>
|
||||||
|
<ellipse cx="66" cy="92" rx="6" ry="5" fill="#86efac" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- legs over the wand -->
|
||||||
|
<ellipse cx="42" cy="100" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<ellipse cx="60" cy="102" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<!-- yellow feet hanging past shaft -->
|
||||||
|
<ellipse cx="38" cy="106" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="64" cy="108" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
|
||||||
|
<!-- left arm forward -->
|
||||||
|
<ellipse cx="30" cy="82" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="24" cy="78" r="5.5" fill="#fbbf24"/>
|
||||||
|
<!-- right arm up -->
|
||||||
|
<ellipse cx="74" cy="74" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="80" cy="68" r="5.5" fill="#fbbf24"/>
|
||||||
|
<circle cx="82" cy="60" r="5" fill="#38bdf8"/>
|
||||||
|
<circle cx="81" cy="58" r="1.8" fill="#e0f2fe"/>
|
||||||
|
|
||||||
|
<!-- ===== HEAD ===== -->
|
||||||
|
<ellipse cx="54" cy="50" rx="26" ry="24" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="54" cy="53" rx="21" ry="17" fill="#fcd34d"/>
|
||||||
|
<ellipse cx="34" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
<ellipse cx="74" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- glasses -->
|
||||||
|
<circle cx="42" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<circle cx="66" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="52" y1="50" x2="56" y2="50" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="32" y1="47" x2="24" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<line x1="76" y1="47" x2="84" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="44" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="68" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="45.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
<circle cx="69.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
|
||||||
|
<!-- snout + mustache -->
|
||||||
|
<ellipse cx="54" cy="62" rx="8" ry="5" fill="#f59e0b"/>
|
||||||
|
<path d="M36 64 Q46 74 54 70 Q62 74 72 64 Q64 72 54 74 Q44 72 36 64 Z" fill="#f8fafc"/>
|
||||||
|
<path d="M44 66 Q54 74 64 66" fill="none" stroke="#92400e" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- BLUE pointed hat -->
|
||||||
|
<path d="M28 44 L54 2 L80 44 Z" fill="#1d4ed8"/>
|
||||||
|
<path d="M36 44 L54 10 L72 44 Z" fill="#3b82f6" opacity="0.5"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="#1e40af"/>
|
||||||
|
<ellipse cx="54" cy="42.5" rx="26" ry="5" fill="#2563eb"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="none" stroke="#f8fafc" stroke-width="2.2"/>
|
||||||
|
<polygon points="54,12 55.6,16.8 60.5,18.4 55.6,20 54,24.8 52.4,20 47.5,18.4 52.4,16.8" fill="#f8fafc"/>
|
||||||
|
<polygon points="42,22 43.1,25.2 46.2,26.4 43.1,27.6 42,30.8 40.9,27.6 37.8,26.4 40.9,25.2" fill="#fef08a"/>
|
||||||
|
<circle cx="54" cy="4" r="6" fill="#f8fafc"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
55
configs/bonfire/compose.yml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
# koopa-bonfire — ground zero (host Caddy → :9021)
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
image: docker.io/bonfirenetworks/bonfire:1.0.5-social-amd64
|
||||||
|
container_name: koopa-bonfire
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: [.env]
|
||||||
|
environment:
|
||||||
|
POSTGRES_HOST: db
|
||||||
|
HOSTNAME: bonfire.hacktivism.ch
|
||||||
|
PUBLIC_PORT: "443"
|
||||||
|
SERVER_PORT: "4000"
|
||||||
|
DB_MIGRATE_INDEXES_CONCURRENTLY: "false"
|
||||||
|
MIX_ENV: prod
|
||||||
|
ports:
|
||||||
|
- "9021:4000"
|
||||||
|
volumes:
|
||||||
|
- ./data/uploads:/opt/app/data/uploads
|
||||||
|
# Bind-mount into priv/static/images (Bonfire serves /images/* from there).
|
||||||
|
- ./data/branding/logo.svg:/opt/app/lib/bonfire-1.0.5-social/priv/static/images/hacktivism-logo.svg:ro
|
||||||
|
- ./data/branding/favicon.svg:/opt/app/lib/bonfire-1.0.5-social/priv/static/images/hacktivism-favicon.svg:ro
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
labels:
|
||||||
|
org.hacktivism.service: bonfire
|
||||||
|
org.hacktivism.host_port: "9021"
|
||||||
|
org.hacktivism.site: bonfire.hacktivism.ch
|
||||||
|
org.hacktivism.managed_by: koopa-admin
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:4000/ || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 15
|
||||||
|
start_period: 180s
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:15-alpine
|
||||||
|
container_name: koopa-bonfire-db
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: [.env]
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_DB: bonfire_db
|
||||||
|
volumes:
|
||||||
|
- db-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres -d bonfire_db"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 20
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
21
configs/bonfire/container-koopa-bonfire-db.service
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# user systemd - Postgres for Bonfire
|
||||||
|
[Unit]
|
||||||
|
Description=Bonfire Postgres (koopa-bonfire-db)
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=15
|
||||||
|
TimeoutStartSec=300
|
||||||
|
TimeoutStopSec=70
|
||||||
|
WorkingDirectory=/home/hernani/koopa-bonfire
|
||||||
|
ExecStart=/usr/bin/podman-compose up -d db
|
||||||
|
ExecStop=/usr/bin/podman stop -t 30 koopa-bonfire-db
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
21
configs/bonfire/container-koopa-bonfire.service
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# user systemd - Bonfire web
|
||||||
|
[Unit]
|
||||||
|
Description=Bonfire web (koopa-bonfire)
|
||||||
|
Wants=network-online.target container-koopa-bonfire-db.service
|
||||||
|
After=network-online.target container-koopa-bonfire-db.service
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=20
|
||||||
|
TimeoutStartSec=300
|
||||||
|
TimeoutStopSec=120
|
||||||
|
WorkingDirectory=/home/hernani/koopa-bonfire
|
||||||
|
ExecStart=/usr/bin/podman-compose up -d web
|
||||||
|
ExecStop=/usr/bin/podman stop -t 30 koopa-bonfire
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
18
configs/bonfire/gitbot-mirror.service
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# user systemd - git.ngi-0.eu -> Bonfire gitbot
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Bonfire gitbot (git.ngi-0.eu poll)
|
||||||
|
Wants=network-online.target container-koopa-bonfire.service
|
||||||
|
After=network-online.target container-koopa-bonfire.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
WorkingDirectory=/home/hernani/koopa-bonfire
|
||||||
|
ExecStart=/usr/bin/python3 /home/hernani/koopa-bonfire/bin/gitbot-mirror.py --loop
|
||||||
|
StandardOutput=append:/home/hernani/koopa-bonfire/gitbot.log
|
||||||
|
StandardError=append:/home/hernani/koopa-bonfire/gitbot.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
11
configs/bonfire/public-feeds.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Bonfire public feeds
|
||||||
|
|
||||||
|
Posts land in the author outbox first. Copy to local/internet feeds or guests see stale timelines.
|
||||||
|
|
||||||
|
**Official:** [Feed structure](https://docs.bonfirenetworks.org/feed_structure.html) (`FeedPublish`).
|
||||||
|
No official republish recipe — SQL + gitbot below are ops workarounds.
|
||||||
|
|
||||||
|
- One-off SQL: `scripts/bonfire/publish-outbox-to-public.sql`
|
||||||
|
- Gitbot: republish every cycle (`gitbot-mirror.py`)
|
||||||
|
|
||||||
|
Check: https://bonfire.hacktivism.ch/feed/local (not only `/`).
|
||||||
29
configs/bonfire/theme-hacktivism.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "hacktivism",
|
||||||
|
"palette": "exchange-dark (git.hacktivism.ch / forgejo theme-hacktivism)",
|
||||||
|
"colors": {
|
||||||
|
"color-base-100": "#1a1410",
|
||||||
|
"color-base-200": "#221c16",
|
||||||
|
"color-base-300": "#2a2018",
|
||||||
|
"color-base-content": "#fff6e8",
|
||||||
|
"color-primary": "#e8a838",
|
||||||
|
"color-primary-content": "#1a1410",
|
||||||
|
"color-secondary": "#3d3128",
|
||||||
|
"color-secondary-content": "#fff6e8",
|
||||||
|
"color-accent": "#3ecfbf",
|
||||||
|
"color-accent-content": "#0e1c1e",
|
||||||
|
"color-neutral": "#14110e",
|
||||||
|
"color-neutral-content": "#ebe0d0",
|
||||||
|
"color-info": "#2563eb",
|
||||||
|
"color-info-content": "#ffffff",
|
||||||
|
"color-success": "#16a34a",
|
||||||
|
"color-success-content": "#ffffff",
|
||||||
|
"color-warning": "#f0d090",
|
||||||
|
"color-warning-content": "#1a1410",
|
||||||
|
"color-error": "#b91c1c",
|
||||||
|
"color-error-content": "#ffffff",
|
||||||
|
"radius-box": "0.875rem",
|
||||||
|
"radius-field": "0.5rem",
|
||||||
|
"radius-selector": "0.75rem"
|
||||||
|
}
|
||||||
|
}
|
||||||
185
configs/caddy/Caddyfile
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
# Internal only (not in the browser URL):
|
||||||
|
# 9010 merchant API | 9011 exchange API | 9012 bank API
|
||||||
|
# 9013 bank landing | 9014 exchange landing | 9015 merchant landing
|
||||||
|
# 9020 castopod | 9021 bonfire | 9022 prime | 9023 bt | 9024 forgejo | | 9200 forgejo-ssh
|
||||||
|
# 9090 tops ng1 | 9091 tops ng2 | 9092 tops ng3
|
||||||
|
|
||||||
|
{
|
||||||
|
email info+koopa@hacktivism.ch
|
||||||
|
http_port 9000
|
||||||
|
https_port 9001
|
||||||
|
auto_https disable_redirects
|
||||||
|
# Caddy listens on 9001 behind VeciGate/https-proxy :443.
|
||||||
|
# Default HTTP/3 would send Alt-Svc: h3=":9001" — break public HTTPS on :443.
|
||||||
|
servers {
|
||||||
|
protocols h1 h2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(proxy_public) {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-Host {host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
header_up X-Forwarded-Port 443
|
||||||
|
header_down Location "^https?://[^/]+:90[0-9]{2}(.*)$" "https://{host}$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
taler.hacktivism.ch {
|
||||||
|
tls /etc/caddy/certs/taler.hacktivism.ch/fullchain.pem /etc/caddy/certs/taler.hacktivism.ch/privkey.pem
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
|
||||||
|
# Public landing first
|
||||||
|
redir / /intro/ 302
|
||||||
|
|
||||||
|
handle /intro* {
|
||||||
|
reverse_proxy 127.0.0.1:9015 {
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPA: /webui → /webui/
|
||||||
|
redir /webui /webui/ 302
|
||||||
|
|
||||||
|
# Merchant API + WebUI (nginx :9010 → unix socket)
|
||||||
|
reverse_proxy https://127.0.0.1:9010 {
|
||||||
|
transport http {
|
||||||
|
tls_insecure_skip_verify
|
||||||
|
}
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exchange.hacktivism.ch {
|
||||||
|
tls /etc/caddy/certs/exchange.hacktivism.ch/fullchain.pem /etc/caddy/certs/exchange.hacktivism.ch/privkey.pem
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
|
||||||
|
# Public landing first
|
||||||
|
redir / /intro/ 302
|
||||||
|
|
||||||
|
handle /intro* {
|
||||||
|
reverse_proxy 127.0.0.1:9014 {
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_proxy 127.0.0.1:9011 {
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bank.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
|
||||||
|
# Public landing first
|
||||||
|
redir / /intro/ 302
|
||||||
|
|
||||||
|
handle /intro* {
|
||||||
|
reverse_proxy 127.0.0.1:9013 {
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static terms/privacy on landing nginx :9013
|
||||||
|
handle /terms* {
|
||||||
|
reverse_proxy 127.0.0.1:9013 {
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle /privacy* {
|
||||||
|
reverse_proxy 127.0.0.1:9013 {
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_proxy 127.0.0.1:9012 {
|
||||||
|
import proxy_public
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
castopod.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9020 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bonfire.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9021 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
flush_interval -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prime.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9022 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
flush_interval -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bt.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9023 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 9024 forgejo HTTP (SSH :9200 host-direct, not via Caddy)
|
||||||
|
git.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9024 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
flush_interval -1
|
||||||
|
transport http {
|
||||||
|
read_timeout 3600s
|
||||||
|
write_timeout 3600s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Taler Operations design previews (static nginx)
|
||||||
|
tops.ng1.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9090 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tops.ng2.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9091 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tops.ng3.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9092 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http://taler.hacktivism.ch, http://exchange.hacktivism.ch, http://bank.hacktivism.ch, http://castopod.hacktivism.ch, http://bonfire.hacktivism.ch, http://prime.hacktivism.ch, http://bt.hacktivism.ch, http://git.hacktivism.ch, http://tops.ng1.hacktivism.ch, http://tops.ng2.hacktivism.ch, http://tops.ng3.hacktivism.ch {
|
||||||
|
handle /.well-known/acme-challenge/* {
|
||||||
|
root * /var/www/acme
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
redir https://{host}{uri} permanent
|
||||||
|
}
|
||||||
|
}
|
||||||
24
configs/caddy/Caddyfile.taler-host
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# LEGACY — historical Taler-only Caddy sketch (merchant backend :8081).
|
||||||
|
# Do NOT use on koopa. Current multi-vhost config:
|
||||||
|
# configs/caddy/Caddyfile (canonical)
|
||||||
|
# host/caddy/Caddyfile (same content)
|
||||||
|
#
|
||||||
|
# Kept only as archaeology of the early taler.hacktivism.ch TLS layout.
|
||||||
|
|
||||||
|
# Multi-domain reverse proxy (TLS terminate on host)
|
||||||
|
# Backend at the time: container nginx TLS on 8081
|
||||||
|
|
||||||
|
{
|
||||||
|
# no global auto-HTTPS email yet; per-site manual certs
|
||||||
|
auto_https disable_redirects
|
||||||
|
}
|
||||||
|
|
||||||
|
taler.hacktivism.ch {
|
||||||
|
tls /etc/caddy/certs/taler.hacktivism.ch/fullchain.pem /etc/caddy/certs/taler.hacktivism.ch/privkey.pem
|
||||||
|
|
||||||
|
reverse_proxy https://127.0.0.1:8081 {
|
||||||
|
transport http {
|
||||||
|
tls_insecure_skip_verify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
configs/caddy/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Caddy config mirrors
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| **`Caddyfile`** | Multi-vhost config mirror (`/etc/caddy/Caddyfile` on host) |
|
||||||
|
| `git.hacktivism.ch.caddy` | Site-block snippet for Forgejo |
|
||||||
|
| `Caddyfile.taler-host` | **Legacy** Taler-only sketch — do not deploy |
|
||||||
|
|
||||||
|
Also: `host/caddy/Caddyfile` (same as `Caddyfile` here).
|
||||||
15
configs/caddy/git.hacktivism.ch.caddy
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# 9024 forgejo HTTP (SSH :9200 host-direct, not via Caddy)
|
||||||
|
# Live: merged into /etc/caddy/Caddyfile (see also full Caddyfile mirror)
|
||||||
|
git.hacktivism.ch {
|
||||||
|
header Alt-Svc "clear"
|
||||||
|
reverse_proxy 127.0.0.1:9024 {
|
||||||
|
header_up Host {host}
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
header_up X-Forwarded-Proto {scheme}
|
||||||
|
flush_interval -1
|
||||||
|
transport http {
|
||||||
|
read_timeout 3600s
|
||||||
|
write_timeout 3600s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
configs/castopod/README.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Castopod — castopod.hacktivism.ch
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Live | `/home/hernani/koopa-castopod/` |
|
||||||
|
| Compose | `compose.yml` (mirror, no secrets) |
|
||||||
|
| Port | **9020** → Caddy |
|
||||||
|
| Site | https://castopod.hacktivism.ch/ |
|
||||||
|
|
||||||
|
Secrets: `koopa-admin-secrets/…/koopa-castopod/` and host `~/koopa-castopod/{.env,users.env}`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/koopa-castopod && set -a && source .env && set +a && podman-compose up -d
|
||||||
|
scripts/castopod/install-systemd.sh
|
||||||
|
scripts/castopod/apply-branding.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Daylogs: `2026/2026-07-09--castopod-content.md` (stack/accounts),
|
||||||
|
`2026/2026-07-13--castopod-boot-branding.md` (boot/branding).
|
||||||
|
|
||||||
|
Podcast content is **not** versioned in admin-log.
|
||||||
64
configs/castopod/assets/css/theme-hacktivism-overlay.css
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* Optional warm-dark overlay for castopod.hacktivism.ch
|
||||||
|
* NOT an official Castopod theme — Castopod only documents 6 accent colors
|
||||||
|
* (pine, crimson, lake, amber, jacaranda, onyx). See:
|
||||||
|
* https://docs.castopod.org/main/en/user-guide/instance/settings/
|
||||||
|
*
|
||||||
|
* Palette matches git.hacktivism.ch / bonfire (exchange-dark):
|
||||||
|
* body #1a1410, cream text, gold accent #e8a838, teal #3ecfbf.
|
||||||
|
* Prefer official theme "amber" first; this CSS deepens the page chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root,
|
||||||
|
.theme-amber,
|
||||||
|
.theme-pine,
|
||||||
|
.theme-onyx {
|
||||||
|
--color-accent-base: 38 78% 56% !important; /* ~#e8a838 */
|
||||||
|
--color-accent-hover: 38 72% 48% !important;
|
||||||
|
--color-accent-muted: 38 60% 32% !important;
|
||||||
|
--color-accent-contrast: 28 22% 8% !important;
|
||||||
|
--color-heading-foreground: 40 80% 75% !important;
|
||||||
|
--color-heading-background: 28 22% 14% !important;
|
||||||
|
--color-background-elevated: 28 18% 14% !important;
|
||||||
|
--color-background-base: 28 22% 8% !important;
|
||||||
|
--color-background-navigation: 28 20% 10% !important;
|
||||||
|
--color-background-header: 28 22% 9% !important;
|
||||||
|
--color-background-highlight: 28 18% 16% !important;
|
||||||
|
--color-background-backdrop: 0 0% 0% !important;
|
||||||
|
--color-border-subtle: 32 18% 28% !important;
|
||||||
|
--color-border-contrast: 40 50% 70% !important;
|
||||||
|
--color-border-navigation: 32 18% 28% !important;
|
||||||
|
--color-text-base: 36 80% 95% !important; /* ~#fff6e8 */
|
||||||
|
--color-text-muted: 32 20% 72% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #1a1410 !important;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(ellipse 90% 55% at 50% 108%, rgba(26, 107, 110, 0.35) 0%, transparent 55%),
|
||||||
|
radial-gradient(circle 420px at 12% 18%, rgba(232, 168, 56, 0.22) 0%, transparent 62%),
|
||||||
|
linear-gradient(165deg, #2c1e14 0%, #1a1410 38%, #12181a 72%, #0e1c1e 100%) !important;
|
||||||
|
background-attachment: fixed !important;
|
||||||
|
color: #fff6e8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #f0c86a;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #5eead4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Primary-ish buttons / chips that use solid white on accent */
|
||||||
|
.bg-white {
|
||||||
|
background-color: rgba(42, 32, 24, 0.92) !important;
|
||||||
|
color: #fff6e8 !important;
|
||||||
|
border: 1px solid rgba(232, 168, 56, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer,
|
||||||
|
.page-footer {
|
||||||
|
border-top: 1px solid rgba(232, 168, 56, 0.22);
|
||||||
|
color: #c9b8a0 !important;
|
||||||
|
}
|
||||||
90
configs/castopod/assets/img/favicon.svg
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Wizard flying on magic wand, blue robe">
|
||||||
|
<!-- Original Kamek-inspired mark; not Nintendo art. No filters/transforms for clean raster. -->
|
||||||
|
|
||||||
|
<!-- motion streaks -->
|
||||||
|
<line x1="4" y1="64" x2="22" y2="64" stroke="#5eead4" stroke-width="2.5" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<line x1="6" y1="74" x2="20" y2="74" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
<line x1="8" y1="54" x2="20" y2="54" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
|
||||||
|
<!-- ground shadow -->
|
||||||
|
<ellipse cx="64" cy="118" rx="30" ry="5" fill="#000" opacity="0.2"/>
|
||||||
|
|
||||||
|
<!-- ===== MAGIC WAND (mount) — diagonal, thick gold shaft ===== -->
|
||||||
|
<!-- sits under the rider from lower-left to upper-right -->
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#92400e" stroke-width="11" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#e8a838" stroke-width="7" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- wand jewel (front tip) -->
|
||||||
|
<circle cx="112" cy="70" r="13" fill="#f59e0b" stroke="#fff7ed" stroke-width="3"/>
|
||||||
|
<circle cx="108" cy="66" r="4.5" fill="#fef9c3"/>
|
||||||
|
<!-- sparkles at tip -->
|
||||||
|
<polygon points="120,54 121.6,58.5 126,60 121.6,61.5 120,66 118.4,61.5 114,60 118.4,58.5" fill="#fef08a"/>
|
||||||
|
<polygon points="102,56 103,59 106,60 103,61 102,64 101,61 98,60 101,59" fill="#5eead4"/>
|
||||||
|
<circle cx="118" cy="78" r="2" fill="#bae6fd"/>
|
||||||
|
|
||||||
|
<!-- ===== RIDER on top of wand ===== -->
|
||||||
|
<!-- blue cape fluttering left -->
|
||||||
|
<path d="M38 78 C18 82, 10 98, 8 110 C22 98, 34 96, 40 92 Z" fill="#1e3a8a"/>
|
||||||
|
<path d="M40 80 C22 88, 14 104, 12 112 C28 100, 36 98, 42 94 Z" fill="#1d4ed8"/>
|
||||||
|
|
||||||
|
<!-- BLUE ROBE (body sitting ON shaft) -->
|
||||||
|
<ellipse cx="54" cy="86" rx="28" ry="22" fill="#1d4ed8"/>
|
||||||
|
<ellipse cx="54" cy="84" rx="24" ry="18" fill="#2563eb"/>
|
||||||
|
<!-- white trim -->
|
||||||
|
<path d="M32 74 Q54 94 76 74" fill="none" stroke="#f8fafc" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M36 82 Q54 98 72 82" fill="none" stroke="#e2e8f0" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- green shell -->
|
||||||
|
<ellipse cx="66" cy="92" rx="13" ry="11" fill="#16a34a"/>
|
||||||
|
<ellipse cx="66" cy="92" rx="6" ry="5" fill="#86efac" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- legs over the wand -->
|
||||||
|
<ellipse cx="42" cy="100" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<ellipse cx="60" cy="102" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<!-- yellow feet hanging past shaft -->
|
||||||
|
<ellipse cx="38" cy="106" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="64" cy="108" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
|
||||||
|
<!-- left arm forward -->
|
||||||
|
<ellipse cx="30" cy="82" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="24" cy="78" r="5.5" fill="#fbbf24"/>
|
||||||
|
<!-- right arm up -->
|
||||||
|
<ellipse cx="74" cy="74" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="80" cy="68" r="5.5" fill="#fbbf24"/>
|
||||||
|
<circle cx="82" cy="60" r="5" fill="#38bdf8"/>
|
||||||
|
<circle cx="81" cy="58" r="1.8" fill="#e0f2fe"/>
|
||||||
|
|
||||||
|
<!-- ===== HEAD ===== -->
|
||||||
|
<ellipse cx="54" cy="50" rx="26" ry="24" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="54" cy="53" rx="21" ry="17" fill="#fcd34d"/>
|
||||||
|
<ellipse cx="34" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
<ellipse cx="74" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- glasses -->
|
||||||
|
<circle cx="42" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<circle cx="66" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="52" y1="50" x2="56" y2="50" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="32" y1="47" x2="24" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<line x1="76" y1="47" x2="84" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="44" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="68" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="45.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
<circle cx="69.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
|
||||||
|
<!-- snout + mustache -->
|
||||||
|
<ellipse cx="54" cy="62" rx="8" ry="5" fill="#f59e0b"/>
|
||||||
|
<path d="M36 64 Q46 74 54 70 Q62 74 72 64 Q64 72 54 74 Q44 72 36 64 Z" fill="#f8fafc"/>
|
||||||
|
<path d="M44 66 Q54 74 64 66" fill="none" stroke="#92400e" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- BLUE pointed hat -->
|
||||||
|
<path d="M28 44 L54 2 L80 44 Z" fill="#1d4ed8"/>
|
||||||
|
<path d="M36 44 L54 10 L72 44 Z" fill="#3b82f6" opacity="0.5"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="#1e40af"/>
|
||||||
|
<ellipse cx="54" cy="42.5" rx="26" ry="5" fill="#2563eb"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="none" stroke="#f8fafc" stroke-width="2.2"/>
|
||||||
|
<polygon points="54,12 55.6,16.8 60.5,18.4 55.6,20 54,24.8 52.4,20 47.5,18.4 52.4,16.8" fill="#f8fafc"/>
|
||||||
|
<polygon points="42,22 43.1,25.2 46.2,26.4 43.1,27.6 42,30.8 40.9,27.6 37.8,26.4 40.9,25.2" fill="#fef08a"/>
|
||||||
|
<circle cx="54" cy="4" r="6" fill="#f8fafc"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
BIN
configs/castopod/assets/img/logo.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
90
configs/castopod/assets/img/logo.svg
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Wizard flying on magic wand, blue robe">
|
||||||
|
<!-- Original Kamek-inspired mark; not Nintendo art. No filters/transforms for clean raster. -->
|
||||||
|
|
||||||
|
<!-- motion streaks -->
|
||||||
|
<line x1="4" y1="64" x2="22" y2="64" stroke="#5eead4" stroke-width="2.5" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<line x1="6" y1="74" x2="20" y2="74" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
<line x1="8" y1="54" x2="20" y2="54" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
|
||||||
|
<!-- ground shadow -->
|
||||||
|
<ellipse cx="64" cy="118" rx="30" ry="5" fill="#000" opacity="0.2"/>
|
||||||
|
|
||||||
|
<!-- ===== MAGIC WAND (mount) — diagonal, thick gold shaft ===== -->
|
||||||
|
<!-- sits under the rider from lower-left to upper-right -->
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#92400e" stroke-width="11" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#e8a838" stroke-width="7" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- wand jewel (front tip) -->
|
||||||
|
<circle cx="112" cy="70" r="13" fill="#f59e0b" stroke="#fff7ed" stroke-width="3"/>
|
||||||
|
<circle cx="108" cy="66" r="4.5" fill="#fef9c3"/>
|
||||||
|
<!-- sparkles at tip -->
|
||||||
|
<polygon points="120,54 121.6,58.5 126,60 121.6,61.5 120,66 118.4,61.5 114,60 118.4,58.5" fill="#fef08a"/>
|
||||||
|
<polygon points="102,56 103,59 106,60 103,61 102,64 101,61 98,60 101,59" fill="#5eead4"/>
|
||||||
|
<circle cx="118" cy="78" r="2" fill="#bae6fd"/>
|
||||||
|
|
||||||
|
<!-- ===== RIDER on top of wand ===== -->
|
||||||
|
<!-- blue cape fluttering left -->
|
||||||
|
<path d="M38 78 C18 82, 10 98, 8 110 C22 98, 34 96, 40 92 Z" fill="#1e3a8a"/>
|
||||||
|
<path d="M40 80 C22 88, 14 104, 12 112 C28 100, 36 98, 42 94 Z" fill="#1d4ed8"/>
|
||||||
|
|
||||||
|
<!-- BLUE ROBE (body sitting ON shaft) -->
|
||||||
|
<ellipse cx="54" cy="86" rx="28" ry="22" fill="#1d4ed8"/>
|
||||||
|
<ellipse cx="54" cy="84" rx="24" ry="18" fill="#2563eb"/>
|
||||||
|
<!-- white trim -->
|
||||||
|
<path d="M32 74 Q54 94 76 74" fill="none" stroke="#f8fafc" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M36 82 Q54 98 72 82" fill="none" stroke="#e2e8f0" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- green shell -->
|
||||||
|
<ellipse cx="66" cy="92" rx="13" ry="11" fill="#16a34a"/>
|
||||||
|
<ellipse cx="66" cy="92" rx="6" ry="5" fill="#86efac" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- legs over the wand -->
|
||||||
|
<ellipse cx="42" cy="100" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<ellipse cx="60" cy="102" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<!-- yellow feet hanging past shaft -->
|
||||||
|
<ellipse cx="38" cy="106" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="64" cy="108" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
|
||||||
|
<!-- left arm forward -->
|
||||||
|
<ellipse cx="30" cy="82" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="24" cy="78" r="5.5" fill="#fbbf24"/>
|
||||||
|
<!-- right arm up -->
|
||||||
|
<ellipse cx="74" cy="74" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="80" cy="68" r="5.5" fill="#fbbf24"/>
|
||||||
|
<circle cx="82" cy="60" r="5" fill="#38bdf8"/>
|
||||||
|
<circle cx="81" cy="58" r="1.8" fill="#e0f2fe"/>
|
||||||
|
|
||||||
|
<!-- ===== HEAD ===== -->
|
||||||
|
<ellipse cx="54" cy="50" rx="26" ry="24" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="54" cy="53" rx="21" ry="17" fill="#fcd34d"/>
|
||||||
|
<ellipse cx="34" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
<ellipse cx="74" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- glasses -->
|
||||||
|
<circle cx="42" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<circle cx="66" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="52" y1="50" x2="56" y2="50" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="32" y1="47" x2="24" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<line x1="76" y1="47" x2="84" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="44" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="68" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="45.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
<circle cx="69.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
|
||||||
|
<!-- snout + mustache -->
|
||||||
|
<ellipse cx="54" cy="62" rx="8" ry="5" fill="#f59e0b"/>
|
||||||
|
<path d="M36 64 Q46 74 54 70 Q62 74 72 64 Q64 72 54 74 Q44 72 36 64 Z" fill="#f8fafc"/>
|
||||||
|
<path d="M44 66 Q54 74 64 66" fill="none" stroke="#92400e" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- BLUE pointed hat -->
|
||||||
|
<path d="M28 44 L54 2 L80 44 Z" fill="#1d4ed8"/>
|
||||||
|
<path d="M36 44 L54 10 L72 44 Z" fill="#3b82f6" opacity="0.5"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="#1e40af"/>
|
||||||
|
<ellipse cx="54" cy="42.5" rx="26" ry="5" fill="#2563eb"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="none" stroke="#f8fafc" stroke-width="2.2"/>
|
||||||
|
<polygon points="54,12 55.6,16.8 60.5,18.4 55.6,20 54,24.8 52.4,20 47.5,18.4 52.4,16.8" fill="#f8fafc"/>
|
||||||
|
<polygon points="42,22 43.1,25.2 46.2,26.4 43.1,27.6 42,30.8 40.9,27.6 37.8,26.4 40.9,25.2" fill="#fef08a"/>
|
||||||
|
<circle cx="54" cy="4" r="6" fill="#f8fafc"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
81
configs/castopod/compose.yml
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
# koopa-castopod — Castopod behind host Caddy on :9020
|
||||||
|
# Docs: https://docs.castopod.org/main/en/getting-started/docker/
|
||||||
|
services:
|
||||||
|
castopod:
|
||||||
|
image: docker.io/castopod/castopod:1
|
||||||
|
container_name: koopa-castopod
|
||||||
|
volumes:
|
||||||
|
- castopod-media:/app/public/media
|
||||||
|
# optional branding host dir (logo + CSS overlay); safe if empty
|
||||||
|
- ./branding:/branding:ro
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: castopod
|
||||||
|
MYSQL_USER: castopod
|
||||||
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||||
|
CP_BASEURL: ${CP_BASEURL}
|
||||||
|
CP_ANALYTICS_SALT: ${CP_ANALYTICS_SALT}
|
||||||
|
CP_CACHE_HANDLER: redis
|
||||||
|
CP_REDIS_HOST: redis
|
||||||
|
CP_REDIS_PASSWORD: ${MYSQL_PASSWORD}
|
||||||
|
networks:
|
||||||
|
- castopod-app
|
||||||
|
- castopod-db
|
||||||
|
ports:
|
||||||
|
- "9020:8080"
|
||||||
|
labels:
|
||||||
|
org.hacktivism.service: castopod
|
||||||
|
org.hacktivism.host_port: "9020"
|
||||||
|
org.hacktivism.site: castopod.hacktivism.ch
|
||||||
|
org.hacktivism.managed_by: koopa-admin
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s
|
||||||
|
depends_on:
|
||||||
|
mariadb:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
mariadb:
|
||||||
|
image: docker.io/library/mariadb:11
|
||||||
|
container_name: koopa-castopod-mariadb
|
||||||
|
networks:
|
||||||
|
- castopod-db
|
||||||
|
volumes:
|
||||||
|
- castopod-db:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||||
|
MYSQL_DATABASE: castopod
|
||||||
|
MYSQL_USER: castopod
|
||||||
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
start_period: 20s
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: docker.io/library/redis:7-alpine
|
||||||
|
container_name: koopa-castopod-redis
|
||||||
|
command: --requirepass ${MYSQL_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- castopod-cache:/data
|
||||||
|
networks:
|
||||||
|
- castopod-app
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
castopod-media:
|
||||||
|
castopod-db:
|
||||||
|
castopod-cache:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
castopod-app:
|
||||||
|
castopod-db:
|
||||||
|
internal: true
|
||||||
21
configs/castopod/container-koopa-castopod-mariadb.service
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# user systemd — MariaDB for Castopod
|
||||||
|
[Unit]
|
||||||
|
Description=Castopod MariaDB (koopa-castopod-mariadb)
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=15
|
||||||
|
TimeoutStartSec=300
|
||||||
|
TimeoutStopSec=70
|
||||||
|
WorkingDirectory=/home/hernani/koopa-castopod
|
||||||
|
ExecStart=/usr/bin/podman-compose up -d mariadb
|
||||||
|
ExecStop=/usr/bin/podman stop -t 30 koopa-castopod-mariadb
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
21
configs/castopod/container-koopa-castopod-redis.service
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# user systemd — Redis for Castopod
|
||||||
|
[Unit]
|
||||||
|
Description=Castopod Redis (koopa-castopod-redis)
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=15
|
||||||
|
TimeoutStartSec=180
|
||||||
|
TimeoutStopSec=40
|
||||||
|
WorkingDirectory=/home/hernani/koopa-castopod
|
||||||
|
ExecStart=/usr/bin/podman-compose up -d redis
|
||||||
|
ExecStop=/usr/bin/podman stop -t 15 koopa-castopod-redis
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
21
configs/castopod/container-koopa-castopod.service
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# user systemd — Castopod web (depends on MariaDB + Redis)
|
||||||
|
[Unit]
|
||||||
|
Description=Castopod web (koopa-castopod)
|
||||||
|
Wants=network-online.target container-koopa-castopod-mariadb.service container-koopa-castopod-redis.service
|
||||||
|
After=network-online.target container-koopa-castopod-mariadb.service container-koopa-castopod-redis.service
|
||||||
|
RequiresMountsFor=%t/containers
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Environment=PODMAN_SYSTEMD_UNIT=%n
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=20
|
||||||
|
TimeoutStartSec=300
|
||||||
|
TimeoutStopSec=120
|
||||||
|
WorkingDirectory=/home/hernani/koopa-castopod
|
||||||
|
ExecStart=/usr/bin/podman-compose up -d castopod
|
||||||
|
ExecStop=/usr/bin/podman stop -t 30 koopa-castopod
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
23
configs/exchange-landing/README.md
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Exchange landing (`exchange.hacktivism.ch` intro)
|
||||||
|
|
||||||
|
Static intro (stats + links) for the **GOA exchange**. Withdraw QR removed (was wrong).
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Container | `taler-hacktivism-exchange-ansible` |
|
||||||
|
| In-container path | `/var/www/exchange-landing/` |
|
||||||
|
| nginx | listen **9014** (`nginx-landing.conf`) |
|
||||||
|
| **Host port to publish / wire** | **`9014`** → `127.0.0.1:9014` |
|
||||||
|
| Public path (once Caddy wired) | e.g. `https://exchange.hacktivism.ch/intro/` *or* dedicated intro host |
|
||||||
|
| Link previews | Open Graph / Twitter meta → `og-goa-shop.png` (1200×630 GOA shop grid) |
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from laptop → koopa
|
||||||
|
scp -r configs/exchange-landing/* koopa:/tmp/exchange-landing/
|
||||||
|
# on koopa: install nginx if missing, copy files, enable site, publish port
|
||||||
|
# ensure og-goa-shop.png is under /var/www/exchange-landing/ (served as /intro/og-goa-shop.png)
|
||||||
|
```
|
||||||
|
|
||||||
|
See `scripts/taler-landing/deploy-landings.sh`.
|
||||||
603
configs/exchange-landing/index.html
Normal file
|
|
@ -0,0 +1,603 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>GOA Exchange</title>
|
||||||
|
<meta name="description" content="GOA GNU Taler exchange — withdraw coins, then try GOA shop products on the merchant." />
|
||||||
|
<link rel="canonical" href="https://exchange.hacktivism.ch/intro/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:site_name" content="hacktivism.ch · GOA" />
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
|
<meta property="og:title" content="GOA Exchange" />
|
||||||
|
<meta property="og:description" content="GOA exchange for GNU Taler — withdraw coins and explore GOA shop products." />
|
||||||
|
<meta property="og:url" content="https://exchange.hacktivism.ch/intro/" />
|
||||||
|
<meta property="og:image" content="https://exchange.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
|
||||||
|
<meta property="og:image:secure_url" content="https://exchange.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
|
||||||
|
<meta property="og:image:type" content="image/png" />
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta property="og:image:alt" content="GOA Shop — Intergalactic · GOA · no IBAN — pay with GNU Taler" />
|
||||||
|
<link rel="image_src" href="https://exchange.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="GOA Exchange" />
|
||||||
|
<meta name="twitter:description" content="GOA exchange for GNU Taler — withdraw coins and explore GOA shop products (Intergalactic · GOA · no IBAN)." />
|
||||||
|
<meta name="twitter:image" content="https://exchange.hacktivism.ch/intro/og-goa-shop.png?v=android1" />
|
||||||
|
<meta name="twitter:image:alt" content="GOA Shop — Intergalactic · GOA · no IBAN — pay with GNU Taler" />
|
||||||
|
<base href="/intro/" />
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #1a1410; --card: rgba(42, 32, 24, 0.82); --text: #fff6e8; --muted: #c9b8a0;
|
||||||
|
--accent2: #3ecfbf; --border: rgba(232, 168, 56, 0.28);
|
||||||
|
--glow-gold: rgba(232, 168, 56, 0.35); --glow-teal: rgba(62, 207, 191, 0.22);
|
||||||
|
--val: #f0d090;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0; min-height: 100vh;
|
||||||
|
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||||
|
color: var(--text); line-height: 1.55; background-color: var(--bg);
|
||||||
|
background-image:
|
||||||
|
radial-gradient(ellipse 90% 55% at 50% 108%, rgba(26, 107, 110, 0.55) 0%, transparent 55%),
|
||||||
|
radial-gradient(circle 420px at 12% 18%, var(--glow-gold) 0%, transparent 62%),
|
||||||
|
linear-gradient(165deg, #2c1e14 0%, #1a1410 38%, #12181a 72%, #0e1c1e 100%);
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
main { max-width: 42rem; margin: 0 auto; padding: 2.25rem 1.25rem 3.5rem; }
|
||||||
|
.stack-bar {
|
||||||
|
margin: 0; padding: 0.35rem 0.75rem; background: #0042b3; color: #d6e4ff;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 0.62rem; font-weight: 500; letter-spacing: 0.02em; line-height: 1.35;
|
||||||
|
text-align: center; border-bottom: 1px solid rgba(164, 201, 255, 0.35);
|
||||||
|
}
|
||||||
|
.stack-bar strong { color: #fff; font-weight: 650; }
|
||||||
|
.stack-bar .sep { color: rgba(164, 201, 255, 0.55); margin: 0 0.2rem; }
|
||||||
|
.stack-bar a {
|
||||||
|
color: #a4c9ff; text-decoration: none;
|
||||||
|
border-bottom: 1px solid rgba(164, 201, 255, 0.4);
|
||||||
|
}
|
||||||
|
.stack-bar a:hover { color: #fff; border-bottom-color: #fff; }
|
||||||
|
.hero { text-align: center; margin: 0 0 1.25rem; }
|
||||||
|
h1 {
|
||||||
|
font-size: 1.85rem; font-weight: 750; margin: 0 0 0.45rem;
|
||||||
|
background: linear-gradient(120deg, #fff6e8 0%, #f0c86a 45%, #3ecfbf 120%);
|
||||||
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
||||||
|
}
|
||||||
|
.sub { color: var(--muted); max-width: 32rem; margin: 0 auto; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block; font-size: 0.75rem; font-weight: 700; letter-spacing: 0.04em;
|
||||||
|
padding: 0.2rem 0.7rem; border-radius: 999px; margin-bottom: 0.75rem;
|
||||||
|
border: 1px solid rgba(232, 168, 56, 0.4); color: #f0d090;
|
||||||
|
background: linear-gradient(135deg, rgba(232, 168, 56, 0.2), rgba(62, 207, 191, 0.15));
|
||||||
|
}
|
||||||
|
.stats {
|
||||||
|
background: rgba(18, 28, 28, 0.55); border: 1px solid rgba(62, 207, 191, 0.28);
|
||||||
|
border-radius: 16px; padding: 1.1rem 1.15rem; margin: 0 0 1.1rem;
|
||||||
|
}
|
||||||
|
.stats h2 {
|
||||||
|
margin: 0 0 0.35rem; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase; color: var(--muted); text-align: center;
|
||||||
|
}
|
||||||
|
.note { text-align: center; font-size: 0.82rem; color: var(--muted); margin: 0 0 0.9rem; }
|
||||||
|
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.65rem; }
|
||||||
|
@media (min-width: 420px) { .stats-grid { grid-template-columns: 1fr 1fr 1fr; } }
|
||||||
|
.stat { background: rgba(0,0,0,0.22); border-radius: 10px; padding: 0.55rem 0.65rem; }
|
||||||
|
.stat .k { display: block; font-size: 0.68rem; color: var(--muted); text-transform: uppercase; text-align: center; margin-bottom: 0.15rem; letter-spacing: 0.04em; }
|
||||||
|
.stat .v { display: block; font-size: 1.05rem; font-weight: 750; color: var(--val); text-align: center; font-variant-numeric: tabular-nums; word-break: break-word; }
|
||||||
|
.stat .v.teal { color: #5eead4; }
|
||||||
|
.stat .v.sm { font-size: 0.92rem; }
|
||||||
|
.stats-foot { margin: 0.75rem 0 0; font-size: 0.72rem; color: var(--muted); text-align: center; }
|
||||||
|
.stats-foot.err { color: #f0a090; }
|
||||||
|
.stats-run-note {
|
||||||
|
margin: 0.35rem 0 0; font-size: 0.7rem; color: #f0a090; text-align: center;
|
||||||
|
}
|
||||||
|
.sub-block {
|
||||||
|
margin-top: 0.85rem; padding: 0.75rem 0.85rem;
|
||||||
|
border-radius: 12px; border: 1px solid rgba(232, 168, 56, 0.22);
|
||||||
|
background: rgba(0,0,0,0.18);
|
||||||
|
}
|
||||||
|
.sub-block h3 {
|
||||||
|
margin: 0 0 0.5rem; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.1em;
|
||||||
|
text-transform: uppercase; color: var(--muted); text-align: center;
|
||||||
|
}
|
||||||
|
.ladder {
|
||||||
|
display: flex; flex-wrap: wrap; gap: 0.35rem; justify-content: center;
|
||||||
|
}
|
||||||
|
.pill {
|
||||||
|
font-size: 0.72rem; font-weight: 700; padding: 0.18rem 0.45rem; border-radius: 999px;
|
||||||
|
border: 1px solid rgba(62, 207, 191, 0.35); color: #99f6e4;
|
||||||
|
background: rgba(62, 207, 191, 0.1); font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.by-denom { font-size: 0.82rem; color: var(--muted); text-align: center; }
|
||||||
|
.by-denom table { width: 100%; border-collapse: collapse; margin-top: 0.35rem; }
|
||||||
|
.by-denom th, .by-denom td {
|
||||||
|
padding: 0.28rem 0.35rem; border-bottom: 1px solid rgba(255,255,255,0.06);
|
||||||
|
text-align: center; font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.by-denom th { color: var(--muted); font-size: 0.68rem; text-transform: uppercase; font-weight: 700; }
|
||||||
|
.by-denom td { color: var(--val); font-weight: 650; }
|
||||||
|
.step-card {
|
||||||
|
background: var(--card); border: 1px solid var(--border); border-radius: 18px;
|
||||||
|
padding: 1.3rem 1.25rem; margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
.step-card h2 { margin: 0 0 0.4rem; text-align: center; font-size: 1.2rem; }
|
||||||
|
.step-tagline { text-align: center; color: var(--muted); margin: 0 0 1rem; }
|
||||||
|
.step-body { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
|
||||||
|
.cta {
|
||||||
|
display: inline-block; text-decoration: none; font-weight: 750; padding: 0.7rem 1.15rem;
|
||||||
|
border-radius: 12px; background: linear-gradient(135deg, #f0c86a, #c47a18); color: #1a1208;
|
||||||
|
}
|
||||||
|
.cta.teal { background: linear-gradient(135deg, #5eead4, #0d9488); color: #042f2e; }
|
||||||
|
a.cta.wallet-open,
|
||||||
|
a.cta.teal.wallet-open {
|
||||||
|
display: inline-flex !important; align-items: center; justify-content: center;
|
||||||
|
width: auto !important; min-width: 0 !important; max-width: none !important;
|
||||||
|
margin: 0.15rem auto 0 !important; padding: 0.28rem 0.7rem !important;
|
||||||
|
font-size: 0.78rem !important; font-weight: 650; line-height: 1.2;
|
||||||
|
border-radius: 999px !important;
|
||||||
|
box-shadow: 0 1px 4px rgba(20, 184, 166, 0.22) !important;
|
||||||
|
white-space: nowrap; flex: 0 0 auto; align-self: center !important;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.links { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; }
|
||||||
|
.cta-sec {
|
||||||
|
display: block; text-align: center; text-decoration: none; color: var(--accent2);
|
||||||
|
border: 1px solid rgba(62, 207, 191, 0.4); padding: 0.55rem; border-radius: 10px; font-weight: 600;
|
||||||
|
}
|
||||||
|
.meta { font-size: 0.78rem; color: var(--muted); word-break: break-all; text-align: center; }
|
||||||
|
.app-links {
|
||||||
|
display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; width: 100%;
|
||||||
|
}
|
||||||
|
.app-btn {
|
||||||
|
display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem;
|
||||||
|
min-width: 9rem; text-decoration: none; font-weight: 700; font-size: 0.88rem;
|
||||||
|
padding: 0.55rem 0.85rem; border-radius: 10px;
|
||||||
|
border: 1px solid rgba(232, 168, 56, 0.4); background: rgba(0,0,0,0.28); color: #f0d090;
|
||||||
|
}
|
||||||
|
.app-btn.ios { border-color: rgba(232, 168, 56, 0.4); color: #f0d090; }
|
||||||
|
.app-btn.hub {
|
||||||
|
flex: 1 1 100%; border-color: rgba(201, 184, 160, 0.35); color: var(--muted);
|
||||||
|
font-weight: 600; font-size: 0.82rem; gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.wallet-fold {
|
||||||
|
margin: 0 0 1.15rem;
|
||||||
|
border: 1px solid rgba(232, 168, 56, 0.35);
|
||||||
|
border-radius: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(18, 28, 28, 0.55);
|
||||||
|
}
|
||||||
|
.wallet-fold-toggle {
|
||||||
|
display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
|
||||||
|
width: 100%; margin: 0; padding: 0.75rem 0.9rem; border: 0;
|
||||||
|
background: transparent; color: #f0d090; font: inherit; font-size: 0.95rem;
|
||||||
|
font-weight: 750; text-align: left; cursor: pointer;
|
||||||
|
}
|
||||||
|
.wallet-fold-toggle:hover { background: rgba(232, 168, 56, 0.1); }
|
||||||
|
.wallet-fold-toggle-text { flex: 1; }
|
||||||
|
.wallet-fold-chevron {
|
||||||
|
flex-shrink: 0; color: #3ecfbf; font-size: 1.1rem; font-weight: 700;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
.wallet-fold.open .wallet-fold-chevron { transform: rotate(90deg); }
|
||||||
|
.wallet-fold-body {
|
||||||
|
display: none; padding: 0.15rem 0.9rem 1rem;
|
||||||
|
border-top: 1px solid rgba(232, 168, 56, 0.28);
|
||||||
|
}
|
||||||
|
.wallet-fold.open .wallet-fold-body { display: block; }
|
||||||
|
.wallet-fold-body .app-links { margin-top: 0.55rem; }
|
||||||
|
button.app-btn.cli-toggle {
|
||||||
|
flex: 1 1 100%; width: 100%; justify-content: flex-start; gap: 0.5rem;
|
||||||
|
font: inherit; font-weight: 700; font-size: 0.88rem; cursor: pointer;
|
||||||
|
color: #f0d090; border-color: rgba(232, 168, 56, 0.4); background: rgba(0,0,0,0.28);
|
||||||
|
}
|
||||||
|
button.app-btn.cli-toggle .cli-chevron {
|
||||||
|
margin-left: auto; flex-shrink: 0; color: #3ecfbf;
|
||||||
|
font-size: 1.15rem; font-weight: 700; line-height: 1;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
}
|
||||||
|
button.app-btn.cli-toggle[aria-expanded="true"] .cli-chevron { transform: rotate(90deg); }
|
||||||
|
button.app-btn.cli-toggle[aria-expanded="true"] {
|
||||||
|
border-color: rgba(62, 207, 191, 0.5); background: rgba(62, 207, 191, 0.08);
|
||||||
|
}
|
||||||
|
.cli-panel {
|
||||||
|
display: none; width: 100%; margin: 0.35rem 0 0.55rem; text-align: left;
|
||||||
|
border: 1px solid rgba(232, 168, 56, 0.32); border-radius: 12px;
|
||||||
|
background: rgba(0, 0, 0, 0.28); padding: 0.75rem 0.85rem 0.9rem; box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.cli-panel.open { display: block; }
|
||||||
|
.cli-panel .cli-lead {
|
||||||
|
margin: 0 0 0.55rem; color: var(--muted); font-size: 0.84rem; line-height: 1.4;
|
||||||
|
}
|
||||||
|
.cli-panel .cli-lead a { color: #e8c878; }
|
||||||
|
.cli-block {
|
||||||
|
margin: 0; padding: 0.55rem 0.65rem; border-radius: 10px;
|
||||||
|
background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(240, 200, 106, 0.2);
|
||||||
|
color: #e8e0d0; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
font-size: 0.7rem; line-height: 1.45; overflow-x: auto; white-space: pre; user-select: all;
|
||||||
|
}
|
||||||
|
.cli-block .cmt { color: #8a9a90; }
|
||||||
|
.sym-gold path, .sym-gold circle { fill: #f0c86a; stroke: #f0c86a; }
|
||||||
|
.sym-gold path[data-ring], .sym-gold circle[data-ring] { fill: none; stroke: #f0c86a; }
|
||||||
|
.sym-blue path, .sym-blue circle { fill: #3ecfbf; stroke: #3ecfbf; }
|
||||||
|
.sym-blue path[data-ring], .sym-blue circle[data-ring] { fill: none; stroke: #3ecfbf; }
|
||||||
|
.app-ico { width: 1.15rem; height: 1.15rem; flex-shrink: 0; display: block; }
|
||||||
|
.app-ico:not(.browser-ico) path,
|
||||||
|
.app-ico:not(.browser-ico) circle,
|
||||||
|
.app-ico:not(.browser-ico) rect { fill: currentColor; }
|
||||||
|
/* hub browser marks use explicit gold/blue fills in SVG */
|
||||||
|
.app-ico.stroke-ico path, .app-ico.stroke-ico circle, .app-ico.stroke-ico rect {
|
||||||
|
fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.app-ico.branch-ico circle { fill: currentColor; stroke: none; }
|
||||||
|
.app-ico.branch-ico path {
|
||||||
|
fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
|
||||||
|
}
|
||||||
|
.app-btn:not(.hub) .app-ico { color: #f0c86a; }
|
||||||
|
.app-btn.ios .app-ico { color: #f0c86a; }
|
||||||
|
.hub-icons { display: inline-flex; align-items: center; gap: 0.4rem; }
|
||||||
|
.hub-icons .app-ico { width: 1.25rem; height: 1.25rem; }
|
||||||
|
.hub-icons .browser-ico { border-radius: 3px; flex-shrink: 0; }
|
||||||
|
.step-tagline .app-ico {
|
||||||
|
width: 1rem; height: 1rem; display: inline-block; vertical-align: -0.15em; margin: 0 0.12em;
|
||||||
|
}
|
||||||
|
.step-tagline .app-ico.android { color: #f0c86a; }
|
||||||
|
.step-tagline .app-ico.ios { color: #f0c86a; }
|
||||||
|
.step-body ol { margin: 0; padding-left: 1.2rem; width: 100%; align-self: stretch; color: var(--muted); }
|
||||||
|
.step-body li { margin: 0.35rem 0; }
|
||||||
|
footer { margin-top: 1.75rem; text-align: center; color: var(--muted); font-size: 0.85rem; }
|
||||||
|
footer a { color: #e8c878; }
|
||||||
|
code { font-family: ui-monospace, Menlo, monospace; font-size: 0.86em; background: rgba(0,0,0,0.35); padding: 0.1em 0.35em; border-radius: 4px; color: #f0d090; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p class="stack-bar" role="note" title="Taler software on this exchange service (links → git.taler.net tags)">
|
||||||
|
<strong>stack</strong><span class="sep">·</span>
|
||||||
|
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-exchange 1.6.6</a>
|
||||||
|
<span class="sep">·</span>
|
||||||
|
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">libtalerexchange 1.6.6</a>
|
||||||
|
<span class="sep">·</span>
|
||||||
|
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-exchange-database 1.6.6</a>
|
||||||
|
<span class="sep">·</span>
|
||||||
|
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-exchange-offline 1.6.6</a>
|
||||||
|
<span class="sep">·</span>
|
||||||
|
<a href="https://git.taler.net/taler/exchange/commit/6dbfa41e3fc6657eff28d11166d24301cdfa93ff.html" target="_blank" rel="noopener noreferrer" title="exchange tag v1.6.6">taler-terms-generator 1.6.6</a>
|
||||||
|
</p>
|
||||||
|
<main>
|
||||||
|
<header class="hero">
|
||||||
|
<span class="badge">GOA · Exchange</span>
|
||||||
|
<h1>GOA Exchange</h1>
|
||||||
|
<p class="sub">Explorational exchange — coins, denoms, one-step withdraw.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="stats" id="stats-coins" aria-labelledby="coins-title">
|
||||||
|
<h2 id="coins-title">Coins</h2>
|
||||||
|
<p class="note">From exchange DB · coin lifecycle</p>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat"><span class="k">Known coins</span><span class="v teal" id="st-coins">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Live (remaining)</span><span class="v" id="st-coins-live">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Spent / zero</span><span class="v" id="st-coins-spent">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Remaining amt</span><span class="v teal sm" id="st-remaining">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Withdraw ops</span><span class="v" id="st-withdraw">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Withdraw amt</span><span class="v teal sm" id="st-withdraw-amt">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Refresh</span><span class="v" id="st-refresh">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Coin deposits</span><span class="v" id="st-coin-dep">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Batch deposits</span><span class="v" id="st-batch-dep">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Recoup</span><span class="v" id="st-recoup">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Refunds</span><span class="v" id="st-refunds">—</span></div>
|
||||||
|
<div class="stat"><span class="k">History events</span><span class="v" id="st-hist">—</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="sub-block" id="by-denom-wrap" hidden>
|
||||||
|
<h3>Known coins by value</h3>
|
||||||
|
<div class="by-denom" id="st-by-denom"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="stats" id="stats-denoms" aria-labelledby="denoms-title">
|
||||||
|
<h2 id="denoms-title">Denominations</h2>
|
||||||
|
<p class="note">Signed keys in DB · public ladder on <code>/keys</code></p>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat"><span class="k">Denom keys</span><span class="v teal" id="st-denoms">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Unique values</span><span class="v" id="st-denom-vals">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Withdrawable now</span><span class="v teal" id="st-denoms-wd">—</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="sub-block">
|
||||||
|
<h3>Value ladder</h3>
|
||||||
|
<div class="ladder" id="st-ladder"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="stats" id="stats-wire" aria-labelledby="wire-title">
|
||||||
|
<h2 id="wire-title">Reserves · wire</h2>
|
||||||
|
<p class="note">Backing money movement</p>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat"><span class="k">Reserves</span><span class="v teal" id="st-reserves">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Wire in</span><span class="v" id="st-wire-in-n">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Wire in amt</span><span class="v teal sm" id="st-wire-in-amt">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Wire out</span><span class="v" id="st-wire-out">—</span></div>
|
||||||
|
</div>
|
||||||
|
<p class="stats-foot" id="st-foot">Loading…</p>
|
||||||
|
<p class="stats-run-note" id="st-run-note" hidden></p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="stats" id="stats-perf" aria-labelledby="perf-title">
|
||||||
|
<h2 id="perf-title">Performance</h2>
|
||||||
|
<p class="note">Live HTTP probes + container memory (RSS)</p>
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat"><span class="k">/keys</span><span class="v teal" id="st-keys-ms">—</span></div>
|
||||||
|
<div class="stat"><span class="k">/config</span><span class="v" id="st-config-ms">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Loadavg</span><span class="v sm" id="st-load">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Container</span><span class="v teal" id="st-mem-ctr">—</span></div>
|
||||||
|
<div class="stat"><span class="k">PostgreSQL</span><span class="v" id="st-mem-pg">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Taler procs</span><span class="v" id="st-mem-taler">—</span></div>
|
||||||
|
<div class="stat"><span class="k">Nginx</span><span class="v sm" id="st-mem-nginx">—</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="sub-block" style="margin-top:0.85rem">
|
||||||
|
<h3>Top processes (RSS)</h3>
|
||||||
|
<div id="st-mem-top" class="by-denom"></div>
|
||||||
|
</div>
|
||||||
|
<p class="stats-foot" id="st-perf-foot">Memory from /proc + cgroup inside podman container.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="wallet-fold" id="wallet-fold">
|
||||||
|
<button type="button" class="wallet-fold-toggle" id="wallet-fold-toggle"
|
||||||
|
aria-expanded="false" aria-controls="wallet-fold-body">
|
||||||
|
<span class="wallet-fold-toggle-text">Get the wallet · Android / iOS / web / packages</span>
|
||||||
|
<span class="wallet-fold-chevron" aria-hidden="true">›</span>
|
||||||
|
</button>
|
||||||
|
<div class="wallet-fold-body" id="wallet-fold-body" hidden>
|
||||||
|
<div class="app-links">
|
||||||
|
<a class="app-btn" href="https://play.google.com/store/apps/details?id=net.taler.wallet" target="_blank" rel="noopener noreferrer" aria-label="Android Play Store">
|
||||||
|
<svg class="app-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.2 2.4l-.9-1.6c-.1-.2 0-.4.2-.5.2-.1.4 0 .5.2l.9 1.6c.8-.3 1.7-.5 2.6-.5s1.8.2 2.6.5l.9-1.6c.1-.2.3-.3.5-.2.2.1.3.3.2.5l-.9 1.6c1.8.8 3.1 2.5 3.3 4.6H4.9c.2-2.1 1.5-3.8 3.3-4.6zM7 8.5c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zm10 0c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zM5 10.5h14c.6 0 1 .4 1 1v6.2c0 1.3-1 2.3-2.3 2.3h-.5v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-4.8v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-.5C5.9 20 5 19 5 17.7v-6.2c0-.6.4-1 1-1z"/></svg>
|
||||||
|
Play Store
|
||||||
|
</a>
|
||||||
|
<a class="app-btn" href="https://f-droid.org/en/packages/net.taler.wallet.fdroid/" target="_blank" rel="noopener noreferrer" aria-label="Android F-Droid">
|
||||||
|
<svg class="app-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M8.2 2.4l-.9-1.6c-.1-.2 0-.4.2-.5.2-.1.4 0 .5.2l.9 1.6c.8-.3 1.7-.5 2.6-.5s1.8.2 2.6.5l.9-1.6c.1-.2.3-.3.5-.2.2.1.3.3.2.5l-.9 1.6c1.8.8 3.1 2.5 3.3 4.6H4.9c.2-2.1 1.5-3.8 3.3-4.6zM7 8.5c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zm10 0c.6 0 1 .4 1 1s-.4 1-1 1-1-.4-1-1 .4-1 1-1zM5 10.5h14c.6 0 1 .4 1 1v6.2c0 1.3-1 2.3-2.3 2.3h-.5v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-4.8v2.2c0 .5-.4.9-.9.9s-.9-.4-.9-.9v-2.2h-.5C5.9 20 5 19 5 17.7v-6.2c0-.6.4-1 1-1z"/></svg>
|
||||||
|
F-Droid
|
||||||
|
</a>
|
||||||
|
<a class="app-btn ios" href="https://apps.apple.com/app/taler-wallet/id6463440117" target="_blank" rel="noopener noreferrer" aria-label="iOS App Store">
|
||||||
|
<svg class="app-ico" viewBox="0 0 24 24" aria-hidden="true"><path d="M16.7 12.6c0-2.1 1.7-3.1 1.8-3.2-1-1.4-2.5-1.6-3-1.7-1.3-.1-2.5.8-3.1.8s-1.6-.7-2.7-.7c-1.4 0-2.7.8-3.4 2.1-1.5 2.5-.4 6.3 1 8.3.7 1 1.5 2.1 2.6 2 1-.1 1.5-.7 2.8-.7s1.7.7 2.8.7 1.9-1 2.6-2c.8-1.1 1.1-2.2 1.1-2.3-.1 0-2.2-.8-2.2-3.3zm-2-6.2c.6-.7 1-1.7.9-2.7-1 .1-2.1.6-2.8 1.4-.6.7-1.1 1.7-.9 2.6 1 .1 2-.5 2.8-1.3z"/></svg>
|
||||||
|
App Store
|
||||||
|
</a>
|
||||||
|
<a class="app-btn hub" href="https://wallet.taler.net/" target="_blank" rel="noopener noreferrer" aria-label="Web browsers (experimental): Chrome, Firefox, Opera, Ubuntu Touch">
|
||||||
|
<span class="hub-icons" aria-hidden="true" title="Chrome · Firefox · Opera · Ubuntu Touch">
|
||||||
|
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Chrome">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="#3ecfbf" opacity="0.22"/>
|
||||||
|
<path fill="#3ecfbf" d="M12 2a10 10 0 0 1 8.66 5H12a4 4 0 0 0-3.46 2L5.1 4.1A10 10 0 0 1 12 2z"/>
|
||||||
|
<path fill="#3ecfbf" opacity="0.8" d="M20.66 7A10 10 0 0 1 12 22l3.46-6A4 4 0 0 0 12 8h8.66z"/>
|
||||||
|
<path fill="#3ecfbf" opacity="0.55" d="M5.1 4.1 8.54 9A4 4 0 0 0 12 16l-3.46 6A10 10 0 0 1 5.1 4.1z"/>
|
||||||
|
<circle cx="12" cy="12" r="3.2" fill="#3ecfbf"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Firefox">
|
||||||
|
<circle cx="12" cy="12.2" r="8.2" fill="#3ecfbf" opacity="0.28"/>
|
||||||
|
<path fill="#3ecfbf" d="M20.2 9.2c-.2-1.4-1.1-2.6-2.2-3.4.5 1.1.6 2.3.3 3.4-.9-1.6-2.5-2.6-4.4-2.6-1.3 0-2.5.4-3.4 1.1C9.3 5.4 10.9 3.8 13 3.2c-2.5.1-4.7 1.4-6.1 3.4C5.3 8.2 4.6 10.2 4.8 12.3c.3 3.9 3.5 7 7.4 7.1 3.6.1 6.7-2.2 7.7-5.5.4-1.4.4-2.9.3-4.7z"/>
|
||||||
|
<circle cx="12.2" cy="13.2" r="2.4" fill="#3ecfbf"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Opera">
|
||||||
|
<circle cx="12" cy="12" r="9.2" fill="none" stroke="#3ecfbf" stroke-width="2"/>
|
||||||
|
<ellipse cx="12" cy="12" rx="4.15" ry="7.1" fill="none" stroke="#3ecfbf" stroke-width="1.85"/>
|
||||||
|
</svg>
|
||||||
|
<svg class="app-ico browser-ico" viewBox="0 0 24 24" role="img" aria-label="Ubuntu Touch">
|
||||||
|
<circle cx="12" cy="12" r="2.25" fill="#3ecfbf"/>
|
||||||
|
<circle cx="12" cy="4.85" r="1.7" fill="#3ecfbf"/>
|
||||||
|
<circle cx="18.15" cy="15.55" r="1.7" fill="#3ecfbf"/>
|
||||||
|
<circle cx="5.85" cy="15.55" r="1.7" fill="#3ecfbf"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
Web browsers (experimental)
|
||||||
|
</a>
|
||||||
|
<button type="button" class="app-btn hub cli-toggle" id="pkg-fold-toggle"
|
||||||
|
aria-expanded="false" aria-controls="pkg-fold-body"
|
||||||
|
aria-label="Linux packages: Debian and Ubuntu (taler-wallet-cli)">
|
||||||
|
<svg class="app-ico browser-ico penguin-ico" viewBox="0 0 24 24" role="img" aria-label="Linux">
|
||||||
|
<ellipse cx="12" cy="14.2" rx="6.4" ry="7.2" fill="#f0c86a"/>
|
||||||
|
<ellipse cx="12" cy="15.2" rx="3.6" ry="4.8" fill="#1a1410" opacity="0.35"/>
|
||||||
|
<circle cx="12" cy="7" r="4.1" fill="#f0c86a"/>
|
||||||
|
<circle cx="10.35" cy="6.6" r="0.85" fill="#1a1410"/>
|
||||||
|
<circle cx="13.65" cy="6.6" r="0.85" fill="#1a1410"/>
|
||||||
|
<path fill="#e8a838" d="M11.15 8.1 12 9.55 12.85 8.1z"/>
|
||||||
|
<ellipse cx="9.4" cy="21" rx="2.1" ry="1.05" fill="#e8a838"/>
|
||||||
|
<ellipse cx="14.6" cy="21" rx="2.1" ry="1.05" fill="#e8a838"/>
|
||||||
|
<ellipse cx="6.6" cy="13.5" rx="1.4" ry="2.6" fill="#f0c86a" transform="rotate(-18 6.6 13.5)"/>
|
||||||
|
<ellipse cx="17.4" cy="13.5" rx="1.4" ry="2.6" fill="#f0c86a" transform="rotate(18 17.4 13.5)"/>
|
||||||
|
</svg>
|
||||||
|
Debian · Ubuntu packages
|
||||||
|
<span class="cli-chevron" aria-hidden="true">›</span>
|
||||||
|
</button>
|
||||||
|
<div class="cli-panel" id="pkg-fold-body" hidden>
|
||||||
|
<p class="cli-lead">
|
||||||
|
Official <strong>taler-wallet-cli</strong> from
|
||||||
|
<a href="https://deb.taler.net/" target="_blank" rel="noopener noreferrer">deb.taler.net</a>.
|
||||||
|
More on the <a href="https://bank.hacktivism.ch/intro/">bank landing</a>.
|
||||||
|
</p>
|
||||||
|
<pre class="cli-block"><span class="cmt"># Debian example</span>
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y taler-wallet-cli</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="step-card">
|
||||||
|
<h2>Withdraw GOA</h2>
|
||||||
|
<p class="step-tagline">Use the bank landing or wallet to add the exchange and withdraw.</p>
|
||||||
|
<div class="step-body">
|
||||||
|
<a class="cta teal" href="https://bank.hacktivism.ch/intro/">Bank landing →</a>
|
||||||
|
<div class="links">
|
||||||
|
<a class="cta-sec" href="https://exchange.hacktivism.ch/keys">/keys</a>
|
||||||
|
<a class="cta-sec" href="https://exchange.hacktivism.ch/terms">/terms</a>
|
||||||
|
<a class="cta-sec" href="https://exchange.hacktivism.ch/privacy">/privacy</a>
|
||||||
|
<a class="cta-sec" href="https://exchange.hacktivism.ch/config">/config</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="https://bank.hacktivism.ch/intro/">Bank</a> ·
|
||||||
|
<a href="https://exchange.hacktivism.ch/intro/">Exchange</a> ·
|
||||||
|
<a href="https://taler.hacktivism.ch/intro/">Merchant</a> ·
|
||||||
|
<a href="https://exchange.hacktivism.ch/terms">Terms</a> ·
|
||||||
|
<a href="https://exchange.hacktivism.ch/privacy">Privacy</a> ·
|
||||||
|
<a href="https://git.hacktivism.ch/hernani/koopa-admin-log/src/branch/main/configs/exchange-landing">Source</a>
|
||||||
|
<p class="privacy-note" style="margin:0.65rem 0 0;font-size:0.78rem;color:var(--muted);line-height:1.4">
|
||||||
|
Privacy: processing under Swiss FADP (revDSG). What is retained is listed on
|
||||||
|
<a href="https://exchange.hacktivism.ch/privacy" style="color:#5eead4">/privacy</a>.
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</main>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
function set(id, v) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) el.textContent = v == null || v === "" ? "—" : String(v);
|
||||||
|
}
|
||||||
|
function msLabel(ms, http) {
|
||||||
|
if (ms == null || ms === "") return "—";
|
||||||
|
var s = ms + " ms";
|
||||||
|
if (http && http !== "200") s += " (" + http + ")";
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
fetch("/intro/stats.json", { cache: "no-store" })
|
||||||
|
.then(function (r) { if (!r.ok) throw new Error("x"); return r.json(); })
|
||||||
|
.then(function (d) {
|
||||||
|
if (!d || !d.ok) throw new Error("bad");
|
||||||
|
set("st-coins", d.known_coins);
|
||||||
|
set("st-coins-live", d.coins_live);
|
||||||
|
set("st-coins-spent", d.coins_spent);
|
||||||
|
set("st-remaining", d.coins_remaining_amount);
|
||||||
|
set("st-withdraw", d.withdraw_ops);
|
||||||
|
set("st-withdraw-amt", d.withdraw_amount);
|
||||||
|
set("st-refresh", d.refresh_ops);
|
||||||
|
set("st-coin-dep", d.coin_deposits);
|
||||||
|
set("st-batch-dep", d.batch_deposits);
|
||||||
|
set("st-recoup", d.recoup);
|
||||||
|
set("st-refunds", d.refunds);
|
||||||
|
set("st-hist", d.coin_history_events);
|
||||||
|
set("st-denoms", d.denominations);
|
||||||
|
set("st-denom-vals", d.denom_values);
|
||||||
|
set("st-denoms-wd", d.denoms_withdrawable);
|
||||||
|
set("st-reserves", d.reserves);
|
||||||
|
set("st-wire-in-n", d.wire_in_count);
|
||||||
|
set("st-wire-in-amt", d.wire_in_amount);
|
||||||
|
set("st-wire-out", d.wire_out);
|
||||||
|
|
||||||
|
var ladder = document.getElementById("st-ladder");
|
||||||
|
if (ladder) {
|
||||||
|
ladder.innerHTML = "";
|
||||||
|
(d.denom_ladder || []).forEach(function (x) {
|
||||||
|
var s = document.createElement("span");
|
||||||
|
s.className = "pill";
|
||||||
|
s.textContent = x.value + (x.keys > 1 ? " ×" + x.keys : "");
|
||||||
|
ladder.appendChild(s);
|
||||||
|
});
|
||||||
|
if (!(d.denom_ladder || []).length) ladder.textContent = "—";
|
||||||
|
}
|
||||||
|
|
||||||
|
var by = d.by_denom || [];
|
||||||
|
var wrap = document.getElementById("by-denom-wrap");
|
||||||
|
var box = document.getElementById("st-by-denom");
|
||||||
|
if (wrap && box && by.length) {
|
||||||
|
wrap.hidden = false;
|
||||||
|
var html = "<table><thead><tr><th>Value</th><th>Coins</th><th>Live</th></tr></thead><tbody>";
|
||||||
|
by.forEach(function (r) {
|
||||||
|
html += "<tr><td>" + r.value + "</td><td>" + r.coins + "</td><td>" + r.live + "</td></tr>";
|
||||||
|
});
|
||||||
|
html += "</tbody></table>";
|
||||||
|
box.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
var foot = document.getElementById("st-foot");
|
||||||
|
if (foot) {
|
||||||
|
foot.textContent = (d.wirewatch_running ? "wirewatch on · " : "wirewatch off · ") +
|
||||||
|
(d.generated_at_human || d.generated_at || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = d.performance || {};
|
||||||
|
set("st-keys-ms", msLabel(p.keys_ms, p.keys_http));
|
||||||
|
set("st-config-ms", msLabel(p.config_ms, p.config_http));
|
||||||
|
set("st-load", p.loadavg || "—");
|
||||||
|
var mem = p.memory || {};
|
||||||
|
set("st-mem-ctr", mem.container_rss_human || "—");
|
||||||
|
set("st-mem-pg", mem.postgres_rss_human
|
||||||
|
? mem.postgres_rss_human + (mem.postgres_n ? " · " + mem.postgres_n + "p" : "")
|
||||||
|
: "—");
|
||||||
|
set("st-mem-taler", mem.taler_rss_human
|
||||||
|
? mem.taler_rss_human + (mem.taler_n ? " · " + mem.taler_n + "p" : "")
|
||||||
|
: "—");
|
||||||
|
set("st-mem-nginx", mem.nginx_rss_human || "—");
|
||||||
|
var topBox = document.getElementById("st-mem-top");
|
||||||
|
if (topBox) {
|
||||||
|
var tops = mem.top || [];
|
||||||
|
if (!tops.length) {
|
||||||
|
topBox.textContent = "—";
|
||||||
|
} else {
|
||||||
|
var th = "<table><thead><tr><th>RSS</th><th>Comm</th><th>Command</th></tr></thead><tbody>";
|
||||||
|
tops.forEach(function (t) {
|
||||||
|
th += "<tr><td>" + (t.rss_human || "?") + "</td><td>" +
|
||||||
|
(t.comm || "") + "</td><td style=\"font-size:0.72rem;word-break:break-all\">" +
|
||||||
|
(t.cmd || "") + "</td></tr>";
|
||||||
|
});
|
||||||
|
th += "</tbody></table>";
|
||||||
|
topBox.innerHTML = th;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var pf = document.getElementById("st-perf-foot");
|
||||||
|
if (pf) {
|
||||||
|
pf.textContent = "Container RSS + process groups · " +
|
||||||
|
(d.generated_at_human || d.generated_at || "");
|
||||||
|
}
|
||||||
|
return fetch("/intro/stats-run.json", { cache: "no-store" })
|
||||||
|
.then(function (r) { return r.ok ? r.json() : null; })
|
||||||
|
.catch(function () { return null; });
|
||||||
|
})
|
||||||
|
.then(function (run) {
|
||||||
|
var note = document.getElementById("st-run-note");
|
||||||
|
if (!note) return;
|
||||||
|
if (run && run.ok === false) {
|
||||||
|
note.hidden = false;
|
||||||
|
note.textContent = "Last refresh failed" +
|
||||||
|
(run.at_human ? " · " + run.at_human : "") +
|
||||||
|
" · showing last good data";
|
||||||
|
} else {
|
||||||
|
note.hidden = true;
|
||||||
|
note.textContent = "";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
var foot = document.getElementById("st-foot");
|
||||||
|
if (foot) foot.innerHTML = 'Stats offline · <a href="https://bank.hacktivism.ch/intro/" style="color:#e8c878">bank.hacktivism.ch</a>';
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
var fold = document.getElementById("wallet-fold");
|
||||||
|
var btn = document.getElementById("wallet-fold-toggle");
|
||||||
|
var body = document.getElementById("wallet-fold-body");
|
||||||
|
if (fold && btn && body) {
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
var open = !fold.classList.contains("open");
|
||||||
|
fold.classList.toggle("open", open);
|
||||||
|
btn.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
|
if (open) body.removeAttribute("hidden");
|
||||||
|
else body.setAttribute("hidden", "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function wireCli(btnId, bodyId) {
|
||||||
|
var b = document.getElementById(btnId);
|
||||||
|
var p = document.getElementById(bodyId);
|
||||||
|
if (!b || !p) return;
|
||||||
|
b.addEventListener("click", function () {
|
||||||
|
var open = !p.classList.contains("open");
|
||||||
|
p.classList.toggle("open", open);
|
||||||
|
b.setAttribute("aria-expanded", open ? "true" : "false");
|
||||||
|
if (open) p.removeAttribute("hidden");
|
||||||
|
else p.setAttribute("hidden", "");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
wireCli("pkg-fold-toggle", "pkg-fold-body");
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
configs/exchange-landing/nginx-landing.conf
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Exchange landing — :9014 (behind Caddy — no port in Location)
|
||||||
|
server {
|
||||||
|
listen 9014;
|
||||||
|
listen [::]:9014;
|
||||||
|
server_name exchange.hacktivism.ch _;
|
||||||
|
root /var/www/exchange-landing;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
absolute_redirect off;
|
||||||
|
port_in_redirect off;
|
||||||
|
|
||||||
|
location = / {
|
||||||
|
return 302 /intro/;
|
||||||
|
}
|
||||||
|
location = /intro {
|
||||||
|
return 302 /intro/;
|
||||||
|
}
|
||||||
|
location /intro/ {
|
||||||
|
alias /var/www/exchange-landing/;
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
configs/exchange-landing/og-goa-shop.png
Normal file
|
After Width: | Height: | Size: 544 KiB |
BIN
configs/exchange-landing/qr-logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
1
configs/exchange-landing/qr-logo.svg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
61
configs/firewalld/public-ports.md
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# firewalld zone public (eno1)
|
||||||
|
|
||||||
|
## Current (2026-07-10, verified)
|
||||||
|
|
||||||
|
Expected open ports after Forgejo git-SSH:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo firewall-cmd --list-ports
|
||||||
|
# … 80/tcp 443/tcp 8080/tcp 9000/tcp 9001/tcp 23235/tcp 9200/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
| Port | Purpose | LAN probe (2026-07-10) |
|
||||||
|
|------|---------|------------------------|
|
||||||
|
| 80/tcp | `http-proxy` → Caddy 9000 (IPv6 HTTP/ACME) | via Caddy path |
|
||||||
|
| **443/tcp** | `https-proxy` → Caddy 9001 (IPv6 HTTPS) | via Caddy path |
|
||||||
|
| 9000/tcp | Caddy HTTP (WAN:80 → 9000) | **OK** |
|
||||||
|
| 9001/tcp | Caddy HTTPS (WAN:443 → 9001) | **OK** |
|
||||||
|
| 8080/tcp | Tor OR | — |
|
||||||
|
| 23235/tcp | (legacy list; host SSH is :22, WAN via VeciGate 23235→22) | — |
|
||||||
|
| **9200/tcp** | **Forgejo git-SSH** (podman rootlessport) | **OK** after add+reload |
|
||||||
|
|
||||||
|
### Not opened on purpose
|
||||||
|
|
||||||
|
| Port | Note |
|
||||||
|
|------|------|
|
||||||
|
| 9020–9024 | rootless apps; public HTTP only via Caddy → `127.0.0.1:90xx` |
|
||||||
|
| 9024 | Forgejo HTTP internal to Caddy only |
|
||||||
|
|
||||||
|
LAN `nc` to 9021/9024 → **connection refused** is expected without firewalld open (and not required).
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo firewall-cmd --permanent --add-port=9200/tcp
|
||||||
|
sudo firewall-cmd --reload # or systemctl restart firewalld
|
||||||
|
sudo firewall-cmd --list-ports
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify (from another host on LAN / WAN)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nc -vz 192.168.100.95 9200
|
||||||
|
nc -vz 212.51.151.254 9200 # hairpin / public A
|
||||||
|
ssh -p 9200 -T git@git.hacktivism.ch
|
||||||
|
# expect: Hi there, … authenticated … Forgejo does not provide shell access.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why 443
|
||||||
|
|
||||||
|
IPv4 HTTPS is DNAT’d to **9001**. IPv6 has **no DNAT** — clients hit host **:443** directly.
|
||||||
|
|
||||||
|
### Why 9200
|
||||||
|
|
||||||
|
Forgejo advertises `SSH_PORT=9200`. Traffic hits **host:9200** (not Caddy). Without firewalld **9200/tcp**, clients get **connection refused** even while `ss` shows LISTEN and localhost works.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
| When | Change |
|
||||||
|
|------|--------|
|
||||||
|
| 2026-07-09 | 443/tcp re-added for IPv6 |
|
||||||
|
| 2026-07-10 | **9200/tcp** for Forgejo git-SSH (verified LAN + hairpin + SSH auth) |
|
||||||
60
configs/forgejo/README.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Forgejo (git.hacktivism.ch)
|
||||||
|
|
||||||
|
Rootless stack on koopa: `~/koopa-forgejo/` (podman user hernani).
|
||||||
|
Site: https://git.hacktivism.ch — HTTP `:9024`, git-SSH `:9200`.
|
||||||
|
|
||||||
|
## Branding theme: `hacktivism` (exchange-dark)
|
||||||
|
|
||||||
|
Global default and intended look match **exchange.hacktivism.ch**:
|
||||||
|
|
||||||
|
- Warm dark body (`#1a1410`), cream text (`#fff6e8`)
|
||||||
|
- Gold primary (`#e8a838` / `#f0c86a`), teal accents (`#3ecfbf`)
|
||||||
|
- Readable contrast on body, menus, code, forms, labels, messages
|
||||||
|
|
||||||
|
### Files (this repo)
|
||||||
|
|
||||||
|
| Path | Role |
|
||||||
|
|------|------|
|
||||||
|
| `assets/css/theme-hacktivism.css` | Full dark theme (based on forgejo-dark vars + exchange palette) |
|
||||||
|
| `assets/img/logo.svg` / `logo.png` / `favicon.svg` | Kamek-inspired logo (blue robe, flying on wand); not Nintendo art |
|
||||||
|
| `compose.yml` | Rootless compose + `FORGEJO__ui__DEFAULT_THEME=hacktivism` |
|
||||||
|
| `app.ini.example` | Config sketch |
|
||||||
|
|
||||||
|
### Live paths on koopa
|
||||||
|
|
||||||
|
```
|
||||||
|
~/koopa-forgejo/data/custom/public/assets/css/theme-hacktivism.css
|
||||||
|
~/koopa-forgejo/data/custom/public/assets/img/{logo.svg,logo.png,favicon.svg}
|
||||||
|
~/koopa-forgejo/data/custom/conf/app.ini # [ui] DEFAULT_THEME = hacktivism
|
||||||
|
```
|
||||||
|
|
||||||
|
Compose also sets `FORGEJO__ui__DEFAULT_THEME=hacktivism`.
|
||||||
|
|
||||||
|
### Deploy assets (do not wipe git data)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
install -m 644 configs/forgejo/assets/css/theme-hacktivism.css \
|
||||||
|
~/koopa-forgejo/data/custom/public/assets/css/
|
||||||
|
install -m 644 configs/forgejo/assets/img/* \
|
||||||
|
~/koopa-forgejo/data/custom/public/assets/img/
|
||||||
|
# optional mirror if present:
|
||||||
|
# cp same under data/gitea/custom/public/assets/...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Never** remove `~/koopa-forgejo/data/git/` — repositories live there (`hernani/koopa-admin-log`, …).
|
||||||
|
|
||||||
|
### Logged-in users
|
||||||
|
|
||||||
|
`DEFAULT_THEME` only applies to new sessions / users without a preference.
|
||||||
|
Existing accounts store `user.theme` in Postgres. Force all users:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman exec koopa-forgejo-db psql -U forgejo -d forgejo \
|
||||||
|
-c "UPDATE \"user\" SET theme = 'hacktivism';"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or: Profile → Appearance → theme **hacktivism**, then hard-reload.
|
||||||
|
|
||||||
|
### Registration
|
||||||
|
|
||||||
|
Public signup is disabled (`DISABLE_REGISTRATION`, no registration button).
|
||||||
79
configs/forgejo/app.ini.example
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
APP_NAME = hacktivism git
|
||||||
|
RUN_USER = git
|
||||||
|
RUN_MODE = prod
|
||||||
|
WORK_PATH = /var/lib/gitea
|
||||||
|
|
||||||
|
[repository]
|
||||||
|
ROOT = /var/lib/gitea/git/repositories
|
||||||
|
ENABLE_PUSH_CREATE_USER = true
|
||||||
|
ENABLE_PUSH_CREATE_ORG = true
|
||||||
|
DEFAULT_PRIVATE = public
|
||||||
|
|
||||||
|
[repository.local]
|
||||||
|
LOCAL_COPY_PATH = /tmp/gitea/local-repo
|
||||||
|
|
||||||
|
[repository.upload]
|
||||||
|
TEMP_PATH = /tmp/gitea/uploads
|
||||||
|
|
||||||
|
[server]
|
||||||
|
APP_DATA_PATH = /var/lib/gitea
|
||||||
|
SSH_DOMAIN = git.hacktivism.ch
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
ROOT_URL = https://git.hacktivism.ch/
|
||||||
|
DISABLE_SSH = false
|
||||||
|
; In rootless gitea container only internal ssh server is supported
|
||||||
|
START_SSH_SERVER = true
|
||||||
|
SSH_PORT = 9200
|
||||||
|
SSH_LISTEN_PORT = 2222
|
||||||
|
BUILTIN_SSH_SERVER_USER = git
|
||||||
|
LFS_START_SERVER =
|
||||||
|
DOMAIN = git.hacktivism.ch
|
||||||
|
LANDING_PAGE = explore
|
||||||
|
|
||||||
|
[database]
|
||||||
|
PATH = /var/lib/gitea/data/gitea.db
|
||||||
|
DB_TYPE = postgres
|
||||||
|
HOST = db:5432
|
||||||
|
NAME = forgejo
|
||||||
|
USER = forgejo
|
||||||
|
PASSWD = @inline-secret@
|
||||||
|
|
||||||
|
[session]
|
||||||
|
PROVIDER_CONFIG = /var/lib/gitea/data/sessions
|
||||||
|
PROVIDER = memory
|
||||||
|
|
||||||
|
[picture]
|
||||||
|
AVATAR_UPLOAD_PATH = /var/lib/gitea/data/avatars
|
||||||
|
REPOSITORY_AVATAR_UPLOAD_PATH = /var/lib/gitea/data/repo-avatars
|
||||||
|
|
||||||
|
[attachment]
|
||||||
|
PATH = /var/lib/gitea/data/attachments
|
||||||
|
|
||||||
|
[log]
|
||||||
|
ROOT_PATH = /var/lib/gitea/data/log
|
||||||
|
LEVEL = Info
|
||||||
|
|
||||||
|
[security]
|
||||||
|
INSTALL_LOCK = true
|
||||||
|
SECRET_KEY = @inline-secret@
|
||||||
|
REVERSE_PROXY_LIMIT = 1
|
||||||
|
REVERSE_PROXY_TRUSTED_PROXIES = *
|
||||||
|
INTERNAL_TOKEN = @inline-secret@
|
||||||
|
|
||||||
|
[service]
|
||||||
|
DISABLE_REGISTRATION = true
|
||||||
|
REQUIRE_SIGNIN_VIEW = false
|
||||||
|
SHOW_REGISTRATION_BUTTON = false
|
||||||
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
|
||||||
|
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||||
|
|
||||||
|
[lfs]
|
||||||
|
PATH = /var/lib/gitea/git/lfs
|
||||||
|
|
||||||
|
[openid]
|
||||||
|
ENABLE_OPENID_SIGNUP = false
|
||||||
|
ENABLE_OPENID_SIGNIN = false
|
||||||
|
|
||||||
|
[oauth2]
|
||||||
|
JWT_SECRET = @inline-secret@
|
||||||
|
|
||||||
275
configs/forgejo/assets/css/theme-hacktivism.css
Normal file
90
configs/forgejo/assets/img/favicon.svg
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Wizard flying on magic wand, blue robe">
|
||||||
|
<!-- Original Kamek-inspired mark; not Nintendo art. No filters/transforms for clean raster. -->
|
||||||
|
|
||||||
|
<!-- motion streaks -->
|
||||||
|
<line x1="4" y1="64" x2="22" y2="64" stroke="#5eead4" stroke-width="2.5" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<line x1="6" y1="74" x2="20" y2="74" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
<line x1="8" y1="54" x2="20" y2="54" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
|
||||||
|
<!-- ground shadow -->
|
||||||
|
<ellipse cx="64" cy="118" rx="30" ry="5" fill="#000" opacity="0.2"/>
|
||||||
|
|
||||||
|
<!-- ===== MAGIC WAND (mount) — diagonal, thick gold shaft ===== -->
|
||||||
|
<!-- sits under the rider from lower-left to upper-right -->
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#92400e" stroke-width="11" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#e8a838" stroke-width="7" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- wand jewel (front tip) -->
|
||||||
|
<circle cx="112" cy="70" r="13" fill="#f59e0b" stroke="#fff7ed" stroke-width="3"/>
|
||||||
|
<circle cx="108" cy="66" r="4.5" fill="#fef9c3"/>
|
||||||
|
<!-- sparkles at tip -->
|
||||||
|
<polygon points="120,54 121.6,58.5 126,60 121.6,61.5 120,66 118.4,61.5 114,60 118.4,58.5" fill="#fef08a"/>
|
||||||
|
<polygon points="102,56 103,59 106,60 103,61 102,64 101,61 98,60 101,59" fill="#5eead4"/>
|
||||||
|
<circle cx="118" cy="78" r="2" fill="#bae6fd"/>
|
||||||
|
|
||||||
|
<!-- ===== RIDER on top of wand ===== -->
|
||||||
|
<!-- blue cape fluttering left -->
|
||||||
|
<path d="M38 78 C18 82, 10 98, 8 110 C22 98, 34 96, 40 92 Z" fill="#1e3a8a"/>
|
||||||
|
<path d="M40 80 C22 88, 14 104, 12 112 C28 100, 36 98, 42 94 Z" fill="#1d4ed8"/>
|
||||||
|
|
||||||
|
<!-- BLUE ROBE (body sitting ON shaft) -->
|
||||||
|
<ellipse cx="54" cy="86" rx="28" ry="22" fill="#1d4ed8"/>
|
||||||
|
<ellipse cx="54" cy="84" rx="24" ry="18" fill="#2563eb"/>
|
||||||
|
<!-- white trim -->
|
||||||
|
<path d="M32 74 Q54 94 76 74" fill="none" stroke="#f8fafc" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M36 82 Q54 98 72 82" fill="none" stroke="#e2e8f0" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- green shell -->
|
||||||
|
<ellipse cx="66" cy="92" rx="13" ry="11" fill="#16a34a"/>
|
||||||
|
<ellipse cx="66" cy="92" rx="6" ry="5" fill="#86efac" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- legs over the wand -->
|
||||||
|
<ellipse cx="42" cy="100" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<ellipse cx="60" cy="102" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<!-- yellow feet hanging past shaft -->
|
||||||
|
<ellipse cx="38" cy="106" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="64" cy="108" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
|
||||||
|
<!-- left arm forward -->
|
||||||
|
<ellipse cx="30" cy="82" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="24" cy="78" r="5.5" fill="#fbbf24"/>
|
||||||
|
<!-- right arm up -->
|
||||||
|
<ellipse cx="74" cy="74" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="80" cy="68" r="5.5" fill="#fbbf24"/>
|
||||||
|
<circle cx="82" cy="60" r="5" fill="#38bdf8"/>
|
||||||
|
<circle cx="81" cy="58" r="1.8" fill="#e0f2fe"/>
|
||||||
|
|
||||||
|
<!-- ===== HEAD ===== -->
|
||||||
|
<ellipse cx="54" cy="50" rx="26" ry="24" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="54" cy="53" rx="21" ry="17" fill="#fcd34d"/>
|
||||||
|
<ellipse cx="34" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
<ellipse cx="74" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- glasses -->
|
||||||
|
<circle cx="42" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<circle cx="66" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="52" y1="50" x2="56" y2="50" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="32" y1="47" x2="24" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<line x1="76" y1="47" x2="84" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="44" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="68" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="45.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
<circle cx="69.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
|
||||||
|
<!-- snout + mustache -->
|
||||||
|
<ellipse cx="54" cy="62" rx="8" ry="5" fill="#f59e0b"/>
|
||||||
|
<path d="M36 64 Q46 74 54 70 Q62 74 72 64 Q64 72 54 74 Q44 72 36 64 Z" fill="#f8fafc"/>
|
||||||
|
<path d="M44 66 Q54 74 64 66" fill="none" stroke="#92400e" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- BLUE pointed hat -->
|
||||||
|
<path d="M28 44 L54 2 L80 44 Z" fill="#1d4ed8"/>
|
||||||
|
<path d="M36 44 L54 10 L72 44 Z" fill="#3b82f6" opacity="0.5"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="#1e40af"/>
|
||||||
|
<ellipse cx="54" cy="42.5" rx="26" ry="5" fill="#2563eb"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="none" stroke="#f8fafc" stroke-width="2.2"/>
|
||||||
|
<polygon points="54,12 55.6,16.8 60.5,18.4 55.6,20 54,24.8 52.4,20 47.5,18.4 52.4,16.8" fill="#f8fafc"/>
|
||||||
|
<polygon points="42,22 43.1,25.2 46.2,26.4 43.1,27.6 42,30.8 40.9,27.6 37.8,26.4 40.9,25.2" fill="#fef08a"/>
|
||||||
|
<circle cx="54" cy="4" r="6" fill="#f8fafc"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
BIN
configs/forgejo/assets/img/logo.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
90
configs/forgejo/assets/img/logo.svg
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128" role="img" aria-label="Wizard flying on magic wand, blue robe">
|
||||||
|
<!-- Original Kamek-inspired mark; not Nintendo art. No filters/transforms for clean raster. -->
|
||||||
|
|
||||||
|
<!-- motion streaks -->
|
||||||
|
<line x1="4" y1="64" x2="22" y2="64" stroke="#5eead4" stroke-width="2.5" stroke-linecap="round" opacity="0.55"/>
|
||||||
|
<line x1="6" y1="74" x2="20" y2="74" stroke="#38bdf8" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
<line x1="8" y1="54" x2="20" y2="54" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.45"/>
|
||||||
|
|
||||||
|
<!-- ground shadow -->
|
||||||
|
<ellipse cx="64" cy="118" rx="30" ry="5" fill="#000" opacity="0.2"/>
|
||||||
|
|
||||||
|
<!-- ===== MAGIC WAND (mount) — diagonal, thick gold shaft ===== -->
|
||||||
|
<!-- sits under the rider from lower-left to upper-right -->
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#92400e" stroke-width="11" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#e8a838" stroke-width="7" stroke-linecap="round"/>
|
||||||
|
<line x1="6" y1="100" x2="108" y2="72" stroke="#fde68a" stroke-width="2" stroke-linecap="round" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- wand jewel (front tip) -->
|
||||||
|
<circle cx="112" cy="70" r="13" fill="#f59e0b" stroke="#fff7ed" stroke-width="3"/>
|
||||||
|
<circle cx="108" cy="66" r="4.5" fill="#fef9c3"/>
|
||||||
|
<!-- sparkles at tip -->
|
||||||
|
<polygon points="120,54 121.6,58.5 126,60 121.6,61.5 120,66 118.4,61.5 114,60 118.4,58.5" fill="#fef08a"/>
|
||||||
|
<polygon points="102,56 103,59 106,60 103,61 102,64 101,61 98,60 101,59" fill="#5eead4"/>
|
||||||
|
<circle cx="118" cy="78" r="2" fill="#bae6fd"/>
|
||||||
|
|
||||||
|
<!-- ===== RIDER on top of wand ===== -->
|
||||||
|
<!-- blue cape fluttering left -->
|
||||||
|
<path d="M38 78 C18 82, 10 98, 8 110 C22 98, 34 96, 40 92 Z" fill="#1e3a8a"/>
|
||||||
|
<path d="M40 80 C22 88, 14 104, 12 112 C28 100, 36 98, 42 94 Z" fill="#1d4ed8"/>
|
||||||
|
|
||||||
|
<!-- BLUE ROBE (body sitting ON shaft) -->
|
||||||
|
<ellipse cx="54" cy="86" rx="28" ry="22" fill="#1d4ed8"/>
|
||||||
|
<ellipse cx="54" cy="84" rx="24" ry="18" fill="#2563eb"/>
|
||||||
|
<!-- white trim -->
|
||||||
|
<path d="M32 74 Q54 94 76 74" fill="none" stroke="#f8fafc" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M36 82 Q54 98 72 82" fill="none" stroke="#e2e8f0" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- green shell -->
|
||||||
|
<ellipse cx="66" cy="92" rx="13" ry="11" fill="#16a34a"/>
|
||||||
|
<ellipse cx="66" cy="92" rx="6" ry="5" fill="#86efac" opacity="0.75"/>
|
||||||
|
|
||||||
|
<!-- legs over the wand -->
|
||||||
|
<ellipse cx="42" cy="100" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<ellipse cx="60" cy="102" rx="10" ry="7" fill="#1e40af"/>
|
||||||
|
<!-- yellow feet hanging past shaft -->
|
||||||
|
<ellipse cx="38" cy="106" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="64" cy="108" rx="7" ry="4.5" fill="#fbbf24"/>
|
||||||
|
|
||||||
|
<!-- left arm forward -->
|
||||||
|
<ellipse cx="30" cy="82" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="24" cy="78" r="5.5" fill="#fbbf24"/>
|
||||||
|
<!-- right arm up -->
|
||||||
|
<ellipse cx="74" cy="74" rx="9" ry="7" fill="#1d4ed8"/>
|
||||||
|
<circle cx="80" cy="68" r="5.5" fill="#fbbf24"/>
|
||||||
|
<circle cx="82" cy="60" r="5" fill="#38bdf8"/>
|
||||||
|
<circle cx="81" cy="58" r="1.8" fill="#e0f2fe"/>
|
||||||
|
|
||||||
|
<!-- ===== HEAD ===== -->
|
||||||
|
<ellipse cx="54" cy="50" rx="26" ry="24" fill="#fbbf24"/>
|
||||||
|
<ellipse cx="54" cy="53" rx="21" ry="17" fill="#fcd34d"/>
|
||||||
|
<ellipse cx="34" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
<ellipse cx="74" cy="54" rx="6" ry="4" fill="#fb923c" opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- glasses -->
|
||||||
|
<circle cx="42" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<circle cx="66" cy="50" r="10" fill="#bae6fd" fill-opacity="0.4" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="52" y1="50" x2="56" y2="50" stroke="#0f172a" stroke-width="3"/>
|
||||||
|
<line x1="32" y1="47" x2="24" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<line x1="76" y1="47" x2="84" y2="42" stroke="#0f172a" stroke-width="2.5" stroke-linecap="round"/>
|
||||||
|
<circle cx="44" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="68" cy="50" r="3" fill="#0f172a"/>
|
||||||
|
<circle cx="45.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
<circle cx="69.2" cy="48.8" r="1.1" fill="#fff"/>
|
||||||
|
|
||||||
|
<!-- snout + mustache -->
|
||||||
|
<ellipse cx="54" cy="62" rx="8" ry="5" fill="#f59e0b"/>
|
||||||
|
<path d="M36 64 Q46 74 54 70 Q62 74 72 64 Q64 72 54 74 Q44 72 36 64 Z" fill="#f8fafc"/>
|
||||||
|
<path d="M44 66 Q54 74 64 66" fill="none" stroke="#92400e" stroke-width="2.2" stroke-linecap="round"/>
|
||||||
|
|
||||||
|
<!-- BLUE pointed hat -->
|
||||||
|
<path d="M28 44 L54 2 L80 44 Z" fill="#1d4ed8"/>
|
||||||
|
<path d="M36 44 L54 10 L72 44 Z" fill="#3b82f6" opacity="0.5"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="#1e40af"/>
|
||||||
|
<ellipse cx="54" cy="42.5" rx="26" ry="5" fill="#2563eb"/>
|
||||||
|
<ellipse cx="54" cy="44" rx="30" ry="8" fill="none" stroke="#f8fafc" stroke-width="2.2"/>
|
||||||
|
<polygon points="54,12 55.6,16.8 60.5,18.4 55.6,20 54,24.8 52.4,20 47.5,18.4 52.4,16.8" fill="#f8fafc"/>
|
||||||
|
<polygon points="42,22 43.1,25.2 46.2,26.4 43.1,27.6 42,30.8 40.9,27.6 37.8,26.4 40.9,25.2" fill="#fef08a"/>
|
||||||
|
<circle cx="54" cy="4" r="6" fill="#f8fafc"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
86
configs/forgejo/compose.yml
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# koopa-forgejo — ROOTLESS Forgejo image + rootless podman (user hernani)
|
||||||
|
# Site: https://git.hacktivism.ch | HTTP :9024 | SSH :9200
|
||||||
|
services:
|
||||||
|
forgejo:
|
||||||
|
image: codeberg.org/forgejo/forgejo:11-rootless
|
||||||
|
container_name: koopa-forgejo
|
||||||
|
restart: unless-stopped
|
||||||
|
user: "1000:1000"
|
||||||
|
userns_mode: keep-id
|
||||||
|
env_file: [.env]
|
||||||
|
environment:
|
||||||
|
USER_UID: "1000"
|
||||||
|
USER_GID: "1000"
|
||||||
|
FORGEJO__database__DB_TYPE: postgres
|
||||||
|
FORGEJO__database__HOST: db:5432
|
||||||
|
FORGEJO__database__NAME: forgejo
|
||||||
|
FORGEJO__database__USER: forgejo
|
||||||
|
FORGEJO__database__PASSWD: ${FORGEJO_DB_PASSWORD}
|
||||||
|
FORGEJO__server__DOMAIN: git.hacktivism.ch
|
||||||
|
FORGEJO__server__SSH_DOMAIN: git.hacktivism.ch
|
||||||
|
FORGEJO__server__ROOT_URL: https://git.hacktivism.ch/
|
||||||
|
FORGEJO__server__HTTP_PORT: "3000"
|
||||||
|
FORGEJO__server__SSH_PORT: "9200"
|
||||||
|
FORGEJO__server__SSH_LISTEN_PORT: "2222"
|
||||||
|
FORGEJO__server__START_SSH_SERVER: "true"
|
||||||
|
FORGEJO__server__DISABLE_SSH: "false"
|
||||||
|
FORGEJO__server__LANDING_PAGE: explore
|
||||||
|
FORGEJO__service__DISABLE_REGISTRATION: "true"
|
||||||
|
FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION: "false"
|
||||||
|
FORGEJO__service__SHOW_REGISTRATION_BUTTON: "false"
|
||||||
|
FORGEJO__service__REQUIRE_SIGNIN_VIEW: "false"
|
||||||
|
FORGEJO__service__DEFAULT_ALLOW_CREATE_ORGANIZATION: "true"
|
||||||
|
FORGEJO__openid__ENABLE_OPENID_SIGNIN: "false"
|
||||||
|
FORGEJO__openid__ENABLE_OPENID_SIGNUP: "false"
|
||||||
|
FORGEJO__repository__DEFAULT_PRIVATE: public
|
||||||
|
FORGEJO__repository__ENABLE_PUSH_CREATE_USER: "true"
|
||||||
|
FORGEJO__repository__ENABLE_PUSH_CREATE_ORG: "true"
|
||||||
|
FORGEJO__security__INSTALL_LOCK: "true"
|
||||||
|
FORGEJO__session__PROVIDER: memory
|
||||||
|
FORGEJO__log__LEVEL: Info
|
||||||
|
FORGEJO____APP_NAME: "hacktivism git"
|
||||||
|
FORGEJO__ui__DEFAULT_THEME: hacktivism
|
||||||
|
FORGEJO__ui__THEMES: hacktivism,forgejo-auto,forgejo-light,forgejo-dark,gitea-auto,gitea-light,gitea-dark
|
||||||
|
ports:
|
||||||
|
- "9024:3000"
|
||||||
|
- "9200:2222"
|
||||||
|
volumes:
|
||||||
|
- ./data:/var/lib/gitea
|
||||||
|
- ./config:/etc/gitea
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
labels:
|
||||||
|
org.hacktivism.service: forgejo
|
||||||
|
org.hacktivism.variant: rootless
|
||||||
|
org.hacktivism.host_port: "9024"
|
||||||
|
org.hacktivism.ssh_port: "9200"
|
||||||
|
org.hacktivism.site: git.hacktivism.ch
|
||||||
|
org.hacktivism.managed_by: koopa-admin
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:3000/api/healthz"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 10
|
||||||
|
start_period: 90s
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:16-alpine
|
||||||
|
container_name: koopa-forgejo-db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: forgejo
|
||||||
|
POSTGRES_PASSWORD: ${FORGEJO_DB_PASSWORD}
|
||||||
|
POSTGRES_DB: forgejo
|
||||||
|
volumes:
|
||||||
|
- db-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U forgejo -d forgejo"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 20
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
42
configs/merchant-landing/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Merchant landing (`taler.hacktivism.ch` intro)
|
||||||
|
|
||||||
|
Static intro for the **GOA merchant** backend (dual currency GOA + CHF).
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|------|--------|
|
||||||
|
| Container | `taler-hacktivism` |
|
||||||
|
| In-container path | `/var/www/merchant-landing/` |
|
||||||
|
| nginx | listen **9015** (`nginx-landing.conf`) |
|
||||||
|
| **Host port to publish / wire** | **`9015`** → `127.0.0.1:9015` |
|
||||||
|
| Public path | `https://taler.hacktivism.ch/intro/` |
|
||||||
|
|
||||||
|
## Features (page)
|
||||||
|
|
||||||
|
| Piece | Notes |
|
||||||
|
|-------|--------|
|
||||||
|
| Wallet fold | Collapsed “Get the wallet · Android / iOS / web browsers” |
|
||||||
|
| Web icons | Chrome · Firefox · Opera · Ubuntu Touch (gold+blue; [taler.net wallet](https://www.taler.net/de/wallet.html)) |
|
||||||
|
| GOA shop samples | `data-product` buttons → public template pay (`goa-shop` instance) |
|
||||||
|
| Link previews | Open Graph / Twitter meta → `og-goa-shop.png` (1200×630) for chats & social |
|
||||||
|
| Shop pay popup | `shop-pay.js` / `shop-pay.css` — **QR_Taler** (blue ring + `qr-logo.svg`), settlement payto expandable |
|
||||||
|
| Get GOA first | Hint + link to bank (plain “pay here”) |
|
||||||
|
| Stats | Dual-currency aggregates; **recent activity** split GOA/CHF (≤5 each); refund badge violet |
|
||||||
|
| Performance | Live HTTP probes + container RSS |
|
||||||
|
|
||||||
|
Shop product ids/amounts must stay in sync with
|
||||||
|
`scripts/taler-monitoring/check_e2e.sh` → `E2E_SHOP_PRODUCTS`
|
||||||
|
(e2e random-picks **2** products per run by default).
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman cp configs/merchant-landing/index.html \
|
||||||
|
taler-hacktivism:/var/www/merchant-landing/index.html
|
||||||
|
podman cp configs/merchant-landing/og-goa-shop.png \
|
||||||
|
taler-hacktivism:/var/www/merchant-landing/og-goa-shop.png
|
||||||
|
podman cp configs/merchant-landing/shop-pay.js \
|
||||||
|
taler-hacktivism:/var/www/merchant-landing/shop-pay.js
|
||||||
|
# shop-pay.css, qr-logo.svg, qrcode.min.js as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
Also see `scripts/taler-landing/deploy-landings.sh` if present.
|
||||||
1016
configs/merchant-landing/index.html
Normal file
24
configs/merchant-landing/nginx-landing.conf
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Merchant landing — :9015 (behind Caddy — no port in Location)
|
||||||
|
server {
|
||||||
|
listen 9015;
|
||||||
|
listen [::]:9015;
|
||||||
|
server_name taler.hacktivism.ch _;
|
||||||
|
root /var/www/merchant-landing;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
absolute_redirect off;
|
||||||
|
port_in_redirect off;
|
||||||
|
|
||||||
|
location = / {
|
||||||
|
return 302 /intro/;
|
||||||
|
}
|
||||||
|
location = /intro {
|
||||||
|
return 302 /intro/;
|
||||||
|
}
|
||||||
|
location /intro/ {
|
||||||
|
alias /var/www/merchant-landing/;
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
configs/merchant-landing/og-goa-shop.png
Normal file
|
After Width: | Height: | Size: 544 KiB |
BIN
configs/merchant-landing/qr-logo.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |