podbox/README.md
redbeardymcgee ce65dc3e30 add weechat
This is currently incomplete. The volumes mount and weechat launches,
but it is flooded with permission errors when trying to set up.

Might mount them all individually just to see what happens but that's
probably not a sustainable idea going forward.
2024-11-02 17:01:50 -05:00

534 lines
12 KiB
Markdown

# podbox
![Connectable!](./resource/image/connectable-status.png)
## Installation
My proof of concept server running this container stack is built on AlmaLinux
9.4. `podman` and `systemd` with `quadlet` support is required if you are using another
distro.
> [!WARNING] Perform `dnf update` immediately
### [Repositories](https://wiki.almalinux.org/repos/)
These may not really be necessary to set up, but you should absolutely review
them and decide for yourself.
- [AlmaLinux](https://wiki.almalinux.org/repos/AlmaLinux.html)
- [CentOS SIGs](https://wiki.almalinux.org/repos/CentOS.html)
- [Extra](https://wiki.almalinux.org/repos/Extras.html)
- EPEL and CRB
- `dnf install epel-release`
- `dnf config-manager --set-enabled crb`
- ELRepo
- `dnf install elrepo-release`
- [RPM Fusion](https://wiki.almalinux.org/documentation/epel-and-rpmfusion.html)
## SSH
SSH is optional, but highly encouraged. Cockpit gives you a terminal too, but
that's nowhere near as good as what you can do with a real terminal emulator
and ssh clients.
```bash
dnf install openssh-server
## Generate strong key on your laptop or workstation/desktop
ssh-keygen -t ed25519 -a 32 -f ~/.ssh/"$localhost-to-$remotehost"
## Copy key to AlmaLinux
ssh-copy-id -i ~/.ssh/"$localhost-to-$remotehost" "$user@$remotehost"
```
### Override `sshd` config
We don't want to allow anyone to login as root remotely ever. You must be a
`sudoer` with public key auth to elevate to root.
```bash
printf '%s\n' 'PermitRootLogin no' > /etc/ssh/sshd_config.d/01-root.conf
printf '%s\n' \
'PubkeyAuthentication yes' \
'PasswordAuthentication no' > /etc/ssh/sshd_config.d/01-pubkey.conf
```
## Cockpit -> https://ip-addr:9090
> [!WARNING] Disable the firewall if you are lazy
> Exposing ports for other services can be exhausting and I have not learned
> how to do this for containers properly. Each container may need a new rule
> for something, not sure.
> ```bash
> systemctl disable --now firewalld
> ```
Enable the socket-activated cockpit service and allow it through the firewall.
```bash
systemctl enable --now cockpit.socket
# FIXME: Unnecessary? Default works?
firewall-cmd --permanent --zone=public --add-service=cockpit
firewall-cmd --reload
```
### Add SSH keys
> [!TIP] Skip if you copied your keys with `ssh-copy-id` above.
`Accounts` -> `Your account` -> `Authorized public SSH keys` -> `Add Key`
### Install SELinux troubleshoot tool
This is a component for Cockpit.
```bash
dnf install setroubleshoot-server
```
## Podman
Podman is a daemonless container hypervisor. This document prepares a fully
rootless environment for our containers to run in.
### Install
```bash
dnf install podman
systemctl enable --now podman
```
> [!NOTE] Read the docs.
> `man podman-systemd.unit`
### slirp4netns
> [!TODO]
> This may not be necessary but my system is currently using it.
```bash
dnf install slirp4netns
```
### Install DNS server for `podman`
> [!TODO]
> Not sure how to resolve these correctly yet but the journal logs it
> so it's running for something.
```bash
dnf install aardvark-dns
```
### Enable unprivileged port binding
> [!NOTE] This is only necessary if you are setting up the reverse proxy.
```bash
printf '%s\n' 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-unprivileged-port-binding.conf
sysctl 'net.ipv4.ip_unprivileged_port_start=80'
```
### Prepare container user
This user will be the owner of all containers with no login shell or root
privileges.
```bash
# Prepare a group id outside of the normal range
groupadd --gid 2000 $ctuser
# Create user with restrictions
# We need the $HOME to live in
useradd --create-home \
--shell /usr/bin/false \
--password $ctuser_pw \
--no-user-group \
--gid $ctuser \
--groups systemd-journal \
--uid 2000 \
$ctuser
usermod --lock $ctuser # Lock user from password login
# Add container sub-ids
usermod --add-subuids 200000-299999 --add-subgids 200000-299999 $ctuser
# Start $ctuser session at boot without login
loginctl enable-linger $ctuser
```
> [!TIP] Optionally setup ssh keys to directly login to $ctuser.
> [!NOTE] The login shell doesn't exist.
> Launch `bash -l` manually to get a shell or else your `ssh` will exit with a
> status of 1.
### Setup $ctuser env
```bash
# Switch to user (`-i` doesn't work without a login shell)
sudo -u $ctuser bash -l
# Create dirs
mkdir -p ~/.config/{containers/systemd,environment.d} ~/containers/storage
# Prepare `systemd --user` env
echo 'XDG_RUNTIME_DIR=/run/user/$UID' >> ~/.config/environment.d/10-xdg.conf
# Enable container auto-update
podman system migrate
# WARNING: Set strict versions for all containers or risk catastrophe
systemctl --user enable --now podman-auto-update
exit
```
### ~/.config/containers/systemd/protonvpn.network
This is a small internal network for this stack of containers to share.
```ini
[Unit]
Description=ProtonVPN
After=network-online.target
[Install]
WantedBy=default.target
[Network]
NetworkName=protonvpn
Subnet=172.25.0.0/28
Gateway=172.25.0.1
DNS=1.1.1.1
```
### ~/.config/containers/systemd/gluetun.container
This is our VPN container. This example uses ProtonVPN.
> [!WARNING] I disabled SELinux to not deal with this for every other issue.
> /etc/selinux/config -> `SELINUX=disabled`
Temporarily set SELinux policy to allow containers to use devices.
```bash
setsebool -P container_use_devices 1
```
> [!TIP] Get protonvpn user/pass
> [OpenVpnIKEv2](https://account.proton.me/u/0/vpn/OpenVpnIKEv2)
```ini
[Unit]
Description=gluetun VPN
After=protonvpn-network.service
PartOf=protonvpn-network.service
[Service]
Restart=on-failure
TimeoutStartSec=900
[Install]
WantedBy=default.target
[Container]
Image=docker.io/qmcgaw/gluetun:$gluetun_version
ContainerName=gluetun
HostName=gluetun
AutoUpdate=registry
AddCapability=NET_ADMIN
AddDevice=/dev/net/tun:/dev/net/tun
Network=protonvpn
Volume=/volumes/gluetun/auth/config.toml:/gluetun/auth/config.toml
Environment=TZ=$timezone
Environment=UPDATER_PERIOD=24h
Environment=UPDATER_VPN_SERVICE_PROVIDERS=protonvpn
Environment=VPN_SERVICE_PROVIDER=protonvpn
# The trailing `+pmp` is for port forwarding
Environment=OPENVPN_USER=${openvpn_user}+pmp
Environment=OPENVPN_PASSWORD=$openvpn_password
Environment=OPENVPN_CIPHERS=aes-256-gcm
Environment=SERVER_COUNTRIES=$countries
Environment=VPN_PORT_FORWARDING=on
Environment=FIREWALL_DEBUG=on
```
### /volumes/gluetun/auth/config.toml
This allows us to query the `gluetun` API for the forwarded port without
needing an API user and password.
> [!WARNING] Do not expose the API to the internet.
```toml
[[roles]]
name = "qbittorrent"
routes = ["GET /v1/openvpn/portforwarded"]
auth = "none"
```
### ~/.config/containers/systemd/qbittorrent.container
> [!NOTE] Check $qbt_version from tags on dockerhub.
> [qbittorrentofficial](https://docker.io/qbittorrentofficial/qbittorrent-nox)
```ini
[Unit]
Description=qbittorrent client
After=gluetun.service
BindsTo=gluetun.service
[Service]
Restart=on-failure
TimeoutStartSec=900
[Install]
WantedBy=default.target
[Container]
Image=docker.io/qbittorrentofficial/qbittorrent-nox:$qbt_version
ContainerName=qbittorrent
HostName=qbittorrent
AutoUpdate=registry
Network=container:gluetun
Volume=/volumes/qbittorrent/config:/config
Volume=/volumes/qbittorrent/downloads:/downloads
Environment=QBT_LEGAL_NOTICE=confirm
Environment=QBT_VERSION=$qbt_version
Environment=TZ=$timezone
```
### ~/.config/containers/systemd/qbittorrent-port-forward.container
This updates the `qbittorrent` configuration to match the forwarded port from
`gluetun`.
> [!TIP] Check the ip address of most containers.
> `podman exec -it $container_name ip addr show`
```ini
[Unit]
Description=Port forward updater for qbittorrent over gluetun
After=gluetun.service
After=qbittorrent.service
BindsTo=gluetun.service
BindsTo=qbittorrent.service
[Service]
Restart=on-failure
TimeoutStartSec=900
[Install]
WantedBy=default.target
[Container]
# TODO: Replace this with one that has tags
# Probably have to repack my own
Image=docker.io/mjmeli/qbittorrent-port-forward-gluetun-server:latest
ContainerName=qbittorrent-port-forward
HostName=qbittorrent-port-forward
AutoUpdate=registry
Network=container:gluetun
Environment=QBT_USERNAME=$qbt_user
Environment=QBT_PASSWORD=$qbt_password
Environment=QBT_ADDR=http://localhost:8080
Environment=GTN_ADDR=http://localhost:8000
```
### ~/.config/containers/systemd/seedboxapi.container
This ensures that your torrent session stays in sync with your MAM session.
> [!NOTE] Set your dynamic session with ASN lock now to view the $mam_id.
```ini
[Unit]
Description=Update qbittorrent session IP for tracker
After=qbittorrent.service
After=gluetun.service
BindsTo=gluetun.service
BindsTo=qbittorrent.service
[Service]
Restart=on-failure
TimeoutStartSec=900
[Install]
WantedBy=default.target
[Container]
# TODO: Is `latest` safe for this container?
Image=docker.io/myanonamouse/seedboxapi:latest
ContainerName=seedboxapi
HostName=seedboxapi
AutoUpdate=registry
Network=container:gluetun
Volume=/volumes/seedboxapi/config:/config
Environment=DEBUG=1
Environment=mam_id=$mam_id
Environment=interval=1
```
### ~/.config/containers/systemd/pointspend.container
> [!TIP] Optional bonus points spender.
> Useful to maintain VIP and not hit max 99999.
```ini
[Unit]
Description=Bonus points spender
After=qbittorrent.service
After=gluetun.service
BindsTo=gluetun.service
BindsTo=qbittorrent.service
[Service]
Restart=on-failure
TimeoutStartSec=900
[Install]
WantedBy=default.target
[Container]
# TODO: Is `latest` safe for this container?
Image=docker.io/myanonamouse/pointspend:latest
ContainerName=pointspend
HostName=pointspend
AutoUpdate=registry
Network=container:gluetun
Environment=MAMID=$mam_id
Environment=BUFFER=10000
Environment=WEDGEHOURS=0
Environment=VIP=1
```
### ~/.config/containers/systemd/weechat.container
Optional container to add an always on IRC client.
```ini
[Unit]
Description=IRC client
After=gluetun.service
BindsTo=gluetun.service
[Service]
Restart=on-failure
TimeoutStartSec=900
[Install]
WantedBy=default.target
[Container]
Image=docker.io/weechat/weechat:latest-alpine-slim
ContainerName=weechat
HostName=weechat
AutoUpdate=registry
Network=container:gluetun
Volume=/volumes/weechat/dot-config:/home/user/.config
Volume=/volumes/weechat/dot-cache:/home/user/.cache
Volume=/volumes/weechat/dot-local/share:/home/user/.local/share
# FIXME: Better way to attach stdin and tty
PodmanArgs=-a stdin --tty=true
```
#### Attach and connect
```bash
podman attach weechat
/set irc.look.smart_filter on
/set irc.server_default.msg_part ""
/set irc.server_default.msg_quit ""
/set irc.ctcp.clientinfo ""
/set irc.ctcp.finger ""
/set irc.ctcp.source ""
/set irc.ctcp.time ""
/set irc.ctcp.userinfo ""
/set irc.ctcp.version ""
/set irc.ctcp.ping ""
/plugin unload xfer
/set weechat.plugin.autoload "*,!xfer"
/filter add irc_smart * irc_smart_filter *
/server add mam irc.myanonamouse.net/6697
/set irc.server.mam.username $irc_username
/set irc.server_default.autojoin_dynamic on
/set irc.server.mam.autojoin "#anonamouse.net"
/set irc.server.mam.nicks "$irc_nick1,$irc_nick2"
/connect mam
```
### ~/.config/containers/systemd/caddy.container
This is an optional container to add a reverse proxy (and more).
> [!TODO] Needs to be filled out.
> Works as is but doesn't do anything with a default config.
```ini
[Unit]
Description=Reverse proxy
After=protonvpn-network.service
[Service]
Restart=on-failure
[Install]
WantedBy=default.target
[Container]
Image=docker.io/caddy:2
ContainerName=caddy
HostName=caddy
Network=protonvpn
PublishPort=80:80
PublishPort=443:443
PublishPort=443:443/udp
Volume=/volumes/caddy/config:/config
Volume=/volumes/caddy/etc/caddy/Caddyfile:/etc/caddy/Caddyfile
Volume=/volumes/caddy/srv:/srv
Volume=/volumes/caddy/data:/data
```
### /volumes/caddy/etc/caddy/Caddyfile
```
# The Caddyfile is an easy way to configure your Caddy web server.
#
# Unless the file starts with a global options block, the first
# uncommented line is always the address of your site.
#
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace ":80" below with your
# domain name.
:80 {
# Set this path to your site's directory.
root * /usr/share/caddy
# Enable the static file server.
file_server
# Another common task is to set up a reverse proxy:
# reverse_proxy localhost:8080
# Or serve a PHP site through php-fpm:
# php_fastcgi localhost:9000
}
# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile
```