Compare commits

...

9 commits
0.2.2 ... trunk

Author SHA1 Message Date
sunlaud
9192a4d45a Feature: filter operator to identify tiddlers loaded by TiddlyPWA 2025-07-23 17:21:42 +02:00
sunlaud
7ff63eb696 Bugfix: login dialog is obscured by side panel
Reproduces when
* small screens (e.g. mobile)
* Notebook theme installed
* app wiki is stored with side panel opened
2025-06-17 02:39:31 +03:00
Val Packett
8c55b22aa7 Remove outdated readme badges 2025-04-02 20:34:22 -03:00
Val Packett
2961396310 Switch to Forgejo Actions 2025-04-02 20:34:18 -03:00
Val Packett
05d24a9efd Server: fix base64 ArrayBuffer typechecking with new Deno 2025-04-02 20:12:12 -03:00
Val Packett
ef786f611a Docs: systemd unit and various clarifications 2025-04-02 15:35:02 -03:00
Val Packett
71509921d6 Update woodpecker config for 3.0.0 2025-02-26 18:07:35 -03:00
Val Packett
1541ba5589 Move the site to bnnuy 2025-01-07 00:28:35 -03:00
telumire
9693281cb4 add trimming to the "UploadApp" tiddler 2024-11-19 10:53:42 +00:00
16 changed files with 189 additions and 47 deletions

View file

@ -0,0 +1,22 @@
---
on:
push:
branches: [trunk]
pull_request:
types: [opened, synchronize, reopened]
jobs:
test:
runs-on: val-arm64
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
- name: Get Deno
run: curl -fsSL https://deno.land/install.sh | sh
- name: Run server tests
run: /root/.deno/bin/deno test server
- name: Try hash-admin-password
run: echo uwu | /root/.deno/bin/deno run server/hash-admin-password.ts
- name: Check client build
run: /root/.deno/bin/deno run --allow-env --allow-read --allow-write=output npm:tiddlywiki@5.3.5 --build
- name: Check formatting
run: /root/.deno/bin/deno fmt --check --ignore="**/*.css" plugins server

View file

@ -0,0 +1,22 @@
---
on:
push:
branches: [release]
jobs:
test:
runs-on: val-arm64
steps:
- name: Checkout
uses: https://code.forgejo.org/actions/checkout@v4
- name: Fetch theme
run: mkdir notebook && curl -L https://github.com/valpackett/Notebook/archive/e2c61ccd6e9db5cfcbc77e54eccc8b5961da5831.tar.gz | tar -xvzf - -C notebook --strip-components=1
- name: Build client
run: npx tiddlywiki@5.3.5 --build
env:
TIDDLYWIKI_THEME_PATH: notebook/themes
TIDDLYWIKI_PLUGIN_PATH: notebook/plugins
- name: Upload result
run: sh bunny.sh
env:
BUNNY_BUCKET: ${{ secrets.BUNNY_BUCKET }}
BUNNY_KEY: ${{ secrets.BUNNY_KEY }}

View file

@ -1,9 +0,0 @@
steps:
test:
image: denoland/deno:alpine-1.45.4
commands:
- deno test
- DENO_FUTURE=1 deno test
- echo uwu | DENO_FUTURE=1 deno run server/hash-admin-password.ts
- deno fmt --check
- DENO_FUTURE=1 deno run --allow-env --allow-read --allow-write=output npm:tiddlywiki@5.3.5 --build

View file

@ -1,5 +1,3 @@
[![CI status](https://ci.codeberg.org/api/badges/valpackett/tiddlypwa/status.svg)](https://ci.codeberg.org/valpackett/tiddlypwa)
[![Netlify Status](https://api.netlify.com/api/v1/badges/2c2cbd41-1ced-4f78-acc7-83889f95bcc2/deploy-status)](https://app.netlify.com/sites/tiddly-packett-cool/deploys)
[![Support me on Patreon](https://img.shields.io/badge/dynamic/json?logo=patreon&color=%23e85b46&label=support%20me%20on%20patreon&query=data.attributes.patron_count&suffix=%20patrons&url=https%3A%2F%2Fwww.patreon.com%2Fapi%2Fcampaigns%2F9395291)](https://www.patreon.com/valpackett)
# TiddlyPWA

8
bunny.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
set -e
cd output
find * -type f -exec curl --fail --request PUT \
--url https://storage.bunnycdn.com/$BUNNY_BUCKET/{} \
--header "AccessKey: $BUNNY_KEY" \
--header "Content-Type: application/octet-stream" \
--data-binary @{} \;

View file

@ -1,18 +0,0 @@
[build]
publish = "output"
command = """
mkdir notebook; curl -L https://github.com/valpackett/Notebook/archive/e2c61ccd6e9db5cfcbc77e54eccc8b5961da5831.tar.gz | tar -xvzf - -C notebook --strip-components=1 &&
TIDDLYWIKI_THEME_PATH=notebook/themes TIDDLYWIKI_PLUGIN_PATH=notebook/plugins npx tiddlywiki@5.3.5 --build
"""
[[headers]]
for = "/*"
[headers.values]
x-content-type-options = "nosniff"
x-frame-options = "SAMEORIGIN"
referrer-policy = "no-referrer-when-downgrade"
[[redirects]]
from = "/w/:name/:file"
to = "/app/:file"
status = 200

View file

@ -15,7 +15,7 @@ Formatted with `deno fmt`.
const dm = $tw.utils.domMaker;
module.exports.BootstrapModal = class {
wrapper = dm('div', { class: 'tc-modal-wrapper', style: { 'z-index': 1500 } }); // below alerts, above hide-sidebar-btn
wrapper = dm('div', { class: 'tc-modal-wrapper', style: { 'z-index': 4000 } }); // below alerts, above hide-sidebar-btn and others (e.g. side panel in Notebook theme has z-index=3000)
constructor() {
$tw.utils.addClass(document.body, 'tc-modal-prevent-scroll');
this.wrapper.appendChild(dm('div', { class: 'tc-modal-backdrop' }));

View file

@ -0,0 +1,36 @@
/*\
title: $:/plugins/valpackett/tiddlypwa/filters.js
type: application/javascript
module-type: isfilteroperator
Filter function for TiddlyPWA-managed tiddler identification
Licensed under 0BSD, see license.tid.
Formatted with `deno fmt`.
\*/
'use strict';
exports.tiddlypwa = function (source, prefix, options) {
const results = [];
const pwaStorage = $tw.syncer.syncadaptor;
if (!pwaStorage || !pwaStorage.tiddlersInFile) {
return ['Error: TiddlyPWA not available'];
}
if (prefix === '!') {
source(function (tiddler, title) {
if (pwaStorage.tiddlersInFile.has(title)) {
results.push(title);
}
});
} else {
source(function (tiddler, title) {
if (!pwaStorage.tiddlersInFile.has(title)) {
results.push(title);
}
});
}
return results;
};

View file

@ -0,0 +1,11 @@
title: $:/plugins/valpackett/tiddlypwa/filters
!!! Filter Operators
TiddlyPWA provides filter operator to identify tiddlers it manages:
* `[is[tiddlypwa]]` - tiddlers created/loaded by TiddlyPWA (from local DB or server)
* `[!is[tiddlypwa]]` - inversion of the above, i.e. tiddlers loaded from app wiki html file
This may be useful to find system tiddlers created by you, or to check shadow tiddler origins
(app file vs DB/server) or when exporting (to e.g. backup everything stored on server including system tiddlers).

View file

@ -3,7 +3,7 @@ tags: $:/tags/PageTemplate
<$reveal type="nomatch" state="$:/temp/HideUpdate" text="yes">
<$reveal type="match" state="$:/status/TiddlyPWAUpdateAvailable" text="yes">
<div class="tc-plugin-reload-warning" role="alert" style="z-index:2000">
<div class="tc-plugin-reload-warning" role="alert" style="z-index:5000">
~TiddlyWiki has been updated, please <$button message="tiddlypwa-browser-refresh">reload the page</$button>!
<$button set="$:/temp/HideUpdate" setTo="yes" class="tc-btn-invisible">{{$:/core/images/close-button}}</$button>

View file

@ -5,7 +5,7 @@
"author": "Val Packett",
"version": "0.2.2",
"core-version": ">=5.2.0",
"list": "readme license",
"list": "readme filters license",
"source": "https://codeberg.org/valpackett/tiddlypwa",
"demo": "https://tiddly.packett.cool/",
"plugin-type": "plugin"

View file

@ -16,3 +16,5 @@ please do [[throw some monies her way|https://www.patreon.com/valpackett]] :3
<$action-navigate $to="$:/ControlPanel"/>
Configure the plugin in the Control Panel
</$button>
See ''filters'' tab for filter operators.

View file

@ -1,10 +1,20 @@
title: $:/plugins/valpackett/tiddlypwa/upload-app-form
\procedure input(tiddler,id,tag:input,default)
<$tiddler tiddler=<<tiddler>> >
<$edit-text inputActions=<<inputActions>> id=<<id>> tag=<<tag>> default=<<default>> />
</$tiddler>
\end
\procedure inputActions()
<$action-setfield text={{{ [<actionValue>trim[]] }}} />
\end
<div class="tc-control-panel">
|tc-table-no-border tc-max-width tc-first-col-min-width|k
| URL|<$edit-text id="tpwa-endpoint-url" tiddler="$:/temp/TiddlyPWAServerURL" tag="input" default="https://" /> |
| Token|<$edit-text tiddler="$:/temp/TiddlyPWAServerToken" tag="input" default="" /> |
| URL|<<input id:"tpwa-endpoint-url" tiddler:"$:/temp/TiddlyPWAServerURL" default:"https://" >> |
| Token|<<input tiddler:"$:/temp/TiddlyPWAServerToken">> |
| |<$button><$action-sendmessage $message="tiddlypwa-upload-app-wiki" publishFilter={{$:/plugins/valpackett/tiddlypwa/app-filter}} uploadUrl={{$:/temp/TiddlyPWAServerURL}} uploadToken={{$:/temp/TiddlyPWAServerToken}} />Upload</$button> |
</div>
</div>

View file

@ -64,7 +64,7 @@ function supportsEncoding(headers: Headers, enc: string): boolean {
function processEtag(etag: Uint8Array, headers: Headers): [boolean, string] {
const supportsBrotli = supportsEncoding(headers, 'br');
return [supportsBrotli, '"' + base64.encode(etag) + (supportsBrotli ? '-b' : '-x') + '"'];
return [supportsBrotli, '"' + base64.encode(etag.buffer as ArrayBuffer) + (supportsBrotli ? '-b' : '-x') + '"'];
}
function notifyMonitors(token: string, browserToken: string) {
@ -132,11 +132,11 @@ export class TiddlyPWASyncApp {
// console.log('ServHas', base64nourl.encode(thash as Uint8Array), mtime, modsince, mtime < modsince);
ctrl.enqueue(
(firstWritten ? '\n,' : '\n') + JSON.stringify({
thash: thash ? base64nourl.encode(thash as Uint8Array) : null,
iv: iv ? base64nourl.encode(iv as Uint8Array) : null,
ct: ct ? base64nourl.encode(ct as Uint8Array) : null,
sbiv: sbiv ? base64nourl.encode(sbiv as Uint8Array) : null,
sbct: sbct ? base64nourl.encode(sbct as Uint8Array) : null,
thash: thash ? base64nourl.encode(thash.buffer as ArrayBuffer) : null,
iv: iv ? base64nourl.encode(iv.buffer as ArrayBuffer) : null,
ct: ct ? base64nourl.encode(ct.buffer as ArrayBuffer) : null,
sbiv: sbiv ? base64nourl.encode(sbiv.buffer as ArrayBuffer) : null,
sbct: sbct ? base64nourl.encode(sbct.buffer as ArrayBuffer) : null,
mtime,
deleted,
}),
@ -174,7 +174,7 @@ export class TiddlyPWASyncApp {
if (note !== undefined && typeof note !== 'string') {
return Response.json({ error: 'EPROTO' }, { headers: respHdrs, status: 400 });
}
const token = base64.encode(crypto.getRandomValues(new Uint8Array(32)));
const token = base64.encode(crypto.getRandomValues(new Uint8Array(32)).buffer);
this.db.createWiki(token, note);
return Response.json({ token }, { headers: respHdrs, status: 201 });
}

View file

@ -6,7 +6,7 @@ tags: [[TiddlyPWA Docs]]
Opening the `/` home page of the hosted sync server will let you access the admin console with the admin password.
There, you can create "wikis", i.e. storage slots that can store synchronized tiddlers ''and'' an app wiki, i.e. the TiddlyWiki HTML file with plugins and themes in it.
Hosting the app wiki on the sync server is not obligatory, but ''highly recommend'' because it simplifies setup on multiple devices and allows easy plugin/theme installation.
Hosting the app wiki on the sync server is not obligatory, but ''highly recommended'' because it simplifies setup on multiple devices and allows easy plugin/theme installation.
Once you have created a storage slot, you get a token that you can use for syncing the tiddlers and uploading the app wiki.
@ -16,4 +16,4 @@ Once you have created a storage slot, you get a token that you can use for synci
Upon uploading the app wiki, you'll see the URL for the hosted app wiki. Now you should bookmark that URL and use it to access the wiki.
It will also be accessible from the admin panel, by clicking the app wiki size.
Enjoy!
Enjoy!

View file

@ -26,12 +26,16 @@ or use a unix domain socket passing the path as `--socket` (you'll need to both
You can pass the `--dotenv` flag to make the app read variables from a `.env` file (which is mostly used in development with the included file that contains a hash of the `test` password.)
You can customize the location of the Deno module cache using the `DENO_DIR` environment variable.
You really need to have TLS (HTTPS) working, so run this behind a reverse proxy like [[Caddy|https://caddyserver.com/]], [[H2O|https://h2o.examp1e.net/]] or [[Nginx|https://nginx.org/en/]].
Caddy is famous for fully integrated [[automatic HTTPS|https://caddyserver.com/docs/automatic-https]] support, supporting [[Let's Encrypt|https://letsencrypt.org/]] on the public web as well working with [[Tailscale|https://tailscale.com/]]'s HTTPS support.
(Also [[caddy-tailscale|https://github.com/tailscale/caddy-tailscale]] exists for hosting a bunch of as separate Tailscale hosts!)
(Though [[Tailscale Serve|https://tailscale.com/kb/1312/serve]] can do basic TLS reverse proxying inside of tailscaled itself; also [[caddy-tailscale|https://github.com/tailscale/caddy-tailscale]] exists for hosting a bunch of as separate Tailscale hosts!)
When running behind a reverse proxy that rewrites paths, you can customize the base path used for wiki using the `--basepath` flag. It should match the respective Caddy rewrite directive / H2O path / Nginx location, without a trailing backslash. By default, the server assumes no path rewriting takes place.
If you're running Linux with systemd, see below for an example unit file.
{{AfterServerHosting}}
!! Updating
@ -45,8 +49,11 @@ To refresh the cached version of the server scripts, you can use this command:
Thanks to Deno providing [[sandboxing|https://docs.deno.com/runtime/manual/basics/permissions]] by default, the server process does not get permission to access anything other than what was specified in the `--allow-*` flags.
You can be confident that unless there's a horrible bug in the Deno runtime, the code is unable to touch anything outside of the database directory, nor is it able to contact external network services, nor launch processes.
If you're paranoid enough to audit all the server code, you can use the [[deno info|https://docs.deno.com/runtime/manual/tools/dependency_inspector]] dependency inspector and/or [[deno vendor|https://docs.deno.com/runtime/manual/tools/vendor]] which conveniently places everything in a friendly directory tree instead of the cache, making it a lot more convenient to review.
You can then add `--no-remote --import-map path/to/vendor/import_map.json` flags to the `deno run` invocation (and you can still refer to the URL!) to strongly guarantee that Deno will only run code from the directory you reviewed.
If you're running it on Linux with systemd, systemd options can (and should) be used to enforce an additional layer of sandboxing.
The example unit file below uses them.
If you're paranoid enough to audit all the server code, you can use the [[deno info|https://docs.deno.com/runtime/manual/tools/dependency_inspector]] dependency inspector.
You can also check out Deno's `--vendor` and `--cached-only` flags.
!! Single-Binary Deployment
@ -64,3 +71,56 @@ deno compile -o tiddlypwa-sync-server \
ADMIN_PASSWORD_HASH=Zn…PQ ADMIN_PASSWORD_SALT=q6…0o DB_PATH=/var/db/tiddly/pwa.db ./tiddlypwa-sync-server
```
!! Example systemd unit file
```
[Unit]
Description=TiddlyPWA sync server
Documentation=https://tiddly.packett.cool/
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=tiddlypwa
Group=tiddlypwa
DynamicUser=yes
StateDirectory=tiddlypwa
CacheDirectory=tiddlypwa
NoNewPrivileges=yes
PrivateTmp=yes
PrivateUsers=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectHostname=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectKernelTunables=yes
ProtectClock=yes
ProtectProc=noaccess
ProcSubset=pid
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
LockPersonality=yes
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
CapabilityBoundingSet=
EnvironmentFile=/etc/tiddlypwa.env
Environment=DB_PATH=/var/lib/tiddlypwa/pwa.db
Environment=DENO_DIR=/var/cache/tiddlypwa/deno
ExecStart=/usr/bin/deno --unstable-broadcast-channel --allow-env \
--allow-read=/var/lib/tiddlypwa --allow-write=/var/lib/tiddlypwa \
--allow-net=:7770 \
https://codeberg.org/valpackett/tiddlypwa/raw/branch/release/server/run.ts --port 7770
Restart=on-failure
[Install]
WantedBy=multi-user.target
```
Put the above into `/etc/systemd/system/tiddlypwa.service` and write the admin hash/salt variables to `/etc/tiddlypwa.env`.