diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..788480d082655da6a5a747ef986be7c9fc43c3b5 --- /dev/null +++ b/.env.example @@ -0,0 +1,26 @@ +# shellcheck disable +HOME_DOMAIN=local.test +ADMIN_EMAIL= +USER_ID="501" +GROUP_ID="20" + +APP_ROOT=./.apps +STORE_ROOT=/Volumes +CLOVER_ROOT=/Volumes/clover +MEDIA_ROOT=/Volumes/media + +MAILER_ADDRESS=smtp.example.com +MAILER_USERNAME=me@paperclover.net +MAILER_PASSWORD=yolo + +POSTGRES_PASSWORD= +POSTGRES_PASSWORD_AUTHELIA= +POSTGRES_PASSWORD_FORGEJO= +POSTGRES_PASSWORD_DAWARICH= +POSTGRES_PASSWORD_JELLYFIN= + +CLOVER_SOT_KEY= + +FORGEJO_SERVER_LFS_JWT_SECRET= +FORGEJO_OAUTH2_JWT_SECRET= +FORGEJO_SECURITY_INTERNAL_TOKEN= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2ba30b8b70a47d9d0173a99cc0d4210580ab3bb7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +/.apps +/certs +/sitegen diff --git a/compose.yaml b/compose.yaml new file mode 100755 index 0000000000000000000000000000000000000000..edef565d1b165e4f8671bcfa9856dcb3aa2ac6a2 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,423 @@ +services: + # web server + caddy: + container_name: caddy + image: caddy:latest + ports: + - 80:80 + - 443:443 + restart: unless-stopped + user: "$USER_ID:$GROUP_ID" + volumes: + - "./config:/etc/caddy:ro" + - "${APP_ROOT}/caddy:/data/caddy:rw" + environment: + - HOME_DOMAIN + develop: + watch: + - path: ./config/Caddyfile + action: restart + # SSO + authelia: + image: "authelia/authelia" + container_name: authelia + volumes: + - "./config/authelia:/config" + restart: "unless-stopped" + secrets: + - "authelia-jwt-secret" + - "authelia-session-secret" + - "authelia-encryption-key" + environment: + PUID: "$USER_ID" + PGID: "$GROUP_ID" + TZ: America/Los_Angelas + AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: "/run/secrets/authelia-jwt-secret" + AUTHELIA_SESSION_SECRET_FILE: "/run/secrets/authelia-session-secret" + AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: "/run/secrets/authelia-encryption-key" + AUTHELIA_STORAGE_POSTGRES_ADDRESS: postgres:5432 + AUTHELIA_STORAGE_POSTGRES_DATABASE: authelia + AUTHELIA_STORAGE_POSTGRES_USERNAME: authelia + AUTHELIA_STORAGE_POSTGRES_PASSWORD: "${POSTGRES_PASSWORD_AUTHELIA}" + AUTHELIA_NOTIFIER_SMTP_ADDRESS: "smtp://${MAILER_ADDRESS}" + AUTHELIA_NOTIFIER_SMTP_USERNAME: "${MAILER_USERNAME}" + AUTHELIA_NOTIFIER_SMTP_PASSWORD: "${MAILER_PASSWORD}" + # file manager + copyparty: # port 3923 + image: copyparty/ac + container_name: copyparty + user: "$USER_ID:$GROUP_ID" + volumes: + - "${STORE_ROOT}:/w" + - "${APP_ROOT}/copyparty:/cfg" + - ./config/copyparty.conf:/cfg/copyparty.conf + restart: unless-stopped + # paper clover's database service + clover-source-of-truth: + build: + context: sitegen/ + dockerfile: src/source-of-truth.dockerfile + pull_policy: build + container_name: clover-source-of-truth + user: "$USER_ID:$GROUP_ID" + environment: + - "PORT=80" + - "CLOVER_DB=/data" + - "CLOVER_FILE_RAW=/published" + - "CLOVER_FILE_DERIVED=/data/derived" + - "CLOVER_SOT_KEY=${CLOVER_SOT_KEY}" + restart: unless-stopped + volumes: + - "$CLOVER_ROOT/Documents/Config/paperclover:/data:rw" + - "$CLOVER_ROOT/Published:/published:ro" + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80"] + interval: 10s + timeout: 5s + retries: 3 + start_period: 40s + # git hosting + forgejo: # port 3000 + container_name: forgejo + environment: + USER_UID: "$USER_ID" + USER_GID: "$GROUP_ID" + FORGEJO_CUSTOM: "/custom" + FORGEJO__MAILER__SMTP_ADDR: "${MAILER_ADDRESS}" + FORGEJO__MAILER__USER: "${MAILER_USERNAME}" + FORGEJO__MAILER__PASSWD: "${MAILER_PASSWORD}" + FORGEJO__SERVER__LFS_JWT_SECRET: "${FORGEJO_SERVER_LFS_JWT_SECRET}" + FORGEJO__OAUTH2__JWT_SECRET: "${FORGEJO_OAUTH2_JWT_SECRET}" + FORGEJO__SECURITY__SECRET_KEY: "${FORGEJO_SECURITY_SECRET_KEY}" + FORGEJO__SECURITY__INTERNAL_TOKEN: "${FORGEJO_SECURITY_INTERNAL_TOKEN}" + image: code.forgejo.org/forgejo/forgejo:13 + ports: + - 22:22 + - 40244:3000 + restart: unless-stopped + volumes: + - "${APP_ROOT}/forgejo:/data:rw" + - "/etc/timezone:/etc/timezone:ro" + - "/etc/localtime:/etc/localtime:ro" + - ./config/forgejo:/custom:ro + - ./config/font:/custom/public/assets/font:ro + forgejo-anubis: + container_name: forgejo-anubis + image: ghcr.io/techarohq/anubis:latest + user: "$USER_ID:$GROUP_ID" + restart: unless-stopped + environment: + BIND: :80 + TARGET: http://forgejo:3000 + forgejo-runner: + container_name: forgejo-runner + image: code.forgejo.org/forgejo/runner:12 + links: + - docker-in-docker + depends_on: + docker-in-docker: + condition: service_started + environment: + DOCKER_HOST: tcp://docker-in-docker:2375 + user: "$USER_ID:$GROUP_ID" + volumes: + - "${APP_ROOT}/forgejo-runner:/data" + restart: unless-stopped + command: forgejo-runner daemon + docker-in-docker: + container_name: docker-in-docker + image: docker:dind + privileged: "true" + command: + - dockerd + - -H + - tcp://0.0.0.0:2375 + - --tls=false + restart: unless-stopped + # media consumption + jellyfin: # port 8096 + container_name: jellyfin + user: "$USER_ID:$GROUP_ID" + environment: + - "JELLYFIN_PublishedServerUrl=https://jelly.${HOME_DOMAIN}" + image: jellyfin/jellyfin + restart: unless-stopped + ports: + - 8096:8096 + volumes: + - "${MEDIA_ROOT}:/media:rw" + - "${APP_ROOT}/jellyfin:/config:rw" + - "${APP_ROOT}/jellyfin/cache:/cache:rw" + - "./config/jellyfin:/config/custom:ro" + - "./config/jellyfin/branding.xml:/config/config/branding.xml:ro" + - "./config/font:/usr/local/share/fonts/custom:ro" + navidrome: # port 4533 + container_name: navidrome + image: deluan/navidrome:latest + user: "$USER_ID:$GROUP_ID" + restart: unless-stopped + volumes: + - "${APP_ROOT}/navidrone:/data:rw" + - "${MEDIA_ROOT}/music:/music:ro" + environment: + - ND_SCANNER_GROUPALBUMRELEASES=1 + - ND_BASEURL=https://nd.${HOME_DOMAIN}/ + - ND_PID_ALBUM=folder + # media aquisition + qbittorrent: # webui on port 23938 + platform: linux/amd64 # not available for aarch64 + container_name: qbittorrent + image: binhex/arch-qbittorrentvpn + restart: unless-stopped + cap_add: + - NET_ADMIN + secrets: + - openvpn-credentials + environment: + - VPN_ENABLED=yes + - VPN_PROV=pia + - VPN_CLIENT=openvpn + - ENABLE_STARTUP_SCRIPTS=no + - ENABLE_PRIVOXY=no + - STRICT_PORT_FORWARD=no + - USERSPACE_WIREGUARD=no + - ENABLE_SOCKS=no + - SOCKS_USER=admin + - SOCKS_PASS=socks + - LAN_NETWORK=192.168.86.0/24 + - NAME_SERVERS=84.200.69.80,37.235.1.174,1.1.1.1,37.235.1.177,84.200.70.40,1.0.0.1 + - VPN_INPUT_PORTS=1234 + - VPN_OUTPUT_PORTS=5678 + - DEBUG=false + - WEBUI_PORT=23938 + - UMASK=000 + - PUID=$USER_ID + - PGID=$GROUP_ID + volumes: + - "${TORRENT_TEMP:-${APP_ROOT}/qbittorrent/tmp}:/data/tmp" + - "${MEDIA_ROOT}:/data/media" + - "${APP_ROOT}/qbittorrent:/config" + - "./config/openvpn:/config/openvpn:ro" + - "/etc/localtime:/etc/localtime:ro" + # jackett: # port 9117 + # container_name: jackett + # environment: + # - "PUID=$USER_ID" + # - "PGID=$GROUP_ID" + # - "TZ=America/Los_Angelas" + # image: lscr.io/linuxserver/jackett:latest + # restart: unless-stopped + # volumes: + # - "${APP_ROOT}/jackett:/config/Jackett" + # sonarr: # port 8989 + # container_name: sonarr + # environment: + # - "PUID=$USER_ID" + # - "PGID=$GROUP_ID" + # - "TZ=America/Los_Angelas" + # image: lscr.io/linuxserver/sonarr:latest + # volumes: + # - "${APP_ROOT}/sonarr:/config" + # - "${TORRENT_TEMP:-${APP_ROOT}/qbittorrent/tmp}:/data/tmp" + # - "${MEDIA_ROOT}:/data/media" + # restart: unless-stopped + # radarr: # port 7878 + # container_name: radarr + # environment: + # - "PUID=$USER_ID" + # - "PGID=$GROUP_ID" + # - "TZ=America/Los_Angelas" + # image: lscr.io/linuxserver/radarr:latest + # volumes: + # - "${APP_ROOT}/radarr:/config" + # - "${TORRENT_TEMP:-${APP_ROOT}/qbittorrent/tmp}:/data/tmp" + # - "${MEDIA_ROOT}:/data/media" + # restart: unless-stopped + # language models + opencode: # port 4096 + container_name: opencode + build: + context: config/opencode/ + dockerfile: Dockerfile + pull_policy: build + user: "$USER_ID:$GROUP_ID" + volumes: + - "${APP_ROOT}/opencode/.ssh:/home/clover/.ssh" + - "${APP_ROOT}/opencode/.config:/home/clover/.config" + - "${APP_ROOT}/opencode/.local:/home/clover/.local" + - "${APP_ROOT}/opencode/code:/home/clover/code" + # databases + postgres: + container_name: postgres + build: + context: config/postgres/ + dockerfile: Dockerfile + args: + USER_ID: $USER_ID + GROUP_ID: $GROUP_ID + pull_policy: build + restart: unless-stopped + user: "$USER_ID:$GROUP_ID" + environment: + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" + POSTGRES_PASSWORD_FORGEJO: "${POSTGRES_PASSWORD_FORGEJO}" + POSTGRES_PASSWORD_AUTHELIA: "${POSTGRES_PASSWORD_AUTHELIA}" + POSTGRES_PASSWORD_DAWARICH: "${POSTGRES_PASSWORD_DAWARICH}" + POSTGRES_PASSWORD_JELLYFIN: "${POSTGRES_PASSWORD_JELLYFIN}" + volumes: + - "${APP_ROOT}/pg_data:/var/lib/postgresql/data/" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U authelia -d authelia"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + pgadmin: # port 80 + container_name: pgadmin + image: "docker.io/dpage/pgadmin4:latest" + user: "5050:5050" + environment: + PGADMIN_DEFAULT_EMAIL: "pgadmin4@pgadmin.org" + PGADMIN_DEFAULT_PASSWORD: "secret" + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False" + PGADMIN_CONFIG_SERVER_MODE: "False" + volumes: + - "${APP_ROOT}/pg_admin:/var/lib/pgadmin" + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + redis: # port 6379 + container_name: redis + image: redis:8 + user: "$USER_ID:$GROUP_ID" + restart: unless-stopped + volumes: + - "${APP_ROOT}/redis:/data" + redis-insight: # port 5540 + container_name: redis-insight + image: redis/redisinsight:latest + environment: + RI_REDIS_HOST: redis + restart: unless-stopped + # location history + dawarich-app: # port 30161 + image: freikin/dawarich:latest + container_name: dawarich-app + volumes: + - "${APP_ROOT}/dawarich_public:/var/app/public" + - "${APP_ROOT}/dawarich_watched:/var/app/tmp/imports/watched" + - "${APP_ROOT}/dawarich_storage:/var/app/storage" + - "${APP_ROOT}/dawarich_data:/dawarich_db_data" + entrypoint: web-entrypoint.sh + command: + - bin/rails + - server + - -p + - "30161" + - -b + - "::" + restart: on-failure + user: "$USER_ID:$GROUP_ID" + environment: + RAILS_ENV: production + REDIS_URL: "redis://redis:6379" + DATABASE_HOST: postgres + DATABASE_USERNAME: dawarich + DATABASE_PASSWORD: "${POSTGRES_PASSWORD_DAWARICH}" + DATABASE_NAME: dawarich + MIN_MINUTES_SPENT_IN_CITY: 60 + APPLICATION_HOSTS: "localhost,zenith,dawarich.${HOME_DOMAIN}" + TIME_ZONE: America/Los_Angeles + APPLICATION_PROTOCOL: http + PROMETHEUS_EXPORTER_ENABLED: "false" + PROMETHEUS_EXPORTER_HOST: 0.0.0.0 + PROMETHEUS_EXPORTER_PORT: 9394 + SELF_HOSTED: "true" + STORE_GEODATA: "true" + deploy: + resources: + limits: + cpus: "0.50" + memory: 4G + dawarich-sidekiq: + image: freikin/dawarich:latest + container_name: dawarich-sidekiq + volumes: + - "${APP_ROOT}/dawarich_public:/var/app/public" + - "${APP_ROOT}/dawarich_watched:/var/app/tmp/imports/watched" + - "${APP_ROOT}/dawarich_storage:/var/app/storage" + entrypoint: sidekiq-entrypoint.sh + command: ["sidekiq"] + restart: on-failure + user: "$USER_ID:$GROUP_ID" + environment: + RAILS_ENV: production + REDIS_URL: "redis://redis:6379" + DATABASE_HOST: postgres + DATABASE_USERNAME: dawarich + DATABASE_PASSWORD: "${POSTGRES_PASSWORD_DAWARICH}" + DATABASE_NAME: dawarich + APPLICATION_HOSTS: localhost + BACKGROUND_PROCESSING_CONCURRENCY: 10 + APPLICATION_PROTOCOL: http + PROMETHEUS_EXPORTER_ENABLED: "false" + PROMETHEUS_EXPORTER_HOST: dawarich_app + PROMETHEUS_EXPORTER_PORT: 9394 + SELF_HOSTED: "true" + STORE_GEODATA: "true" + logging: + driver: json-file + options: + max-size: 100m + max-file: "5" + # file sync + syncthing: # port 8384 + image: lscr.io/linuxserver/syncthing:latest + container_name: syncthing + environment: + PUID: "$USER_ID" + PGID: "$GROUP_ID" + TZ: America/Los_Angelas + volumes: + - "${APP_ROOT}/syncthing:/config" + - "${STORE_ROOT}:${STORE_ROOT}" + ports: + - 22000:22000/tcp + - 22000:22000/udp + - 21027:21027/udp + restart: unless-stopped + # administration + openspeedtest: # port 3000 + restart: unless-stopped + container_name: openspeedtest + image: openspeedtest/latest + user: "$USER_ID:$GROUP_ID" + technitium-dns: + image: technitium/dns-server + container_name: technitium-dns + user: "$USER_ID:$GROUP_ID" + ports: + - 53:53/udp + - 53:53/tcp + - 67:67/udp + - 68:68/udp + - 5380:5380/tcp # Web console + - 8053:8053/tcp # dns over http + volumes: + - "${APP_ROOT}/technitium/config:/app/config" + - "${APP_ROOT}/technitium/ssl:/etc/ssl" + - "${APP_ROOT}/technitium/logs:/app/config/logs" + restart: unless-stopped + sysctls: + - net.ipv4.ip_local_port_range=1024 65000 + +secrets: + openvpn-credentials: + file: ./secrets/openvpn-credentials.txt + authelia-jwt-secret: + file: ./secrets/authelia-jwt-secret.txt + authelia-session-secret: + file: ./secrets/authelia-session-secret.txt + authelia-encryption-key: + file: ./secrets/authelia-encryption-key.txt diff --git a/config/Caddyfile b/config/Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..f1e2126799b927e7c5b452e519f1c459d944103b --- /dev/null +++ b/config/Caddyfile @@ -0,0 +1,115 @@ +local.test, *.local.test { + tls internal +} + +(auth) { + @not_websocket not header Connection Upgrade + forward_auth @not_websocket authelia:9091 { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Email Remote-Name + } +} + +# jelly.{$HOME_DOMAIN} +import ./jellyfin/Caddyfile + +auth.{$HOME_DOMAIN} { + reverse_proxy "authelia:9091" +} +dawarich.{$HOME_DOMAIN} { + reverse_proxy "dawarich:30161" +} +db.{$HOME_DOMAIN} { + reverse_proxy "clover-source-of-truth:80" +} +nas.{$HOME_DOMAIN} { + forward_auth authelia:9091 { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Email Remote-Name + } + route { + @login path /login + redir @login https://auth.{$HOME_DOMAIN}?rd=https%3A%2F%2Fnas.{$HOME_DOMAIN}{?rd} + + @logout path /logout + redir @logout https://auth.{$HOME_DOMAIN}/logout?rd=https://nas.{$HOME_DOMAIN} + + reverse_proxy copyparty:3923 + } +} +git.{$HOME_DOMAIN} { + reverse_proxy "forgejo-anubis:80" { + header_up X-Real-Ip {remote_host} + header_up X-Http-Version {http.request.proto} + } +} +music.{$HOME_DOMAIN} { + reverse_proxy "navidrome:4533" +} +qbt.{$HOME_DOMAIN} { + import auth + reverse_proxy "qbittorrent:23938" +} +snr.{$HOME_DOMAIN} { + import auth + reverse_proxy "sonarr:8989" { + header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + } +} +rdr.{$HOME_DOMAIN} { + import auth + reverse_proxy "radarr:7878" { + header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + } +} +jkt.{$HOME_DOMAIN} { + import auth + reverse_proxy "jackett:9117" { + header_up Authorization "Basic dXNlcm5hbWU6cGFzc3dvcmQ=" + } +} +speedtest.{$HOME_DOMAIN} { + reverse_proxy "openspeedtest:3000" +} +opencode.{$HOME_DOMAIN} { + import auth + reverse_proxy "opencode:3923" +} +pg.{$HOME_DOMAIN} { + import auth + reverse_proxy "pgadmin:80" +} +redis.{$HOME_DOMAIN} { + import auth + reverse_proxy "redis-insight:5540" +} +sync.{$HOME_DOMAIN} { + import auth + reverse_proxy "syncthing:8384" +} + +# redirections +paperclover.dev { + redir "https://git.paperclover.net{uri}" + tls { + on_demand + } +} + +:80, :443 { + handle_errors { + rewrite * /{err.status_code} + reverse_proxy https://http.cat { + header_up Host {upstream_hostport} + replace_status {err.status_code} + } + } + + handle_path /generate_204 { + respond "" 204 + } + handle { + respond "the 'Not Found' page was not found: {http.request.host}" 404 + } +} + diff --git a/config/authelia/configuration.yml b/config/authelia/configuration.yml new file mode 100644 index 0000000000000000000000000000000000000000..ef1ee0a4397ca9f450b06b837ea95877961976f1 --- /dev/null +++ b/config/authelia/configuration.yml @@ -0,0 +1,46 @@ +storage: + postgres: + address: "tcp://postgres:5432" + database: "authelia" + schema: "public" + username: "authelia" + timeout: "5s" +authentication_backend: + refresh_interval: "5m" + file: + watch: true + path: "/config/users.yaml" + search: + email: true + case_insensitive: true +totp: + issuer: "local.test" +session: + name: "authelia_session" + same_site: "lax" + inactivity: "5m" + expiration: "1h" + remember_me: "3M" + cookies: + - name: "clover-authelia" + domain: "local.test" + authelia_url: "https://auth.local.test" +notifier: + disable_startup_check: true + smtp: + address: "smtp://127.0.0.1:25" + timeout: "5s" + username: "test" + password: "password" + sender: "clover bot " + identifier: "localhost" + subject: "[Authelia] {title}" +access_control: + default_policy: one_factor + rules: + - domain: "nas.local.test" + policy: bypass # optional +regulation: + max_retries: 10 + find_time: "2 minutes" + ban_time: "5 minutes" diff --git a/config/authelia/users.yaml b/config/authelia/users.yaml new file mode 100644 index 0000000000000000000000000000000000000000..de8efcecb5f335738d87acdee0556758717b5519 --- /dev/null +++ b/config/authelia/users.yaml @@ -0,0 +1,11 @@ +users: + snow: + disabled: false + displayname: "snow" + password: "$6$rounds=50000$BpLnfgDsc2WD8F2q$Zis.ixdg9s/UOJYrs56b5QEZFiZECu0qZVNsIYxBaNJ7ucIL.nlxVCT5tqh8KHG8X4tlwCFm5r6NTOZZ5qRFN/" + email: "account@paperclover.net" + fish: + disabled: false + displayname: "fish" + password: "$6$rounds=50000$BpLnfgDsc2WD8F2q$Zis.ixdg9s/UOJYrs56b5QEZFiZECu0qZVNsIYxBaNJ7ucIL.nlxVCT5tqh8KHG8X4tlwCFm5r6NTOZZ5qRFN/" + email: "julia@paperclover.net" diff --git a/config/copyparty.conf b/config/copyparty.conf new file mode 100644 index 0000000000000000000000000000000000000000..34021c99f163ec8f2519364465fc03a1ce71c606 --- /dev/null +++ b/config/copyparty.conf @@ -0,0 +1,41 @@ +[global] + # e2dsa # enable file indexing and filesystem scanning + # e2ts # enable multimedia indexing + ansi # enable colors in log messages + shr: /shr # set the virtual path for shares + df: 16 # stop accepting uploads if less than 16 GB free disk space + ver # show copyparty version in the controlpanel + theme: 2 # monokai + name: clover nas # change the server-name that's displayed in the browser + stats, nos-dup # enable the prometheus endpoint, but disable the dupes counter (too slow) + + # authelia + xff-src: lan # accept X-Forwarded-For from `lan` + idp-h-usr: remote-user + idp-h-grp: remote-groups + idp-login: /login?rd={dst} # caddy redirect to avoid hardcoding a domain here + idp-logout: /logout + idp-login-t: with sso (snow sign on) + +[/] # webroot + /w + accs: + rwmda: snow +[/clover] + /w/clover + accs: + rwmda: snow +[/media] # mediaroot + /w/media + accs: + rwmda: snow + rwmd: rain, fish, akira +[/home/${u}] + /w/home/${u} + accs: + rwmda: ${u}, @su +[/clover/Public] + /w/clover/Public + accs: + r: * + rwm: @acct diff --git a/config/font/README.txt b/config/font/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..85995d370b2f0366db8a28763ce93106b2b2648c --- /dev/null +++ b/config/font/README.txt @@ -0,0 +1,14 @@ +The fonts in this directory MUST be purchased to use: +https://www.arrowtype.com/name-sans +https://www.arrowtype.com/name-mono + +The full license from ArrowType: https://www.arrowtype.com/licensing/commercial + +This license specifically grants me, Clover M Caruso, the right to embed these +webfonts on domains I control. + +> The webfonts may only be installed on website domains that are under your +> direct control. + +Since this git repo is hosted on a domain I control, using hardware I control +(local git server in my home), I diff --git a/config/font/nm.woff2 b/config/font/nm.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..9e6c9984adffaedb0834daf39aae42940504b4ce Binary files /dev/null and b/config/font/nm.woff2 differ diff --git a/config/font/nmb.woff2 b/config/font/nmb.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..b8d7dfc8060a25f161032aa2bc070c7cfaa57929 Binary files /dev/null and b/config/font/nmb.woff2 differ diff --git a/config/font/nmi.woff2 b/config/font/nmi.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..6ebda3062e1f546301b4fa6ffd41b082b71b60ba Binary files /dev/null and b/config/font/nmi.woff2 differ diff --git a/config/font/nsv.woff2 b/config/font/nsv.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..c9d6290f4ec927b5daaca52a7e4adc7199109fd2 Binary files /dev/null and b/config/font/nsv.woff2 differ diff --git a/config/font/nsvi.woff2 b/config/font/nsvi.woff2 new file mode 100755 index 0000000000000000000000000000000000000000..8ed5df211c9c5868d26933bee48a8e976003410b Binary files /dev/null and b/config/font/nsvi.woff2 differ diff --git a/config/forgejo/conf/app.ini b/config/forgejo/conf/app.ini new file mode 100755 index 0000000000000000000000000000000000000000..777d39804c2b2da439ff24fbc60a8b974c2a5258 --- /dev/null +++ b/config/forgejo/conf/app.ini @@ -0,0 +1,105 @@ +APP_NAME="clover's git" +APP_SLOGAL=and then we knew, just like paper airplanes: that we could fly... +RUN_MODE=prod +RUN_USER=git +WORK_PATH=/data/work + +[repository] +MAX_CREATION_LIMIT=0 +ROOT=/data/git/repositories +DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH=true +DEFAULT_REPO_UNITS=repo.code,repo.issues,repo.pulls +DISABLE_STARS=true +DEFAULT_BRANCH=master + +[repository.local] +LOCAL_COPY_PATH=/data/work/tmp/local-repo + +[repository.upload] +TEMP_PATH=/data/work/uploads + +[ui] +EXPLORE_PAGING_NUM=100 +ISSUE_PAGING_NUM=500 +MEMBERS_PAGING_NUM=100 +THEMES=clover +DEFAULT_THEME=clover + +[server] +APP_DATA_PATH=/data/work +DOMAIN=git.paperclover.net +SSH_DOMAIN=git.paperclover.net +HTTP_PORT=3000 +ROOT_URL=https://git.paperclover.net/ +DISABLE_SSH=false +SSH_PORT=22 +SSH_LISTEN_PORT=22 +LFS_START_SERVER=true +OFFLINE_MODE=true +LANDING_PAGE=/explore/repos + +[database] +PATH=/data/work/gitea.db +DB_TYPE=sqlite3 +HOST=localhost:3306 +NAME=gitea +USER=root +PASSWD= +LOG_SQL=false +SCHEMA= +SSL_MODE=disable + +[indexer] +ISSUE_INDEXER_PATH=/data/work/indexers/issues.bleve + +[session] +PROVIDER_CONFIG=/data/work/sessions +PROVIDER=file + +[picture] +AVATAR_UPLOAD_PATH=/data/work/avatars +REPOSITORY_AVATAR_UPLOAD_PATH=/data/work/repo-avatars + +[admin] +DISABLE_REGULAR_ORG_CREATION=true + +[attachment] +PATH=/data/work/attachments + +[log] +MODE=console +LEVEL=info +ROOT_PATH=/data/work/log + +[security] +INSTALL_LOCK=true +REVERSE_PROXY_LIMIT=1 +REVERSE_PROXY_TRUSTED_PROXIES=* +PASSWORD_HASH_ALGO=pbkdf2_hi + +[service] +DISABLE_REGISTRATION=false +REQUIRE_SIGNIN_VIEW=false +REGISTER_EMAIL_CONFIRM=true +ENABLE_NOTIFY_MAIL=true +ALLOW_ONLY_EXTERNAL_REGISTRATION=false +DEFAULT_KEEP_EMAIL_PRIVATE=false +DEFAULT_ALLOW_CREATE_ORGANIZATION=true +DEFAULT_ENABLE_TIMETRACKING=true +NO_REPLY_ADDRESS=noreply.localhost + +[lfs] +PATH = /data/git/lfs + +[mailer] +ENABLED = true +FROM=noreply.git@paperclover.net +PROTOCOL=smtps +SMTP_PORT=587 +IS_TLS_ENABLED=true + +[repository.pull-request] +DEFAULT_MERGE_STYLE=squash + +[repository.signing] +DEFAULT_TRUST_MODEL=committer diff --git a/config/forgejo/public/assets/css/theme-clover.css b/config/forgejo/public/assets/css/theme-clover.css new file mode 100755 index 0000000000000000000000000000000000000000..f29c74736dc2ff9f85b66028cd5418dd4f054919 --- /dev/null +++ b/config/forgejo/public/assets/css/theme-clover.css @@ -0,0 +1,150 @@ +@import "theme-forgejo-light.css"; +@import "theme-forgejo-dark.css" (prefers-color-scheme: dark); + +@font-face { + font-family: 'Name Mono'; + src: url('../font/ATNameMono-Regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Name Mono'; + src: url('../font/ATNameMono-Bold.woff2') format('woff2'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Name Mono'; + src: url('../font/ATNameMono-Italic.woff2') format('woff2'); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Name Sans'; + src: url('../font/ATNameSansVariable-Regular.woff2') format('woff2') tech(variations); + font-weight: 100 900; + font-style: normal; + font-display: swap; + font-stretch: 75% 125%; +} + +@font-face { + font-family: 'Name Sans'; + src: url('../font/ATNameSansVariable-Italic.woff2') format('woff2') tech(variations); + font-weight: 100 900; + font-style: italic; + font-display: swap; + font-stretch: 75% 125%; +} + +:root { + --fonts-proportional: 'Name Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif !important; + --fonts-monospace: 'Name Mono', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace !important; + --fonts-emoji: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji' !important; + --steel-900: oklch(0.15 0.08 320); + --steel-850: oklch(0.18 0.07 320); + --steel-800: oklch(0.21 0.06 320); + --steel-750: oklch(0.24 0.06 320); + --steel-700: oklch(0.27 0.04 320); + --steel-650: oklch(0.30 0.05 320); + --steel-600: oklch(0.35 0.04 320); + --steel-550: oklch(0.40 0.03 320); + --steel-500: oklch(0.45 0.02 320); + --steel-450: oklch(0.50 0.03 320); + --steel-400: oklch(0.55 0.04 320); + --steel-350: oklch(0.60 0.04 320); + --steel-300: oklch(0.65 0.06 320); + --steel-250: oklch(0.70 0.06 320); + --steel-200: oklch(0.75 0.07 320); + --steel-150: oklch(0.90 0.04 320); + --steel-100: oklch(0.95 0.01 320); + + --zinc-900: oklch(calc(0.15 * 1.05) calc(0.08/1.5) 320); + --zinc-850: oklch(calc(0.18 * 1.05) calc(0.07/1.5) 320); + --zinc-800: oklch(calc(0.21 * 1.05) calc(0.06/1.5) 320); + --zinc-750: oklch(calc(0.24 * 1.05) calc(0.06/1.5) 320); + --zinc-700: oklch(calc(0.27 * 1.05) calc(0.04/1.5) 320); + --zinc-650: oklch(calc(0.30 * 1.05) calc(0.05/1.5) 320); + --zinc-600: oklch(calc(0.35 * 1.05) calc(0.04/1.5) 320); + --zinc-550: oklch(calc(0.40 * 1.05) calc(0.03/1.5) 320); + --zinc-500: oklch(calc(0.45 * 1.05) calc(0.02/1.5) 320); + --zinc-450: oklch(calc(0.50 * 1.05) calc(0.03/1.5) 320); + --zinc-400: oklch(calc(0.55 * 1.05) calc(0.04/1.5) 320); + --zinc-350: oklch(calc(0.60 * 1.05) calc(0.04/1.5) 320); + --zinc-300: oklch(calc(0.65 * 1.05) calc(0.06/1.5) 320); + --zinc-250: oklch(calc(0.70 * 1.05) calc(0.06/1.5) 320); + --zinc-200: oklch(calc(0.75 * 1.05) calc(0.07/1.5) 320); + --zinc-150: oklch(calc(0.90 * 1.05) calc(0.04/1.5) 320); + --zinc-100: oklch(calc(0.95 * 1.05) calc(0.01/1.5) 320); + --zinc-50: oklch(calc(0.95 * 1.05) calc(0.01/1.5) 320); + + --color-primary: #00896e; + --color-primary-contrast: #000; + --color-primary-dark-1: color-mix(in srgb, var(--color-primary) 90%, white); + --color-primary-dark-2: color-mix(in srgb, var(--color-primary) 80%, white); + --color-primary-dark-3: color-mix(in srgb, var(--color-primary) 70%, white); + --color-primary-dark-4: color-mix(in srgb, var(--color-primary) 60%, white); + --color-primary-dark-5: color-mix(in srgb, var(--color-primary) 50%, white); + --color-primary-dark-6: color-mix(in srgb, var(--color-primary) 40%, white); + --color-primary-dark-7: color-mix(in srgb, var(--color-primary) 30%, white); + --color-primary-light-1: color-mix(in srgb, var(--color-primary) 90%, black); + --color-primary-light-2: color-mix(in srgb, var(--color-primary) 80%, black); + --color-primary-light-3: color-mix(in srgb, var(--color-primary) 70%, black); + --color-primary-light-4: color-mix(in srgb, var(--color-primary) 60%, black); + --color-primary-light-5: color-mix(in srgb, var(--color-primary) 50%, black); + --color-primary-light-6: color-mix(in srgb, var(--color-primary) 40%, black); + --color-primary-light-7: color-mix(in srgb, var(--color-primary) 30%, black); + --color-primary-alpha-10: color-mix(in srgb, var(--color-primary) 10%, transparent); + --color-primary-alpha-20: color-mix(in srgb, var(--color-primary) 20%, transparent); + --color-primary-alpha-30: color-mix(in srgb, var(--color-primary) 30%, transparent); + --color-primary-alpha-40: color-mix(in srgb, var(--color-primary) 40%, transparent); + --color-primary-alpha-50: color-mix(in srgb, var(--color-primary) 50%, transparent); + --color-primary-alpha-60: color-mix(in srgb, var(--color-primary) 60%, transparent); + --color-primary-alpha-70: color-mix(in srgb, var(--color-primary) 70%, transparent); + --color-primary-alpha-80: color-mix(in srgb, var(--color-primary) 80%, transparent); + --color-primary-alpha-90: color-mix(in srgb, var(--color-primary) 90%, transparent); +} + +@media (prefers-color-scheme: dark) { + :root { + --is-dark-theme: true; + --color-primary: #45F0CE; + } +} + +.warning { + --color-primary: #c35b06; + @media (prefers-color-scheme:dark) { + --color-primary: #ff9843; + } + --color-primary-contrast: #000; + --color-primary-dark-1: color-mix(in srgb, var(--color-primary) 90%, white); + --color-primary-dark-2: color-mix(in srgb, var(--color-primary) 80%, white); + --color-primary-dark-3: color-mix(in srgb, var(--color-primary) 70%, white); + --color-primary-dark-4: color-mix(in srgb, var(--color-primary) 60%, white); + --color-primary-dark-5: color-mix(in srgb, var(--color-primary) 50%, white); + --color-primary-dark-6: color-mix(in srgb, var(--color-primary) 40%, white); + --color-primary-dark-7: color-mix(in srgb, var(--color-primary) 30%, white); + --color-primary-light-1: color-mix(in srgb, var(--color-primary) 90%, black); + --color-primary-light-2: color-mix(in srgb, var(--color-primary) 80%, black); + --color-primary-light-3: color-mix(in srgb, var(--color-primary) 70%, black); + --color-primary-light-4: color-mix(in srgb, var(--color-primary) 60%, black); + --color-primary-light-5: color-mix(in srgb, var(--color-primary) 50%, black); + --color-primary-light-6: color-mix(in srgb, var(--color-primary) 40%, black); + --color-primary-light-7: color-mix(in srgb, var(--color-primary) 30%, black); + --color-primary-alpha-10: color-mix(in srgb, var(--color-primary) 10%, transparent); + --color-primary-alpha-20: color-mix(in srgb, var(--color-primary) 20%, transparent); + --color-primary-alpha-30: color-mix(in srgb, var(--color-primary) 30%, transparent); + --color-primary-alpha-40: color-mix(in srgb, var(--color-primary) 40%, transparent); + --color-primary-alpha-50: color-mix(in srgb, var(--color-primary) 50%, transparent); + --color-primary-alpha-60: color-mix(in srgb, var(--color-primary) 60%, transparent); + --color-primary-alpha-70: color-mix(in srgb, var(--color-primary) 70%, transparent); + --color-primary-alpha-80: color-mix(in srgb, var(--color-primary) 80%, transparent); + --color-primary-alpha-90: color-mix(in srgb, var(--color-primary) 90%, transparent); +} \ No newline at end of file diff --git a/config/forgejo/public/assets/font/.gitignore b/config/forgejo/public/assets/font/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/config/forgejo/public/assets/img/apple-touch-icon.png b/config/forgejo/public/assets/img/apple-touch-icon.png new file mode 100755 index 0000000000000000000000000000000000000000..1c2567c388bc0a8344112cf1e4cbd5c3f8936758 Binary files /dev/null and b/config/forgejo/public/assets/img/apple-touch-icon.png differ diff --git a/config/forgejo/public/assets/img/avatar_default.png b/config/forgejo/public/assets/img/avatar_default.png new file mode 100755 index 0000000000000000000000000000000000000000..4b8d377688ca1ea41add72b56fedc180d615c022 Binary files /dev/null and b/config/forgejo/public/assets/img/avatar_default.png differ diff --git a/config/forgejo/public/assets/img/favicon.png b/config/forgejo/public/assets/img/favicon.png new file mode 100755 index 0000000000000000000000000000000000000000..a7f70a66bbe6acdec25f0d398e92e888455adf47 Binary files /dev/null and b/config/forgejo/public/assets/img/favicon.png differ diff --git a/config/forgejo/public/assets/img/favicon.svg b/config/forgejo/public/assets/img/favicon.svg new file mode 100755 index 0000000000000000000000000000000000000000..9c3fca97af63e77f49b58162ed760747dfbc78ba --- /dev/null +++ b/config/forgejo/public/assets/img/favicon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/config/forgejo/public/assets/img/logo.png b/config/forgejo/public/assets/img/logo.png new file mode 100755 index 0000000000000000000000000000000000000000..a7f70a66bbe6acdec25f0d398e92e888455adf47 Binary files /dev/null and b/config/forgejo/public/assets/img/logo.png differ diff --git a/config/forgejo/public/assets/img/logo.svg b/config/forgejo/public/assets/img/logo.svg new file mode 100755 index 0000000000000000000000000000000000000000..9c3fca97af63e77f49b58162ed760747dfbc78ba --- /dev/null +++ b/config/forgejo/public/assets/img/logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/config/jellyfin/Caddyfile b/config/jellyfin/Caddyfile new file mode 100644 index 0000000000000000000000000000000000000000..b4f7dabab533ab98199e0d0232f04dbb81ed73bd --- /dev/null +++ b/config/jellyfin/Caddyfile @@ -0,0 +1,91 @@ +jelly.{$HOME_DOMAIN} { + handle_path /web/main.jellyfin*.css { + header Content-Type "text/css" + respond 200 { + body ` + @import url("/original{http.request.uri.path}") layer(base); + @import url("/original/web/themes/dark/theme.css") layer(basetheme); + @import url("https://jellyfin.catppuccin.com/theme.css") layer(cat); + @import url("https://jellyfin.catppuccin.com/catppuccin-macchiato.css"); + @import url("https://jellyfin.catppuccin.com/catppuccin-latte.css") (prefers-color-scheme: light); + @font-face { + font-family: 'Name Sans'; + src: url('/web/extra/font/ATNameSansVariable-Regular.woff2') format('woff2') tech(variations); + font-weight: 100 900; + font-style: normal; + font-display: swap; + font-stretch: 75% 125%; + } + + @font-face { + font-family: 'Name Sans'; + src: url('/web/extra/font/ATNameSansVariable-Italic.woff2') format('woff2') tech(variations); + font-weight: 100 900; + font-style: italic; + font-display: swap; + font-stretch: 75% 125%; + } + + body { font-family: "Name Sans", sans-serif;} + .preload, .touch-menu-la, .mainDrawer-scrollContainer { + background-color: var(--main-background); + color: var(--main-text); + } + :root { + --main-color: var(--sapphire); + --jf-font-button: 500 0.875rem / 1.75 "Name Sans", sans-serif; + --jf-font-h1: 300 1.8rem / 1.167 "Name Sans", sans-serif; + --jf-font-h2: 300 1.5rem / 1.2 "Name Sans", sans-serif; + --jf-font-h3: 400 1.17rem / 1.167 "Name Sans", sans-serif; + --jf-font-h4: 400 2.125rem / 1.235 "Name Sans", sans-serif; + --jf-font-h5: 400 1.5rem / 1.334 "Name Sans", sans-serif; + --jf-font-h6: 500 1.25rem / 1.6 "Name Sans", sans-serif; + --jf-font-subtitle1: 400 1rem / 1.75 "Name Sans", sans-serif; + --jf-font-subtitle2: 500 0.875rem / 1.57 "Name Sans", sans-serif; + --jf-font-body1: 400 1rem / 1.5 "Name Sans", sans-serif; + --jf-font-body2: 400 0.875rem / 1.43 "Name Sans", sans-serif; + --jf-font-caption: 400 0.75rem / 1.66 "Name Sans", sans-serif; + --jf-font-overline: 400 0.75rem / 2.66 "Name Sans", sans-serif; + --jf-font-inherit: inherit inherit / inherit inherit; + } + + .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-banner.png)!important; } + .layout-tv .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-icon.png)!important; } + .splashLogo { background-image: url(/web/extra/cloverfin-icon.png)!important; } + + @media (min-device-width: 992px) { + .splashLogo { background-image: url(/web/extra/cloverfin-banner.png)!important; } + } + + @media (prefers-color-scheme: light) { + .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-banner-light.png)!important;} + .layout-tv .pageTitleWithDefaultLogo { background-image: url(/web/extra/cloverfin-icon-light.png)!important;} + .splashLogo { background-image: url(/web/extra/cloverfin-icon-light.png)!important; } + } + + @media (min-device-width: 992px) and (prefers-color-scheme: light) { + .splashLogo { background-image: url(/web/extra/cloverfin-banner-light.png)!important; } + } + ` + } + } + handle_path /web/themes/dark/theme.css { + header Content-Type "text/css" + respond 200 "" + } + handle_path /original/* { + uri strip_prefix /original + reverse_proxy "jellyfin:8096" + } + handle_path /web/extra/font/* { + uri strip_prefix /web/extra/font + root /etc/caddy/font + file_server + } + handle_path /web/extra/* { + uri strip_prefix /web/extra + root /etc/caddy/jellyfin + file_server + } + reverse_proxy "jellyfin:8096" +} diff --git a/config/jellyfin/branding.xml b/config/jellyfin/branding.xml new file mode 100644 index 0000000000000000000000000000000000000000..1edaeb907b0fbf2362d0dd7c47d4cd029460855a --- /dev/null +++ b/config/jellyfin/branding.xml @@ -0,0 +1,8 @@ + + + and then we knew, just like paper airplanes: that we could fly. + + true + /config/upload/splashscreen-upload.jpg + + diff --git a/config/jellyfin/cloverfin-banner-light.png b/config/jellyfin/cloverfin-banner-light.png new file mode 100644 index 0000000000000000000000000000000000000000..f8846702f72dee2db2a55bd797637a5c9fa2e214 Binary files /dev/null and b/config/jellyfin/cloverfin-banner-light.png differ diff --git a/config/jellyfin/cloverfin-banner.png b/config/jellyfin/cloverfin-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..df4f2b5c2fe14d0e892b116617c5c47ccd8884cc Binary files /dev/null and b/config/jellyfin/cloverfin-banner.png differ diff --git a/config/jellyfin/cloverfin-icon-light.png b/config/jellyfin/cloverfin-icon-light.png new file mode 100644 index 0000000000000000000000000000000000000000..f7dbad39b95ce9f608438621a6fcb2e992db562d Binary files /dev/null and b/config/jellyfin/cloverfin-icon-light.png differ diff --git a/config/jellyfin/cloverfin-icon.png b/config/jellyfin/cloverfin-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0e19f82c1d9c7321672ba5ac04e416256fbd80cc Binary files /dev/null and b/config/jellyfin/cloverfin-icon.png differ diff --git a/config/jellyfin/font/.gitignore b/config/jellyfin/font/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/config/opencode/Dockerfile b/config/opencode/Dockerfile new file mode 100755 index 0000000000000000000000000000000000000000..97413c0d239d9286293040a80c6bf867ece8ae2e --- /dev/null +++ b/config/opencode/Dockerfile @@ -0,0 +1,57 @@ +FROM fedora:latest + +# system utilities +RUN dnf update -y && \ + dnf install -y \ + neovim \ + bash \ + cargo \ + curl \ + git \ + jq \ + llvm \ + tree \ + unzip \ + wget \ + zsh \ + && dnf clean all +RUN curl -fsSL https://git.paperclover.net/nix/config/src/branch/main/packages/autofmt/autofmt.js -o /usr/local/bin/autofmt && \ + chmod +x /usr/local/bin/autofmt + +# create user +RUN groupadd -g 3000 agent && \ + useradd -u 3000 -g 3000 -m -s /bin/zsh agent +USER agent +ENV HOME=/home/agent +WORKDIR /home/agent + +# nvm +ENV NODE_VERSION=24 +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bash && \ + . "$HOME/.nvm/nvm.sh" && \ + nvm install $NODE_VERSION && \ + ln -s "$(dirname $(dirname $(nvm which $NODE_VERSION)))" $HOME/.nvm-current && \ + npm i -g pnpm typescript jsr + +ENV EDITOR=nvim +RUN echo "TERM=xterm-256color" > $HOME/.zshrc + +# curl installations +RUN curl -fsSL https://deno.land/install.sh | sh +RUN curl -fsSL https://dprint.dev/install.sh | sh +RUN curl -fsSL https://opencode.ai/install | bash + +# cargo binstall +RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash +RUN cargo binstall --strategies crate-meta-data jj-cli -y + +ENV PATH="$PATH:$HOME/.nvm-current/bin:\ +$HOME/.opencode/bin:\ +$HOME/.deno/bin:\ +$HOME/.dprint/bin:\ +$HOME/.cargo/bin" + +ENV SHELL=/bin/zsh +WORKDIR /code +EXPOSE 4096 +CMD ["opencode", "serve", "--hostname", "0.0.0.0"] diff --git a/config/openvpn/ca.rsa.2048.crt b/config/openvpn/ca.rsa.2048.crt new file mode 100755 index 0000000000000000000000000000000000000000..6deea606316a5de9f3fccc36b896a1a459eeb3b3 --- /dev/null +++ b/config/openvpn/ca.rsa.2048.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV +BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu +dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx +IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB +FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1 +MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex +EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg +QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE +AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50 +ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy +bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD +L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX +lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp +cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/ +8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB +/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC +OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL +y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO +sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM +b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G +A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg +SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz +czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j +b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn +a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU +ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3 +7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC +GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz +1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt +YDQ8z9v+DMO6iwyIDRiU +-----END CERTIFICATE----- diff --git a/config/openvpn/crl.rsa.2048.pem b/config/openvpn/crl.rsa.2048.pem new file mode 100755 index 0000000000000000000000000000000000000000..a58ef56cf946e83a664fdf5e8401d1cf4700b54f --- /dev/null +++ b/config/openvpn/crl.rsa.2048.pem @@ -0,0 +1,15 @@ +-----BEGIN X509 CRL----- +MIICWDCCAUAwDQYJKoZIhvcNAQENBQAwgegxCzAJBgNVBAYTAlVTMQswCQYDVQQI +EwJDQTETMBEGA1UEBxMKTG9zQW5nZWxlczEgMB4GA1UEChMXUHJpdmF0ZSBJbnRl +cm5ldCBBY2Nlc3MxIDAeBgNVBAsTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAw +HgYDVQQDExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UEKRMXUHJpdmF0 +ZSBJbnRlcm5ldCBBY2Nlc3MxLzAtBgkqhkiG9w0BCQEWIHNlY3VyZUBwcml2YXRl +aW50ZXJuZXRhY2Nlc3MuY29tFw0xNjA3MDgxOTAwNDZaFw0zNjA3MDMxOTAwNDZa +MCYwEQIBARcMMTYwNzA4MTkwMDQ2MBECAQYXDDE2MDcwODE5MDA0NjANBgkqhkiG +9w0BAQ0FAAOCAQEAQZo9X97ci8EcPYu/uK2HB152OZbeZCINmYyluLDOdcSvg6B5 +jI+ffKN3laDvczsG6CxmY3jNyc79XVpEYUnq4rT3FfveW1+Ralf+Vf38HdpwB8EW +B4hZlQ205+21CALLvZvR8HcPxC9KEnev1mU46wkTiov0EKc+EdRxkj5yMgv0V2Re +ze7AP+NQ9ykvDScH4eYCsmufNpIjBLhpLE2cuZZXBLcPhuRzVoU3l7A9lvzG9mjA +5YijHJGHNjlWFqyrn1CfYS6koa4TGEPngBoAziWRbDGdhEgJABHrpoaFYaL61zqy +MR6jC0K2ps9qyZAN74LEBedEfK7tBOzWMwr58A== +-----END X509 CRL----- diff --git a/config/openvpn/profile.ovpn b/config/openvpn/profile.ovpn new file mode 100755 index 0000000000000000000000000000000000000000..4a4db53356e3aae8166da459a18a13318b0c3a77 --- /dev/null +++ b/config/openvpn/profile.ovpn @@ -0,0 +1,53 @@ +client +dev tun +proto udp +remote us-siliconvalley.privacy.network 1198 +resolv-retry infinite +nobind +persist-key +cipher aes-128-cbc +auth sha1 +tls-client +remote-cert-tls server + +auth-user-pass /run/secrets/openvpn-credentials +comp-lzo no +verb 1 + + +-----BEGIN CERTIFICATE----- +MIIFqzCCBJOgAwIBAgIJAKZ7D5Yv87qDMA0GCSqGSIb3DQEBDQUAMIHoMQswCQYD +VQQGEwJVUzELMAkGA1UECBMCQ0ExEzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNV +BAoTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIElu +dGVybmV0IEFjY2VzczEgMB4GA1UEAxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3Mx +IDAeBgNVBCkTF1ByaXZhdGUgSW50ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkB +FiBzZWN1cmVAcHJpdmF0ZWludGVybmV0YWNjZXNzLmNvbTAeFw0xNDA0MTcxNzM1 +MThaFw0zNDA0MTIxNzM1MThaMIHoMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex +EzARBgNVBAcTCkxvc0FuZ2VsZXMxIDAeBgNVBAoTF1ByaXZhdGUgSW50ZXJuZXQg +QWNjZXNzMSAwHgYDVQQLExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4GA1UE +AxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBCkTF1ByaXZhdGUgSW50 +ZXJuZXQgQWNjZXNzMS8wLQYJKoZIhvcNAQkBFiBzZWN1cmVAcHJpdmF0ZWludGVy +bmV0YWNjZXNzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPXD +L1L9tX6DGf36liA7UBTy5I869z0UVo3lImfOs/GSiFKPtInlesP65577nd7UNzzX +lH/P/CnFPdBWlLp5ze3HRBCc/Avgr5CdMRkEsySL5GHBZsx6w2cayQ2EcRhVTwWp +cdldeNO+pPr9rIgPrtXqT4SWViTQRBeGM8CDxAyTopTsobjSiYZCF9Ta1gunl0G/ +8Vfp+SXfYCC+ZzWvP+L1pFhPRqzQQ8k+wMZIovObK1s+nlwPaLyayzw9a8sUnvWB +/5rGPdIYnQWPgoNlLN9HpSmsAcw2z8DXI9pIxbr74cb3/HSfuYGOLkRqrOk6h4RC +OfuWoTrZup1uEOn+fw8CAwEAAaOCAVQwggFQMB0GA1UdDgQWBBQv63nQ/pJAt5tL +y8VJcbHe22ZOsjCCAR8GA1UdIwSCARYwggESgBQv63nQ/pJAt5tLy8VJcbHe22ZO +sqGB7qSB6zCB6DELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRMwEQYDVQQHEwpM +b3NBbmdlbGVzMSAwHgYDVQQKExdQcml2YXRlIEludGVybmV0IEFjY2VzczEgMB4G +A1UECxMXUHJpdmF0ZSBJbnRlcm5ldCBBY2Nlc3MxIDAeBgNVBAMTF1ByaXZhdGUg +SW50ZXJuZXQgQWNjZXNzMSAwHgYDVQQpExdQcml2YXRlIEludGVybmV0IEFjY2Vz +czEvMC0GCSqGSIb3DQEJARYgc2VjdXJlQHByaXZhdGVpbnRlcm5ldGFjY2Vzcy5j +b22CCQCmew+WL/O6gzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4IBAQAn +a5PgrtxfwTumD4+3/SYvwoD66cB8IcK//h1mCzAduU8KgUXocLx7QgJWo9lnZ8xU +ryXvWab2usg4fqk7FPi00bED4f4qVQFVfGfPZIH9QQ7/48bPM9RyfzImZWUCenK3 +7pdw4Bvgoys2rHLHbGen7f28knT2j/cbMxd78tQc20TIObGjo8+ISTRclSTRBtyC +GohseKYpTS9himFERpUgNtefvYHbn70mIOzfOJFTVqfrptf9jXa9N8Mpy3ayfodz +1wiqdteqFXkTYoSDctgKMiZ6GdocK9nMroQipIQtpnwd4yBDWIyC6Bvlkrq5TQUt +YDQ8z9v+DMO6iwyIDRiU +-----END CERTIFICATE----- + + +disable-occ diff --git a/config/postgres/Dockerfile b/config/postgres/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..d80f0fa76c7a405e9e96c4a4ebc0ffcc3817b55b --- /dev/null +++ b/config/postgres/Dockerfile @@ -0,0 +1,19 @@ +FROM postgres:17 + +ARG USER_ID +ARG GROUP_ID + +RUN groupmod -g $GROUP_ID postgres || true +RUN usermod -u $USER_ID -g $GROUP_ID postgres + +ENV POSTGIS_MAJOR 3 +RUN apt-get update \ + && apt-cache showpkg postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR \ + && apt-get install -y --no-install-recommends \ + postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR \ + postgresql-$PG_MAJOR-postgis-$POSTGIS_MAJOR-scripts \ + && rm -rf /var/lib/apt/lists/* + +COPY ./init.sh /usr/local/bin/clover-postgres-init.sh + +ENTRYPOINT ["/usr/local/bin/clover-postgres-init.sh"] diff --git a/config/postgres/init.sh b/config/postgres/init.sh new file mode 100755 index 0000000000000000000000000000000000000000..2f983ac5f344d7fcc766fc1134589a3073a27d7e --- /dev/null +++ b/config/postgres/init.sh @@ -0,0 +1,40 @@ +#!/bin/sh +set -e +docker-entrypoint.sh postgres & +PG_PID=$! +printf "Waiting for database\n" +until pg_isready -U postgres; do + sleep 1 +done + +ensure_db_user() { + printf "Configuring Database %s\n" "$1" + user="$1" + pass="$2" + + psql -v ON_ERROR_STOP=1 -U postgres postgres <<-SQL + DO \$\$ + BEGIN + IF EXISTS (SELECT FROM pg_user WHERE usename = '$user') THEN + ALTER USER "$user" WITH PASSWORD '$pass'; + ELSE + CREATE USER "$user" WITH PASSWORD '$pass'; + END IF; + END + \$\$; + + SELECT 'CREATE DATABASE "$user" OWNER "$user"' + WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '$user') + \gexec +SQL +} + +ensure_db_user "clover" "$POSTGRES_PASSWORD" +ensure_db_user "authelia" "$POSTGRES_PASSWORD_AUTHELIA" +ensure_db_user "dawarich" "$POSTGRES_PASSWORD_DAWARICH" +ensure_db_user "forgejo" "$POSTGRES_PASSWORD_FORGEJO" +ensure_db_user "jellyfin" "$POSTGRES_PASSWORD_JELLYFIN" + +printf "Everything is ready!" + +wait $PG_PID diff --git a/local-dns.sh b/local-dns.sh new file mode 100644 index 0000000000000000000000000000000000000000..2b5d6ad889fe82f990f982f9e13e06cd1bf00cd6 --- /dev/null +++ b/local-dns.sh @@ -0,0 +1,16 @@ +#!/bin/sh +/opt/homebrew/bin/brew install dnsmasq +echo "address=/.local.test/127.0.0.1" >"$(/opt/homebrew/bin/brew --prefix)/etc/dnsmasq.conf" +echo "port=53" >>"$(/opt/homebrew/bin/brew --prefix)/etc/dnsmasq.conf" +sudo /opt/homebrew/bin/brew services restart dnsmasq +sudo mkdir -p /etc/resolver +echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/local.test + +docker compose up -d caddy + +while ! [ -f .apps/caddy/pki/authorities/local/root.crt ]; do + echo waiting for certificate + sleep 1 +done + +sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain .apps/caddy/pki/authorities/local/root.crt diff --git a/readme.md b/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..7de3b548a041c05ed2b84b81b6dd9de7a0a7c8fb --- /dev/null +++ b/readme.md @@ -0,0 +1,31 @@ +# clover's home infrastructure + +this codebase is used for all docker services on my home nas, including personal +customization. + +## usage + +``` +git clone https://git.paperclover.net/clo/home-infra.git +cd home-infra + +# setup repo +rm config/font/* +git clone https://git.paperclover.net/clo/sitegen.git +cp .env.example .env + +# fill secrets +vi .env +vi secrets/authelia-encryption-key.txt +vi secrets/authelia-session-secret.txt +vi secrets/authelia-jwt-secret.txt +vi secrets/openvpn-credentials.txt + +docker compose up -d +``` + +## license + +this repository is for example usage only. you may not re-host it in full, +privately or publicly, but you may learn from my usage. please note that some +components here are under licenses which do not permit redistribution. diff --git a/secrets/.gitignore b/secrets/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2211df63dd2831aa0cfc38ba1ebc95e3c4620894 --- /dev/null +++ b/secrets/.gitignore @@ -0,0 +1 @@ +*.txt