Compare commits

...

19 Commits

16 changed files with 660 additions and 229 deletions

View File

@ -1,11 +1,13 @@
{
email neale@woozle.org
#debug
}
(restricted-access) {
forward_auth simpleauth:8080 {
uri /
copy_headers X-Simpleauth-Token
copy_headers X-Simpleauth-Username
header_down X-Simpleauth-Domain "woozle.org"
}
}
@ -15,19 +17,11 @@ git.woozle.org {
drive.woozle.org {
import restricted-access
reverse_proxy webfs:5000
}
# XXX: browsing says method not allowed
@nondav {
method HEAD GET
}
# route overrides built-in ordering
route {
file_server @nondav {
root /srv/
browse /browser.html
}
reverse_proxy webdav:8000
}
media.woozle.org {
reverse_proxy jellyfin:8096
}
# XXX: have this use caddy auth
@ -35,89 +29,69 @@ ancestry.woozle.org {
reverse_proxy geneweb:2317
}
photos.woozle.org {
import restricted-access
reverse_proxy pigallery2:80
}
##
## handle sends original path
## handle_path truncates path
##
(deergrove) {
handle_path /ddns/* {
deergrove.woozle.org {
import restricted-access
handle_path /ddns/* {
reverse_proxy ddns:8000
}
handle /transmission/* {
import restricted-access
reverse_proxy host.docker.internal:9091
reverse_proxy transmission:9091
}
handle /nzbget/* {
import restricted-access
reverse_proxy nzbget:6789
}
handle /sonarr/* {
import restricted-access
reverse_proxy sonarr:8989
}
handle /radarr/* {
import restricted-access
reverse_proxy radarr:7878
}
handle /readarr/* {
import restricted-access
reverse_proxy readarr:8787
}
handle /lidarr/* {
import restricted-access
reverse_proxy lidarr:8686
}
handle /prowlarr/* {
import restricted-access
reverse_proxy prowlarr:9696
}
handle /unmanic/* {
reverse_proxy unmanic:8888
}
handle_path /sucker/* {
import restricted-access
reverse_proxy host.docker.internal:5880
reverse_proxy host.lan:5801
}
handle_path /netdata/* {
reverse_proxy netdata:19999
}
# Octoprint serves up broken webcam URLs
uri replace /webcam/ /octoprint/webcam/
handle_path /octoprint/* {
import restricted-access
reverse_proxy {
to 192.168.86.20:80
header_up X-Script-Name "/octoprint"
}
}
handle /webcam/* {
# Octoprint doesn't properly prefix webcam URLs
import restricted-access
reverse_proxy {
to 192.168.86.20:80
}
}
handle_path /public/* {
file_server {
root /srv/storage/public
}
}
handle {
import restricted-access
file_server {
root /www
reverse_proxy portal:8080
}
}
}
deergrove.woozle.org {
import deergrove
}
sweetums.lan {
tls internal
import deergrove
}

View File

@ -1,7 +1,7 @@
. {
bind lan
bind 192.168.86.2
hosts {
192.168.86.2 sweetums.woozle.org deergrove.woozle.org drive.woozle.org git.woozle.org ancestry.woozle.org
192.168.86.2 sweetums.woozle.org deergrove.woozle.org drive.woozle.org git.woozle.org ancestry.woozle.org media.woozle.org photos.woozle.org auth.woozle.org
fallthrough
}
forward . 8.8.8.8

View File

@ -4,14 +4,16 @@ This is the stuff I run on my little Raspberry Pi.
I guess I fiddle around with it pretty frequently.
## Routing
## Portal
My ISP uses Carrier-Grade NAT,
which I would have called IP Masquerading.
In the [www](www) directory is a static HTML/JavaScript portal thing I wrote.
It doesn't need any server configuration,
you just edit the HTML to manage what apps it serves.
This means I can't bind ports on a routeable IP.
So instead what I do is run this SSH connection off to my cloud server,
listens for incoming connections on port 5800,
and then have my cloud server proxy stuff to port 5800.
Other things like this exist,
but they all require ridiculous things like relational databases or weird config files.
It's a gross kludge but it works well :)
I should probably package this up or something,
but then I'd have to put effort into publicizing it,
and run a ticketing system or something,
so meh.

9
homelab/TODO.md Normal file
View File

@ -0,0 +1,9 @@
* Single Sign-On
* [x] Replace simpleauth with somebody else's project
* [x] Set up Forgejo OIDC to Authelia (there's a guide on Authelia's site)
* [x] Persist "remember me" across reboots
* LDAP restrictions
* [x] People can only r/w their own storage
* [x] Public storage
* [x] Per-Group storage
* [x] Media-Sucker secure setup (bind to 0.0.0.0 opens to internet)

View File

@ -1,10 +1,12 @@
#! /bin/sh
caddy_hash () {
echo -n "$1 "
echo "$2" | docker run --rm -i caddy caddy hash-password
}
stack=$(basename $(pwd))
docker --context deergrove stack deploy -c docker-compose.yaml --prune $stack
extra="--resolve-image changed"
if [ "$1" = "--slow" ]; then
extra=
shift
fi
docker --context deergrove stack deploy -c docker-compose.yaml --prune $extra "$@" $stack
#docker --context deergrove compose up --detach

View File

@ -28,28 +28,44 @@ services:
configs:
- source: Caddyfile
target: /etc/caddy/Caddyfile
- source: index.html
target: /www/index.html
- source: index.mjs
target: /www/index.mjs
- source: index.css
target: /www/index.css
- source: browser.html
target: /browser.html
extra_hosts:
- host.docker.internal:host-gateway
- host.lan:192.168.86.2
simpleauth:
image: git.woozle.org/neale/simpleauth
command:
- -secret
- /run/secrets/simpleauth.key
secrets:
- passwd
- simpleauth.key
portal:
image: git.woozle.org/neale/portal
configs:
- source: portal.json
target: /web/portal.json
- source: deergrove.png
target: /web/portal.png
jellyfin:
image: jellyfin/jellyfin
deploy:
replicas: 0
environment:
TZ: US/Mountain
volumes:
- type: bind
source: /srv/sys/jellyfin/config
target: /config
- type: bind
source: /srv/sys/jellyfin/cache
target: /cache
- type: bind
source: /srv/media/
target: /srv/media/
read_only: true
plex:
image: ghcr.io/linuxserver/plex:1.29.2
image: lscr.io/linuxserver/plex:latest
networks:
- hostnet
environment:
@ -60,14 +76,29 @@ services:
source: /srv/sys/plex
target: /config
- type: bind
source: /srv
target: /srv
source: /srv/media/
target: /srv/media/
read_only: true
pigallery2:
image: bpatrik/pigallery2:latest
volumes:
- type: bind
source: /srv/sys/pigallery2/config
target: /app/data/config
- type: bind
source: /srv/sys/pigallery2/db
target: /app/data/db
- type: bind
source: /srv/sys/pigallery2/cache
target: /app/data/cache
- type: bind
source: /srv/media/photos
target: /srv/media/photos
read_only: true
bind:
propagation: rslave
transmission:
image: lscr.io/linuxserver/transmission
image: lscr.io/linuxserver/transmission:latest
volumes:
- type: bind
source: /srv/sys/transmission
@ -75,8 +106,11 @@ services:
- type: bind
source: /srv/incoming
target: /srv/incoming
networks:
- hostnet
environment:
PEERPORT: "51413"
ports:
- 51413:51413
- 51413:51413/udp
sonarr:
image: lscr.io/linuxserver/sonarr
@ -138,7 +172,7 @@ services:
source: /srv/incoming
target: /srv/incoming
prowlarr:
image: lscr.io/linuxserver/prowlarr:develop
image: lscr.io/linuxserver/prowlarr:latest
extra_hosts:
- host.docker.internal:host-gateway
volumes:
@ -157,10 +191,16 @@ services:
target: /srv/incoming
gitea:
image: gitea/gitea:1
environment:
USER_UID: 1000
USER_GID: 1000
image: codeberg.org/forgejo/forgejo:1.18-rootless
secrets:
- source: gitea.ini
target: /etc/gitea/app.ini
uid: "1000"
gid: "1000"
mode: 0400
configs:
- source: gitea-robots.txt
target: /var/lib/gitea/custom/robots.txt
volumes:
- type: bind
source: /srv/sys/gitea
@ -184,18 +224,37 @@ services:
source: /srv/sys/atlas/status
target: /var/atlas-probe/status
netdata:
image: netdata/netdata
hostname: "{{.Node.Hostname}}"
deploy:
replicas: 0
environment:
NETDATA_DISABLE_CLOUD: "1"
cap_add:
- SYS_PTRACE
volumes:
- type: bind
source: /
target: /host
read_only: true
- type: bind
source: /srv/sys/netdata/lib
target: /var/lib/netdata
- type: bind
source: /srv/sys/netdata/cache
target: /var/cache/netdata
configs:
- source: netdata.conf
target: /etc/netdata/netdata.conf
geneweb:
image: ravermeister/geneweb
volumes:
- type: bind
source: /srv/sys/geneweb/etc
target: /usr/local/share/geneweb/etc
- type: bind
source: /srv/sys/geneweb/share/data
source: /srv/sys/geneweb/
target: /usr/local/share/geneweb/share/data
- type: bind
source: /srv/sys/geneweb/log
target: /usr/local/share/geneweb/log
samba:
image: dperson/samba
@ -221,15 +280,21 @@ services:
- published: 445
target: 445
webdav:
image: micromata/dave
webfs:
image: sigoden/dufs
volumes:
- type: bind
source: /srv
target: /data
configs:
- source: dave.yaml
target: /config/config.yaml
source: /srv/storage
target: /srv/storage
- type: bind
source: /srv/incoming
target: /srv/incoming
- type: bind
source: /srv/media
target: /srv/media
command:
- -A
- /srv
user: "911:911"
ddns:
@ -242,6 +307,8 @@ services:
target: /updater/data
tunnel:
deploy:
replicas: 0
image: lscr.io/linuxserver/openssh-server
user: abc
entrypoint:
@ -272,22 +339,22 @@ configs:
name: dave.yaml-v3
Corefile:
file: Corefile
name: Corefile-v2
name: Corefile-v7
Caddyfile:
file: Caddyfile
name: Caddyfile-v80
index.html:
file: www/index.html
name: index.html-v32
index.mjs:
file: www/index.mjs
name: index.mjs-v1
index.css:
file: www/index.css
name: index.css-v1
browser.html:
file: www/browser.html
name: browser.html-v3
name: Caddyfile-v145
portal.json:
file: portal.json
name: portal.json-v6
deergrove.png:
file: www/deergrove.png
name: deergrove.png-v1
netdata.conf:
file: netdata.conf
name: netdata.conf-v1
gitea-robots.txt:
file: gitea-robots.txt
name: gitea-robots.txt-v1
secrets:
passwd:
@ -302,6 +369,24 @@ secrets:
known_hosts:
file: secrets/known_hosts
name: known_hosts-v1
gitea.ini:
file: secrets/gitea.ini
name: gitea.ini-v4
jwt.secret:
file: secrets/jwt.secret
name: jwt.secret-v1
storage.secret:
file: secrets/storage.secret
name: storage.secret-v1
session.secret:
file: secrets/session.secret
name: session.secret-v1
users.yaml:
file: secrets/users.yaml
name: users.yaml-v9
authelia.oidc.yaml:
file: secrets/authelia.oidc.yaml
name: authelia.oidc.yaml-v2
networks:
hostnet:

76
homelab/portal.json Normal file
View File

@ -0,0 +1,76 @@
[
{
"title": "Storage",
"href": "https://drive.woozle.org/",
"icon": "https://drive.woozle.org/storage/public/icons/cloud-folder.png",
"target": "_blank"
},
{
"title": "Photos",
"href": "https://photos.woozle.org/",
"icon": "https://photos.woozle.org/assets/icon_inv.png",
"target": "_blank"
},
{
"title": "Git",
"href": "https://git.woozle.org/",
"icon": "https://git.woozle.org/assets/img/logo.svg",
"target": "_blank"
},
{
"title": "Genealogy",
"href": "https://ancestry.woozle.org/",
"icon": "https://ancestry.woozle.org/images/arbre_start.png",
"target": "_blank"
},
{
"title": "Movies",
"href": "https://deergrove.woozle.org/radarr/",
"icon": "/radarr/Content/Images/logo.svg"
},
{
"title": "Episodes",
"href": "https://deergrove.woozle.org/sonarr/",
"icon": "/sonarr/Content/Images/logo.svg"
},
{
"title": "Music",
"href": "https://deergrove.woozle.org/lidarr/",
"icon": "/lidarr/Content/Images/logo.svg"
},
{
"title": "Books",
"href": "https://deergrove.woozle.org/readarr/",
"icon": "/readarr/Content/Images/logo.svg"
},
{
"title": "Media Sucker",
"href": "https://deergrove.woozle.org/sucker/",
"icon": "/sucker/cd-dvd.svg"
},
{
"title": "Searcher",
"href": "https://deergrove.woozle.org/prowlarr/",
"icon": "/prowlarr/Content/Images/logo.png"
},
{
"title": "Usenet",
"href": "https://deergrove.woozle.org/nzbget/",
"icon": "/nzbget/img/favicon-256x256.png"
},
{
"title": "BitTorrent",
"href": "https://deergrove.woozle.org/transmission/web/",
"icon": "/transmission/web/images/webclip-icon.png"
},
{
"title": "3D Printer",
"href": "https://deergrove.woozle.org/octoprint/",
"icon": "/octoprint/static/img/logo.png"
},
{
"title": "Host Stats",
"href": "/stat.html",
"app": "stat"
}
}

View File

@ -1,47 +1,363 @@
version: "3.8"
services:
jellyfin:
image: ghcr.io/linuxserver/jellyfin:10.7.7
coredns:
image: coredns/coredns
networks:
- hostnet
configs:
- source: Corefile
target: /Corefile
caddy:
image: caddy:2-alpine
ports:
- target: 8096
published: 8096
- target: 7359
published: 7359
protocol: udp
- target: 1900
published: 1900
protocol: udp
- target: 443
published: 443
mode: host
- target: 80
published: 80
mode: host
volumes:
- type: bind
source: /srv
target: /srv
read_only: true
- type: bind
source: /srv/sys/caddy
target: /data/caddy
configs:
- source: Caddyfile
target: /etc/caddy/Caddyfile
- source: index.html
target: /www/index.html
- source: index.mjs
target: /www/index.mjs
- source: deergrove.png
target: /www/deergrove.png
- source: index.css
target: /www/index.css
- source: browser.html
target: /browser.html
extra_hosts:
- host.docker.internal:host-gateway
authelia:
image: authelia/authelia
environment:
AUTHELIA_JWT_SECRET_FILE: /run/secrets/jwt.secret
AUTHELIA_SESSION_SECRET_FILE: /run/secrets/session.secret
AUTHELIA_STORAGE_ENCRYPTION_FILE: /run/secrets/storage.secret
secrets:
- jwt.secret
- session.secret
- storage.secret
- users.yaml
configs:
- source: authelia.yaml
target: /config/configuration.yml
volumes:
- type: bind
source: /srv/sys/authelia
target: /srv/sys/authelia
jellyfin:
image: jellyfin/jellyfin
environment:
TZ: US/Mountain
volumes:
- type: bind
source: /mnt/ext/srv/jellyfin
source: /srv/sys/jellyfin/config
target: /config
- type: bind
source: /media
target: /media
source: /srv/sys/jellyfin/cache
target: /cache
- type: bind
source: /srv/media
target: /srv/media
read_only: true
plex:
image: ghcr.io/linuxserver/plex:1.29.2
networks:
- hostnet
environment:
TZ: US/Mountain
VERSION: public
volumes:
- type: bind
source: /srv/sys/plex
target: /config
- type: bind
source: /srv
target: /srv
read_only: true
transmission:
image: lscr.io/linuxserver/transmission
volumes:
- type: bind
source: /srv/sys/transmission
target: /config
- type: bind
source: /srv/incoming
target: /srv/incoming
networks:
- hostnet
sonarr:
image: lscr.io/linuxserver/sonarr
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- type: bind
source: /srv/sys/sonarr
target: /config
- type: bind
source: /srv/media/tv
target: /srv/media/tv
- type: bind
source: /srv/incoming
target: /srv/incoming
radarr:
image: lscr.io/linuxserver/radarr
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- type: bind
source: /srv/sys/radarr
target: /config
- type: bind
source: /srv/media/movies
target: /srv/media/movies
- type: bind
source: /srv/incoming
target: /srv/incoming
lidarr:
image: lscr.io/linuxserver/lidarr
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- type: bind
source: /srv/sys/lidarr
target: /config
- type: bind
source: /srv/media/music
target: /srv/media/music
- type: bind
source: /srv/incoming
target: /srv/incoming
readarr:
image: lscr.io/linuxserver/readarr:develop
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- type: bind
source: /srv/sys/readarr
target: /config
- type: bind
source: /srv/media/books
target: /srv/media/books
- type: bind
source: /srv/media/audiobooks
target: /srv/media/audiobooks
- type: bind
source: /srv/incoming
target: /srv/incoming
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
extra_hosts:
- host.docker.internal:host-gateway
volumes:
- type: bind
source: /srv/sys/prowlarr
target: /config
nzbget:
image: lscr.io/linuxserver/nzbget
volumes:
- type: bind
source: /srv/sys/nzbget
target: /config
- type: bind
source: /srv/incoming
target: /srv/incoming
forgejo:
image: codeberg.org/forgejo/forgejo:1.18-rootless
secrets:
- source: forgejo.ini
target: /etc/gitea/app.ini
uid: "1000"
gid: "1000"
mode: 0400
volumes:
- type: bind
source: /srv/sys/forgejo
target: /data
- type: bind
source: /etc/timezone
target: /etc/timezone
read_only: true
- type: bind
source: /etc/localtime
target: /etc/localtime
read_only: true
atlas:
image: ctassisf/ripe-atlas-alpine:arm64v8
volumes:
- type: bind
source: /srv/sys/atlas/etc
target: /var/atlas-probe/etc
- type: bind
source: /srv/sys/atlas/status
target: /var/atlas-probe/status
geneweb:
image: ravermeister/geneweb
volumes:
- type: bind
source: /srv/sys/geneweb/etc
target: /usr/local/share/geneweb/etc
- type: bind
source: /srv/sys/geneweb/share/data
target: /usr/local/share/geneweb/share/data
- type: bind
source: /srv/sys/geneweb/log
target: /usr/local/share/geneweb/log
samba:
image: dperson/samba
volumes:
- type: bind
source: /srv
target: /srv
bind:
propagation: rslave
- type: bind
source: /dev/video10
target: /dev/video10
- type: bind
source: /dev/video11
target: /dev/video11
- type: bind
source: /dev/video12
target: /dev/video12
- type: bind
source: /dev/video13
target: /dev/video13
- type: bind
source: /dev/video14
target: /dev/video14
- type: bind
source: /dev/video15
target: /dev/video15
- type: bind
source: /dev/video16
target: /dev/video16
environment:
NMBD: enable
RECYCLE: disable
USERID: 911
GROUPID: 911
# name;path;browse;readonly;guest
SHARE1: drive;/srv;yes;no;no
SHARE2: retropie;/srv/media/games/retropie;yes;yes;yes
env_file:
- secrets/samba-users.env
ports:
- published: 139
target: 139
- published: 445
target: 445
webdav:
image: micromata/dave
volumes:
- type: bind
source: /srv
target: /data
configs:
- source: dave.yaml
target: /config/config.yaml
user: "911:911"
ddns:
image: qmcgaw/ddns-updater
dns:
- 1.1.1.1
volumes:
- type: bind
source: /srv/sys/ddns-updater
target: /updater/data
tunnel:
deploy:
replicas: 0
image: lscr.io/linuxserver/openssh-server
user: abc
entrypoint:
- /usr/bin/ssh
- -N
- -R 172.17.0.1:5880:caddy:80 # 172.17.0.1 = docker host IP
- -R :5822:host.docker.internal:22
- -o ServerAliveInterval=30
- core@melville.woozle.org
extra_hosts:
- host.docker.internal:host-gateway
secrets:
- source: tunnel
target: /config/.ssh/id_rsa
uid: "911"
gid: "911"
mode: 0600
- source: known_hosts
target: /config/.ssh/known_hosts
uid: "911"
gid: "911"
mode: 0600
configs:
dave.yaml:
file: dave.yaml
name: dave.yaml-v3
Corefile:
file: Corefile
name: Corefile-v3
Caddyfile:
file: Caddyfile
name: Caddyfile-v89
index.html:
file: www/index.html
name: index.html-v36
index.mjs:
file: www/index.mjs
name: index.mjs-v1
index.css:
file: www/index.css
name: index.css-v1
browser.html:
file: www/browser.html
name: browser.html-v3
deergrove.png:
file: www/deergrove.png
name: deergrove.png-v1
authelia.yaml:
file: authelia.yaml
name: authelia.yaml-v1
secrets:
passwd:
file: secrets/passwd
name: passwd-v2
simpleauth.key:
file: secrets/simpleauth.key
name: simpleauth.key-v1
tunnel:
file: secrets/tunnel
name: tunnel-v1
known_hosts:
file: secrets/known_hosts
name: known_hosts-v1
forgejo.ini:
file: secrets/forgejo.ini
name: forgejo.ini-v1
jwt.secret:
file: secrets/jwt.secret
name: jwt.secret-v1
storage.secret:
file: secrets/storage.secret
name: storage.secret-v1
session.secret:
file: secrets/session.secret
name: session.secret-v1
users.yaml:
file: secrets/users.yaml
name: users.yaml-v1
networks:
hostnet:
external: true
name: host

View File

@ -1,36 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Woozle Drive</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/dom111/webdav-js/assets/css/style-min.css">
<script>
function init() {
// This is dumb, but webdav-min.js doesn't have any checks to make sure the document is loaded.
let scr = document.head.appendChild(document.createElement("script"))
scr.src = "https://cdn.jsdelivr.net/gh/dom111/webdav-js/src/webdav-min.js"
for (let e of document.querySelectorAll(".listing")) {
console.log("Let's pray the WebDAV stuff works!", e)
e.remove()
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init)
} else {
init()
}
</script>
</head>
<body>
<ul class="listing">
{{range .Items}}
<li>
<a href="{{html .URL}}">{{html .Name}}</a>
<span class="size" data-value="{{.Size}}">{{.HumanSize}}</span>
<time>{{.HumanModTime "2006-01-02T15:04:05Z"}}</time>
</li>
{{end}}
</ul>
</body>
</html>

BIN
homelab/www/deergrove.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -27,7 +27,7 @@ nav a {
text-decoration: none;
white-space: nowrap;
}
nav a[target] {
nav a[data-no-menu] {
display: none;
}
nav a:hover {

View File

@ -4,29 +4,12 @@
<title>Deer Grove</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="icons/deergrove.png">
<link rel="icon" href="deergrove.png">
<link rel="stylesheet" href="index.css">
<script src="index.mjs" type="module"></script>
</head>
<body>
<nav>
<a href="/sonarr/" data-icon="/sonarr/Content/Images/logo.svg" title="Episode manager">Shows</a>
<a href="/radarr/" data-icon="/radarr/Content/Images/logo.svg" title="Movie manager">Movies</a>
<a href="/lidarr/" data-icon="/lidarr/Content/Images/logo.svg" title="Music manager">Music</a>
<a href="/readarr/" data-icon="/readarr/Content/Images/logo.svg" title="Book manager">Books</a>
<a href="/sucker/" data-icon="/sucker/cd-dvd.svg" title="Media Sucker">Sucker</a>
<hr>
<a href="/prowlarr/" data-icon="/prowlarr/Content/Images/logo.png" title="Indexer/Searcher">Search</a>
<a href="/nzbget/" data-icon="/nzbget/img/favicon-256x256.png" title="Usenet downloader">Usenet</a>
<a href="/transmission/web/" data-icon="/transmission/web/style/transmission/images/logo.png" title="BitTorrent downloader">BitTorrent</a>
<hr>
<a href="/octoprint/" data-icon="/octoprint/static/img/logo.png" title="3D Printer Front-End">Octoprint</a>
<a href="/wallart/" data-icon="/wallart/wallart.png" title="Wall Art uploader">Wall Art</a>
<!-- Items that launch a new tab don't appear in the top menu -->
<a href="https://git.woozle.org" target="_blank" data-icon="https://git.woozle.org/assets/img/logo.svg" title="Git repositories">Git</a>
<a href="https://drive.woozle.org/" target="_blank" data-icon="/public/icons/cloud-folder.png" titled="Shared storage">Drive</a>
<a href="https://ancestry.woozle.org/" target="_blank" data-icon="https://ancestry.woozle.org/images/favicon_gwd.png" title="Genealogy">Ancestry</a>
</nav>
<section id="app">
<iframe></iframe>

View File

@ -1,8 +1,5 @@
let frames = {}
function activate(event, element) {
if (element.target) {
return
}
event.preventDefault()
let parent = element.parentElement
@ -49,7 +46,7 @@ function frameLoaded(frame) {
}
let defaultIcon = null
function init() {
async function init() {
let doc = document.querySelector("iframe").contentDocument
defaultIcon = document.querySelector("link[rel~='icon']").href
@ -68,22 +65,40 @@ function init() {
let icons = doc.body.appendChild(doc.createElement("section"))
icons.classList.add("icons")
for (let link of document.querySelectorAll("nav a")) {
let dlink = icons.appendChild(link.cloneNode(true))
dlink.textContent = ""
let portalURL = new URL("portal.json", window.location)
let resp = await fetch(portalURL)
let obj = await resp.json()
let nav = document.querySelector("nav")
for (let app of obj) {
let hlink = null
if (app.target != "_blank") {
hlink = nav.appendChild(document.createElement("a"))
hlink.href = app.href
hlink.textContent = app.title
if (app.target) {
hlink.target = app.target
} else {
hlink.addEventListener("click", event => activate(event, hlink))
}
}
if (link.dataset.icon) {
let dlink = icons.appendChild(doc.createElement("a"))
dlink.href = app.href
if (app.target) {
dlink.target = app.target
} else {
dlink.addEventListener("click", event => activate(event, hlink))
}
if (app.icon) {
let icon = dlink.appendChild(doc.createElement("img"))
icon.src = link.dataset.icon
icon.src = app.icon
icon.alt = app.title
icon.title = app.title
icon.style.objectFit = "cover"
} else {
let text = dlink.appendChild(doc.createElement("div"))
text.textContent = link.textContent
text.textContent = app.title
}
// Make both of them update the selected tab
link.addEventListener("click", event => activate(event, link))
dlink.addEventListener("click", event => activate(event, link))
}
}

View File

@ -17,6 +17,11 @@ deergrove.woozle.org, git.woozle.org, ancestry.woozle.org, drive.woozle.org {
reverse_proxy host.docker.internal:5880
}
passwords.woozle.org {
reverse_proxy /notifications/hub vaultwarden:3012
reverse_proxy vaultwarden:80
}
www.woozle.org, woozle.org {
root * /srv/www/woozle.org
file_server

View File

@ -36,5 +36,5 @@ services:
configs:
Caddyfile:
file: Caddyfile
name: Caddyfile-v9
name: Caddyfile-v11