Compare commits

..

19 Commits

16 changed files with 660 additions and 229 deletions

View File

@ -1,11 +1,13 @@
{ {
email neale@woozle.org email neale@woozle.org
#debug
} }
(restricted-access) { (restricted-access) {
forward_auth simpleauth:8080 { forward_auth simpleauth:8080 {
uri / 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 { drive.woozle.org {
import restricted-access import restricted-access
reverse_proxy webfs:5000
}
# XXX: browsing says method not allowed media.woozle.org {
@nondav { reverse_proxy jellyfin:8096
method HEAD GET
}
# route overrides built-in ordering
route {
file_server @nondav {
root /srv/
browse /browser.html
}
reverse_proxy webdav:8000
}
} }
# XXX: have this use caddy auth # XXX: have this use caddy auth
@ -35,89 +29,69 @@ ancestry.woozle.org {
reverse_proxy geneweb:2317 reverse_proxy geneweb:2317
} }
photos.woozle.org {
import restricted-access
reverse_proxy pigallery2:80
}
## ##
## handle sends original path ## handle sends original path
## handle_path truncates path ## handle_path truncates path
## ##
(deergrove) { deergrove.woozle.org {
handle_path /ddns/* {
import restricted-access import restricted-access
handle_path /ddns/* {
reverse_proxy ddns:8000 reverse_proxy ddns:8000
} }
handle /transmission/* { handle /transmission/* {
import restricted-access reverse_proxy transmission:9091
reverse_proxy host.docker.internal:9091
} }
handle /nzbget/* { handle /nzbget/* {
import restricted-access
reverse_proxy nzbget:6789 reverse_proxy nzbget:6789
} }
handle /sonarr/* { handle /sonarr/* {
import restricted-access
reverse_proxy sonarr:8989 reverse_proxy sonarr:8989
} }
handle /radarr/* { handle /radarr/* {
import restricted-access
reverse_proxy radarr:7878 reverse_proxy radarr:7878
} }
handle /readarr/* { handle /readarr/* {
import restricted-access
reverse_proxy readarr:8787 reverse_proxy readarr:8787
} }
handle /lidarr/* { handle /lidarr/* {
import restricted-access
reverse_proxy lidarr:8686 reverse_proxy lidarr:8686
} }
handle /prowlarr/* { handle /prowlarr/* {
import restricted-access
reverse_proxy prowlarr:9696 reverse_proxy prowlarr:9696
} }
handle /unmanic/* {
reverse_proxy unmanic:8888
}
handle_path /sucker/* { handle_path /sucker/* {
import restricted-access reverse_proxy host.lan:5801
reverse_proxy host.docker.internal:5880
} }
handle_path /netdata/* {
reverse_proxy netdata:19999
}
# Octoprint serves up broken webcam URLs
uri replace /webcam/ /octoprint/webcam/
handle_path /octoprint/* { handle_path /octoprint/* {
import restricted-access
reverse_proxy { reverse_proxy {
to 192.168.86.20:80 to 192.168.86.20:80
header_up X-Script-Name "/octoprint" 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 { handle {
import restricted-access reverse_proxy portal:8080
file_server {
root /www
}
} }
} }
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 { 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 fallthrough
} }
forward . 8.8.8.8 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. I guess I fiddle around with it pretty frequently.
## Routing ## Portal
My ISP uses Carrier-Grade NAT, In the [www](www) directory is a static HTML/JavaScript portal thing I wrote.
which I would have called IP Masquerading. 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. Other things like this exist,
So instead what I do is run this SSH connection off to my cloud server, but they all require ridiculous things like relational databases or weird config files.
listens for incoming connections on port 5800,
and then have my cloud server proxy stuff to port 5800.
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 #! /bin/sh
caddy_hash () {
echo -n "$1 "
echo "$2" | docker run --rm -i caddy caddy hash-password
}
stack=$(basename $(pwd)) 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: configs:
- source: Caddyfile - source: Caddyfile
target: /etc/caddy/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: extra_hosts:
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
- host.lan:192.168.86.2
simpleauth: simpleauth:
image: git.woozle.org/neale/simpleauth image: git.woozle.org/neale/simpleauth
command:
- -secret
- /run/secrets/simpleauth.key
secrets: secrets:
- passwd - passwd
- simpleauth.key - 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: plex:
image: ghcr.io/linuxserver/plex:1.29.2 image: lscr.io/linuxserver/plex:latest
networks: networks:
- hostnet - hostnet
environment: environment:
@ -60,14 +76,29 @@ services:
source: /srv/sys/plex source: /srv/sys/plex
target: /config target: /config
- type: bind - type: bind
source: /srv source: /srv/media/
target: /srv 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 read_only: true
bind:
propagation: rslave
transmission: transmission:
image: lscr.io/linuxserver/transmission image: lscr.io/linuxserver/transmission:latest
volumes: volumes:
- type: bind - type: bind
source: /srv/sys/transmission source: /srv/sys/transmission
@ -75,8 +106,11 @@ services:
- type: bind - type: bind
source: /srv/incoming source: /srv/incoming
target: /srv/incoming target: /srv/incoming
networks: environment:
- hostnet PEERPORT: "51413"
ports:
- 51413:51413
- 51413:51413/udp
sonarr: sonarr:
image: lscr.io/linuxserver/sonarr image: lscr.io/linuxserver/sonarr
@ -138,7 +172,7 @@ services:
source: /srv/incoming source: /srv/incoming
target: /srv/incoming target: /srv/incoming
prowlarr: prowlarr:
image: lscr.io/linuxserver/prowlarr:develop image: lscr.io/linuxserver/prowlarr:latest
extra_hosts: extra_hosts:
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
volumes: volumes:
@ -157,10 +191,16 @@ services:
target: /srv/incoming target: /srv/incoming
gitea: gitea:
image: gitea/gitea:1 image: codeberg.org/forgejo/forgejo:1.18-rootless
environment: secrets:
USER_UID: 1000 - source: gitea.ini
USER_GID: 1000 target: /etc/gitea/app.ini
uid: "1000"
gid: "1000"
mode: 0400
configs:
- source: gitea-robots.txt
target: /var/lib/gitea/custom/robots.txt
volumes: volumes:
- type: bind - type: bind
source: /srv/sys/gitea source: /srv/sys/gitea
@ -184,18 +224,37 @@ services:
source: /srv/sys/atlas/status source: /srv/sys/atlas/status
target: /var/atlas-probe/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: geneweb:
image: ravermeister/geneweb image: ravermeister/geneweb
volumes: volumes:
- type: bind - type: bind
source: /srv/sys/geneweb/etc source: /srv/sys/geneweb/
target: /usr/local/share/geneweb/etc
- type: bind
source: /srv/sys/geneweb/share/data
target: /usr/local/share/geneweb/share/data target: /usr/local/share/geneweb/share/data
- type: bind
source: /srv/sys/geneweb/log
target: /usr/local/share/geneweb/log
samba: samba:
image: dperson/samba image: dperson/samba
@ -221,15 +280,21 @@ services:
- published: 445 - published: 445
target: 445 target: 445
webdav: webfs:
image: micromata/dave image: sigoden/dufs
volumes: volumes:
- type: bind - type: bind
source: /srv source: /srv/storage
target: /data target: /srv/storage
configs: - type: bind
- source: dave.yaml source: /srv/incoming
target: /config/config.yaml target: /srv/incoming
- type: bind
source: /srv/media
target: /srv/media
command:
- -A
- /srv
user: "911:911" user: "911:911"
ddns: ddns:
@ -242,6 +307,8 @@ services:
target: /updater/data target: /updater/data
tunnel: tunnel:
deploy:
replicas: 0
image: lscr.io/linuxserver/openssh-server image: lscr.io/linuxserver/openssh-server
user: abc user: abc
entrypoint: entrypoint:
@ -272,22 +339,22 @@ configs:
name: dave.yaml-v3 name: dave.yaml-v3
Corefile: Corefile:
file: Corefile file: Corefile
name: Corefile-v2 name: Corefile-v7
Caddyfile: Caddyfile:
file: Caddyfile file: Caddyfile
name: Caddyfile-v80 name: Caddyfile-v145
index.html: portal.json:
file: www/index.html file: portal.json
name: index.html-v32 name: portal.json-v6
index.mjs: deergrove.png:
file: www/index.mjs file: www/deergrove.png
name: index.mjs-v1 name: deergrove.png-v1
index.css: netdata.conf:
file: www/index.css file: netdata.conf
name: index.css-v1 name: netdata.conf-v1
browser.html: gitea-robots.txt:
file: www/browser.html file: gitea-robots.txt
name: browser.html-v3 name: gitea-robots.txt-v1
secrets: secrets:
passwd: passwd:
@ -302,6 +369,24 @@ secrets:
known_hosts: known_hosts:
file: secrets/known_hosts file: secrets/known_hosts
name: known_hosts-v1 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: networks:
hostnet: 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" version: "3.8"
services: services:
jellyfin: coredns:
image: ghcr.io/linuxserver/jellyfin:10.7.7 image: coredns/coredns
networks:
- hostnet
configs:
- source: Corefile
target: /Corefile
caddy:
image: caddy:2-alpine
ports: ports:
- target: 8096 - target: 443
published: 8096 published: 443
- target: 7359 mode: host
published: 7359 - target: 80
protocol: udp published: 80
- target: 1900 mode: host
published: 1900 volumes:
protocol: udp - 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: environment:
TZ: US/Mountain TZ: US/Mountain
volumes: volumes:
- type: bind - type: bind
source: /mnt/ext/srv/jellyfin source: /srv/sys/jellyfin/config
target: /config target: /config
- type: bind - type: bind
source: /media source: /srv/sys/jellyfin/cache
target: /media target: /cache
- type: bind
source: /srv/media
target: /srv/media
read_only: true 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: bind:
propagation: rslave propagation: rslave
- type: bind environment:
source: /dev/video10 NMBD: enable
target: /dev/video10 RECYCLE: disable
- type: bind USERID: 911
source: /dev/video11 GROUPID: 911
target: /dev/video11 # name;path;browse;readonly;guest
- type: bind SHARE1: drive;/srv;yes;no;no
source: /dev/video12 SHARE2: retropie;/srv/media/games/retropie;yes;yes;yes
target: /dev/video12 env_file:
- type: bind - secrets/samba-users.env
source: /dev/video13 ports:
target: /dev/video13 - published: 139
- type: bind target: 139
source: /dev/video14 - published: 445
target: /dev/video14 target: 445
- type: bind
source: /dev/video15 webdav:
target: /dev/video15 image: micromata/dave
- type: bind volumes:
source: /dev/video16 - type: bind
target: /dev/video16 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; text-decoration: none;
white-space: nowrap; white-space: nowrap;
} }
nav a[target] { nav a[data-no-menu] {
display: none; display: none;
} }
nav a:hover { nav a:hover {

View File

@ -4,29 +4,12 @@
<title>Deer Grove</title> <title>Deer Grove</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <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"> <link rel="stylesheet" href="index.css">
<script src="index.mjs" type="module"></script> <script src="index.mjs" type="module"></script>
</head> </head>
<body> <body>
<nav> <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> </nav>
<section id="app"> <section id="app">
<iframe></iframe> <iframe></iframe>

View File

@ -1,8 +1,5 @@
let frames = {} let frames = {}
function activate(event, element) { function activate(event, element) {
if (element.target) {
return
}
event.preventDefault() event.preventDefault()
let parent = element.parentElement let parent = element.parentElement
@ -49,7 +46,7 @@ function frameLoaded(frame) {
} }
let defaultIcon = null let defaultIcon = null
function init() { async function init() {
let doc = document.querySelector("iframe").contentDocument let doc = document.querySelector("iframe").contentDocument
defaultIcon = document.querySelector("link[rel~='icon']").href defaultIcon = document.querySelector("link[rel~='icon']").href
@ -68,22 +65,40 @@ function init() {
let icons = doc.body.appendChild(doc.createElement("section")) let icons = doc.body.appendChild(doc.createElement("section"))
icons.classList.add("icons") icons.classList.add("icons")
for (let link of document.querySelectorAll("nav a")) { let portalURL = new URL("portal.json", window.location)
let dlink = icons.appendChild(link.cloneNode(true)) let resp = await fetch(portalURL)
dlink.textContent = "" 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")) 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" icon.style.objectFit = "cover"
} else { } else {
let text = dlink.appendChild(doc.createElement("div")) 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 reverse_proxy host.docker.internal:5880
} }
passwords.woozle.org {
reverse_proxy /notifications/hub vaultwarden:3012
reverse_proxy vaultwarden:80
}
www.woozle.org, woozle.org { www.woozle.org, woozle.org {
root * /srv/www/woozle.org root * /srv/www/woozle.org
file_server file_server

View File

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