pmtiles-swarm 0.15.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +32 -5
- package/docs/publishing.md +9 -1
- package/docs/subscribing.md +23 -3
- package/package.json +1 -1
- package/src/api.js +2 -1
- package/src/publisher.js +6 -1
- package/src/tilejson.js +3 -1
- package/src/web/index.html +20 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@
|
|
|
7
7
|
### 🐞 Bug fixes
|
|
8
8
|
- _...Add new stuff here..._
|
|
9
9
|
|
|
10
|
+
## 0.15.2
|
|
11
|
+
### 🐞 Bug fixes
|
|
12
|
+
- **A mutable magnet is named after its category rather than a build.** `dn=` was taken from
|
|
13
|
+
whichever archive was newest when the string was generated, so a magnet whose whole purpose is to
|
|
14
|
+
resolve to *the current* build carried the name of one particular build — dated the moment the
|
|
15
|
+
next one landed. It now reads `dn=openmaptiles`.
|
|
16
|
+
|
|
17
|
+
Nothing depended on it: `dn` is a display hint, replaced by the real name as soon as metadata
|
|
18
|
+
arrives. It was simply describing the wrong thing.
|
|
19
|
+
|
|
20
|
+
## 0.15.1
|
|
21
|
+
### 🐞 Bug fixes
|
|
22
|
+
- **The downloaded bar showed nothing on an archive that was plainly downloading.** A column on
|
|
23
|
+
that bar covers many pieces, and the sidecar reduced "held" by `all` — so a column lit only when
|
|
24
|
+
every piece beneath it had arrived, and an archive 18% complete showed an empty bar. Fixed in
|
|
25
|
+
pmtiles-torrent 0.4.4, which reports a proportion; the console now shades those columns by it,
|
|
26
|
+
with a floor so the first few percent of a download are visible rather than indistinguishable
|
|
27
|
+
from none.
|
|
28
|
+
|
|
29
|
+
Peer bars had the opposite fault and are fixed the same way, so a peer holding a little no longer
|
|
30
|
+
reads as a seed.
|
|
31
|
+
|
|
32
|
+
Renders correctly against either sidecar: the newer one never rounds a non-empty column below 2,
|
|
33
|
+
so values above 1 identify the new encoding.
|
|
34
|
+
|
|
10
35
|
## 0.15.0
|
|
11
36
|
### ✨ Features and improvements
|
|
12
37
|
- **The DHT routing table is remembered between runs**, which is the difference between publishing
|
package/README.md
CHANGED
|
@@ -115,8 +115,31 @@ subscribers forward, and they fail differently, so publishing both is cheap insu
|
|
|
115
115
|
|
|
116
116
|
- **RSS** — easy to consume, understood by existing clients, needs a server that stays up.
|
|
117
117
|
- **BEP 46** — an ed25519-signed DHT record naming the current infohash, addressed by public key
|
|
118
|
-
rather than infohash (`magnet:?xs=urn:btpk:…`). No server needed
|
|
119
|
-
|
|
118
|
+
rather than infohash (`magnet:?xs=urn:btpk:…`). No server needed at all.
|
|
119
|
+
|
|
120
|
+
Publishing those records is built in. The node that builds gets a key — `pmtiles-swarm
|
|
121
|
+
publisher-key` — and announces the newest archive in each category, salted by category name so one
|
|
122
|
+
keypair addresses all of them. Records expire from the DHT after about two hours, so it republishes
|
|
123
|
+
on a timer; that timer is the feature rather than an optimisation. Serving nodes need nothing: they
|
|
124
|
+
receive the public half on the catalog entry and hand it out in the TileJSON, which is why a
|
|
125
|
+
serving tier can be compromised without anyone being able to publish.
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{ "mutable": { "publish": true, "keyPath": "/etc/pmtiles-swarm/publisher.pem" } }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The routing table is remembered between runs (`dht-nodes.json` in the data directory), which
|
|
132
|
+
matters more than it sounds: bootstrapping from hostnames alone was measured working about one
|
|
133
|
+
start in seven on a domestic connection, and a saved table turns that into every start. It is the
|
|
134
|
+
same thing libtorrent does, and why its DHT works on hosts where a fresh socket does not.
|
|
135
|
+
|
|
136
|
+
**What a style should point at** is then the category's TileJSON URL with that magnet in its
|
|
137
|
+
fragment — the console's Categories page has a **For a style** row that gives you the whole
|
|
138
|
+
string. A fragment is never sent in an HTTP request, so ordinary clients fetch the TileJSON and
|
|
139
|
+
ignore it, while a swarm-aware one reads the magnet before making any call and can start when the
|
|
140
|
+
server cannot answer. With `xs=urn:btpk:` rather than an infohash it does not go stale on the next
|
|
141
|
+
build either. See [docs/serving-tiles.md](docs/serving-tiles.md) and
|
|
142
|
+
[docs/security.md](docs/security.md), because that key behaves unlike every other secret here.
|
|
120
143
|
|
|
121
144
|
## Configuration
|
|
122
145
|
|
|
@@ -742,9 +765,13 @@ another node, watched web locations including directory listings and date templa
|
|
|
742
765
|
limits and what is left of them, in-place settings reload and process restart, and two-node
|
|
743
766
|
subscription sync in both directions over both RSS and the catalog API.
|
|
744
767
|
|
|
745
|
-
|
|
746
|
-
BEP 46
|
|
747
|
-
|
|
768
|
+
Running in production: watch-folder imports feeding a nightly planet build, an 18-archive library
|
|
769
|
+
of roughly 2.5 TB behind HAProxy, and BEP 46 records published to a live DHT — 54 nodes stored the
|
|
770
|
+
last one.
|
|
771
|
+
|
|
772
|
+
Not yet exercised: the qBittorrent engine against a real instance, and **resolving** a BEP 46
|
|
773
|
+
record from outside the publishing network. Nodes accept the records and report storing them, which
|
|
774
|
+
is not the same claim as a stranger reading one back, and only the second matters to a subscriber.
|
|
748
775
|
|
|
749
776
|
## License and attribution
|
|
750
777
|
|
package/docs/publishing.md
CHANGED
|
@@ -779,4 +779,12 @@ rebuild, which fail differently, so publishing both is cheap insurance:
|
|
|
779
779
|
public key rather than infohash (`magnet:?xs=urn:btpk:…`). No server required, but the
|
|
780
780
|
record expires and must be republished.
|
|
781
781
|
|
|
782
|
-
|
|
782
|
+
Publishing those records is built in: give the building node a key with `pmtiles-swarm
|
|
783
|
+
publisher-key`, set `mutable.publish`, and it announces the newest archive in each category
|
|
784
|
+
and republishes on a timer. Serving nodes need nothing — they receive the public half on
|
|
785
|
+
the catalog entry and hand it out in the TileJSON.
|
|
786
|
+
|
|
787
|
+
See [subscribing.md](subscribing.md) for the shape of it,
|
|
788
|
+
[serving-tiles.md](serving-tiles.md#a-fragment-that-survives-a-rebuild) for what a style
|
|
789
|
+
should then point at, and [security.md](security.md#the-publisher-key-is-not-a-credential)
|
|
790
|
+
for why that key wants treating like a signing key rather than an API token.
|
package/docs/subscribing.md
CHANGED
|
@@ -419,9 +419,29 @@ RSS and BEP 46 fail in opposite ways, which is the argument for publishing both:
|
|
|
419
419
|
| RSS | a server that stays up | the server goes away |
|
|
420
420
|
| BEP 46 | periodic republishing | the record expires (hours, not days) |
|
|
421
421
|
|
|
422
|
-
**
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
**This is built in.** The code above is what a host application would call; a node only
|
|
423
|
+
needs a key and a setting:
|
|
424
|
+
|
|
425
|
+
```sh
|
|
426
|
+
pmtiles-swarm publisher-key > /etc/pmtiles-swarm/publisher.pem
|
|
427
|
+
chmod 400 /etc/pmtiles-swarm/publisher.pem
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
```json
|
|
431
|
+
{ "mutable": { "publish": true, "keyPath": "/etc/pmtiles-swarm/publisher.pem" } }
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
It then announces the newest archive in each category, salted by category name so one key
|
|
435
|
+
addresses all of them, and republishes before the records expire. Only the node that builds
|
|
436
|
+
needs the key — see
|
|
437
|
+
[running-as-a-service.md](running-as-a-service.md#the-publisher-key) for setting it up and
|
|
438
|
+
[security.md](security.md#the-publisher-key-is-not-a-credential) for why it is not a
|
|
439
|
+
credential.
|
|
440
|
+
|
|
441
|
+
**Status:** publishing is verified against a live DHT — records stored by 54 nodes at the
|
|
442
|
+
last check. **Resolving** one from outside the publishing network is not yet verified:
|
|
443
|
+
nodes accepting a record is not the same claim as a stranger reading it back, and only the
|
|
444
|
+
second matters to a subscriber.
|
|
425
445
|
|
|
426
446
|
## Making the archives serveable
|
|
427
447
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmtiles-swarm",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "BitTorrent distribution for PMTiles map archives: create torrents, watch folders, publish and subscribe to RSS feeds, and seed through qBittorrent or an embedded client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/api.js
CHANGED
|
@@ -73,7 +73,8 @@ function styleUrlFor(category, newest, base) {
|
|
|
73
73
|
const magnet = newest?.mutable?.publicKey
|
|
74
74
|
? mutableMagnet(newest.mutable.publicKey, {
|
|
75
75
|
salt: newest.mutable.salt ?? category,
|
|
76
|
-
|
|
76
|
+
// The category, since that is what this magnet resolves to.
|
|
77
|
+
name: newest.mutable.salt ?? category,
|
|
77
78
|
webSeeds: newest.webSeeds,
|
|
78
79
|
})
|
|
79
80
|
: newest?.magnet;
|
package/src/publisher.js
CHANGED
|
@@ -214,7 +214,12 @@ export class MutablePublisher {
|
|
|
214
214
|
magnetFor(category, entry) {
|
|
215
215
|
return mutableMagnet(this.#key.publicKey, {
|
|
216
216
|
salt: category,
|
|
217
|
-
|
|
217
|
+
// The category rather than the build. This magnet resolves to whichever
|
|
218
|
+
// archive is current, so naming one of them dates the string the moment
|
|
219
|
+
// the next build lands. `dn` is only a label -- the real name arrives
|
|
220
|
+
// with the metadata and overrides it -- so nothing depends on this
|
|
221
|
+
// beyond being honest about what the magnet identifies.
|
|
222
|
+
name: category,
|
|
218
223
|
webSeeds: entry?.webSeeds,
|
|
219
224
|
});
|
|
220
225
|
}
|
package/src/tilejson.js
CHANGED
|
@@ -100,7 +100,9 @@ function buildTorrentBlock(entry, root) {
|
|
|
100
100
|
// and buildable by any node, because it contains only the public half.
|
|
101
101
|
magnet: mutableMagnet(entry.mutable.publicKey, {
|
|
102
102
|
salt: entry.mutable.salt,
|
|
103
|
-
|
|
103
|
+
// The category, not this build: the record resolves to whichever
|
|
104
|
+
// archive is current, and `dn` is a label the metadata replaces.
|
|
105
|
+
name: entry.mutable.salt,
|
|
104
106
|
webSeeds: entry.webSeeds,
|
|
105
107
|
}),
|
|
106
108
|
};
|
package/src/web/index.html
CHANGED
|
@@ -2894,9 +2894,17 @@
|
|
|
2894
2894
|
};
|
|
2895
2895
|
const [r, g, b] = rgb(accent);
|
|
2896
2896
|
|
|
2897
|
-
// Availability is a count, so it shades
|
|
2898
|
-
//
|
|
2899
|
-
|
|
2897
|
+
// Availability is a count, so it shades by rarity. The other two are
|
|
2898
|
+
// proportions now: a column covers many pieces — 178,000 of them
|
|
2899
|
+
// across a bar a thousand wide on a 698 GiB archive — so "held" is a
|
|
2900
|
+
// fraction of its slice rather than a yes or no. Reduced any other way
|
|
2901
|
+
// the bar only tells the truth at 0% and 100%.
|
|
2902
|
+
//
|
|
2903
|
+
// A sidecar older than pmtiles-torrent 0.4.4 still sends 0 or 1, and
|
|
2904
|
+
// is recognised by nothing exceeding 1: the newer one never rounds a
|
|
2905
|
+
// non-empty bucket below 2, precisely so this test works.
|
|
2906
|
+
const ceiling = kind === 'availability' ? Math.max(1, ...bytesIn) : 255;
|
|
2907
|
+
const proportional = kind !== 'availability' && Math.max(0, ...bytesIn) > 1;
|
|
2900
2908
|
|
|
2901
2909
|
for (let index = 0; index < bytesIn.length; index += 1) {
|
|
2902
2910
|
const value = bytesIn[index];
|
|
@@ -2905,7 +2913,15 @@
|
|
|
2905
2913
|
image.data[at + 3] = 0;
|
|
2906
2914
|
continue;
|
|
2907
2915
|
}
|
|
2908
|
-
|
|
2916
|
+
// A floor, because a column holding a little is worth seeing: without
|
|
2917
|
+
// one, the first few percent of a download are indistinguishable
|
|
2918
|
+
// from none at all.
|
|
2919
|
+
const strength =
|
|
2920
|
+
kind === 'availability'
|
|
2921
|
+
? 0.35 + 0.65 * (value / ceiling)
|
|
2922
|
+
: proportional
|
|
2923
|
+
? Math.max(0.18, value / 255)
|
|
2924
|
+
: 1;
|
|
2909
2925
|
image.data[at] = r;
|
|
2910
2926
|
image.data[at + 1] = g;
|
|
2911
2927
|
image.data[at + 2] = b;
|