diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c49bd7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.env
diff --git a/containers/Bastille/Bastille.md b/containers/Bastille/Bastille.md
new file mode 100644
index 0000000..9d70d9a
--- /dev/null
+++ b/containers/Bastille/Bastille.md
@@ -0,0 +1,213 @@
+# Bastille
+
+This is my guide for getting Bastille BSD up-and-running.
+
+First make sure that FreeBSD is up-to-date:
+
+*NOTE* This takes a *long* time on a Raspberry PI. Only do this if you have a lot of time on your hands!
+
+You may be smart to `tmux` first.
+
+```sh
+tmux
+```
+
+```sh
+freebsd-update fetch install
+```
+
+```sh
+reboot
+```
+
+After reboot, check again:
+
+```sh
+freebsd-update install
+```
+
+Verify your version:
+
+```sh
+freebsd-version
+```
+
+## Setup
+
+First we need to make a backup of `pf.conf`, if you already setup pf before, otherwise you can skip this step.
+
+```sh
+mv /etc/pf.conf /etc/pf.conf.backup
+```
+
+And then
+
+```sh
+bastille setup
+```
+
+This will setup the loopback interface and create a `/etc/pf.conf`.
+
+You need to manually add the following to `/etc/pf.conf`, at the bottom, in order to allow http, https and RDP:
+
+```
+pass in inet proto tcp from any to any port { 80, 443, 3389 } flags S/SA keep state
+```
+
+Then start it:
+
+```sh
+service pf start
+```
+
+The `bastille setup` will try to configure the wrong config file and complain. We need to fix the zfs stuff manually.
+
+And change, assuming you created a zpool named `data`.
+
+For example (WATCH OUT, BELOW COMMAND IS DANGEROUS):
+
+```sh
+zpool create -f data /dev/ada0
+```
+
+Change bastille.conf
+
+```sh
+nvim /usr/local/etc/bastille/bastille.conf
+```
+
+```
+bastille_zfs_enable="YES"
+bastille_zfs_zpool="data"
+```
+
+And just in case, run the setup again:
+
+```sh
+bastille setup zfs
+```
+
+## Start
+
+Ok, now start Bastille:
+
+```sh
+service bastille restart
+```
+
+Bootstrap:
+
+```sh
+bastille bootstrap 14.2-RELEASE update
+```
+
+## Create a container
+
+Figure out your network card:
+
+```sh
+ifconfig
+```
+
+You don't want the loopback but your real card that connects to the internet. The KVM virtual machine has `vtnet0` and the Raspberry PI has `genet0`, the Lenovo Thinkcentre has `em0`.
+
+```sh
+# Lenovo Thinkcentre
+bastille create alcatraz 14.2-RELEASE 192.168.1.201 em0
+```
+
+If you want to have exlusive packages in the jail and not share the host packages, do this:
+
+```sh
+bastille pkg alcatraz bootstrap
+bastille pkg alcatraz update
+```
+
+Alternatively, you can mount the package cache:
+
+```sh
+# Optional
+bastille mount alcatraz /var/cache/pkg/ /var/cache/pkg/ nullfs rw 0 0
+```
+
+I like to install my favorites since I use them quite often:
+
+```sh
+bastille pkg alcatraz install -y tmux git neovim
+```
+
+Test it:
+
+```sh
+bastille pkg alcatraz install -y apache24
+bastille sysrc alcatraz apache24_enable=YES
+bastille service alcatraz apache24 start
+```
+
+Now go to the ip address with your browser on another machine:
+
+http://192.168.1.201/
+
+You should see "It works!"
+
+Alternatively:
+
+```sh
+curl http://192.168.1.201/
+```
+
+You should see:
+
+```html
+
It works!
+```
+
+Now destroy it:
+
+```sh
+bastille stop alcatraz
+bastille destroy force alcatraz
+```
+
+# Using ports
+
+```sh
+bastille create alcatraz 14.2-RELEASE 192.168.1.201 em0
+bastille pkg alcatraz bootstrap
+bastille pkg alcatraz update
+bastille pkg alcatraz install -y git
+bastille cmd alcatraz git clone --depth 1 https://git.FreeBSD.org/ports.git /usr/ports
+```
+
+and then go in the console:
+
+```sh
+bastille console alcatraz
+```
+
+within the console...
+
+```sh
+export BATCH=yes
+cd /usr/ports/www/apache24/ && make install clean
+exit
+```
+
+enable and start it ...
+
+```sh
+bastille sysrc alcatraz apache24_enable=YES
+bastille service alcatraz apache24 start
+```
+
+Test it:
+
+```sh
+curl http://192.168.1.201/
+```
+
+Destroy it:
+
+```sh
+bastille destroy force alcatraz
+```
diff --git a/containers/Bastille/Caddy.md b/containers/Bastille/Caddy.md
new file mode 100644
index 0000000..fca7ad8
--- /dev/null
+++ b/containers/Bastille/Caddy.md
@@ -0,0 +1,42 @@
+# Caddy
+
+```sh
+bastille create caddy 14.2-RELEASE 192.168.1.200 genet0
+bastille pkg caddy bootstrap
+bastille pkg caddy update
+bastille pkg caddy install -y caddy
+```
+
+After installing Caddy you see some instructions. To reshow them:
+
+```sh
+bastille pkg caddy info -D caddy
+```
+
+Edit the Caddyfile:
+
+```sh
+bastille pkg caddy install -y tmux neovim
+bastille console caddy
+tmux
+nvim /usr/local/etc/caddy/Caddyfile
+```
+
+Start the service.
+
+```sh
+bastille service caddy caddy enable
+bastille service caddy caddy start
+```
+
+See the logs:
+
+```sh
+bastille cmd caddy cat /var/log/caddy/caddy.log
+```
+
+To quickly look at the caddyfile:
+
+```sh
+bastille cmd caddy cat /usr/local/etc/caddy/Caddyfile
+```
\ No newline at end of file
diff --git a/containers/Bastille/MariaDB.md b/containers/Bastille/MariaDB.md
new file mode 100644
index 0000000..4166b3d
--- /dev/null
+++ b/containers/Bastille/MariaDB.md
@@ -0,0 +1,31 @@
+# MariaDB
+
+```sh
+bastille pkg alcatraz install -y mariadb114-server mariadb114-client
+```
+
+Repeat the message:
+
+```sh
+bastille pkg alcatraz info -D mariadb114-server
+```
+
+Enable and start
+
+```sh
+bastille sysrc alcatraz mysql_enable=YES
+bastille service alcatraz mysql-server start
+```
+
+Create nextcloud database and user
+
+```sh
+bastille cmd alcatraz mysql
+```
+
+```sql
+CREATE DATABASE nextcloud;
+CREATE USER 'nextcloud'@'192.168.1.201' IDENTIFIED BY '1234sys!';
+GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'192.168.1.201';
+FLUSH PRIVILEGES;
+```
diff --git a/containers/Bastille/Nextcloud/Nextcloud.md b/containers/Bastille/Nextcloud/Nextcloud.md
new file mode 100644
index 0000000..81586b4
--- /dev/null
+++ b/containers/Bastille/Nextcloud/Nextcloud.md
@@ -0,0 +1,73 @@
+# Nextcloud
+
+My standard setup:
+
+```sh
+bastille create alcatraz 14.2-RELEASE 192.168.1.201 em0
+bastille mount alcatraz /var/cache/pkg/ /var/cache/pkg/ nullfs rw 0 0
+bastille pkg alcatraz install -y tmux git neovim sudo
+```
+
+## Trying it my way
+
+```sh
+bastille pkg alcatraz install -y php83 nextcloud-php83 php83-pecl-APCu php83-extensions sd nginx
+```
+
+Repeat the message:
+
+```sh
+bastille pkg alcatraz info -D www/nextcloud
+```
+
+Additional:
+
+```sh
+bastille cmd alcatraz cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini
+bastille cmd alcatraz sd 'memory_limit = 128M' 'memory_limit = -1' /usr/local/etc/php.ini
+```
+
+We need to go in and out of the console for this one:
+
+```sh
+bastille console alcatraz
+echo 'apc.enable_cli = 1' >> /usr/local/etc/php.ini
+exit
+```
+
+Follow the instructions for [MariaDB](../MariaDB.md).
+
+Now run the installer
+
+```sh
+bastille cmd alcatraz sudo -u www php /usr/local/www/nextcloud/occ maintenance:install \
+--database='mysql' --database-host='127.0.0.1' --database-name='nextcloud' \
+--database-user='nextcloud' --database-pass='1234sys!' \
+--admin-user='admin' --admin-pass='1234sys!'
+```
+
+You should see:
+
+```
+[alcatraz]:
+Nextcloud was successfully installed
+[alcatraz]: 0
+```
+
+Edit nginx.conf for nextcloud.
+
+```sh
+bastille cmd alcatraz nvim /usr/local/etc/nginx/nginx.conf
+```
+
+Use [this file](./nginx.conf).
+
+It's based on: https://docs.nextcloud.com/server/stable/admin_manual/installation/nginx.html#nextcloud-in-the-webroot-of-nginx
+
+
+```sh
+bastille service alcatraz php_fpm enable
+bastille service alcatraz php_fpm start
+bastille service alcatraz nginx enable
+bastille service alcatraz nginx start
+```
\ No newline at end of file
diff --git a/containers/Bastille/Nextcloud/nginx.conf b/containers/Bastille/Nextcloud/nginx.conf
new file mode 100644
index 0000000..45971b1
--- /dev/null
+++ b/containers/Bastille/Nextcloud/nginx.conf
@@ -0,0 +1,265 @@
+#user nobody;
+worker_processes 1;
+
+# This default error log path is compiled-in to make sure configuration parsing
+# errors are logged somewhere, especially during unattended boot when stderr
+# isn't normally logged anywhere. This path will be touched on every nginx
+# start regardless of error log location configured here. See
+# https://trac.nginx.org/nginx/ticket/147 for more info.
+#
+#error_log /var/log/nginx/error.log;
+#
+
+#pid logs/nginx.pid;
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ # '$status $body_bytes_sent "$http_referer" '
+ # '"$http_user_agent" "$http_x_forwarded_for"';
+
+ #access_log logs/access.log main;
+
+ sendfile on;
+ #tcp_nopush on;
+
+ #keepalive_timeout 0;
+ keepalive_timeout 65;
+
+ #gzip on;
+
+ upstream php-handler {
+ server 127.0.0.1:9000;
+ #server unix:/var/run/php/php7.4-fpm.sock;
+ }
+
+ # Set the `immutable` cache control options only for assets with a cache busting `v` argument
+ map $arg_v $asset_immutable {
+ "" "";
+ default ", immutable";
+ }
+
+ # server {
+ # listen 80;
+ # listen [::]:80;
+ # server_name cloud.example.com;
+ # # enforce https
+ # return 301 https://$server_name:443$request_uri;
+ # }
+
+ server {
+ #listen 80;
+ #listen [::]:80;
+ server_name 192.168.1.201;
+
+ # Path to the root of your installation
+ root /usr/local/www/nextcloud;
+
+ # Use Mozilla's guidelines for SSL/TLS settings
+ # https://mozilla.github.io/server-side-tls/ssl-config-generator/
+ #ssl_certificate /etc/ssl/nginx/cloud.example.com.crt;
+ #ssl_certificate_key /etc/ssl/nginx/cloud.example.com.key;
+
+ # Prevent nginx HTTP Server Detection
+ server_tokens off;
+
+ # HSTS settings
+ # WARNING: Only add the preload option once you read about
+ # the consequences in https://hstspreload.org/. This option
+ # will add the domain to a hardcoded list that is shipped
+ # in all major browsers and getting removed from this list
+ # could take several months.
+ #add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;
+
+ # set max upload size and increase upload timeout:
+ client_max_body_size 512M;
+ client_body_timeout 300s;
+ fastcgi_buffers 64 4K;
+
+ # Enable gzip but do not remove ETag headers
+ gzip on;
+ gzip_vary on;
+ gzip_comp_level 4;
+ gzip_min_length 256;
+ gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
+ gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
+
+ # Pagespeed is not supported by Nextcloud, so if your server is built
+ # with the `ngx_pagespeed` module, uncomment this line to disable it.
+ #pagespeed off;
+
+ # The settings allows you to optimize the HTTP2 bandwidth.
+ # See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
+ # for tuning hints
+ client_body_buffer_size 512k;
+
+ # HTTP response headers borrowed from Nextcloud `.htaccess`
+ add_header Referrer-Policy "no-referrer" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Permitted-Cross-Domain-Policies "none" always;
+ add_header X-Robots-Tag "noindex, nofollow" always;
+ add_header X-XSS-Protection "1; mode=block" always;
+
+ # Remove X-Powered-By, which is an information leak
+ fastcgi_hide_header X-Powered-By;
+
+ # Set .mjs and .wasm MIME types
+ # Either include it in the default mime.types list
+ # and include that list explicitly or add the file extension
+ # only for Nextcloud like below:
+ include mime.types;
+ types {
+ text/javascript mjs;
+ application/wasm wasm;
+ }
+
+ # Specify how to handle directories -- specifying `/index.php$request_uri`
+ # here as the fallback means that Nginx always exhibits the desired behaviour
+ # when a client requests a path that corresponds to a directory that exists
+ # on the server. In particular, if that directory contains an index.php file,
+ # that file is correctly served; if it doesn't, then the request is passed to
+ # the front-end controller. This consistent behaviour means that we don't need
+ # to specify custom rules for certain paths (e.g. images and other assets,
+ # `/updater`, `/ocs-provider`), and thus
+ # `try_files $uri $uri/ /index.php$request_uri`
+ # always provides the desired behaviour.
+ index index.php index.html /index.php$request_uri;
+
+ # Rule borrowed from `.htaccess` to handle Microsoft DAV clients
+ location = / {
+ if ( $http_user_agent ~ ^DavClnt ) {
+ return 302 /remote.php/webdav/$is_args$args;
+ }
+ }
+
+ location = /robots.txt {
+ allow all;
+ log_not_found off;
+ access_log off;
+ }
+
+ # Make a regex exception for `/.well-known` so that clients can still
+ # access it despite the existence of the regex rule
+ # `location ~ /(\.|autotest|...)` which would otherwise handle requests
+ # for `/.well-known`.
+ location ^~ /.well-known {
+ # The rules in this block are an adaptation of the rules
+ # in `.htaccess` that concern `/.well-known`.
+
+ location = /.well-known/carddav { return 301 /remote.php/dav/; }
+ location = /.well-known/caldav { return 301 /remote.php/dav/; }
+
+ location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
+ location /.well-known/pki-validation { try_files $uri $uri/ =404; }
+
+ # Let Nextcloud's API for `/.well-known` URIs handle all other
+ # requests by passing them to the front-end controller.
+ return 301 /index.php$request_uri;
+ }
+
+ # Rules borrowed from `.htaccess` to hide certain paths from clients
+ location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
+ location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
+
+ # Ensure this block, which passes PHP files to the PHP process, is above the blocks
+ # which handle static assets (as seen below). If this block is not declared first,
+ # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
+ # to the URI, resulting in a HTTP 500 error response.
+ location ~ \.php(?:$|/) {
+ # Required for legacy support
+ rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;
+
+ fastcgi_split_path_info ^(.+?\.php)(/.*)$;
+ set $path_info $fastcgi_path_info;
+
+ try_files $fastcgi_script_name =404;
+
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param PATH_INFO $path_info;
+ fastcgi_param HTTPS on;
+
+ fastcgi_param modHeadersAvailable true; # Avoid sending the security headers twice
+ fastcgi_param front_controller_active true; # Enable pretty urls
+ fastcgi_pass php-handler;
+
+ fastcgi_intercept_errors on;
+ fastcgi_request_buffering off;
+
+ fastcgi_max_temp_file_size 0;
+ }
+
+ # Serve static files
+ location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac)$ {
+ try_files $uri /index.php$request_uri;
+ # HTTP response headers borrowed from Nextcloud `.htaccess`
+ add_header Cache-Control "public, max-age=15778463$asset_immutable";
+ add_header Referrer-Policy "no-referrer" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Permitted-Cross-Domain-Policies "none" always;
+ add_header X-Robots-Tag "noindex, nofollow" always;
+ add_header X-XSS-Protection "1; mode=block" always;
+ access_log off; # Optional: Don't log access to assets
+ }
+
+ location ~ \.(otf|woff2?)$ {
+ try_files $uri /index.php$request_uri;
+ expires 7d; # Cache-Control policy borrowed from `.htaccess`
+ access_log off; # Optional: Don't log access to assets
+ }
+
+ # Rule borrowed from `.htaccess`
+ location /remote {
+ return 301 /remote.php$request_uri;
+ }
+
+ location / {
+ try_files $uri $uri/ /index.php$request_uri;
+ }
+ }
+
+ # another virtual host using mix of IP-, name-, and port-based configuration
+ #
+ #server {
+ # listen 8000;
+ # listen somename:8080;
+ # server_name somename alias another.alias;
+
+ # location / {
+ # root html;
+ # index index.html index.htm;
+ # }
+ #}
+
+
+ # HTTPS server
+ #
+ #server {
+ # listen 443 ssl;
+ # server_name localhost;
+
+ # ssl_certificate cert.pem;
+ # ssl_certificate_key cert.key;
+
+ # ssl_session_cache shared:SSL:1m;
+ # ssl_session_timeout 5m;
+
+ # ssl_ciphers HIGH:!aNULL:!MD5;
+ # ssl_prefer_server_ciphers on;
+
+ # location / {
+ # root html;
+ # index index.html index.htm;
+ # }
+ #}
+
+}
diff --git a/containers/Bastille/Postgres.md b/containers/Bastille/Postgres.md
new file mode 100644
index 0000000..ddd64c6
--- /dev/null
+++ b/containers/Bastille/Postgres.md
@@ -0,0 +1,54 @@
+# Postgres
+
+```sh
+bastille create postgresql 14.2-RELEASE 192.168.1.203 em0
+bastille config postgresql set allow.sysvipc=1
+bastille restart postgresql
+bastille pkg postgresql bootstrap
+bastille pkg postgresql update
+bastille pkg postgresql install -y postgresql15-server postgresql15-client
+bastille service postgresql postgresql enable
+bastille service postgresql postgresql initdb
+bastille service postgresql postgresql start
+```
+
+You need to change `/var/db/postgres/data15/postgresql.conf`
+
+```sh
+nvim /var/db/postgres/data15/postgresql.conf
+```
+
+To listen to the ip address:
+
+```
+listen_addresses = '192.168.1.203'
+```
+
+And restart.
+
+We need to allow communications via the jails. Add this to pf.conf on the host:
+
+```
+pass in on $ext_if proto tcp from 192.168.1.202 to 192.168.1.203 port 5432
+pass out on $ext_if proto tcp from 192.168.1.203 to 192.168.1.202 port 5432
+```
+
+Add a user, for example nextcloud:
+
+```sh
+su - postgres
+createuser nextcloud
+createdb nextcloud -O admin
+psql nextcloud
+alter role nextcloud with encrypted password 'yourpassword';
+grant all privileges on database nextcloud to nextcloud;
+exit
+exit
+```
+
+Add this to `/var/db/postgres/data15/pg_hba.conf`
+
+```
+host nextcloud nextcloud 0.0.0.0/0 scram-sha-256
+host nextcloud nextcloud ::/0 scram-sha-256
+```
diff --git a/containers/Bastille/nginx.md b/containers/Bastille/nginx.md
new file mode 100644
index 0000000..a6f75e8
--- /dev/null
+++ b/containers/Bastille/nginx.md
@@ -0,0 +1,10 @@
+# nginx
+
+```sh
+bastille create nginx 14.2-RELEASE 192.168.1.200 genet0
+bastille pkg nginx bootstrap
+bastille pkg nginx update
+bastille pkg nginx install -y nginx
+bastille service nginx nginx enable
+bastille service nginx nginx start
+```
diff --git a/containers/docker/audiobookshelf/compose.yaml b/containers/docker/audiobookshelf/compose.yaml
new file mode 100644
index 0000000..c99d15a
--- /dev/null
+++ b/containers/docker/audiobookshelf/compose.yaml
@@ -0,0 +1,13 @@
+services:
+ audiobookshelf:
+ image: ghcr.io/advplyr/audiobookshelf:latest
+ # user: 1004:1004
+ ports:
+ - 13378:80
+ volumes:
+ - /home/audiobookshelf/audiobookshelf/audiobooks:/audiobooks
+ - /home/audiobookshelf/audiobookshelf/podcasts:/podcasts
+ - /home/audiobookshelf/audiobookshelf/config:/config
+ - /home/audiobookshelf/audiobookshelf/metadata:/metadata
+ environment:
+ - TZ=Europe/Amsterdam
diff --git a/containers/docker/httpd/compose.yaml b/containers/docker/httpd/compose.yaml
new file mode 100644
index 0000000..9da83c0
--- /dev/null
+++ b/containers/docker/httpd/compose.yaml
@@ -0,0 +1,9 @@
+services:
+
+ httpd:
+ image: httpd
+ restart: always
+ ports:
+ - 8081:80
+ # volumes:
+ # - ./www:/usr/local/apache2/htdocs
diff --git a/containers/docker/httpd/www/index.html b/containers/docker/httpd/www/index.html
new file mode 100644
index 0000000..7f7697a
--- /dev/null
+++ b/containers/docker/httpd/www/index.html
@@ -0,0 +1,8 @@
+
+
+ Hello world!
+
+
+ Hello world!
+
+
\ No newline at end of file
diff --git a/containers/docker/immich/compose.yaml b/containers/docker/immich/compose.yaml
new file mode 100644
index 0000000..f16611f
--- /dev/null
+++ b/containers/docker/immich/compose.yaml
@@ -0,0 +1,84 @@
+# You need to get the example .env file:
+# wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
+
+name: immich
+
+services:
+ immich-server:
+ container_name: immich_server
+ image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
+ extends:
+ file: hwaccel.transcoding.yml
+ service: quicksync # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
+ volumes:
+ # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
+ - ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /etc/localtime:/etc/localtime:ro
+ - /home/immich/external/:/external
+ env_file:
+ - .env
+ ports:
+ - '2283:2283'
+ depends_on:
+ - redis
+ - database
+ restart: always
+ healthcheck:
+ disable: false
+
+ immich-machine-learning:
+ container_name: immich_machine_learning
+ # For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
+ # Example tag: ${IMMICH_VERSION:-release}-cuda
+ image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
+ # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
+ # file: hwaccel.ml.yml
+ # service: cpu # set to one of [armnn, cuda, openvino, openvino-wsl] for accelerated inference - use the `-wsl` version for WSL2 where applicable
+ volumes:
+ - model-cache:/cache
+ env_file:
+ - .env
+ restart: always
+ healthcheck:
+ disable: false
+
+ redis:
+ container_name: immich_redis
+ image: docker.io/redis:6.2-alpine@sha256:eaba718fecd1196d88533de7ba49bf903ad33664a92debb24660a922ecd9cac8
+ healthcheck:
+ test: redis-cli ping || exit 1
+ restart: always
+
+ database:
+ container_name: immich_postgres
+ image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
+ environment:
+ POSTGRES_PASSWORD: ${DB_PASSWORD}
+ POSTGRES_USER: ${DB_USERNAME}
+ POSTGRES_DB: ${DB_DATABASE_NAME}
+ POSTGRES_INITDB_ARGS: '--data-checksums'
+ volumes:
+ # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
+ - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
+ healthcheck:
+ test: >-
+ pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1;
+ Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align
+ --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')";
+ echo "checksum failure count is $$Chksum";
+ [ "$$Chksum" = '0' ] || exit 1
+ interval: 5m
+ start_interval: 30s
+ start_period: 5m
+ command: >-
+ postgres
+ -c shared_preload_libraries=vectors.so
+ -c 'search_path="$$user", public, vectors'
+ -c logging_collector=on
+ -c max_wal_size=2GB
+ -c shared_buffers=512MB
+ -c wal_compression=on
+ restart: always
+
+volumes:
+ model-cache:
diff --git a/containers/docker/immich/hwaccel.transcoding.yml b/containers/docker/immich/hwaccel.transcoding.yml
new file mode 100644
index 0000000..33fb7b3
--- /dev/null
+++ b/containers/docker/immich/hwaccel.transcoding.yml
@@ -0,0 +1,54 @@
+# Configurations for hardware-accelerated transcoding
+
+# If using Unraid or another platform that doesn't allow multiple Compose files,
+# you can inline the config for a backend by copying its contents
+# into the immich-microservices service in the docker-compose.yml file.
+
+# See https://immich.app/docs/features/hardware-transcoding for more info on using hardware transcoding.
+
+services:
+ cpu: {}
+
+ nvenc:
+ deploy:
+ resources:
+ reservations:
+ devices:
+ - driver: nvidia
+ count: 1
+ capabilities:
+ - gpu
+ - compute
+ - video
+
+ quicksync:
+ devices:
+ - /dev/dri:/dev/dri
+
+ rkmpp:
+ security_opt: # enables full access to /sys and /proc, still far better than privileged: true
+ - systempaths=unconfined
+ - apparmor=unconfined
+ group_add:
+ - video
+ devices:
+ - /dev/rga:/dev/rga
+ - /dev/dri:/dev/dri
+ - /dev/dma_heap:/dev/dma_heap
+ - /dev/mpp_service:/dev/mpp_service
+ #- /dev/mali0:/dev/mali0 # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
+ volumes:
+ #- /etc/OpenCL:/etc/OpenCL:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
+ #- /usr/lib/aarch64-linux-gnu/libmali.so.1:/usr/lib/aarch64-linux-gnu/libmali.so.1:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
+
+ vaapi:
+ devices:
+ - /dev/dri:/dev/dri
+
+ vaapi-wsl: # use this for VAAPI if you're running Immich in WSL2
+ devices:
+ - /dev/dri:/dev/dri
+ volumes:
+ - /usr/lib/wsl:/usr/lib/wsl
+ environment:
+ - LIBVA_DRIVER_NAME=d3d12
diff --git a/containers/docker/jellyfin/compose.yaml b/containers/docker/jellyfin/compose.yaml
new file mode 100644
index 0000000..25a3691
--- /dev/null
+++ b/containers/docker/jellyfin/compose.yaml
@@ -0,0 +1,30 @@
+services:
+ jellyfin:
+ image: jellyfin/jellyfin
+ container_name: jellyfin
+ user: 1003:1003
+ network_mode: 'host'
+ volumes:
+ - /home/jellyfin/jellyfin/config:/config
+ - /home/jellyfin/jellyfin/cache:/cache
+ - type: bind
+ source: /home/jellyfin/jellyfin/media
+ target: /media
+ # - type: bind
+ # source: /path/to/media2
+ # target: /media2
+ # read_only: true
+ # Optional - extra fonts to be used during transcoding with subtitle burn-in
+ # - type: bind
+ # source: /path/to/fonts
+ # target: /usr/local/share/fonts/custom
+ # read_only: true
+ restart: 'unless-stopped'
+ # Optional - alternative address used for autodiscovery
+ # environment:
+ # - JELLYFIN_PublishedServerUrl=http://example.com
+ # Optional - may be necessary for docker healthcheck to pass if running in host network mode
+ # extra_hosts:
+ # - 'host.docker.internal:host-gateway'
+ devices:
+ - "/dev/dri:/dev/dri" # Intel QSV
\ No newline at end of file
diff --git a/containers/docker/multitenant/.gitignore b/containers/docker/multitenant/.gitignore
new file mode 100644
index 0000000..293d9b7
--- /dev/null
+++ b/containers/docker/multitenant/.gitignore
@@ -0,0 +1,3 @@
+infra/nginx-proxy-manager_data/
+infra/portainer_data/
+lemmy/volumes/
diff --git a/containers/docker/multitenant/README.md b/containers/docker/multitenant/README.md
new file mode 100644
index 0000000..3f7020e
--- /dev/null
+++ b/containers/docker/multitenant/README.md
@@ -0,0 +1,55 @@
+# multitenant
+
+## nginx-proxy-manager
+
+```sh
+docker network create infra
+```
+
+Admin: http://127.0.0.1:81/
+
+```
+admin@example.com
+changeme
+```
+
+## Wordpress
+
+We need to add domain names in our `/etc/hosts` file:
+
+```
+127.0.0.1 moni0.codecompost.nl
+127.0.0.1 moni1.codecompost.nl
+```
+
+And pass a project name to the docker-compose command:
+
+```
+docker-compose -p moni0 up --build --force-recreate -d
+```
+
+This will prepend `moni0_` to the containers (and add a `_1` apparently)
+
+```
+Creating network "moni0_internal_network" with the default driver
+Creating volume "moni0_wordpress" with default driver
+Creating volume "moni0_db" with default driver
+Creating moni0_db_1 ... done
+Creating moni0_wordpress_1 ... done
+```
+
+In the Nginx Proxy Manager, we can add the proxy host:
+
+
+
+We can go ahead and start a second wordpress with:
+
+```
+docker-compose -p moni1 up --build --force-recreate -d
+```
+
+And then you can configure it like this:
+
+
+
+Make sure that `moni1.codecompost.nl` is also in your hosts file, otherwise it won't work!
\ No newline at end of file
diff --git a/containers/docker/multitenant/infra/docker-compose.yml b/containers/docker/multitenant/infra/docker-compose.yml
new file mode 100644
index 0000000..040df87
--- /dev/null
+++ b/containers/docker/multitenant/infra/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '3.8'
+services:
+
+ nginx-proxy-manager:
+ image: 'jc21/nginx-proxy-manager:latest'
+ restart: unless-stopped
+ ports:
+ - '80:80'
+ - '81:81'
+ - '443:443'
+ volumes:
+ - ./nginx-proxy-manager_data/data:/data
+ - ./nginx-proxy-manager_data/letsencrypt:/etc/letsencrypt
+ networks:
+ - infra
+
+# portainer:
+# image: 'portainer/portainer-ce:latest'
+# restart: always
+# ports:
+# - '8000:8000'
+# - '9443:9443'
+# volumes:
+# - /var/run/docker.sock:/var/run/docker.sock
+# - ./portainer_data:/data
+# networks:
+# - infra
+
+networks:
+ infra:
+ external: true
diff --git a/containers/docker/multitenant/jellyfin/run_with_podman.sh b/containers/docker/multitenant/jellyfin/run_with_podman.sh
new file mode 100755
index 0000000..d521033
--- /dev/null
+++ b/containers/docker/multitenant/jellyfin/run_with_podman.sh
@@ -0,0 +1,12 @@
+podman run \
+ --detach \
+ --label "io.containers.autoupdate=registry" \
+ --name jellyfin \
+ --publish 8096:8096/tcp \
+ --rm \
+ --user $(id -u):$(id -g) \
+ --userns keep-id \
+ --volume jellyfin-cache:/cache:Z \
+ --volume jellyfin-config:/config:Z \
+ --mount type=bind,source=/home/moni/media/,destination=/media,ro=true,relabel=private \
+ docker.io/jellyfin/jellyfin:latest
\ No newline at end of file
diff --git a/containers/docker/multitenant/lemmy/customPostgresql.conf b/containers/docker/multitenant/lemmy/customPostgresql.conf
new file mode 100644
index 0000000..49428e4
--- /dev/null
+++ b/containers/docker/multitenant/lemmy/customPostgresql.conf
@@ -0,0 +1,30 @@
+# DB Version: 15
+# OS Type: linux
+# DB Type: web
+# Total Memory (RAM): 8 GB
+# CPUs num: 4
+# Data Storage: ssd
+
+max_connections = 200
+shared_buffers = 2GB
+effective_cache_size = 6GB
+maintenance_work_mem = 512MB
+checkpoint_completion_target = 0.9
+checkpoint_timeout = 86400
+wal_buffers = 16MB
+default_statistics_target = 100
+random_page_cost = 1.1
+effective_io_concurrency = 200
+work_mem = 5242kB
+min_wal_size = 1GB
+max_wal_size = 30GB
+max_worker_processes = 4
+max_parallel_workers_per_gather = 2
+max_parallel_workers = 4
+max_parallel_maintenance_workers = 2
+
+# Other custom params
+temp_file_size=1GB
+synchronous_commit=off
+# This one shouldn't be on regularly, because DB migrations often take a long time
+# statement_timeout = 10000
diff --git a/containers/docker/multitenant/lemmy/docker-compose.yml b/containers/docker/multitenant/lemmy/docker-compose.yml
new file mode 100644
index 0000000..b6b2f62
--- /dev/null
+++ b/containers/docker/multitenant/lemmy/docker-compose.yml
@@ -0,0 +1,114 @@
+version: "3.7"
+
+x-logging: &default-logging
+ driver: "json-file"
+ options:
+ max-size: "50m"
+ max-file: "4"
+
+services:
+ proxy:
+ image: nginx:1-alpine
+ # ports:
+ # # actual and only port facing any connection from outside
+ # # Note, change the left number if port 1236 is already in use on your system
+ # # You could use port 80 if you won't use a reverse proxy
+ # - "1236:8536"
+ volumes:
+ - ./nginx_internal.conf:/etc/nginx/nginx.conf:ro,Z
+ restart: always
+ logging: *default-logging
+ depends_on:
+ - pictrs
+ - lemmy-ui
+ networks:
+ - lemmy-internal
+ - infra
+
+ lemmy:
+ image: dessalines/lemmy:latest
+ hostname: lemmy
+ restart: always
+ logging: *default-logging
+ environment:
+ - RUST_LOG="warn"
+ - LEMMY_CORS_ORIGIN=http://lemmy.codecompost.nl
+ volumes:
+ - ./lemmy.hjson:/config/config.hjson:Z
+ depends_on:
+ - postgres
+ - pictrs
+ networks:
+ - lemmy-internal
+
+ lemmy-ui:
+ image: dessalines/lemmy-ui:latest
+ environment:
+ - LEMMY_UI_LEMMY_INTERNAL_HOST=lemmy:8536
+ - LEMMY_UI_LEMMY_EXTERNAL_HOST=lemmy.codecompost.nl
+ - LEMMY_UI_HTTPS=true
+ volumes:
+ - ./volumes/lemmy-ui/extra_themes:/app/extra_themes
+ depends_on:
+ - lemmy
+ restart: always
+ logging: *default-logging
+ networks:
+ - lemmy-internal
+
+ pictrs:
+ image: asonix/pictrs:0.4.0-rc.7
+ # this needs to match the pictrs url in lemmy.hjson
+ hostname: pictrs
+ # we can set options to pictrs like this, here we set max. image size and forced format for conversion
+ # entrypoint: /sbin/tini -- /usr/local/bin/pict-rs -p /mnt -m 4 --image-format webp
+ environment:
+ - PICTRS_OPENTELEMETRY_URL=http://otel:4137
+ - PICTRS__API_KEY=JmT3cMDL252EJw
+ - RUST_LOG=debug
+ - RUST_BACKTRACE=full
+ - PICTRS__MEDIA__VIDEO_CODEC=vp9
+ - PICTRS__MEDIA__GIF__MAX_WIDTH=256
+ - PICTRS__MEDIA__GIF__MAX_HEIGHT=256
+ - PICTRS__MEDIA__GIF__MAX_AREA=65536
+ - PICTRS__MEDIA__GIF__MAX_FRAME_COUNT=400
+ user: 991:991
+ volumes:
+ - ./volumes/pictrs:/mnt:Z
+ restart: always
+ logging: *default-logging
+ deploy:
+ resources:
+ limits:
+ memory: 690m
+ networks:
+ - lemmy-internal
+
+ postgres:
+ image: postgres:15-alpine
+ hostname: postgres
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=JmT3cMDL252EJw
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres:/var/lib/postgresql/data:Z
+ - ./customPostgresql.conf:/etc/postgresql.conf
+ restart: always
+ logging: *default-logging
+ networks:
+ - lemmy-internal
+
+ postfix:
+ image: mwader/postfix-relay
+ environment:
+ - POSTFIX_myhostname=codecompost.nl
+ restart: "always"
+ logging: *default-logging
+ networks:
+ - lemmy-internal
+
+networks:
+ lemmy-internal:
+ infra:
+ external: true
\ No newline at end of file
diff --git a/containers/docker/multitenant/lemmy/lemmy.hjson b/containers/docker/multitenant/lemmy/lemmy.hjson
new file mode 100644
index 0000000..a27c04e
--- /dev/null
+++ b/containers/docker/multitenant/lemmy/lemmy.hjson
@@ -0,0 +1,19 @@
+{
+ # for more info about the config, check out the documentation
+ # https://join-lemmy.org/docs/en/administration/configuration.html
+
+ database: {
+ host: postgres
+ password: "JmT3cMDL252EJw"
+ }
+ hostname: "lemmy.codecompost.nl"
+ pictrs: {
+ url: "http://pictrs:8080/"
+ api_key: "JmT3cMDL252EJw"
+ }
+ email: {
+ smtp_server: "postfix:25"
+ smtp_from_address: "noreply@codecompost.nl"
+ tls_type: "none"
+ }
+}
diff --git a/containers/docker/multitenant/lemmy/nginx_internal.conf b/containers/docker/multitenant/lemmy/nginx_internal.conf
new file mode 100644
index 0000000..cffc77b
--- /dev/null
+++ b/containers/docker/multitenant/lemmy/nginx_internal.conf
@@ -0,0 +1,96 @@
+worker_processes auto;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ # We construct a string consistent of the "request method" and "http accept header"
+ # and then apply soem ~simply regexp matches to that combination to decide on the
+ # HTTP upstream we should proxy the request to.
+ #
+ # Example strings:
+ #
+ # "GET:application/activity+json"
+ # "GET:text/html"
+ # "POST:application/activity+json"
+ #
+ # You can see some basic match tests in this regex101 matching this configuration
+ # https://regex101.com/r/vwMJNc/1
+ #
+ # Learn more about nginx maps here http://nginx.org/en/docs/http/ngx_http_map_module.html
+ map "$request_method:$http_accept" $proxpass {
+ # If no explicit matches exists below, send traffic to lemmy-ui
+ default "http://lemmy-ui";
+
+ # GET/HEAD requests that accepts ActivityPub or Linked Data JSON should go to lemmy.
+ #
+ # These requests are used by Mastodon and other fediverse instances to look up profile information,
+ # discover site information and so on.
+ "~^(?:GET|HEAD):.*?application\/(?:activity|ld)\+json" "http://lemmy";
+
+ # All non-GET/HEAD requests should go to lemmy
+ #
+ # Rather than calling out POST, PUT, DELETE, PATCH, CONNECT and all the verbs manually
+ # we simply negate the GET|HEAD pattern from above and accept all possibly $http_accept values
+ "~^(?!(GET|HEAD)).*:" "http://lemmy";
+ }
+
+ upstream lemmy {
+ # this needs to map to the lemmy (server) docker service hostname
+ server "lemmy:8536";
+ }
+
+ upstream lemmy-ui {
+ # this needs to map to the lemmy-ui docker service hostname
+ server "lemmy-ui:1234";
+ }
+
+ server {
+ # this is the port inside docker, not the public one yet
+ listen 1236;
+ listen 8536;
+
+ # change if needed, this is facing the public web
+ server_name localhost;
+ server_tokens off;
+
+ gzip on;
+ gzip_types text/css application/javascript image/svg+xml;
+ gzip_vary on;
+
+ # Upload limit, relevant for pictrs
+ client_max_body_size 20M;
+
+ add_header X-Frame-Options SAMEORIGIN;
+ add_header X-Content-Type-Options nosniff;
+ add_header X-XSS-Protection "1; mode=block";
+
+ # frontend general requests
+ location / {
+ proxy_pass $proxpass;
+
+ rewrite ^(.+)/+$ $1 permanent;
+
+ # Send actual client IP upstream
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+
+ # backend
+ location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) {
+ proxy_pass "http://lemmy";
+
+ # proxy common stuff
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+
+ # Send actual client IP upstream
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ }
+}
diff --git a/containers/docker/multitenant/mastodon/.env.production b/containers/docker/multitenant/mastodon/.env.production
new file mode 100644
index 0000000..93a732c
--- /dev/null
+++ b/containers/docker/multitenant/mastodon/.env.production
@@ -0,0 +1,28 @@
+# Generated with mastodon:setup on 2023-07-06 02:48:24 UTC
+
+# Some variables in this file will be interpreted differently whether you are
+# using docker-compose or not.
+
+LOCAL_DOMAIN=mastodon.codecompost.nl
+SINGLE_USER_MODE=true
+SECRET_KEY_BASE=2f845c9336267ad5ddf79ca900d3940990c0a8010c7c3a5321d5cda8e5b9ac32ebcc75ea67396977bb65c7c970f71d123312cd180c6a1e7b12e608eee3d63e27
+OTP_SECRET=e8c4370e30704c85552fd48d766c859323ebd28aaaffa8a9943687b2ac925610eeba1bc9b3a472ae1b684862e7d02ea7d5b69a02624d395626b0cd9fef4aeb2c
+VAPID_PRIVATE_KEY=fHCAaeeQ_HxQBKvw9jzVrTxO9mpoup46luhPmKZsRhI=
+VAPID_PUBLIC_KEY=BKkmTVZwiL4hmqYt91AsWvB4DS38MnFygDumi9jCqjSw3IClXA5WhxCQnYSpGzOKvvrN-Lgy4Fm2Rx5CPhmelS0=
+DB_HOST=moni-mastodon-db-1
+DB_PORT=5432
+DB_NAME=mastodon
+DB_USER=mastodon
+DB_PASS=mastodon
+REDIS_HOST=moni-mastodon-redis-1
+REDIS_PORT=6379
+REDIS_PASSWORD=
+SMTP_SERVER=smtp.mailgun.org
+SMTP_PORT=587
+SMTP_LOGIN=
+SMTP_PASSWORD=
+SMTP_AUTH_METHOD=plain
+SMTP_OPENSSL_VERIFY_MODE=none
+SMTP_ENABLE_STARTTLS=auto
+SMTP_FROM_ADDRESS=Mastodon
+LOCAL_HTTPS=false
diff --git a/containers/docker/multitenant/mastodon/docker-compose.yml b/containers/docker/multitenant/mastodon/docker-compose.yml
new file mode 100644
index 0000000..a3916ba
--- /dev/null
+++ b/containers/docker/multitenant/mastodon/docker-compose.yml
@@ -0,0 +1,137 @@
+version: '3'
+services:
+ db:
+ restart: always
+ image: postgres:14-alpine
+ shm_size: 256mb
+ networks:
+ - internal_network
+ healthcheck:
+ test: ['CMD', 'pg_isready', '-U', 'postgres']
+ volumes:
+ - postgres14:/var/lib/postgresql/data
+ environment:
+ - POSTGRES_USER=mastodon
+ - POSTGRES_PASSWORD=mastodon
+
+ redis:
+ restart: always
+ image: redis:7-alpine
+ networks:
+ - internal_network
+ healthcheck:
+ test: ['CMD', 'redis-cli', 'ping']
+ volumes:
+ - redis:/data
+
+ # es:
+ # restart: always
+ # image: docker.elastic.co/elasticsearch/elasticsearch:7.17.4
+ # environment:
+ # - "ES_JAVA_OPTS=-Xms512m -Xmx512m -Des.enforce.bootstrap.checks=true"
+ # - "xpack.license.self_generated.type=basic"
+ # - "xpack.security.enabled=false"
+ # - "xpack.watcher.enabled=false"
+ # - "xpack.graph.enabled=false"
+ # - "xpack.ml.enabled=false"
+ # - "bootstrap.memory_lock=true"
+ # - "cluster.name=es-mastodon"
+ # - "discovery.type=single-node"
+ # - "thread_pool.write.queue_size=1000"
+ # networks:
+ # - infra
+ # - internal_network
+ # healthcheck:
+ # test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
+ # volumes:
+ # - ./elasticsearch:/usr/share/elasticsearch/data
+ # ulimits:
+ # memlock:
+ # soft: -1
+ # hard: -1
+ # nofile:
+ # soft: 65536
+ # hard: 65536
+ # ports:
+ # - '127.0.0.1:9200:9200'
+
+ web:
+ image: ghcr.io/mastodon/mastodon
+ restart: always
+ env_file: .env.production
+ command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
+ networks:
+ - infra
+ - internal_network
+ healthcheck:
+ # prettier-ignore
+ test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:3000/health || exit 1']
+ # ports:
+ # - '127.0.0.1:3000:3000'
+ depends_on:
+ - db
+ - redis
+ # - es
+ volumes:
+ - public_system:/mastodon/public/system
+
+ streaming:
+ image: ghcr.io/mastodon/mastodon
+ restart: always
+ env_file: .env.production
+ command: node ./streaming
+ networks:
+ - infra
+ - internal_network
+ healthcheck:
+ # prettier-ignore
+ test: ['CMD-SHELL', 'wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1']
+ # ports:
+ # - '127.0.0.1:4000:4000'
+ depends_on:
+ - db
+ - redis
+
+ sidekiq:
+ image: ghcr.io/mastodon/mastodon
+ restart: always
+ env_file: .env.production
+ command: bundle exec sidekiq
+ depends_on:
+ - db
+ - redis
+ networks:
+ - infra
+ - internal_network
+ volumes:
+ - public_system:/mastodon/public/system
+ healthcheck:
+ test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"]
+
+ ## Uncomment to enable federation with tor instances along with adding the following ENV variables
+ ## http_proxy=http://privoxy:8118
+ ## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
+ # tor:
+ # image: sirboops/tor
+ # networks:
+ # - infra
+ # - internal_network
+ #
+ # privoxy:
+ # image: sirboops/privoxy
+ # volumes:
+ # - ./priv-config:/opt/config
+ # networks:
+ # - infra
+ # - internal_network
+
+networks:
+ infra:
+ external: true
+ internal_network:
+
+
+volumes:
+ postgres14:
+ redis:
+ public_system:
diff --git a/containers/docker/multitenant/moni0_proxy_host.png b/containers/docker/multitenant/moni0_proxy_host.png
new file mode 100644
index 0000000..94903a1
Binary files /dev/null and b/containers/docker/multitenant/moni0_proxy_host.png differ
diff --git a/containers/docker/multitenant/moni1_proxy_host.png b/containers/docker/multitenant/moni1_proxy_host.png
new file mode 100644
index 0000000..7b4e7fd
Binary files /dev/null and b/containers/docker/multitenant/moni1_proxy_host.png differ
diff --git a/containers/docker/multitenant/nextcloud/REAME.md b/containers/docker/multitenant/nextcloud/REAME.md
new file mode 100644
index 0000000..0e87b2c
--- /dev/null
+++ b/containers/docker/multitenant/nextcloud/REAME.md
@@ -0,0 +1,2 @@
+https://github.com/nextcloud/all-in-one
+
diff --git a/containers/docker/multitenant/nextcloud/docker-compose.yml b/containers/docker/multitenant/nextcloud/docker-compose.yml
new file mode 100644
index 0000000..e54a64c
--- /dev/null
+++ b/containers/docker/multitenant/nextcloud/docker-compose.yml
@@ -0,0 +1,17 @@
+services:
+ nextcloud-aio-mastercontainer:
+ image: nextcloud/all-in-one:latest
+ init: true
+ restart: always
+ container_name: nextcloud-aio-mastercontainer # This line is not allowed to be changed as otherwise AIO will not work correctly
+ volumes:
+ - nextcloud_aio_mastercontainer:/mnt/docker-aio-config # This line is not allowed to be changed as otherwise the built-in backup solution will not work
+ - /var/run/docker.sock:/var/run/docker.sock:ro # May be changed on macOS, Windows or docker rootless. See the applicable documentation. If adjusting, don't forget to also set 'WATCHTOWER_DOCKER_SOCKET_PATH'!
+ ports:
+ - 80:80 # Can be removed when running behind a web server or reverse proxy (like Apache, Nginx, Cloudflare Tunnel and else). See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md
+ - 8080:8080
+ - 8443:8443
+
+volumes: # If you want to store the data on a different drive, see https://github.com/nextcloud/all-in-one#how-to-store-the-filesinstallation-on-a-separate-drive
+ nextcloud_aio_mastercontainer:
+ name: nextcloud_aio_mastercontainer # This line is not allowed to be changed as otherwise the built-in backup solution will not work
diff --git a/containers/docker/multitenant/sites/moni8080/docker-compose.yml b/containers/docker/multitenant/sites/moni8080/docker-compose.yml
new file mode 100644
index 0000000..4ccf31d
--- /dev/null
+++ b/containers/docker/multitenant/sites/moni8080/docker-compose.yml
@@ -0,0 +1,12 @@
+version: '2'
+services:
+ moni8080:
+ image: httpd
+ networks:
+ - npm
+ volumes:
+ - /home/moni/sites/moni8080:/usr/local/apache2/htdocs/
+
+networks:
+ npm:
+ external: true
\ No newline at end of file
diff --git a/containers/docker/multitenant/sites/moni8080/index.html b/containers/docker/multitenant/sites/moni8080/index.html
new file mode 100644
index 0000000..c04ca89
--- /dev/null
+++ b/containers/docker/multitenant/sites/moni8080/index.html
@@ -0,0 +1,8 @@
+
+
+ Moni 8080
+
+
+ This is MONI 8080
+
+
\ No newline at end of file
diff --git a/containers/docker/multitenant/sites/moni8081/docker-compose.yml b/containers/docker/multitenant/sites/moni8081/docker-compose.yml
new file mode 100644
index 0000000..4692c06
--- /dev/null
+++ b/containers/docker/multitenant/sites/moni8081/docker-compose.yml
@@ -0,0 +1,12 @@
+version: '2'
+services:
+ moni8081:
+ image: httpd
+ networks:
+ - npm
+ volumes:
+ - /home/moni/sites/moni8081:/usr/local/apache2/htdocs/
+
+networks:
+ npm:
+ external: true
\ No newline at end of file
diff --git a/containers/docker/multitenant/sites/moni8081/index.html b/containers/docker/multitenant/sites/moni8081/index.html
new file mode 100644
index 0000000..aa79650
--- /dev/null
+++ b/containers/docker/multitenant/sites/moni8081/index.html
@@ -0,0 +1,8 @@
+
+
+ Moni 8081
+
+
+ This is MONI 8081
+
+
\ No newline at end of file
diff --git a/containers/docker/multitenant/sites/moni8082/index.html b/containers/docker/multitenant/sites/moni8082/index.html
new file mode 100644
index 0000000..7efe804
--- /dev/null
+++ b/containers/docker/multitenant/sites/moni8082/index.html
@@ -0,0 +1,8 @@
+
+
+ Moni 8082
+
+
+ This is MONI 8082
+
+
\ No newline at end of file
diff --git a/containers/docker/multitenant/wordpress/Dockerfile b/containers/docker/multitenant/wordpress/Dockerfile
new file mode 100644
index 0000000..3fe8b2d
--- /dev/null
+++ b/containers/docker/multitenant/wordpress/Dockerfile
@@ -0,0 +1,7 @@
+# Not the latest version to be able to test updates
+FROM wordpress:6.0.0
+WORKDIR /usr/src/wordpress
+RUN set -eux; \
+ find /etc/apache2 -name '*.conf' -type f -exec sed -ri -e "s!/var/www/html!$PWD!g" -e "s!Directory /var/www/!Directory $PWD!g" '{}' +; \
+ cp -s wp-config-docker.php wp-config.php
+RUN echo "define('FS_METHOD','direct');" >> wp-config.php
\ No newline at end of file
diff --git a/containers/docker/multitenant/wordpress/docker-compose.yml b/containers/docker/multitenant/wordpress/docker-compose.yml
new file mode 100644
index 0000000..ab9c4fe
--- /dev/null
+++ b/containers/docker/multitenant/wordpress/docker-compose.yml
@@ -0,0 +1,43 @@
+version: '3.1'
+
+services:
+
+ wordpress:
+ image: wordpress
+ restart: always
+ # ports:
+ # - 8080:80
+ depends_on:
+ - db
+ environment:
+ WORDPRESS_DB_HOST: db
+ WORDPRESS_DB_USER: exampleuser
+ WORDPRESS_DB_PASSWORD: examplepass
+ WORDPRESS_DB_NAME: exampledb
+ volumes:
+ - wordpress:/var/www/html
+ networks:
+ - infra
+ - internal_network
+
+ db:
+ image: mysql:5.7
+ restart: always
+ environment:
+ MYSQL_DATABASE: exampledb
+ MYSQL_USER: exampleuser
+ MYSQL_PASSWORD: examplepass
+ MYSQL_RANDOM_ROOT_PASSWORD: '1'
+ volumes:
+ - db:/var/lib/mysql
+ networks:
+ - internal_network
+
+volumes:
+ wordpress:
+ db:
+
+networks:
+ infra:
+ external: true
+ internal_network:
diff --git a/containers/docker/nextcloud/README.md b/containers/docker/nextcloud/README.md
new file mode 100644
index 0000000..83c4509
--- /dev/null
+++ b/containers/docker/nextcloud/README.md
@@ -0,0 +1,40 @@
+# Disabling SSL
+
+```sh
+docker exec -it nextcloud-aio-mastercontainer bash
+```
+
+in the container:
+
+```sh
+vi /etc/apache2/sites-available/mastercontainer.conf
+```
+
+Change the line to disable SSL:
+
+```
+SSLEngine off
+```
+
+Restart https *within the container*:
+
+```sh
+killall httpd
+/usr/sbin/httpd
+```
+
+You can exit after that.
+
+# Run OCC command
+
+```sh
+docker exec -it --user www-data nextcloud-aio-nextcloud /var/www/html/occ
+```
+
+# Brute force protection
+
+To add the Caddy Jails container that is running on `192.168.1.200` to the trusted proxies list:
+
+```sh
+docker exec --user www-data -it nextcloud-aio-nextcloud php occ config:system:set trusted_proxies 2 --value="192.168.1.200"
+```
diff --git a/containers/docker/nextcloud/compose.yaml b/containers/docker/nextcloud/compose.yaml
new file mode 100644
index 0000000..f730270
--- /dev/null
+++ b/containers/docker/nextcloud/compose.yaml
@@ -0,0 +1,67 @@
+services:
+ nextcloud-aio-mastercontainer:
+ image: nextcloud/all-in-one:latest
+ init: true
+ restart: always
+ container_name: nextcloud-aio-mastercontainer # This line is not allowed to be changed as otherwise AIO will not work correctly
+ volumes:
+ - nextcloud_aio_mastercontainer:/mnt/docker-aio-config # This line is not allowed to be changed as otherwise the built-in backup solution will not work
+ - /var/run/docker.sock:/var/run/docker.sock:ro # May be changed on macOS, Windows or docker rootless. See the applicable documentation. If adjusting, don't forget to also set 'WATCHTOWER_DOCKER_SOCKET_PATH'!
+ ports:
+ # - 80:80 # Can be removed when running behind a web server or reverse proxy (like Apache, Nginx, Cloudflare Tunnel and else). See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md
+ - 8080:8080
+ # - 8443:8443 # Can be removed when running behind a web server or reverse proxy (like Apache, Nginx, Cloudflare Tunnel and else). See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md
+ environment: # Is needed when using any of the options below
+ # AIO_DISABLE_BACKUP_SECTION: false # Setting this to true allows to hide the backup section in the AIO interface. See https://github.com/nextcloud/all-in-one#how-to-disable-the-backup-section
+ APACHE_PORT: 11000 # Is needed when running behind a web server or reverse proxy (like Apache, Nginx, Cloudflare Tunnel and else). See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md
+ APACHE_IP_BINDING: 0.0.0.0 # Should be set when running behind a web server or reverse proxy (like Apache, Nginx, Cloudflare Tunnel and else) that is running on the same host. See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md
+ SKIP_DOMAIN_VALIDATION: true
+ ALLOW_INSECURE_ACCESS: true
+ # BORG_RETENTION_POLICY: --keep-within=7d --keep-weekly=4 --keep-monthly=6 # Allows to adjust borgs retention policy. See https://github.com/nextcloud/all-in-one#how-to-adjust-borgs-retention-policy
+ # COLLABORA_SECCOMP_DISABLED: false # Setting this to true allows to disable Collabora's Seccomp feature. See https://github.com/nextcloud/all-in-one#how-to-disable-collaboras-seccomp-feature
+ NEXTCLOUD_DATADIR: /home/nextcloud/nextcloud-data # Allows to set the host directory for Nextcloud's datadir. ⚠️⚠️⚠️ Warning: do not set or adjust this value after the initial Nextcloud installation is done! See https://github.com/nextcloud/all-in-one#how-to-change-the-default-location-of-nextclouds-datadir
+ # NEXTCLOUD_MOUNT: /mnt/ # Allows the Nextcloud container to access the chosen directory on the host. See https://github.com/nextcloud/all-in-one#how-to-allow-the-nextcloud-container-to-access-directories-on-the-host
+ # NEXTCLOUD_UPLOAD_LIMIT: 10G # Can be adjusted if you need more. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-upload-limit-for-nextcloud
+ # NEXTCLOUD_MAX_TIME: 3600 # Can be adjusted if you need more. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-max-execution-time-for-nextcloud
+ # NEXTCLOUD_MEMORY_LIMIT: 512M # Can be adjusted if you need more. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-php-memory-limit-for-nextcloud
+ # NEXTCLOUD_TRUSTED_CACERTS_DIR: /path/to/my/cacerts # CA certificates in this directory will be trusted by the OS of the nexcloud container (Useful e.g. for LDAPS) See See https://github.com/nextcloud/all-in-one#how-to-trust-user-defined-certification-authorities-ca
+ # NEXTCLOUD_STARTUP_APPS: deck twofactor_totp tasks calendar contacts notes # Allows to modify the Nextcloud apps that are installed on starting AIO the first time. See https://github.com/nextcloud/all-in-one#how-to-change-the-nextcloud-apps-that-are-installed-on-the-first-startup
+ # NEXTCLOUD_ADDITIONAL_APKS: imagemagick # This allows to add additional packages to the Nextcloud container permanently. Default is imagemagick but can be overwritten by modifying this value. See https://github.com/nextcloud/all-in-one#how-to-add-os-packages-permanently-to-the-nextcloud-container
+ # NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS: imagick # This allows to add additional php extensions to the Nextcloud container permanently. Default is imagick but can be overwritten by modifying this value. See https://github.com/nextcloud/all-in-one#how-to-add-php-extensions-permanently-to-the-nextcloud-container
+ NEXTCLOUD_ENABLE_DRI_DEVICE: true # This allows to enable the /dev/dri device in the Nextcloud container. ⚠️⚠️⚠️ Warning: this only works if the '/dev/dri' device is present on the host! If it should not exist on your host, don't set this to true as otherwise the Nextcloud container will fail to start! See https://github.com/nextcloud/all-in-one#how-to-enable-hardware-transcoding-for-nextcloud
+ # NEXTCLOUD_KEEP_DISABLED_APPS: false # Setting this to true will keep Nextcloud apps that are disabled in the AIO interface and not uninstall them if they should be installed. See https://github.com/nextcloud/all-in-one#how-to-keep-disabled-apps
+ # TALK_PORT: 3478 # This allows to adjust the port that the talk container is using. See https://github.com/nextcloud/all-in-one#how-to-adjust-the-talk-port
+ # WATCHTOWER_DOCKER_SOCKET_PATH: /var/run/docker.sock # Needs to be specified if the docker socket on the host is not located in the default '/var/run/docker.sock'. Otherwise mastercontainer updates will fail. For macos it needs to be '/var/run/docker.sock'
+ # networks: # Is needed when you want to create the nextcloud-aio network with ipv6-support using this file, see the network config at the bottom of the file
+ # - nextcloud-aio # Is needed when you want to create the nextcloud-aio network with ipv6-support using this file, see the network config at the bottom of the file
+ # security_opt: ["label:disable"] # Is needed when using SELinux
+
+ # # Optional: Caddy reverse proxy. See https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md
+ # # You can find further examples here: https://github.com/nextcloud/all-in-one/discussions/588
+ # caddy:
+ # image: caddy:alpine
+ # restart: always
+ # container_name: caddy
+ # volumes:
+ # - ./Caddyfile:/etc/caddy/Caddyfile
+ # - ./certs:/certs
+ # - ./config:/config
+ # - ./data:/data
+ # - ./sites:/srv
+ # network_mode: "host"
+
+volumes: # If you want to store the data on a different drive, see https://github.com/nextcloud/all-in-one#how-to-store-the-filesinstallation-on-a-separate-drive
+ nextcloud_aio_mastercontainer:
+ name: nextcloud_aio_mastercontainer # This line is not allowed to be changed as otherwise the built-in backup solution will not work
+
+# # Optional: If you need ipv6, follow step 1 and 2 of https://github.com/nextcloud/all-in-one/blob/main/docker-ipv6-support.md first and then uncomment the below config in order to activate ipv6 for the internal nextcloud-aio network.
+# # Please make sure to uncomment also the networking lines of the mastercontainer above in order to actually create the network with docker-compose
+# networks:
+# nextcloud-aio:
+# name: nextcloud-aio # This line is not allowed to be changed as otherwise the created network will not be used by the other containers of AIO
+# driver: bridge
+# enable_ipv6: true
+# ipam:
+# driver: default
+# config:
+# - subnet: fd12:3456:789a:2::/64 # IPv6 subnet to use
diff --git a/containers/docker/nextcloud/restart.sh b/containers/docker/nextcloud/restart.sh
new file mode 100755
index 0000000..356135d
--- /dev/null
+++ b/containers/docker/nextcloud/restart.sh
@@ -0,0 +1,13 @@
+docker stop nextcloud-aio-apache && docker start nextcloud-aio-apache
+docker stop nextcloud-aio-notify-push && docker start nextcloud-aio-notify-push
+docker stop nextcloud-aio-nextcloud && docker start nextcloud-aio-nextcloud
+# docker stop nextcloud-aio-docker-socket-proxy && docker start nextcloud-aio-docker-socket-proxy
+docker stop nextcloud-aio-imaginary && docker start nextcloud-aio-imaginary
+# docker stop nextcloud-aio-fulltextsearch && docker start nextcloud-aio-fulltextsearch
+# docker stop nextcloud-aio-clamav && docker start nextcloud-aio-clamav
+docker stop nextcloud-aio-redis && docker start nextcloud-aio-redis
+docker stop nextcloud-aio-database && docker start nextcloud-aio-database
+docker stop nextcloud-aio-talk && docker start nextcloud-aio-talk
+docker stop nextcloud-aio-collabora && docker start nextcloud-aio-collabora
+# docker stop nextcloud-aio-domaincheck # && docker start nextcloud-aio-domaincheck
+# docker stop nextcloud-aio-mastercontainer && docker start nextcloud-aio-mastercontainer
diff --git a/containers/docker/nextcloud/stop.sh b/containers/docker/nextcloud/stop.sh
new file mode 100755
index 0000000..a297383
--- /dev/null
+++ b/containers/docker/nextcloud/stop.sh
@@ -0,0 +1,13 @@
+docker stop nextcloud-aio-apache
+docker stop nextcloud-aio-notify-push
+docker stop nextcloud-aio-nextcloud
+# docker stop nextcloud-aio-docker-socket-proxy
+docker stop nextcloud-aio-imaginary
+# docker stop nextcloud-aio-fulltextsearch
+# docker stop nextcloud-aio-clamav
+docker stop nextcloud-aio-redis
+docker stop nextcloud-aio-database
+docker stop nextcloud-aio-talk
+docker stop nextcloud-aio-collabora
+# docker stop nextcloud-aio-domaincheck
+# docker stop nextcloud-aio-mastercontainer
diff --git a/containers/docker/photoprism/README.md b/containers/docker/photoprism/README.md
new file mode 100644
index 0000000..adcee06
--- /dev/null
+++ b/containers/docker/photoprism/README.md
@@ -0,0 +1,3 @@
+```sh
+docker compose exec photoprism photoprism --help
+```
diff --git a/containers/docker/photoprism/docker-compose.yml b/containers/docker/photoprism/docker-compose.yml
new file mode 100644
index 0000000..68c42ba
--- /dev/null
+++ b/containers/docker/photoprism/docker-compose.yml
@@ -0,0 +1,152 @@
+# Example Docker Compose config file for PhotoPrism (Linux / AMD64)
+#
+# Note:
+# - Running PhotoPrism on a server with less than 4 GB of swap space or setting a memory/swap limit can cause unexpected
+# restarts ("crashes"), for example, when the indexer temporarily needs more memory to process large files.
+# - If you install PhotoPrism on a public server outside your home network, please always run it behind a secure
+# HTTPS reverse proxy such as Traefik or Caddy. Your files and passwords will otherwise be transmitted
+# in clear text and can be intercepted by anyone, including your provider, hackers, and governments:
+# https://docs.photoprism.app/getting-started/proxies/traefik/
+#
+# Setup Guides:
+# - https://docs.photoprism.app/getting-started/docker-compose/
+# - https://docs.photoprism.app/getting-started/raspberry-pi/
+# - https://www.photoprism.app/kb/activation
+#
+# Troubleshooting Checklists:
+# - https://docs.photoprism.app/getting-started/troubleshooting/
+# - https://docs.photoprism.app/getting-started/troubleshooting/docker/
+# - https://docs.photoprism.app/getting-started/troubleshooting/mariadb/
+#
+# CLI Commands:
+# - https://docs.photoprism.app/getting-started/docker-compose/#command-line-interface
+#
+# All commands may have to be prefixed with "sudo" when not running as root.
+# This will point the home directory shortcut ~ to /root in volume mounts.
+
+services:
+ photoprism:
+ ## Use photoprism/photoprism:preview for testing preview builds:
+ image: photoprism/photoprism:latest
+ ## Don't enable automatic restarts until PhotoPrism has been properly configured and tested!
+ ## If the service gets stuck in a restart loop, this points to a memory, filesystem, network, or database issue:
+ ## https://docs.photoprism.app/getting-started/troubleshooting/#fatal-server-errors
+ # restart: unless-stopped
+ stop_grace_period: 10s
+ depends_on:
+ - mariadb
+ security_opt:
+ - seccomp:unconfined
+ - apparmor:unconfined
+ ## Server port mapping in the format "Host:Container". To use a different port, change the host port on
+ ## the left-hand side and keep the container port, e.g. "80:2342" (for HTTP) or "443:2342 (for HTTPS):
+ ports:
+ - "2342:2342"
+ ## Before you start the service, please check the following config options (and change them as needed):
+ ## https://docs.photoprism.app/getting-started/config-options/
+ environment:
+ PHOTOPRISM_ADMIN_USER: "admin" # admin login username
+ PHOTOPRISM_ADMIN_PASSWORD: "5bp3ptdBGM173t" # initial admin password (8-72 characters)
+ PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
+ PHOTOPRISM_SITE_URL: "http://localhost:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
+ PHOTOPRISM_DISABLE_TLS: "false" # disables HTTPS/TLS even if the site URL starts with https:// and a certificate is available
+ PHOTOPRISM_DEFAULT_TLS: "true" # defaults to a self-signed HTTPS/TLS certificate if no other certificate is available
+ PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
+ PHOTOPRISM_HTTP_COMPRESSION: "gzip" # improves transfer speed and bandwidth utilization (none or gzip)
+ PHOTOPRISM_LOG_LEVEL: "info" # log level: trace, debug, info, warning, error, fatal, or panic
+ PHOTOPRISM_READONLY: "false" # do not modify originals directory (reduced functionality)
+ PHOTOPRISM_EXPERIMENTAL: "false" # enables experimental features
+ PHOTOPRISM_DISABLE_CHOWN: "false" # disables updating storage permissions via chmod and chown on startup
+ PHOTOPRISM_DISABLE_WEBDAV: "false" # disables built-in WebDAV server
+ PHOTOPRISM_DISABLE_SETTINGS: "false" # disables settings UI and API
+ PHOTOPRISM_DISABLE_TENSORFLOW: "false" # disables all features depending on TensorFlow
+ PHOTOPRISM_DISABLE_FACES: "false" # disables face detection and recognition (requires TensorFlow)
+ PHOTOPRISM_DISABLE_CLASSIFICATION: "false" # disables image classification (requires TensorFlow)
+ PHOTOPRISM_DISABLE_VECTORS: "false" # disables vector graphics support
+ PHOTOPRISM_DISABLE_RAW: "false" # disables indexing and conversion of RAW images
+ PHOTOPRISM_RAW_PRESETS: "false" # enables applying user presets when converting RAW images (reduces performance)
+ PHOTOPRISM_SIDECAR_YAML: "true" # creates YAML sidecar files to back up picture metadata
+ PHOTOPRISM_BACKUP_ALBUMS: "true" # creates YAML files to back up album metadata
+ PHOTOPRISM_BACKUP_DATABASE: "true" # creates regular backups based on the configured schedule
+ PHOTOPRISM_BACKUP_SCHEDULE: "daily" # backup SCHEDULE in cron format (e.g. "0 12 * * *" for daily at noon) or at a random time (daily, weekly)
+ PHOTOPRISM_INDEX_SCHEDULE: "" # indexing SCHEDULE in cron format (e.g. "@every 3h" for every 3 hours; "" to disable)
+ PHOTOPRISM_AUTO_INDEX: 300 # delay before automatically indexing files in SECONDS when uploading via WebDAV (-1 to disable)
+ PHOTOPRISM_AUTO_IMPORT: -1 # delay before automatically importing files in SECONDS when uploading via WebDAV (-1 to disable)
+ PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
+ PHOTOPRISM_UPLOAD_NSFW: "true" # allows uploads that MAY be offensive (no effect without TensorFlow)
+ # PHOTOPRISM_DATABASE_DRIVER: "sqlite" # SQLite is an embedded database that does not require a separate database server
+ PHOTOPRISM_DATABASE_DRIVER: "mysql" # MariaDB 10.5.12+ (MySQL successor) offers significantly better performance compared to SQLite
+ PHOTOPRISM_DATABASE_SERVER: "mariadb:3306" # MariaDB database server (hostname:port)
+ PHOTOPRISM_DATABASE_NAME: "photoprism" # MariaDB database schema name
+ PHOTOPRISM_DATABASE_USER: "photoprism" # MariaDB database user name
+ PHOTOPRISM_DATABASE_PASSWORD: "insecure" # MariaDB database user password
+ PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
+ PHOTOPRISM_SITE_DESCRIPTION: "" # meta site description
+ PHOTOPRISM_SITE_AUTHOR: "" # meta site author
+ ## Video Transcoding (https://docs.photoprism.app/getting-started/advanced/transcoding/):
+ PHOTOPRISM_FFMPEG_ENCODER: "intel" # H.264/AVC encoder (software, intel, nvidia, apple, raspberry, or vaapi)
+ # PHOTOPRISM_FFMPEG_SIZE: "1920" # video size limit in pixels (720-7680) (default: 3840)
+ # PHOTOPRISM_FFMPEG_BITRATE: "32" # video bitrate limit in Mbit/s (default: 50)
+ ## Run/install on first startup (options: update https gpu ffmpeg tensorflow davfs clitools clean):
+ # PHOTOPRISM_INIT: "https gpu tensorflow"
+ ## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
+ # PHOTOPRISM_UID: 1000
+ # PHOTOPRISM_GID: 1000
+ # PHOTOPRISM_UMASK: 0000
+ ## Start as non-root user before initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
+ # user: "1000:1000"
+ ## Share hardware devices with FFmpeg and TensorFlow (optional):
+ devices:
+ - "/dev/dri:/dev/dri" # Intel QSV
+ # - "/dev/nvidia0:/dev/nvidia0" # Nvidia CUDA
+ # - "/dev/nvidiactl:/dev/nvidiactl"
+ # - "/dev/nvidia-modeset:/dev/nvidia-modeset"
+ # - "/dev/nvidia-nvswitchctl:/dev/nvidia-nvswitchctl"
+ # - "/dev/nvidia-uvm:/dev/nvidia-uvm"
+ # - "/dev/nvidia-uvm-tools:/dev/nvidia-uvm-tools"
+ # - "/dev/video11:/dev/video11" # Video4Linux Video Encode Device (h264_v4l2m2m)
+ working_dir: "/photoprism" # do not change or remove
+ ## Storage Folders: "~" is a shortcut for your home directory, "." for the current directory
+ volumes:
+ # "/host/folder:/photoprism/folder" # Example
+ - "/home/photoprism/Pictures:/photoprism/originals" # Original media files (DO NOT REMOVE)
+ # - "/example/family:/photoprism/originals/family" # *Additional* media folders can be mounted like this
+ - "/home/photoprism/Import:/photoprism/import" # *Optional* base folder from which files can be imported to originals
+ - "/home/photoprism/storage:/photoprism/storage" # *Writable* storage folder for cache, database, and sidecar files (DO NOT REMOVE)
+
+ ## MariaDB Database Server (recommended)
+ ## see https://docs.photoprism.app/getting-started/faq/#should-i-use-sqlite-mariadb-or-mysql
+ mariadb:
+ image: mariadb:11
+ ## If MariaDB gets stuck in a restart loop, this points to a memory or filesystem issue:
+ ## https://docs.photoprism.app/getting-started/troubleshooting/#fatal-server-errors
+ restart: unless-stopped
+ stop_grace_period: 5s
+ security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239
+ - seccomp:unconfined
+ - apparmor:unconfined
+ command: --innodb-buffer-pool-size=512M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
+ ## Never store database files on an unreliable device such as a USB flash drive, an SD card, or a shared network folder:
+ volumes:
+ - "/home/photoprism/database:/var/lib/mysql" # DO NOT REMOVE
+ environment:
+ MARIADB_AUTO_UPGRADE: "1"
+ MARIADB_INITDB_SKIP_TZINFO: "1"
+ MARIADB_DATABASE: "photoprism"
+ MARIADB_USER: "photoprism"
+ MARIADB_PASSWORD: "insecure"
+ MARIADB_ROOT_PASSWORD: "insecure"
+
+ ## Watchtower upgrades services automatically (optional)
+ ## see https://docs.photoprism.app/getting-started/updates/#watchtower
+ ## activate via "COMPOSE_PROFILES=update docker compose up -d"
+ watchtower:
+ restart: unless-stopped
+ image: containrrr/watchtower
+ profiles: ["update"]
+ environment:
+ WATCHTOWER_CLEANUP: "true"
+ WATCHTOWER_POLL_INTERVAL: 7200 # checks for updates every two hours
+ volumes:
+ - "/var/run/docker.sock:/var/run/docker.sock"
+ - "~/.docker/config.json:/config.json" # optional, for authentication if you have a Docker Hub account
diff --git a/containers/docker/pinepods/compose.yaml b/containers/docker/pinepods/compose.yaml
new file mode 100644
index 0000000..e53d1d9
--- /dev/null
+++ b/containers/docker/pinepods/compose.yaml
@@ -0,0 +1,31 @@
+name: pinepods
+
+services:
+ db:
+ image: postgres:latest
+ env_file: '.env'
+ volumes:
+ - /home/pinepods/pinepods/pgdata:/var/lib/postgresql/data
+ # Exposing the postgres database port is dumb.
+ # ports:
+ # - "5432:5432"
+ restart: always
+
+ valkey:
+ image: valkey/valkey:8-alpine
+ # Exposing a nosql database is expecially dumb.
+ # ports:
+ # - "6379:6379"
+
+ pinepods:
+ image: madeofpendletonwool/pinepods:latest
+ ports:
+ - "8040:8040"
+ env_file: '.env'
+ volumes:
+ # Mount the download and backup locations on the server
+ - /home/pinepods/pinepods/downloads:/opt/pinepods/downloads
+ - /home/pinepods/pinepods/backups:/opt/pinepods/backups
+ depends_on:
+ - db
+ - valkey
\ No newline at end of file
diff --git a/containers/docker/qbittorrent/docker-compose.yml b/containers/docker/qbittorrent/docker-compose.yml
new file mode 100644
index 0000000..8dfae1b
--- /dev/null
+++ b/containers/docker/qbittorrent/docker-compose.yml
@@ -0,0 +1,42 @@
+# https://github.com/nickkjolsing/dockerMullvadVPN
+
+name: torrent
+
+services:
+ openvpn-client:
+ image: ghcr.io/wfg/openvpn-client # Image on Docker. Shoutout to ghcr.io
+ container_name: openvpn-client
+ cap_add:
+ - NET_ADMIN # Needs to be here
+ environment:
+ - KILL_SWITCH=on # Turns off internet access if the VPN connection drops
+ - SUBNETS=192.168.0.0/24,192.168.1.0/24
+ devices:
+ - /dev/net/tun
+ volumes:
+ - /home/jellyfin/mullvad_config_linux_nl_ams:/data/vpn
+ ports:
+ - 8082:8082
+ - 6881:6881
+ - 6881:6881/udp
+ restart: unless-stopped
+
+ qbittorrent:
+ image: lscr.io/linuxserver/qbittorrent:latest
+ container_name: qbittorrent
+ environment:
+ - PUID=1003
+ - PGID=1003
+ - TZ=Europe/Amsterdam
+ - WEBUI_PORT=8082
+ - TORRENTING_PORT=6881
+ volumes:
+ - /home/jellyfin/qbitorrent/appdata:/config
+ - /home/jellyfin/qbitorrent/downloads:/downloads #optional
+ - /home/jellyfin/jellyfin/media:/media
+ # ports:
+ # - 8082:8082
+ # - 6881:6881
+ # - 6881:6881/udp
+ network_mode: container:openvpn-client # This uses the port setting of the openvpn
+ restart: unless-stopped
diff --git a/containers/docker/webtop/compose.yaml b/containers/docker/webtop/compose.yaml
new file mode 100644
index 0000000..4236359
--- /dev/null
+++ b/containers/docker/webtop/compose.yaml
@@ -0,0 +1,24 @@
+services:
+ webtop:
+ image: lscr.io/linuxserver/webtop:fedora-xfce
+ container_name: webtop
+ security_opt:
+ - seccomp:unconfined #optional
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=Europe/Amsterdam
+ - SUBFOLDER=/ #optional
+ - TITLE=Webtop #optional
+ - CUSTOM_USER=moni
+ - PASSWORD=Pd5oBZ3vN31wCkj8
+ volumes:
+ - /home/moni/webtop/data:/config
+ - /var/run/docker.sock:/var/run/docker.sock #optional
+ ports:
+ - 3000:3000
+ - 3001:3001
+ devices:
+ - /dev/dri:/dev/dri #optional
+ shm_size: "1gb" #optional
+ restart: unless-stopped
diff --git a/containers/docker/windows/compose.yaml b/containers/docker/windows/compose.yaml
new file mode 100644
index 0000000..dea39a9
--- /dev/null
+++ b/containers/docker/windows/compose.yaml
@@ -0,0 +1,18 @@
+services:
+ windows:
+ image: dockurr/windows
+ container_name: windows
+ environment:
+ VERSION: "11"
+ devices:
+ - /dev/kvm
+ - /dev/net/tun
+ cap_add:
+ - NET_ADMIN
+ ports:
+ - 8006:8006
+ - 3389:3389/tcp
+ - 3389:3389/udp
+ stop_grace_period: 2m
+ volumes:
+ - /var/win:/storage
diff --git a/containers/docker/wordpress/docker-compose.yml b/containers/docker/wordpress/docker-compose.yml
new file mode 100644
index 0000000..8254dc0
--- /dev/null
+++ b/containers/docker/wordpress/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '3.1'
+
+services:
+
+ wordpress:
+ image: wordpress
+ restart: always
+ ports:
+ - 8080:80
+ environment:
+ WORDPRESS_DB_HOST: db
+ WORDPRESS_DB_USER: exampleuser
+ WORDPRESS_DB_PASSWORD: examplepass
+ WORDPRESS_DB_NAME: exampledb
+ volumes:
+ - wordpress:/var/www/html
+
+ db:
+ image: mysql:8.0
+ restart: always
+ environment:
+ MYSQL_DATABASE: exampledb
+ MYSQL_USER: exampleuser
+ MYSQL_PASSWORD: examplepass
+ MYSQL_RANDOM_ROOT_PASSWORD: '1'
+ volumes:
+ - db:/var/lib/mysql
+
+volumes:
+ wordpress:
+ db:
\ No newline at end of file
diff --git a/homeservers/Backup.md b/homeservers/Backup.md
new file mode 100644
index 0000000..fdc93bd
--- /dev/null
+++ b/homeservers/Backup.md
@@ -0,0 +1,86 @@
+# Syncthing
+
+```sh
+sudo systemctl restart syncthing@photoprism.service
+```
+
+Admin interface: http://192.168.1.10:8384/
+
+I have a user called `photoprism` and the password is in Bitwarden
+
+# Borg Backup to Hetzner Storage Box
+
+For backup I use [Borg](https://borgbackup.readthedocs.io/). I followed the steps described in this community article:
+
+* [Install and Configure BorgBackup](https://community.hetzner.com/tutorials/install-and-configure-borgbackup)
+
+We have two backups in the storage box:
+
+* `/./borgbackup/photoprism`
+* `/./borgbackup/nextcloud`
+
+## Photoprism backup
+
+This is what I did for Photoprism
+
+The init script (which is only done once, be careful it will overwrite!):
+
+```sh
+# (As root)
+export BORG_RSH='ssh -i /root/.ssh/id_ed25519'
+export BORG_PASSPHRASE=""
+borg init --encryption=repokey ssh://u388089@u388089.your-storagebox.de:23/./borgbackup/photoprism
+```
+
+It outputs a key which I put in Bitwarden (you need it to decrypt the backup).
+
+To make a manual backup:
+
+```sh
+export BORG_RSH='ssh -i /root/.ssh/id_ed25519'
+export BORG_PASSPHRASE=""
+borg create --stats ssh://u388089@u388089.your-storagebox.de:23/./borgbackup/photoprism::2024_11_24 /home/photoprism/Import/ /home/photoprism/Pictures/
+```
+
+The above can be found in Bitwarden, look for "Hetzner Borg Backup Script for Photoprism"
+
+I also created a bash script in `/usr/local/bin/photoprism_backup.sh` (as mentioned in the article).
+
+You can find it here in this repo:
+
+* [photoprism_backup.sh](./scripts/photoprism_backup.sh)
+
+## Nextcloud backup
+
+Ok, this is confusing but Nextcloud uses Borgbackup internally. Go to the [aio interface](http://192.168.1.10:8080/) and you'll notice there are backups to:
+
+```
+/root/nextcloudbackup
+```
+
+So nextcloud keeps making backups to this directory. Not ideal (TODO: I gotta find a solution for this).
+
+But I can rclone the data:
+
+```sh
+rclone sync -v /root/nextcloudbackup/ hetzner:/cloned_borgbackup/nextcloud
+```
+
+For this I also created a script and placed it in `/usr/local/bin`
+
+[nextcloud_backup.sh](./scripts/nextcloud_backup.sh)
+
+# Automatic backup
+
+* [cloud_backup.service](./scripts/cloud_backup.service)
+* [cloud_backup.timer](./scripts/cloud_backup.timer)
+
+```sh
+cp -v cloud_backup.service /etc/systemd/system/
+cp -v cloud_backup.timer /etc/systemd/system/
+```
+
+```sh
+systemctl enable cloud_backup.service
+systemctl enable cloud_backup.timer
+```
diff --git a/homeservers/Immich.md b/homeservers/Immich.md
new file mode 100644
index 0000000..02b99ac
--- /dev/null
+++ b/homeservers/Immich.md
@@ -0,0 +1,15 @@
+Compose file:
+
+[compose.yaml](../containers/docker/immich/compose.yaml)
+
+For immich I added a dedicated user:
+
+```sh
+sudo useradd immich
+```
+
+This creates a `/home/immich` directory. We neet to run `cat /etc/passwd` to get the id of the user.
+
+Unfortunately, I have not been able to use the user id for the containers, so everything is written as root. I'm still using `/home/immich`, but all the files are written there as root.
+
+Eventually I might use podman but I don't know enough about it yet to get comfortable with it.
diff --git a/homeservers/Jellyfin.md b/homeservers/Jellyfin.md
new file mode 100644
index 0000000..4606095
--- /dev/null
+++ b/homeservers/Jellyfin.md
@@ -0,0 +1,21 @@
+# Jellyfin
+
+Compose file:
+
+* [compose.yaml](../containers/docker/jellyfin/compose.yaml)
+
+I made a user names `jellyfin` and put its uid:guid in the compose file. All the files are in the home directory of that user `/home/jellyfin`.
+
+The user also has the authorized_keys so I can do this:
+
+```sh
+ssh jellyfin@192.168.1.10
+```
+
+and proceed to add the directories for the media files:
+
+```sh
+cd jellyfin/media/tvshows/
+mkdir "Show I Want To Watch (2020)"
+mkdir "Season 01"
+```
diff --git a/homeservers/Nextcloud.md b/homeservers/Nextcloud.md
new file mode 100644
index 0000000..242ea7f
--- /dev/null
+++ b/homeservers/Nextcloud.md
@@ -0,0 +1,22 @@
+# Nextcloud
+
+I have a dedicated user:
+
+```sh
+sudo useradd nextcloud
+```
+
+I'm running the Nextcloud All-In-One container. In order to make it easy I have a [stop](../containers/docker/nextcloud/stop.sh) and [restart](../containers/docker/nextcloud/stop.sh) script.
+
+Nextcloud runs on
+
+* https://nextcloud.allisonandmoni.online/
+
+When you first start it up, you need to first disable the SSL from within the container:
+
+* [Ugly hack README](../containers/docker/nextcloud/README.md)
+
+And then go to:
+
+* http://192.168.1.11:8080
+
diff --git a/homeservers/PhotoPrism.md b/homeservers/PhotoPrism.md
new file mode 100644
index 0000000..a5615bb
--- /dev/null
+++ b/homeservers/PhotoPrism.md
@@ -0,0 +1,43 @@
+# Photoprism
+
+What we are really running on this machine is Photoprism using this [docker-compose](../containers/docker/photoprism/docker-compose.yml) file, like this:
+
+```sh
+cd ~/projects/stuff/containers/docker/photoprism
+docker compose up --detach
+```
+
+To stop it:
+
+```sh
+docker compose down
+```
+
+To update it:
+
+```sh
+docker compose pull
+```
+
+Photoprism is running on: https://photos.allisonandmoni.online/
+
+I have set up a Systemd service and timer to automatically run the import once an hour. They can be found:
+
+* [photoprism_import.service](./scripts/photoprism_import.service)
+* [photoprism_import_timer.service](./scripts/photoprism_import_timer.service)
+
+To install:
+
+```sh
+cp -v photoprism_import.service /etc/systemd/system/
+cp -v photoprism_import.timer /etc/systemd/system/
+```
+
+Enable:
+
+```sh
+systemctl enable photoprism_import.service
+systemctl enable photoprism_import.timer
+```
+
+In the `compose.yaml` file you'll notice I have the files stored in `/home/photoprism`. That's because I thought I could run it with podman as a dedicated user. I abandoned that idea and am now running it in Docker instead. The directories `/home/photoprism/Pictures` and `/home/photoprism/Import` are all owned by `root`
diff --git a/homeservers/README.md b/homeservers/README.md
new file mode 100644
index 0000000..d61417b
--- /dev/null
+++ b/homeservers/README.md
@@ -0,0 +1,43 @@
+# General
+
+This file describes the setup of the servers I have running at home, with some instructions.
+
+At the moment I'm running:
+
+* Raspberry PI4 8GB: `ssh moni@192.168.1.108`
+* Lenovo Thinkcentre M900, i5-6500T: `ssh moni@192.168.1.10`, 8GB RAM, 500G SSD
+* Lenovo Thinkcentre M900, i5-6500T: `ssh moni@192.168.1.11`, 16GB RAM, 126G NVME + 1TB SSD
+
+For Raspberry PI, see [RPI](./RPI.md). That is where the routing happens.
+For Lenovo Thinkcenter, see below. That is where all the action happens.
+
+# Lenovo Thinkcentre M900, i5-6500T
+
+I have two Lenovo Thinkcentres, one with 8GB of ram and 500GB. The other with 16GB of ram and an NVME of 128GB and an SSD of 1GB.
+
+The idea is to have one in production for all my servers running on `allisonandmoni.online` domain and the other is to play with.
+
+Right now my OS of choice is AlmaLinux, but that can change with my mood.
+
+On my production machine I am running Docker.
+
+You don't need to be root to use it:
+
+```sh
+docker ps -a
+```
+
+Right now I'm running the following containers:
+
+* [PhotoPrism](./PhotoPrism.md), reachable by https://photos.allisonandmoni.online
+* [JellyFin](./Jellyfin.md), reachable by https://jellyfin.allisonandmoni.online/
+* [Nextcloud](./Nextcloud.md), reachable by https://nextcloud.allisonandmoni.online/
+* [qBittorrent](./qBittorrent.md), reachable by https://qbittorrent.allisonandmoni.online/
+* [Immich](./Immich.md), reachable by https://photos2.allisonandmoni.online
+
+# Backup and restore
+
+I have backup and restore scripts and instructions. See here:
+
+* [Backup](./Backup.md)
+* [Restore](./Restore.md)
\ No newline at end of file
diff --git a/homeservers/RPI.md b/homeservers/RPI.md
new file mode 100644
index 0000000..91b2523
--- /dev/null
+++ b/homeservers/RPI.md
@@ -0,0 +1,53 @@
+# Raspberry PI
+
+On the Raspberry PI I am running FreeBSD with Bastille which currently has 1 jail:
+
+```sh
+sudo bastille list
+```
+
+```
+ID IP Address Hostname Path
+1 192.168.1.200 caddy /usr/local/bastille/jails/caddy/root
+```
+
+The ip address 192.168.1.200 is chosen deliberately. On my router (192.168.1.1) I have the following port mappings:
+
+* 32222 to 192.168.1.108:22 (ssh) - Maps to the Raspberry PI 8 for external access
+* 80 to 192.168.1.200:80 (http) - Maps to the Caddy jail
+* 443 to 192.168.1.200:443 (https) - Maps to the Caddy jail
+* 3478 to 192.168.1.10 (Nextcloud talk) - This is a direct connection to the Lenovo machine.
+
+To view the Caddy setup in the Caddy jail, run this:
+
+```sh
+sudo -i
+bastille console caddy
+cat /usr/local/etc/caddy/Caddyfile
+```
+
+Result:
+
+```
+www.allisonandmoni.online {
+ reverse_proxy 192.168.1.10:8081
+}
+
+photos.allisonandmoni.online {
+ reverse_proxy 192.168.1.10:2342
+}
+
+nextcloud.allisonandmoni.online {
+ reverse_proxy 192.168.1.10:11000
+}
+
+jellyfin.allisonandmoni.online {
+ reverse_proxy 192.168.1.10:8096
+}
+```
+
+As you can see I have setup reverse proxies for www, photos and nextcloud for allisonandmoni.online.
+
+The instructions for the caddy setup on my RPI4 can be found in this file:
+
+* [Caddy on FreeBSD](../os/FreeBSD/Bastille.md#caddy)
diff --git a/homeservers/Restore.md b/homeservers/Restore.md
new file mode 100644
index 0000000..c665cdb
--- /dev/null
+++ b/homeservers/Restore.md
@@ -0,0 +1,62 @@
+# Restore
+
+## Borg backup
+
+First we need to install Borg Backup:
+
+```sh
+dnf install -y borgbackup
+```
+
+## Hetzner Storage Box
+
+So I have a Hetzner Storage Box which is reachable by:
+
+```sh
+ssh -p23 u388089@u388089.your-storagebox.de
+```
+
+There are two subdirectories:
+
+```sh
+/borgbackup # This is an actual borg backup of PhotoPrism with history and everything
+/cloned_borgbackup # This is a rclone of a borg backup of nextcloud that happens on my local machine.
+```
+
+First, we're going to talk about restoring PhotoPrism
+
+## Restore Photoprism
+
+List:
+
+```sh
+export BORG_PASSPHRASE=''
+borg list ssh://u388089@u388089.your-storagebox.de:23/./borgbackup/photoprism
+```
+
+(Optional, to see what you have) Mount a snapshot:
+
+```sh
+borg mount ssh://u388089@u388089.your-storagebox.de:23/./borgbackup/photoprism::2025-01-14_04:00 /home/moni/borg_restore/
+```
+
+Unmount it:
+
+```sh
+borg umount /home/moni/borg_restore/
+```
+
+Restore a backup
+
+You might want to tmux first:
+
+```sh
+tmux
+```
+
+```sh
+borg --progress --verbose extract ssh://u388089@u388089.your-storagebox.de:23/./borgbackup/photoprism::2025-01-14_04:00 /home/photoprism/Pictures
+```
+
+Giving a local target directory is not supported. The restore will be in a subdirectory. So if you're in `/home/moni` it will restore to `/home/moni/home/photoprism/Pictures`. You can't change this behavior.
+
diff --git a/homeservers/qBittorrent.md b/homeservers/qBittorrent.md
new file mode 100644
index 0000000..a61d5c8
--- /dev/null
+++ b/homeservers/qBittorrent.md
@@ -0,0 +1,11 @@
+# qBittorrent
+
+Compose file:
+
+* [compose.yaml](../containers/docker/qbittorrent/docker-compose.yml)
+
+qBittorrent uses the same user as jellyfin which writes everything to `/home/jellyfin`
+
+I also put the `jellyfin` uid:guid into the qbitorrent container. The files are downloaded to the `bittorrent/downloads` subdirtory of `jellyfin` home.
+
+When I want to watch something I can copy it from the downloads directory to the media directory of jellyfin.
diff --git a/homeservers/scripts/cloud_backup.service b/homeservers/scripts/cloud_backup.service
new file mode 100644
index 0000000..674c6c1
--- /dev/null
+++ b/homeservers/scripts/cloud_backup.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Run Cloud Backup
+
+[Service]
+ExecStart=/bin/bash /usr/local/bin/cloud_backup.sh
+
+[Install]
+WantedBy=multi-user.target
diff --git a/homeservers/scripts/cloud_backup.sh b/homeservers/scripts/cloud_backup.sh
new file mode 100644
index 0000000..a7544ed
--- /dev/null
+++ b/homeservers/scripts/cloud_backup.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+/bin/bash /usr/local/bin/photoprism_backup.sh
+/bin/bash /usr/local/bin/nextcloud_backup.sh
diff --git a/homeservers/scripts/cloud_backup.timer b/homeservers/scripts/cloud_backup.timer
new file mode 100644
index 0000000..07bf5b3
--- /dev/null
+++ b/homeservers/scripts/cloud_backup.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Run PCloud Backup timer
+
+[Timer]
+OnCalendar=0/4:00:00
+Unit=cloud_backup.service
+
+[Install]
+WantedBy=timers.target
diff --git a/homeservers/scripts/nextcloud_backup.sh b/homeservers/scripts/nextcloud_backup.sh
new file mode 100644
index 0000000..6f3c01b
--- /dev/null
+++ b/homeservers/scripts/nextcloud_backup.sh
@@ -0,0 +1 @@
+rclone sync /root/nextcloudbackup/ hetzner:/cloned_borgbackup/nextcloud --log-file /var/log/nextcloud_backup.log --log-level INFO
\ No newline at end of file
diff --git a/homeservers/scripts/photoprism_backup.sh b/homeservers/scripts/photoprism_backup.sh
new file mode 100644
index 0000000..e282287
--- /dev/null
+++ b/homeservers/scripts/photoprism_backup.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+##
+## Set environment variables
+##
+
+## if you don't use the standard SSH key,
+## you have to specify the path to the key like this
+export BORG_RSH='ssh -i /root/.ssh/id_ed25519'
+
+## You can save your borg passphrase in an environment
+## variable, so you don't need to type it in when using borg
+export BORG_PASSPHRASE=''
+
+##
+## Set some variables
+##
+
+LOG='/var/log/photoprism_backup.log'
+export BACKUP_USER='u388089'
+export REPOSITORY_DIR='photoprism'
+
+## Tip: If using with a Backup Space you have to use
+## 'your-storagebox.de' instead of 'your-backup.de'
+
+export REPOSITORY="ssh://${BACKUP_USER}@${BACKUP_USER}.your-storagebox.de:23/./borgbackup/${REPOSITORY_DIR}"
+
+##
+## Output to a logfile
+##
+
+exec > >(tee -i ${LOG})
+exec 2>&1
+
+echo "###### Backup started: $(date) ######"
+
+##
+## At this place you could perform different tasks
+## that will take place before the backup, e.g.
+##
+## - Create a list of installed software
+## - Create a database dump
+##
+
+##
+## Transfer the files into the repository.
+## In this example the folders root, etc,
+## var/www and home will be saved.
+## In addition you find a list of excludes that should not
+## be in a backup and are excluded by default.
+##
+
+echo "Transfer files ..."
+borg create -v --stats \
+ $REPOSITORY::'{now:%Y-%m-%d_%H:%M}' \
+ /home/photoprism/Import/ \
+ /home/photoprism/Pictures/
+
+echo "###### Backup ended: $(date) ######"
diff --git a/homeservers/scripts/photoprism_import.service b/homeservers/scripts/photoprism_import.service
new file mode 100644
index 0000000..567702e
--- /dev/null
+++ b/homeservers/scripts/photoprism_import.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Run Photoprism Import
+
+[Service]
+ExecStart=docker exec photoprism-photoprism-1 photoprism import
+
+[Install]
+WantedBy=multi-user.target
diff --git a/homeservers/scripts/photoprism_import.timer b/homeservers/scripts/photoprism_import.timer
new file mode 100644
index 0000000..c5237c1
--- /dev/null
+++ b/homeservers/scripts/photoprism_import.timer
@@ -0,0 +1,9 @@
+[Unit]
+Description=Run Photoprism Import script hourly
+
+[Timer]
+OnCalendar=hourly
+Unit=photoprism_import.service
+
+[Install]
+WantedBy=timers.target
diff --git a/laptops/Clevo_Laptop.md b/laptops/Clevo_Laptop.md
new file mode 100644
index 0000000..f3a8e5f
--- /dev/null
+++ b/laptops/Clevo_Laptop.md
@@ -0,0 +1,179 @@
+Clevo laptop which is sold by Tuxedo computers.
+
+# General
+
+Set the hostname:
+
+```sh
+hostnamectl set-hostname moni-fedora # Fedora
+hostnamectl set-hostname moni-opensuse # openSUSE
+```
+
+Install my favorite packages
+
+```sh
+zypper install tmux htop neovim git ncdu podman # openSUSE
+dnf install tmux htop neovim git ncdu podman # Fedora
+```
+
+# Suspend when laptop lid closed
+
+On openSUSE:
+
+On Fedora:
+
+```sh
+nvim /usr/lib/systemd/logind.conf # Fedora & openSUSE
+nvim /etc/systemd/logind.conf # Ubuntu
+```
+
+Uncomment the lines:
+
+```conf
+HandleLidSwitch=suspend
+HandleLidSwitchExternalPower=suspend
+HandleLidSwitchDocked=ignore
+LidSwitchIgnoreInhibited=yes
+```
+
+You need to reboot before it takes effect.
+
+# Wake on suspend
+
+There is a bug in Linux kernel 6. This article explains this:
+
+https://bugzilla.redhat.com/show_bug.cgi?id=2162013
+
+Somehow the touchpad keeps waking up the laptop.
+
+```sh
+cat /proc/acpi/wakeup
+```
+
+Should return:
+
+```
+Device S-state Status Sysfs node
+GPP0 S0 *disabled
+GPP1 S0 *disabled
+GP17 S0 *enabled pci:0000:00:08.1
+```
+
+## Temporarily disable
+
+To temporarily disable it do this:
+
+```sh
+echo disabled > /sys/bus/i2c/devices/i2c-FTCS1000:00/power/wakeup
+```
+
+## Persistent settings
+
+As root:
+
+```sh
+nvim /etc/systemd/system/disable-wakeup.service
+```
+
+Contents:
+
+```
+[Unit]
+Description=Disable wakeup triggers
+
+[Service]
+Type=oneshot
+ExecStart=/bin/sh -c "echo disabled > /sys/bus/i2c/devices/i2c-FTCS1000\:00/power/wakeup ; echo GP17 > /proc/acpi/wakeup"
+ExecStop=/bin/sh -c "echo disabled > /sys/bus/i2c/devices/i2c-FTCS1000\:00/power/wakeup ; echo GP17 > /proc/acpi/wakeup"
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
+```
+
+Then simply enable and start it:
+
+```sh
+systemctl enable disable-wakeup.service
+systemctl start disable-wakeup.service
+```
+
+Reboot the laptop and check to see if the systemd unit works:
+
+```sh
+systemctl status disable-wakeup.service
+```
+
+```sh
+cat /proc/acpi/wakeup
+cat /sys/bus/i2c/devices/i2c-FTCS1000\:00/power/wakeup
+```
+
+Test it by suspending the laptop. You can also use:
+
+```sh
+systemctl suspend -i
+```
+
+# Tuxedo control center
+
+Instructions: https://www.tuxedocomputers.com/en/Add-TUXEDO-software-package-sources.tuxedo
+
+## On openSUSE
+
+See instructions on page and then:
+
+```sh
+zypper refresh && zypper install tuxedo-control-center
+```
+
+
+## On Fedora
+
+```sh
+nvim /etc/yum.repos.d/tuxedo.repo
+```
+
+Contents:
+
+```
+[tuxedo]
+name=tuxedo
+baseurl=https://rpm.tuxedocomputers.com/fedora/40/x86_64/base
+enabled=1
+gpgcheck=1
+gpgkey=https://rpm.tuxedocomputers.com/fedora/40/0x54840598.pub.asc
+skip_if_unavailable=False
+```
+
+Get the key:
+
+```sh
+wget https://rpm.tuxedocomputers.com/fedora/40/0x54840598.pub.asc
+```
+
+And install it:
+
+```sh
+rpm --import ./0x54840598.pub.asc
+```
+
+Now install the control center:
+
+```sh
+dnf update
+dnf install tuxedo-control-center
+```
+
+You need to reboot before it takes effect.
+
+# Virtualization
+
+```sh
+dnf install @virtualization
+```
+
+```sh
+systemctl enable libvirtd
+systemctl start libvirtd
+```
diff --git a/laptops/Ideapad_510.md b/laptops/Ideapad_510.md
new file mode 100644
index 0000000..ab4c295
--- /dev/null
+++ b/laptops/Ideapad_510.md
@@ -0,0 +1,88 @@
+# Lenovo ideapad 510
+
+## Prevent suspend when lid closed
+
+Add a new file:
+
+```sh
+nvim /etc/systemd/logind.conf.d/no-suspend-on-lid.conf
+```
+
+Add this:
+
+```
+[Login]
+HandleLidSwitch=ignore
+HandleLidSwitchExternalPower=ignore
+HandleLidSwitchDocked=ignore
+```
+
+```sh
+systemctl restart systemd-logind
+```
+
+## Wake-on-lan
+
+See my network interfaces:
+
+```sh
+ip link show
+```
+
+```
+1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+2: enp1s0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
+ link/ether 54:e1:ad:9d:a8:74 brd ff:ff:ff:ff:ff:ff
+3: wlp2s0: mtu 1500 qdisc noqueue state DOWN mode DORMANT group default qlen 1000
+ link/ether 52:37:c4:2c:79:38 brd ff:ff:ff:ff:ff:ff permaddr 3c:f8:62:b3:7f:81
+```
+
+Enable for the ethernet
+
+```sh
+ethtool -s enp1s0 wol g
+```
+
+Make it pemanent. Create a systemd unit:
+
+```sh
+nvim /etc/systemd/system/wol.service
+```
+
+```
+[Unit]
+Description=Wake-on-LAN
+Requires=network.target
+After=network.target
+
+[Service]
+ExecStart=/usr/sbin/ethtool -s enp1s0 wol g
+Type=oneshot
+
+[Install]
+WantedBy=multi-user.target
+```
+
+```sh
+systemctl enable wol.service
+```
+
+Check it:
+
+```sh
+systemctl start wol.service
+systemctl status wol.service
+```
+
+Suspend the laptop:
+
+```sh
+systemctl suspend
+```
+
+Wake it up again from another machine:
+
+```sh
+wakeonlan 54:e1:ad:9d:a8:74
+```
\ No newline at end of file
diff --git a/laptops/Laptops.md b/laptops/Laptops.md
new file mode 100644
index 0000000..b487837
--- /dev/null
+++ b/laptops/Laptops.md
@@ -0,0 +1,2 @@
+* [Clevo Laptop](Clevo_Laptop.md)
+* [Lenovo ideapad 510](Ideapad_510.md)
\ No newline at end of file
diff --git a/os/AlmaLinux/AlmaLinux.md b/os/AlmaLinux/AlmaLinux.md
new file mode 100644
index 0000000..181a631
--- /dev/null
+++ b/os/AlmaLinux/AlmaLinux.md
@@ -0,0 +1,90 @@
+# General
+
+Remove the web console and all the other stuff around it:
+
+```sh
+dnf -y remove cockpit*
+firewall-cmd --permanent --remove-service=cockpit
+firewall-cmd --reload
+firewall-cmd --list-all
+```
+
+Update
+
+```sh
+dnf update
+```
+
+Set hostname
+
+```sh
+hostnamectl set-hostname moni-alma
+```
+
+Set up EPEL, follow the instructions:
+
+* https://docs.fedoraproject.org/en-US/epel/getting-started/
+
+And then install my favorites:
+
+```sh
+dnf install -y tmux htop ncdu neovim git
+```
+
+Install docker:
+
+```sh
+dnf -y install dnf-plugins-core
+dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
+dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
+systemctl enable --now docker
+```
+
+Add the `moni` to the docker group:
+
+```sh
+usermod -aG docker moni
+```
+
+You need to log out and log back in and then test it:
+
+```sh
+# run without sudo
+docker run -it --rm -p 8080:80 httpd:2.4
+```
+
+It will run in the foreground because we didn't pass `-d`.
+
+Open your browser to:
+
+* http://192.168.1.11:8080
+* or http://192.168.1.10:8080 for the 8GB server
+
+You can stop the container with CTRL+C. It should clean itself up (`--rm`).
+
+Cleanup:
+
+```sh
+docker image prune --all --force
+```
+
+# SSH
+
+```sh
+nvim /etc/ssh/sshd_config
+```
+
+Uncomment/change these settings:
+
+```conf
+PermitRootLogin no
+# PubkeyAuthentication yes <-- This is the detault, so you don't need to change this
+PasswordAuthentication no
+KbdInteractiveAuthentication no
+```
+
+Reload:
+
+```sh
+sudo systemctl reload sshd
+```
diff --git a/os/FreeBSD/FreeBSD_on_Clevo_Laptop.md b/os/FreeBSD/FreeBSD_on_Clevo_Laptop.md
new file mode 100644
index 0000000..d644db9
--- /dev/null
+++ b/os/FreeBSD/FreeBSD_on_Clevo_Laptop.md
@@ -0,0 +1,280 @@
+# Setup
+
+Install my favorite packages
+
+```sh
+pkg install bash sudo tmux htop neovim git bastille
+```
+
+Add "wheel" to the suoers file:
+
+```sh
+visudo
+```
+
+Change shell
+
+```sh
+chsh -s /usr/local/bin/bash
+```
+
+Do a system update:
+
+```sh
+freebsd-update fetch install
+```
+
+# Wifi
+
+Find out what network cards we have:
+
+```sh
+pciconf -lv | grep -A1 -B3 network
+```
+
+On my Clevo laptop it looks like this:
+
+```
+re0@pci0:2:0:0: class=0x020000 rev=0x15 hdr=0x00 vendor=0x10ec device=0x8168 subvendor=0x1558 subdevice=0xa600
+ vendor = 'Realtek Semiconductor Co., Ltd.'
+ device = 'RTL8111/8168/8211/8411 PCI Express Gigabit Ethernet Controller'
+ class = network
+ subclass = ethernet
+iwlwifi0@pci0:3:0:0: class=0x028000 rev=0x1a hdr=0x00 vendor=0x8086 device=0x2723 subvendor=0x8086 subdevice=0x0084
+ vendor = 'Intel Corporation'
+ device = 'Wi-Fi 6 AX200'
+ class = network
+nvme0@pci0:4:0:0: class=0x010802 rev=0x00 hdr=0x00 vendor=0x144d device=0xa809 subvendor=0x144d subdevice=0xa801
+```
+
+So we have an Intel Wifi.
+
+We're going to configure the wireless card iwlwifi0 to the interface wlan0:
+
+```sh
+ifconfig wlan0 create wlandev iwlwifi0
+```
+
+To make the change persist across reboots:
+
+```sh
+sysrc wlans_iwlwifi0="wlan0"
+```
+
+We need to set the regulatory domain:
+
+```sh
+ifconfig wlan0 regdomain ETSI country NL
+```
+
+To scan the networks. I had to run the command twice to see the list:
+
+```sh
+ifconfig wlan0 up list scan
+```
+
+I see my networks:
+
+```
+SSID/MESH ID BSSID CHAN RATE S:N INT CAPS
+TMNL-3EF981_24G d8:0d:17:b9:b2:f0 11 54M -37:-96 100 EPS HTCAP WME ATH RSN WPS
+TMNL-3EF981 98:0d:67:3e:f9:81 11 54M -72:-96 100 EP APCHANREP RSN WPS BSSLOAD HTCAP VHTCAP VHTOPMODE WME
+TMNL-3EF981_5G d8:0d:17:b9:b2:f1 64 54M -42:-96 100 EP HTCAP VHTCAP VHTOPMODE VHTPWRENV WME ATH RSN WPS
+```
+
+I want to connect to `TMNL-3EF981_5G`. We need to edit the `/etc/wpa_supplicant.conf` file:
+
+```sh
+nvim /etc/wpa_supplicant.conf
+```
+
+The contents. The password need to be set in psk.
+
+```
+country=NL
+network={
+ ssid="TMNL-3EF981_5G"
+ psk="3J6YJHNRG8W7KMMF"
+ priority=5
+}
+```
+
+To set it to use DHCP:
+
+```sh
+sysrc ifconfig_wlan0="WPA SYNCDHCP"
+```
+
+For some reason, we need to add the country code in the rc.conf:
+
+```sh
+sysrc create_args_wlan0="country NL"
+```
+
+Now bring it up!
+
+```sh
+service netif restart
+```
+
+Restart the laptop to see if it persists.
+
+For some reason, it won't work (no suprise, wifi is awful in FreeBSD).
+
+This worked for me after boot
+
+```sh
+ifconfig wlan0 down
+ifconfig wlan0 ssid "TMNL-3EF981_5G"
+ifconfig wlan0 regdomain etsi2 country NL
+service netif restart
+```
+
+# X11
+
+https://docs.freebsd.org/en/books/handbook/x11/
+
+Don't forget to start `tmux`:
+
+```sh
+tmux
+```
+
+Add `moni` to the `video` group:
+
+```sh
+pw groupmod video -m moni
+```
+
+And then install, but don't forget to read the messages when the install is complete! Scroll up with tmux
+
+```sh
+pkg install xorg
+```
+
+This will improve mnuse and touchscreen support:
+
+```sh
+sysctl kern.evdev.rcpt_mask=6
+```
+
+And add this to `/etc/sysctl.conf` to persist it:
+
+```sh
+kern.evdev.rcpt_mask=6
+```
+
+# Amd
+
+```sh
+pkg install drm-kmod
+```
+
+```sh
+sysrc kld_list+=amdgpu
+```
+
+# Kde
+
+```sh
+pkg install kde5
+```
+
+```sh
+sysrc dbus_enable="YES"
+```
+
+```sh
+sysctl net.local.stream.recvspace=65536
+sysctl net.local.stream.sendspace=65536
+```
+
+```sh
+pkg install sddm
+sysrc sddm_enable="YES"
+```
+
+# Fonts
+
+```sh
+pkg install urwfonts
+```
+
+But you're not done yet, you need to add a conf file:
+
+```sh
+nvim /usr/local/etc/X11/xorg.conf.d/90-fonts.conf
+```
+
+With the following:
+
+```
+Section "Files"
+ FontPath "/usr/local/share/fonts/urwfonts/"
+EndSection
+```
+
+# CPU
+
+Too see your CPU 0
+
+```sh
+sysctl dev.cpu.0
+```
+
+If you don't see a temperature:
+
+```sh
+kldload amdtemp
+```
+
+Add it to startup:
+
+```sh
+sysrc kld_list+=amdtemp
+```
+
+# Linux compatibility
+
+```sh
+sysrc linux_enable="YES"
+```
+
+```sh
+service linux start
+```
+
+# Final configs
+
+My `/etc/rc.conf`
+
+```
+hostname="moni-freebsd"
+ifconfig_re0="DHCP"
+sshd_enable="YES"
+moused_enable="YES"
+# Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable
+dumpdev="AUTO"
+kld_list="amdgpu amdtemp"
+dbus_enable="YES"
+sddm_enable="YES"
+linux_enable="YES"
+create_args_wlan0="country NL"
+wlans_iwlwifi0="wlan0"
+ifconfig_wlan0="WPA SYNCDHCP"
+```
+
+My `/etc/sysctl.conf`:
+
+```
+#
+# This file is read when going to multi-user and its contents piped thru
+# ``sysctl'' to adjust kernel values. ``man 5 sysctl.conf'' for details.
+#
+
+# Uncomment this to prevent users from seeing information about processes that
+# are being run under another UID.
+#security.bsd.see_other_uids=0
+
+kern.evdev.rcpt_mask=6
+```
diff --git a/os/FreeBSD/FreeBSD_on_Lenovo_Thinkcentre.md b/os/FreeBSD/FreeBSD_on_Lenovo_Thinkcentre.md
new file mode 100644
index 0000000..b63c33d
--- /dev/null
+++ b/os/FreeBSD/FreeBSD_on_Lenovo_Thinkcentre.md
@@ -0,0 +1,111 @@
+# Setup
+
+First time:
+
+```sh
+su -
+```
+
+Do a system update:
+
+```sh
+freebsd-update fetch install
+```
+
+Update
+
+```sh
+pkg update
+```
+
+Install my favorite packages
+
+```sh
+pkg install -y bash sudo tmux htop neovim git ncdu bastille tailscale aria2
+```
+
+Add "wheel" to the suoers file:
+
+```sh
+visudo
+```
+
+Change shell
+
+```sh
+chsh -s /usr/local/bin/bash
+```
+
+Tailscale
+
+```sh
+service tailscaled enable
+service tailscaled start
+tailscale up
+```
+
+# SSH
+
+```sh
+nvim /etc/ssh/sshd_config
+```
+
+Change this setting:
+
+```
+KbdInteractiveAuthentication no
+```
+
+That should be it. The config file should have these settings, including the commented lines shown as below:
+
+```
+#PermitRootLogin no
+#PubkeyAuthentication yes
+#PasswordAuthentication no
+KbdInteractiveAuthentication no
+#UsePAM yes
+```
+
+Test the setting
+
+```sh
+sshd -t
+```
+
+Reload:
+
+```sh
+service sshd reload
+```
+
+# PF
+
+Note, if you're using Bastille, the `/etc/pf.conf` file is going to look different. See [Bastille](../../containers/Bastille/Bastille.md).
+
+Now we need to get the filewall going.
+
+```sh
+nvim /etc/pf.conf
+```
+
+Contents:
+
+```
+ext_if="em0"
+block in all
+pass in on $ext_if proto tcp to ($ext_if) port ssh
+pass in on $ext_if proto tcp to ($ext_if) port 80
+pass in on $ext_if proto tcp to ($ext_if) port 443
+pass out all keep state
+```
+
+```sh
+sysrc pf_enable=yes
+service pf start
+```
+
+If you get the error 'no host key files found` then
+
+```sh
+ssh-keygen -A
+```
diff --git a/os/FreeBSD/FreeBSD_on_RPI.md b/os/FreeBSD/FreeBSD_on_RPI.md
new file mode 100644
index 0000000..19f11b5
--- /dev/null
+++ b/os/FreeBSD/FreeBSD_on_RPI.md
@@ -0,0 +1,94 @@
+# General
+
+Default username passwords are:
+
+```
+freebsd
+freebsd
+```
+
+and
+
+```
+root
+root
+```
+
+```sh
+ssh freebsd@192.168.1.108
+```
+
+Setup ntpd
+
+You need to make sure that your date is close to the real time
+
+```sh
+date 202406211441
+```
+
+Set the timezone
+
+```sh
+tzsetup
+```
+
+```sh
+sysrc ntpd_enable=YES
+```
+
+Set the time:
+
+```sh
+ntpdate -v -b in.pool.ntp.org
+```
+
+Start the service
+
+```sh
+service ntpd start
+```
+
+Update the system
+
+```sh
+pkg update
+```
+
+Install my favorite packages
+
+```sh
+pkg install bash sudo tmux htop neovim git bastille
+```
+
+Add "wheel" to the suoers file:
+
+```sh
+visudo
+```
+
+Change shell
+
+```sh
+chsh -s /usr/local/bin/bash
+```
+
+Add a user
+
+```sh
+adduser
+```
+
+Change the hostname
+
+```sh
+sysrc hostname="rp4-8"
+```
+
+Delete the freebsd user that comes with the standard installation:
+
+```sh
+rmuser freebsd
+```
+
+* [SSH instructions](FreeBSD_on_Lenovo_Thinkcentre.md#ssh)
+* [PF instructions](FreeBSD_on_Lenovo_Thinkcentre.md#pf). Be careful, the network interface on the PI is called `genet0` not `em0`.
diff --git a/os/FreeBSD/Storage.md b/os/FreeBSD/Storage.md
new file mode 100644
index 0000000..d12fba1
--- /dev/null
+++ b/os/FreeBSD/Storage.md
@@ -0,0 +1,53 @@
+# Storage
+
+First we need to know what drives we have:
+
+```sh
+geom disk list
+```
+
+Show the partitions:
+
+```sh
+gpart show
+```
+
+In FreeBSD the partitions are named with suffix `pX`, for example `p1`, `p2`, etc.
+
+To mount the first partition of the external harddrive:
+
+```sh
+# directory needs to exist
+mkdir /mnt/usb
+
+# mount the first partition p1
+mount -t /dev/da0p1 /mnt/usb
+```
+
+# ZFS
+
+The handbook is actually quite comprehensive:
+
+* https://docs.freebsd.org/en/books/handbook/zfs/
+
+In order for ZFS to work, we need to enable it. On the Raspberry PI it is not enabled by default.
+
+```sh
+service zfs enable
+service zfs start
+```
+
+You need empty space, either an empty partition or drive.
+
+# Mounting different filesystems (for example, a USB SDD)
+
+For ext4, see: https://docs.freebsd.org/en/books/handbook/filesystems/index.html#filesystems-linux
+
+For NTFS, see: https://docs.freebsd.org/en/books/handbook/disks/#using-ntfs
+
+Take into account that NTFS uses "Slices", not "Partitions" so mounting the external NTFS harddrive on the Raspberry PI looks like this:
+
+```sh
+ntfs-3g /dev/da0s1 /mnt/usb
+```
+
diff --git a/ssh.md b/ssh.md
new file mode 100644
index 0000000..b7aeb21
--- /dev/null
+++ b/ssh.md
@@ -0,0 +1,4 @@
+```sh
+eval `ssh-agent -s` && ssh-add -k
+ssh -t -A moni@143.179.250.91 -p 32222 ssh moni@192.168.1.10
+```