Changes in / [a4fea5e:ed5e7ab] in Klonkt


Ignore:
Files:
2 added
5 edited

Legend:

Unmodified
Added
Removed
  • deploy/MULTI-INSTANCE.md

    ra4fea5e red5e7ab  
    8484because the port comes from the same `.env`.
    8585
    86 **Rolling back.** The old `klonkt.service` is disabled and masked, not deleted.
    87 Masked because `disable` alone does not stop `systemctl restart klonkt` from
    88 starting it again, and a resurrected unit no longer finds its `.env` (that moved
    89 with the data): it would fall back to the defaults and write a fresh empty
    90 database into the checkout. To go back, move the data into `/opt/klonkt/storage`,
    91 restore the relative paths in `.env`, then `systemctl unmask klonkt` and
    92 `systemctl enable --now klonkt`.
     86**The old unit is moved aside, not deleted.** It ends up next to its old place
     87as `klonkt.service.retired-<timestamp>`. Stopping and disabling is not enough:
     88`systemctl restart klonkt` starts a disabled unit anyway, which is exactly what
     89an updater generated before the split does. A resurrected `klonkt.service` no
     90longer finds its `.env` (that moved with the data), falls back to the built-in
     91defaults, and writes a fresh empty database into the checkout. Masking does not
     92work here either, because the unit file sits in `/etc/systemd/system` and
     93`systemctl mask` refuses while a real file is there.
     94
     95**Rolling back.** Move the data into `/opt/klonkt/storage`, restore the relative
     96paths in `.env`, move the retired unit file back to
     97`/etc/systemd/system/klonkt.service`, then:
     98
     99```bash
     100sudo systemctl daemon-reload
     101sudo systemctl enable --now klonkt
     102```
    93103
    94104## Adding an instance
  • scripts/klonkt-migrate-data.sh

    ra4fea5e red5e7ab  
    128128run "systemctl daemon-reload"
    129129
     130step "Retiring $OLD_UNIT"
     131# Stopping and disabling is NOT enough: `systemctl restart klonkt` starts a
     132# disabled unit anyway, and that is exactly what an updater generated before
     133# the split does. A resurrected klonkt.service no longer finds its .env (that
     134# moved with the data), falls back to the built-in defaults, and writes a
     135# FRESH EMPTY database into the checkout.
     136#
     137# Masking does not help either: the unit file lives in /etc/systemd/system,
     138# the highest-priority directory, and `systemctl mask` refuses when a real
     139# file is already there ("File ... already exists"). Verified, not assumed.
     140#
     141# So the file is moved aside. systemd then no longer knows the unit at all and
     142# any restart fails loudly with "Unit klonkt.service not found". The file is
     143# kept next to its old place, timestamped, so a rollback is a move back.
     144if [ -f "/etc/systemd/system/$OLD_UNIT" ]; then
     145  run "systemctl stop $OLD_UNIT 2>/dev/null || true"
     146  run "systemctl disable $OLD_UNIT 2>/dev/null || true"
     147  RETIRED="/etc/systemd/system/${OLD_UNIT}.retired-$(date +%Y%m%d%H%M%S)"
     148  run "mv '/etc/systemd/system/$OLD_UNIT' '$RETIRED'"
     149  run "systemctl daemon-reload"
     150  say "stopped, disabled and moved aside → $RETIRED"
     151  say "roll back by moving that file back and running: systemctl daemon-reload"
     152else
     153  say "no $OLD_UNIT unit file to retire"
     154fi
     155
    130156step "Switching to klonkt@$SLUG"
    131 if systemctl list-unit-files "$OLD_UNIT" >/dev/null 2>&1; then
    132   run "systemctl disable --now $OLD_UNIT"
    133   # Disable only removes the autostart link: `systemctl restart klonkt` would
    134   # still START it. That is not theoretical — an updater generated before the
    135   # split does exactly that, and the resurrected unit finds no .env (it moved
    136   # with the data), falls back to the built-in defaults and creates a FRESH
    137   # EMPTY database in the checkout. Masking makes any such call fail loudly.
    138   # Reversible: systemctl unmask klonkt.
    139   run "systemctl mask $OLD_UNIT"
    140   say "disabled and masked $OLD_UNIT (unmask to roll back)"
    141 fi
    142157run "systemctl enable --now 'klonkt@$SLUG'"
    143158
  • scripts/klonkt-refresh-updater.sh

    ra4fea5e red5e7ab  
    7777echo "klonkt-update rewritten: branch ${BRANCH}, code ${KLONKT_DIR}, instances under ${DATA_ROOT}"
    7878
    79 # On a split install the old single unit must not be startable. `disable` alone
    80 # does not stop `systemctl restart klonkt` from starting it, and a resurrected
    81 # klonkt.service has no .env (it moved with the data): it falls back to the
    82 # defaults and writes a fresh empty database into the checkout.
     79# On a split install the old single unit must not be startable at all.
     80# `disable` is not enough (restart starts a disabled unit anyway) and `mask`
     81# refuses while the real file sits in /etc/systemd/system, the highest-priority
     82# directory. Moving the file aside is what actually works: systemd stops
     83# knowing the unit, so any restart fails loudly instead of quietly starting a
     84# second process that writes an empty database into the checkout.
    8385SPLIT=0
    8486for d in "${DATA_ROOT}"/*/; do [ -f "$d/.env" ] && SPLIT=1 && break; done
    85 if [ "$SPLIT" = 1 ] && systemctl list-unit-files klonkt.service >/dev/null 2>&1; then
    86   if ! systemctl is-enabled klonkt.service 2>/dev/null | grep -q masked; then
    87     systemctl stop klonkt.service 2>/dev/null || true
    88     systemctl disable klonkt.service 2>/dev/null || true
    89     systemctl mask klonkt.service
    90     echo "retired klonkt.service: stopped, disabled and masked (unmask to roll back)"
     87if [ "$SPLIT" = 1 ] && [ -f /etc/systemd/system/klonkt.service ]; then
     88  systemctl stop klonkt.service 2>/dev/null || true
     89  systemctl disable klonkt.service 2>/dev/null || true
     90  RETIRED="/etc/systemd/system/klonkt.service.retired-$(date +%Y%m%d%H%M%S)"
     91  if mv /etc/systemd/system/klonkt.service "$RETIRED"; then
     92    systemctl daemon-reload
     93    echo "retired klonkt.service → $RETIRED (move it back + daemon-reload to roll back)"
     94  else
     95    echo "WARNING: could not move /etc/systemd/system/klonkt.service aside."
     96    echo "         Until you do, any 'systemctl restart klonkt' starts a second"
     97    echo "         process that writes an empty database into ${KLONKT_DIR}."
    9198  fi
    9299fi
  • src/config/database.js

    ra4fea5e red5e7ab  
    499499    );
    500500    CREATE INDEX IF NOT EXISTS idx_ap_timeline_slug ON ap_timeline(slug, published);
     501    -- canonicalReactionUri herleidt een permalink naar het object-id door op (slug, url)
     502    -- te zoeken. Zonder deze index viel dat terug op idx_ap_timeline_slug, dus een scan
     503    -- van elke rij van die slug. Dat gebeurt PER REACTIE in getInteractions, en de
     504    -- reactie-migratie erft het in haar re-key-join, die synchroon vóór listen draait:
     505    -- de opstartkosten waren reacties maal tijdlijnrijen.
     506    CREATE INDEX IF NOT EXISTS idx_ap_timeline_url ON ap_timeline(slug, url);
    501507    CREATE TABLE IF NOT EXISTS ap_blocks (
    502508      id INTEGER PRIMARY KEY AUTOINCREMENT,
     
    850856    }
    851857    // slug + object_uri verschillen per bron; de rest is voor alle vier gelijk.
    852     const zet = (naam, gebeurtenis, tabel, slug, uri, kind, extra = '') => `
     858    const zet = (naam, gebeurtenis, tabel, slug, uri, kind, extra = '', wanneer = '') => `
    853859      DROP TRIGGER IF EXISTS ${naam};
    854       CREATE TRIGGER ${naam} AFTER ${gebeurtenis} ON ${tabel} BEGIN
     860      CREATE TRIGGER ${naam} AFTER ${gebeurtenis} ON ${tabel}${wanneer ? ` WHEN ${wanneer}` : ''} BEGIN
    855861        UPDATE ap_feed_rev SET n = n + 1;
    856862        INSERT INTO ap_feed_state (slug, object_uri, rev, kind)
     
    874880      // ap_interactions draagt geen slug: die hangt aan de POST. Vandaar de join,
    875881      // en vandaar dat deze drie niet in de gewone vorm passen.
    876       zet('trg_feed_ia_ins', 'INSERT', 'ap_interactions', '', '', '', `${joinPosts('NEW.object_uri', 'new')} WHERE p.id = NEW.post_id`),
    877       zet('trg_feed_ia_upd', 'UPDATE OF content, media_json, quote_json, embed_json', 'ap_interactions', '', '', '', `${joinPosts('NEW.object_uri', 'updated')} WHERE p.id = NEW.post_id`),
    878       zet('trg_feed_ia_del', 'DELETE', 'ap_interactions', '', '', '', `${joinPosts('OLD.object_uri', 'deleted')} WHERE p.id = OLD.post_id`),
     882      //
     883      // De WHEN op kind='reply' is nodig omdat deze tabel ook likes en announces
     884      // draagt, en die schrijven object_uri = '' (zie recordInteraction). Zonder de
     885      // WHEN bumpte elke inkomende like de rev, werd elke wachter gewekt en kreeg
     886      // die de hele collectie opnieuw terwijl er niets aan veranderd was: precies de
     887      // kosten die de 304 moest wegnemen. Bovendien belandde er dan een rij op de
     888      // lege string in ap_feed_state, die feedChangesSince vervolgens uitdeelt.
     889      // De oude cursor filterde hier wel op kind; bij ap_timeline is dit ook gedaan
     890      // (de UPDATE OF sluit liked/boosted uit) en één tabel verder vergeten.
     891      zet('trg_feed_ia_ins', 'INSERT', 'ap_interactions', '', '', '', `${joinPosts('NEW.object_uri', 'new')} WHERE p.id = NEW.post_id`, "NEW.kind = 'reply'"),
     892      zet('trg_feed_ia_upd', 'UPDATE OF content, media_json, quote_json, embed_json', 'ap_interactions', '', '', '', `${joinPosts('NEW.object_uri', 'updated')} WHERE p.id = NEW.post_id`, "NEW.kind = 'reply'"),
     893      zet('trg_feed_ia_del', 'DELETE', 'ap_interactions', '', '', '', `${joinPosts('OLD.object_uri', 'deleted')} WHERE p.id = OLD.post_id`, "OLD.kind = 'reply'"),
    879894    ].join('\n'));
    880895  } catch (e) {
  • src/services/ActivityPubService.js

    ra4fea5e red5e7ab  
    14351435  const pem = actor && actor.publicKey && actor.publicKey.publicKeyPem;
    14361436  if (!pem) return null;
     1437  // Bind the key to the actor it speaks for. Without this we hand back whatever
     1438  // `id` the fetched document claims, so anyone could host a document carrying a
     1439  // VICTIM's id next to their OWN public key, sign with their own private half,
     1440  // and be believed: the victim's server is never contacted. The caller decides on
     1441  // `verified.id`, so the identity has to come from where the key was FETCHED,
     1442  // never from what the document says about itself.
     1443  // Adds conditions only, and there is no exemption list on purpose: an
     1444  // "unless it's a known peer" escape hatch is exactly the door this closes.
     1445  // Note this does not narrow what we accept in practice, since the line above
     1446  // already requires the embedded publicKey object (an array or a bare URI
     1447  // reference never worked here).
     1448  const key = actor.publicKey;
     1449  try {
     1450    if (new URL(p.keyId).host !== new URL(actor.id).host) return null;   // same origin as the key
     1451    if (key.id && key.id !== p.keyId) return null;                       // this key, not a neighbour's
     1452    if (key.owner && key.owner !== actor.id) return null;                // and it belongs to this actor
     1453  } catch { return null; }                                               // unparseable id or keyId
    14371454  const hs = (p.headers || '(request-target) host date').split(/\s+/);
    14381455  // Behind a reverse proxy the raw Host header is the backend bind (e.g. localhost:3000, when
Note: See TracChangeset for help on using the changeset viewer.