diff --git a/README.md b/README.md index 0bf4597..5caa61e 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ or give me access to your servers. - [bash](#bash) - [Bulk File/Folder Renaming](#bulk-filefolder-renaming) - [Escaping a Stuck SSH Terminal](#escaping-a-stuck-ssh-terminal) + - [Curl output hidden by bash prompt](#curl-output-hidden-by-bash-prompt) - [SSH Setup](#ssh-setup) - [Git GPG Commit Signing](#git-gpg-commit-signing) - [Important Dates and Times](#important-dates-and-times) @@ -133,6 +134,14 @@ done Press the following keys: enter + ~ + . +### Curl output hidden by bash prompt + +Add this to your ~/.curlrc + +```conf +-w "\n" +``` + ## SSH Setup Generate a key (password protect it!) @@ -212,15 +221,16 @@ signed you'll see an output. If not, nothing will show. ## Important Dates and Times -| Time | Day | Description | -| ----- | --- | ---------------------------------- | -| 00:00 | All | Automated builds | -| 00:00 | All | NAS Snapshots | -| 02:00 | All | Backups | -| 04:00 | All | Bare Metal Server Security Updates | -| 05:00 | All | VM Server Security Updates | -| 05:00 | All | Unifi Protect Firmware Updates | -| 06:00 | All | Unifi Network Firmware Updates | +| Time | Day | Description | +| ----- | --- | ------------------------------ | +| 00:00 | All | Automated builds | +| 00:00 | All | NAS Snapshots | +| 01:00 | All | Backups | +| 03:00 | All | App Updates | +| 04:00 | All | Bare Metal Server Updates | +| 05:00 | All | VM Server Updates | +| 05:00 | All | Unifi Protect Firmware Updates | +| 06:00 | All | Unifi Network Firmware Updates | ## Project Lifecycle diff --git a/active/aws_bedrock/bedrock.md b/active/aws_bedrock/bedrock.md new file mode 100644 index 0000000..1dd1619 --- /dev/null +++ b/active/aws_bedrock/bedrock.md @@ -0,0 +1,14 @@ +# AWS Bedrock + +## Endpoints + +Completions endpoint: + +## Model Analysis + +| Model ID | Input Price (Mil) | Output Price (Mil) | Doodle Jump Notes | +| ----------------------- | ----------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| minimax.minimax-m2.5 | $ 0.30 | $ 1.20 | Way too fast and player moves too slow. Identified issue and fixed after second attempt | +| zai.glm-5 | $ 1.00 | $ 3.20 | Perfect game with great graphics. | +| mistral.devstral-2-123b | $ 0.40 | $ 2.00 | Broken game with very basic graphics. Second attempt broken as well. | +| deepseek.v3.2 | $1.35 | $5.40 | Way too fast, platform generation erratic, mobile controls were a nice touch. Fixed speed issue on second attempt but platform generation still bad | diff --git a/active/container_gitea/compose/compose.yaml b/active/container_gitea/compose/compose.yaml deleted file mode 100644 index 606327d..0000000 --- a/active/container_gitea/compose/compose.yaml +++ /dev/null @@ -1,48 +0,0 @@ -services: - gitea: - image: docker.gitea.com/gitea:1.25-rootless - container_name: gitea - environment: - - GITEA__database__DB_TYPE=postgres - - GITEA__database__HOST=postgres:5432 - - GITEA__database__NAME=gitea - - GITEA__database__USER=gitea - - GITEA__database__PASSWD=gitea - security_opt: - - "label=disable" - restart: always - networks: - - gitea - volumes: - - /home/gitea/gitea_data:/data:Z - - /home/gitea/gitea_etc:/etc/gitea:Z - - /home/gitea/gitea_custom:/var/lib/gitea/custom:Z - - /etc/localtime:/etc/localtime:ro - ports: - - "3000:3000" - - "2222:2222" - depends_on: - - postgres - labels: - - "io.containers.autoupdate=registry" - - postgres: - image: docker.io/library/postgres:15 - container_name: postgres - security_opt: - - "label=disable" - restart: always - environment: - - POSTGRES_USER=gitea - - POSTGRES_PASSWORD=gitea - - POSTGRES_DB=gitea - networks: - - gitea - volumes: - - /home/gitea/gitea_postgres:/var/lib/postgresql/data:Z - labels: - - "io.containers.autoupdate=registry" - -networks: - gitea: - enable_ipv6: true \ No newline at end of file diff --git a/active/container_gitea/gitea-compose.yaml b/active/container_gitea/gitea-compose.yaml index 2b788a6..e4c9695 100644 --- a/active/container_gitea/gitea-compose.yaml +++ b/active/container_gitea/gitea-compose.yaml @@ -4,7 +4,7 @@ networks: services: server: - image: docker.gitea.com/gitea:1.25.3 + image: docker.gitea.com/gitea:1.26.1 container_name: gitea environment: - USER_UID=1001 diff --git a/active/container_gitea/quadlets/gitea.container b/active/container_gitea/quadlets/gitea.container deleted file mode 100644 index 677c58f..0000000 --- a/active/container_gitea/quadlets/gitea.container +++ /dev/null @@ -1,22 +0,0 @@ -[Unit] -Requires=postgres.service - -[Container] -AutoUpdate=registry -ContainerName=gitea -Environment=GITEA__database__DB_TYPE=postgres GITEA__database__HOST=postgres:5432 GITEA__database__NAME=gitea GITEA__database__USER=gitea GITEA__database__PASSWD=gitea -Image=docker.gitea.com/gitea:1.25-rootless -Network=gitea.network -PublishPort=3000:3000 -PublishPort=2222:2222 -SecurityLabelDisable=true -Volume=/home/gitea/gitea_data:/data:Z -Volume=/home/gitea/gitea_etc:/etc/gitea:Z -Volume=/home/gitea/gitea_custom:/var/lib/gitea/custom:Z -Volume=/etc/localtime:/etc/localtime:ro - -[Service] -Restart=always - -[Install] -WantedBy=default.target diff --git a/active/container_gitea/quadlets/postgres.container b/active/container_gitea/quadlets/postgres.container deleted file mode 100644 index c05993b..0000000 --- a/active/container_gitea/quadlets/postgres.container +++ /dev/null @@ -1,14 +0,0 @@ -[Container] -AutoUpdate=registry -ContainerName=postgres -Environment=POSTGRES_USER=gitea POSTGRES_PASSWORD=gitea POSTGRES_DB=gitea -Image=docker.io/library/postgres:15 -Network=gitea.network -SecurityLabelDisable=true -Volume=/home/gitea/gitea_postgres:/var/lib/postgresql/data:Z - -[Service] -Restart=always - -[Install] -WantedBy=default.target diff --git a/active/container_immich/release-env b/active/container_immich/release-env index 75d93ef..0b2e7c0 100644 --- a/active/container_immich/release-env +++ b/active/container_immich/release-env @@ -10,7 +10,7 @@ DB_DATA_LOCATION=/srv/immich-db/postgres # TZ=Etc/UTC # The Immich version to use. You can pin this to a specific version like "v2.1.0" -IMMICH_VERSION=v2 +IMMICH_VERSION=v3 # Connection secret for postgres. You should change it to a random password # Please use only the characters `A-Za-z0-9`, without special characters or spaces diff --git a/active/container_keycloak/compose/compose.yaml b/active/container_keycloak/compose/compose.yaml deleted file mode 100644 index 4c353db..0000000 --- a/active/container_keycloak/compose/compose.yaml +++ /dev/null @@ -1,19 +0,0 @@ -services: - keycloak: - container_name: keycloak - restart: always - image: quay.io/keycloak/keycloak:26.4.5 - ports: - - "9443:443" - - "8443:8443" - volumes: - - /home/foobar/data:/var/app/data - security_opt: - - label=disable - userns_mode: keep-id - command: - - "start" - - "--hostname" - - "https://keycloak.reeseapps.com" - - "--hostname-admin" - - "https://keycloak.reeselink.com:8443" \ No newline at end of file diff --git a/active/container_keycloak/keycloak.md b/active/container_keycloak/keycloak.md index aecb63e..2d6c238 100644 --- a/active/container_keycloak/keycloak.md +++ b/active/container_keycloak/keycloak.md @@ -1,186 +1,10 @@ # Podman keycloak - [Podman keycloak](#podman-keycloak) - - [Setup keycloak Project](#setup-keycloak-project) - - [Install Keycloak with Docker](#install-keycloak-with-docker) - - [Install Keycloak with Podman](#install-keycloak-with-podman) - - [Create the keycloak user](#create-the-keycloak-user) - - [Write the keycloak compose spec](#write-the-keycloak-compose-spec) - - [A Note on Volumes](#a-note-on-volumes) - - [Convert keycloak compose spec to quadlets](#convert-keycloak-compose-spec-to-quadlets) - - [Create any container-mounted directories](#create-any-container-mounted-directories) - - [Start and enable your systemd quadlet](#start-and-enable-your-systemd-quadlet) - - [Expose keycloak](#expose-keycloak) - - [firewalld](#firewalld) - - [Backup keycloak](#backup-keycloak) - - [Upgrade keycloak](#upgrade-keycloak) - - [Upgrade Quadlets](#upgrade-quadlets) - - [Uninstall](#uninstall) - - [Notes](#notes) - - [SELinux](#selinux) + - [Adding a Client](#adding-a-client) -## Setup keycloak Project +## Adding a Client -- [ ] Copy and rename this folder to active/container_keycloak -- [ ] Find and replace keycloak with the name of the service. -- [ ] Create the rootless user to run the podman containers -- [ ] Write the compose.yaml spec for your service -- [ ] Convert the compose.yaml spec to a quadlet -- [ ] Install the quadlet on the podman server -- [ ] Expose the quadlet service -- [ ] Install a backup service and timer - -## Install Keycloak with Docker - - - -```bash -# Test in dev mode -docker run -p 8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:26.4.7 start-dev -``` - -## Install Keycloak with Podman - -### Create the keycloak user - -```bash -# SSH into your podman server as root -useradd keycloak -loginctl enable-linger $(id -u keycloak) -systemctl --user --machine=keycloak@.host enable podman-restart -systemctl --user --machine=keycloak@.host enable --now podman.socket -mkdir -p /home/keycloak/.config/containers/systemd -``` - -### Write the keycloak compose spec - - - -```bash -# Based on the example -podman run \ --p 127.0.0.1:8080:8080 \ --e KC_BOOTSTRAP_ADMIN_USERNAME=admin \ --e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \ -quay.io/keycloak/keycloak:26.4.5 start-dev -``` - -#### A Note on Volumes - -Named volumes are stored at `/home/keycloak/.local/share/containers/storage/volumes/`. - -### Convert keycloak compose spec to quadlets - -Run the following to convert a compose.yaml into the various `.container` files for systemd: - -```bash -# Generate the systemd service -podman run \ ---security-opt label=disable \ ---rm \ --v $(pwd)/active/container_keycloak/:/compose \ --v $(pwd)/active/container_keycloak/quadlets:/quadlets \ -quay.io/k9withabone/podlet \ --f /quadlets \ --i \ ---overwrite \ -compose /compose/compose.yaml - -# Copy the files to the server -export PODMAN_SERVER= -scp -r active/container_keycloak/quadlets/. $PODMAN_SERVER:/home/keycloak/.config/containers/systemd/ -ssh $PODMAN_SERVER chown -R keycloak:keycloak /home/keycloak/.config/containers/systemd/ -``` - -### Create any container-mounted directories - -SSH into your podman server as root: - -```bash -machinectl shell keycloak@ -podman unshare -mkdir some_volume -# Chown to the namespaced user with UID 1000 -# This will be some really obscure UID outside the namespace -# This will also solve most permission denied errors -chown -R 1000:1000 some_volume -``` - -### Start and enable your systemd quadlet - -SSH into your podman server as root: - -```bash -machinectl shell keycloak@ -systemctl --user daemon-reload -systemctl --user restart keycloak -# Enable auto-update service which will pull new container images automatically every day -systemctl --user enable --now podman-auto-update.timer -``` - -### Expose keycloak - -1. If you need a domain, follow the [DDNS instructions](/active/container_ddns/ddns.md#install-a-new-ddns-service) -2. For a web service, follow the [Caddy instructions](/active/container_caddy/caddy.md#adding-a-new-caddy-record) -3. Finally, follow your OS's guide for opening ports via its firewall service. - -#### firewalld - -```bash -# command to get current active zone and default zone -firewall-cmd --get-active-zones -firewall-cmd --get-default-zone - -# command to open 443 on tcp -firewall-cmd --permanent --zone= --add-port=443/tcp - -# command to open 80 and 443 on tcp and udp -firewall-cmd --permanent --zone= --add-port={80,443}/{tcp,udp} - -# command to list available services and then open http and https -firewall-cmd --get-services -firewall-cmd --permanent --zone= --add-service={http,https} -``` - -## Backup keycloak - -Follow the [Borg Backup instructions](/active/systemd_borg/borg.md#set-up-a-client-for-backup) - -## Upgrade keycloak - -### Upgrade Quadlets - -Upgrades should be a repeat of [writing the compose spec](#convert-keycloak-compose-spec-to-quadlets) and [installing the quadlets](#start-and-enable-your-systemd-quadlet) - -```bash -export PODMAN_SERVER= -scp -r quadlets/. $PODMAN_SERVER$:/home/keycloak/.config/containers/systemd/ -ssh keycloak systemctl --user daemon-reload -ssh keycloak systemctl --user restart keycloak -``` - -## Uninstall - -```bash -# Stop the user's services -systemctl --user disable podman-restart -podman container stop --all -systemctl --user disable --now podman.socket -systemctl --user disable --now podman-auto-update.timer - -# Delete the user (this won't delete their home directory) -# userdel might spit out an error like: -# userdel: user keycloak is currently used by process 591255 -# kill those processes and try again -userdel keycloak -``` - -## Notes - -### SELinux - - - -:z allows a container to share a mounted volume with all other containers. - -:Z allows a container to reserve a mounted volume and prevents any other container from accessing. +1. Create a new client +2. Clients -> click on client -> Client scopes -> client-frontend-dedicated -> Scope -> turn off Full Scope Allowed +3. \ No newline at end of file diff --git a/active/container_keycloak/quadlets/README.md b/active/container_keycloak/quadlets/README.md deleted file mode 100644 index 0ea23f9..0000000 --- a/active/container_keycloak/quadlets/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Quadlets - -Put your quadlets here. diff --git a/active/container_keycloak/compose/README.md b/active/container_langfuse/compose/README.md similarity index 100% rename from active/container_keycloak/compose/README.md rename to active/container_langfuse/compose/README.md diff --git a/active/container_langfuse/compose/compose.yaml b/active/container_langfuse/compose/compose.yaml new file mode 100644 index 0000000..e71ea7d --- /dev/null +++ b/active/container_langfuse/compose/compose.yaml @@ -0,0 +1,168 @@ +# Make sure to update the credential placeholders with your own secrets. +# We mark them with # CHANGEME in the file below. +# In addition, we recommend to restrict inbound traffic on the host to langfuse-web (port 3000) and minio (port 9090) only. +# All other components are bound to localhost (127.0.0.1) to only accept connections from the local machine. +# External connections from other machines will not be able to reach these services directly. +services: + langfuse-worker: + image: docker.io/langfuse/langfuse-worker:3 + restart: always + ports: + - 127.0.0.1:3030:3030 + environment: &langfuse-worker-env + NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} + DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/postgres} # CHANGEME + SALT: ${SALT:-mysalt} # CHANGEME + ENCRYPTION_KEY: ${ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000} # CHANGEME: generate via `openssl rand -hex 32` + TELEMETRY_ENABLED: ${TELEMETRY_ENABLED:-true} + LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES: ${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false} + CLICKHOUSE_MIGRATION_URL: ${CLICKHOUSE_MIGRATION_URL:-clickhouse://clickhouse:9000} + CLICKHOUSE_URL: ${CLICKHOUSE_URL:-http://clickhouse:8123} + CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME + CLICKHOUSE_CLUSTER_ENABLED: ${CLICKHOUSE_CLUSTER_ENABLED:-false} + LANGFUSE_USE_AZURE_BLOB: ${LANGFUSE_USE_AZURE_BLOB:-false} + LANGFUSE_USE_OCI_NATIVE_OBJECT_STORAGE: ${LANGFUSE_USE_OCI_NATIVE_OBJECT_STORAGE:-false} + LANGFUSE_OCI_AUTH_TYPE: ${LANGFUSE_OCI_AUTH_TYPE:-workload_identity} + LANGFUSE_S3_EVENT_UPLOAD_BUCKET: ${LANGFUSE_S3_EVENT_UPLOAD_BUCKET:-langfuse} + LANGFUSE_S3_EVENT_UPLOAD_REGION: ${LANGFUSE_S3_EVENT_UPLOAD_REGION:-auto} + LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID:-minio} + LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME + LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT: ${LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT:-http://minio:9000} + LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE:-true} + LANGFUSE_S3_EVENT_UPLOAD_PREFIX: ${LANGFUSE_S3_EVENT_UPLOAD_PREFIX:-events/} + LANGFUSE_S3_MEDIA_UPLOAD_BUCKET: ${LANGFUSE_S3_MEDIA_UPLOAD_BUCKET:-langfuse} + LANGFUSE_S3_MEDIA_UPLOAD_REGION: ${LANGFUSE_S3_MEDIA_UPLOAD_REGION:-auto} + LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID: ${LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID:-minio} + LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY: ${LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME + LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT: ${LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT:-http://localhost:9090} + LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE: ${LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE:-true} + LANGFUSE_S3_MEDIA_UPLOAD_PREFIX: ${LANGFUSE_S3_MEDIA_UPLOAD_PREFIX:-media/} + LANGFUSE_S3_BATCH_EXPORT_ENABLED: ${LANGFUSE_S3_BATCH_EXPORT_ENABLED:-false} + LANGFUSE_S3_BATCH_EXPORT_BUCKET: ${LANGFUSE_S3_BATCH_EXPORT_BUCKET:-langfuse} + LANGFUSE_S3_BATCH_EXPORT_PREFIX: ${LANGFUSE_S3_BATCH_EXPORT_PREFIX:-exports/} + LANGFUSE_S3_BATCH_EXPORT_REGION: ${LANGFUSE_S3_BATCH_EXPORT_REGION:-auto} + LANGFUSE_S3_BATCH_EXPORT_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_ENDPOINT:-http://minio:9000} + LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT: ${LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT:-http://localhost:9090} + LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID: ${LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID:-minio} + LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY: ${LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY:-miniosecret} # CHANGEME + LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE: ${LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE:-true} + LANGFUSE_INGESTION_QUEUE_DELAY_MS: ${LANGFUSE_INGESTION_QUEUE_DELAY_MS:-} + LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS: ${LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS:-} + REDIS_HOST: ${REDIS_HOST:-redis} + REDIS_PORT: ${REDIS_PORT:-6379} + REDIS_AUTH: ${REDIS_AUTH:-myredissecret} # CHANGEME + REDIS_TLS_ENABLED: ${REDIS_TLS_ENABLED:-false} + REDIS_TLS_CA: ${REDIS_TLS_CA:-/certs/ca.crt} + REDIS_TLS_CERT: ${REDIS_TLS_CERT:-/certs/redis.crt} + REDIS_TLS_KEY: ${REDIS_TLS_KEY:-/certs/redis.key} + EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-} + SMTP_CONNECTION_URL: ${SMTP_CONNECTION_URL:-} + + langfuse-web: + image: docker.io/langfuse/langfuse:3 + restart: always + ports: + - 3000:3000 + environment: + <<: *langfuse-worker-env + NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-mysecret} # CHANGEME + LANGFUSE_INIT_ORG_ID: ${LANGFUSE_INIT_ORG_ID:-} + LANGFUSE_INIT_ORG_NAME: ${LANGFUSE_INIT_ORG_NAME:-} + LANGFUSE_INIT_PROJECT_ID: ${LANGFUSE_INIT_PROJECT_ID:-} + LANGFUSE_INIT_PROJECT_NAME: ${LANGFUSE_INIT_PROJECT_NAME:-} + LANGFUSE_INIT_PROJECT_PUBLIC_KEY: ${LANGFUSE_INIT_PROJECT_PUBLIC_KEY:-} + LANGFUSE_INIT_PROJECT_SECRET_KEY: ${LANGFUSE_INIT_PROJECT_SECRET_KEY:-} + LANGFUSE_INIT_USER_EMAIL: ${LANGFUSE_INIT_USER_EMAIL:-admin@ducoterra.net} + LANGFUSE_INIT_USER_NAME: ${LANGFUSE_INIT_USER_NAME:-admin} + LANGFUSE_INIT_USER_PASSWORD: ${LANGFUSE_INIT_USER_PASSWORD:-testpass} + + clickhouse: + image: docker.io/clickhouse/clickhouse-server + restart: always + user: "101:101" + environment: + CLICKHOUSE_DB: default + CLICKHOUSE_USER: ${CLICKHOUSE_USER:-clickhouse} + CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-clickhouse} # CHANGEME + volumes: + - langfuse_clickhouse_data:/var/lib/clickhouse + - langfuse_clickhouse_logs:/var/log/clickhouse-server + ports: + - 127.0.0.1:8123:8123 + - 127.0.0.1:9000:9000 + healthcheck: + test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1 + interval: 5s + timeout: 5s + retries: 10 + start_period: 1s + + minio: + image: cgr.dev/chainguard/minio + restart: always + entrypoint: sh + # create the 'langfuse' bucket before starting the service + command: -c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data' + environment: + MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minio} + MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-miniosecret} # CHANGEME + ports: + - 9090:9000 + - 127.0.0.1:9091:9001 + volumes: + - langfuse_minio_data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 1s + timeout: 5s + retries: 5 + start_period: 1s + + redis: + image: docker.io/redis:7 + restart: always + # CHANGEME: row below to secure redis password + command: > + --requirepass ${REDIS_AUTH:-myredissecret} + --maxmemory-policy noeviction + ports: + - 127.0.0.1:6379:6379 + volumes: + - langfuse_redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 3s + timeout: 10s + retries: 10 + + postgres: + image: docker.io/postgres:${POSTGRES_VERSION:-17} + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 3s + timeout: 3s + retries: 10 + environment: + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} # CHANGEME + POSTGRES_DB: ${POSTGRES_DB:-postgres} + TZ: UTC + PGTZ: UTC + ports: + - 127.0.0.1:5432:5432 + volumes: + - langfuse_postgres_data:/var/lib/postgresql/data + +volumes: + langfuse_postgres_data: + driver: local + langfuse_clickhouse_data: + driver: local + langfuse_clickhouse_logs: + driver: local + langfuse_minio_data: + driver: local + langfuse_redis_data: + driver: local \ No newline at end of file diff --git a/active/container_langfuse/langfuse.md b/active/container_langfuse/langfuse.md new file mode 100644 index 0000000..bad669b --- /dev/null +++ b/active/container_langfuse/langfuse.md @@ -0,0 +1,167 @@ +# Podman langfuse + +- [Podman langfuse](#podman-langfuse) + - [Setup langfuse Project](#setup-langfuse-project) + - [Install langfuse](#install-langfuse) + - [Create the langfuse user](#create-the-langfuse-user) + - [Write the langfuse compose spec](#write-the-langfuse-compose-spec) + - [A Note on Volumes](#a-note-on-volumes) + - [Convert langfuse compose spec to quadlets](#convert-langfuse-compose-spec-to-quadlets) + - [Create any container-mounted directories](#create-any-container-mounted-directories) + - [Start and enable your systemd quadlet](#start-and-enable-your-systemd-quadlet) + - [Expose langfuse](#expose-langfuse) + - [firewalld](#firewalld) + - [Backup langfuse](#backup-langfuse) + - [Upgrade langfuse](#upgrade-langfuse) + - [Upgrade Quadlets](#upgrade-quadlets) + - [Uninstall](#uninstall) + - [Notes](#notes) + - [SELinux](#selinux) + +## Setup langfuse Project + +- [ ] Copy and rename this folder to active/container_langfuse +- [ ] Find and replace langfuse with the name of the service. +- [ ] Create the rootless user to run the podman containers +- [ ] Write the compose.yaml spec for your service +- [ ] Convert the compose.yaml spec to a quadlet +- [ ] Install the quadlet on the podman server +- [ ] Expose the quadlet service +- [ ] Install a backup service and timer + +## Install langfuse + +### Create the langfuse user + +```bash +# SSH into your podman server as root +useradd langfuse +loginctl enable-linger $(id -u langfuse) +systemctl --user --machine=langfuse@.host enable podman-restart +systemctl --user --machine=langfuse@.host enable --now podman.socket +mkdir -p /home/langfuse/.config/containers/systemd +``` + +### Write the langfuse compose spec + +Edit the compose.yaml at active/container_langfuse/compose/compose.yaml + +#### A Note on Volumes + +Named volumes are stored at `/home/langfuse/.local/share/containers/storage/volumes/`. + +### Convert langfuse compose spec to quadlets + +Run the following to convert a compose.yaml into the various `.container` files for systemd: + +```bash +# Generate the systemd service +podman run \ +--security-opt label=disable \ +--rm \ +-v $(pwd)/active/container_langfuse/compose:/compose \ +-v $(pwd)/active/container_langfuse/quadlets:/quadlets \ +quay.io/k9withabone/podlet \ +-f /quadlets \ +-i \ +--overwrite \ +compose /compose/compose.yaml + +# Copy the files to the server +export PODMAN_SERVER= +scp -r active/container_langfuse/quadlets/. $PODMAN_SERVER:/home/langfuse/.config/containers/systemd/ +ssh $PODMAN_SERVER chown -R langfuse:langfuse /home/langfuse/.config/containers/systemd/ +``` + +### Create any container-mounted directories + +SSH into your podman server as root: + +```bash +machinectl shell langfuse@ +podman unshare +mkdir some_volume +# Chown to the namespaced user with UID 1000 +# This will be some really obscure UID outside the namespace +# This will also solve most permission denied errors +chown -R 1000:1000 some_volume +``` + +### Start and enable your systemd quadlet + +SSH into your podman server as root: + +```bash +machinectl shell langfuse@ +systemctl --user daemon-reload +systemctl --user restart langfuse +# Enable auto-update service which will pull new container images automatically every day +systemctl --user enable --now podman-auto-update.timer +``` + +### Expose langfuse + +1. If you need a domain, follow the [DDNS instructions](/active/container_ddns/ddns.md#install-a-new-ddns-service) +2. For a web service, follow the [Caddy instructions](/active/container_caddy/caddy.md#adding-a-new-caddy-record) +3. Finally, follow your OS's guide for opening ports via its firewall service. + +#### firewalld + +```bash +# command to get current active zone and default zone +firewall-cmd --get-active-zones +firewall-cmd --get-default-zone + +# command to open 443 on tcp +firewall-cmd --permanent --zone= --add-port=443/tcp + +# command to open 80 and 443 on tcp and udp +firewall-cmd --permanent --zone= --add-port={80,443}/{tcp,udp} + +# command to list available services and then open http and https +firewall-cmd --get-services +firewall-cmd --permanent --zone= --add-service={http,https} +``` + +## Backup langfuse + +Follow the [Borg Backup instructions](/active/systemd_borg/borg.md#set-up-a-client-for-backup) + +## Upgrade langfuse + +### Upgrade Quadlets + +Upgrades should be a repeat of [writing the compose spec](#convert-langfuse-compose-spec-to-quadlets) and [installing the quadlets](#start-and-enable-your-systemd-quadlet) + +```bash +export PODMAN_SERVER= +scp -r quadlets/. $PODMAN_SERVER$:/home/langfuse/.config/containers/systemd/ +ssh langfuse systemctl --user daemon-reload +ssh langfuse systemctl --user restart langfuse +``` + +## Uninstall + +```bash +# Stop the user's services +systemctl --user disable podman-restart +podman container stop --all +systemctl --user disable --now podman.socket +systemctl --user disable --now podman-auto-update.timer + +# Delete the user (this won't delete their home directory) +# userdel might spit out an error like: +# userdel: user langfuse is currently used by process 591255 +# kill those processes and try again +userdel langfuse +``` + +## Notes + +### SELinux + + + +:z allows a container to share a mounted volume with all other containers. + +:Z allows a container to reserve a mounted volume and prevents any other container from accessing. diff --git a/active/container_langfuse/quadlets/langfuse-clickhouse.container b/active/container_langfuse/quadlets/langfuse-clickhouse.container new file mode 100644 index 0000000..7b45b5b --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-clickhouse.container @@ -0,0 +1,21 @@ +[Container] +Pod=langfuse.pod +ContainerName=langfuse-clickhouse +Environment=CLICKHOUSE_DB=default +Environment=CLICKHOUSE_USER=clickhouse +Environment=CLICKHOUSE_PASSWORD=clickhouse +User=101:101 +Image=docker.io/clickhouse/clickhouse-server +Volume=langfuse-clickhouse_data.volume:/var/lib/clickhouse +Volume=langfuse-clickhouse_logs.volume:/var/log/clickhouse-server +HealthCmd=wget --no-verbose --tries=1 --spider http://localhost:8123/ping +HealthInterval=5s +HealthTimeout=5s +HealthRetries=10 +HealthStartPeriod=1s + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-clickhouse_data.volume b/active/container_langfuse/quadlets/langfuse-clickhouse_data.volume new file mode 100644 index 0000000..2a9a29b --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-clickhouse_data.volume @@ -0,0 +1,5 @@ +[Volume] +VolumeName=langfuse_clickhouse_data + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-clickhouse_logs.volume b/active/container_langfuse/quadlets/langfuse-clickhouse_logs.volume new file mode 100644 index 0000000..47f808b --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-clickhouse_logs.volume @@ -0,0 +1,5 @@ +[Volume] +VolumeName=langfuse_clickhouse_logs + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-minio.container b/active/container_langfuse/quadlets/langfuse-minio.container new file mode 100644 index 0000000..d813ea3 --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-minio.container @@ -0,0 +1,20 @@ +[Container] +Pod=langfuse.pod +ContainerName=langfuse-minio +Environment=MINIO_ROOT_USER=minio +Environment=MINIO_ROOT_PASSWORD=miniosecret +Image=cgr.dev/chainguard/minio +Volume=langfuse-minio_data.volume:/data +Entrypoint=sh +Exec=-c 'mkdir -p /data/langfuse && minio server --address ":9000" --console-address ":9001" /data' +HealthCmd=mc ready local +HealthInterval=1s +HealthTimeout=5s +HealthRetries=5 +HealthStartPeriod=1s + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-minio_data.volume b/active/container_langfuse/quadlets/langfuse-minio_data.volume new file mode 100644 index 0000000..d2541b5 --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-minio_data.volume @@ -0,0 +1,5 @@ +[Volume] +VolumeName=langfuse_minio_data + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-postgres.container b/active/container_langfuse/quadlets/langfuse-postgres.container new file mode 100644 index 0000000..0fd8add --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-postgres.container @@ -0,0 +1,20 @@ +[Container] +Pod=langfuse.pod +ContainerName=langfuse-postgres +Environment=POSTGRES_USER=postgres +Environment=POSTGRES_PASSWORD=postgres +Environment=POSTGRES_DB=postgres +Environment=TZ=UTC +Environment=PGTZ=UTC +Image=docker.io/postgres:17 +Volume=langfuse-postgres_data.volume:/var/lib/postgresql/data +HealthCmd=pg_isready -U postgres +HealthInterval=3s +HealthTimeout=3s +HealthRetries=10 + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-postgres_data.volume b/active/container_langfuse/quadlets/langfuse-postgres_data.volume new file mode 100644 index 0000000..1f3976b --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-postgres_data.volume @@ -0,0 +1,5 @@ +[Volume] +VolumeName=langfuse_postgres_data + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-redis.container b/active/container_langfuse/quadlets/langfuse-redis.container new file mode 100644 index 0000000..a022fb5 --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-redis.container @@ -0,0 +1,17 @@ +[Container] +Pod=langfuse.pod +ContainerName=langfuse-redis +Environment=REDIS_AUTH=myredissecret +Image=docker.io/redis:7 +Volume=langfuse-redis_data.volume:/data +Exec=--requirepass myredissecret --maxmemory-policy noeviction +HealthCmd=redis-cli ping +HealthInterval=3s +HealthTimeout=10s +HealthRetries=10 + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-redis_data.volume b/active/container_langfuse/quadlets/langfuse-redis_data.volume new file mode 100644 index 0000000..ead9aca --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-redis_data.volume @@ -0,0 +1,5 @@ +[Volume] +VolumeName=langfuse_redis_data + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-web.container b/active/container_langfuse/quadlets/langfuse-web.container new file mode 100644 index 0000000..d43991a --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-web.container @@ -0,0 +1,71 @@ +[Unit] +Requires=langfuse-postgres.service langfuse-clickhouse.service langfuse-redis.service langfuse-minio.service + +[Container] +Pod=langfuse.pod +ContainerName=langfuse-web +Environment=NEXTAUTH_URL=http://localhost:3000 +Environment=DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres +Environment=SALT=mysalt +Environment=ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 +Environment=TELEMETRY_ENABLED=true +Environment=LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=false +Environment=CLICKHOUSE_MIGRATION_URL=clickhouse://localhost:9000 +Environment=CLICKHOUSE_URL=http://localhost:8123 +Environment=CLICKHOUSE_USER=clickhouse +Environment=CLICKHOUSE_PASSWORD=clickhouse +Environment=CLICKHOUSE_CLUSTER_ENABLED=false +Environment=LANGFUSE_USE_AZURE_BLOB=false +Environment=LANGFUSE_USE_OCI_NATIVE_OBJECT_STORAGE=false +Environment=LANGFUSE_OCI_AUTH_TYPE=workload_identity +Environment=LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse +Environment=LANGFUSE_S3_EVENT_UPLOAD_REGION=auto +Environment=LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio +Environment=LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=miniosecret +Environment=LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://localhost:9000 +Environment=LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true +Environment=LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/ +Environment=LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse +Environment=LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto +Environment=LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio +Environment=LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=miniosecret +Environment=LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 +Environment=LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true +Environment=LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/ +Environment=LANGFUSE_S3_BATCH_EXPORT_ENABLED=false +Environment=LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse +Environment=LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/ +Environment=LANGFUSE_S3_BATCH_EXPORT_REGION=auto +Environment=LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://localhost:9000 +Environment=LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=http://localhost:9090 +Environment=LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio +Environment=LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=miniosecret +Environment=LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true +Environment=LANGFUSE_INGESTION_QUEUE_DELAY_MS= +Environment=LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS= +Environment=REDIS_HOST=localhost +Environment=REDIS_PORT=6379 +Environment=REDIS_AUTH=myredissecret +Environment=REDIS_TLS_ENABLED=false +Environment=REDIS_TLS_CA=/certs/ca.crt +Environment=REDIS_TLS_CERT=/certs/redis.crt +Environment=REDIS_TLS_KEY=/certs/redis.key +Environment=EMAIL_FROM_ADDRESS= +Environment=SMTP_CONNECTION_URL= +Environment=NEXTAUTH_SECRET=mysecret +Environment=LANGFUSE_INIT_ORG_ID= +Environment=LANGFUSE_INIT_ORG_NAME= +Environment=LANGFUSE_INIT_PROJECT_ID= +Environment=LANGFUSE_INIT_PROJECT_NAME= +Environment=LANGFUSE_INIT_PROJECT_PUBLIC_KEY= +Environment=LANGFUSE_INIT_PROJECT_SECRET_KEY= +Environment=LANGFUSE_INIT_USER_EMAIL=admin@ducoterra.net +Environment=LANGFUSE_INIT_USER_NAME=admin +Environment=LANGFUSE_INIT_USER_PASSWORD=testpass +Image=docker.io/langfuse/langfuse:3 + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/active/container_langfuse/quadlets/langfuse-worker.container b/active/container_langfuse/quadlets/langfuse-worker.container new file mode 100644 index 0000000..b790c97 --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse-worker.container @@ -0,0 +1,61 @@ +[Unit] +Requires=langfuse-postgres.service langfuse-clickhouse.service langfuse-redis.service langfuse-minio.service + +[Container] +Pod=langfuse.pod +ContainerName=langfuse-worker +Environment=NEXTAUTH_URL=http://localhost:3000 +Environment=DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres +Environment=SALT=mysalt +Environment=ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 +Environment=TELEMETRY_ENABLED=true +Environment=LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=false +Environment=CLICKHOUSE_MIGRATION_URL=clickhouse://localhost:9000 +Environment=CLICKHOUSE_URL=http://localhost:8123 +Environment=CLICKHOUSE_USER=clickhouse +Environment=CLICKHOUSE_PASSWORD=clickhouse +Environment=CLICKHOUSE_CLUSTER_ENABLED=false +Environment=LANGFUSE_USE_AZURE_BLOB=false +Environment=LANGFUSE_USE_OCI_NATIVE_OBJECT_STORAGE=false +Environment=LANGFUSE_OCI_AUTH_TYPE=workload_identity +Environment=LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse +Environment=LANGFUSE_S3_EVENT_UPLOAD_REGION=auto +Environment=LANGFUSE_S3_EVENT_UPLOAD_ACCESS_KEY_ID=minio +Environment=LANGFUSE_S3_EVENT_UPLOAD_SECRET_ACCESS_KEY=miniosecret +Environment=LANGFUSE_S3_EVENT_UPLOAD_ENDPOINT=http://localhost:9000 +Environment=LANGFUSE_S3_EVENT_UPLOAD_FORCE_PATH_STYLE=true +Environment=LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/ +Environment=LANGFUSE_S3_MEDIA_UPLOAD_BUCKET=langfuse +Environment=LANGFUSE_S3_MEDIA_UPLOAD_REGION=auto +Environment=LANGFUSE_S3_MEDIA_UPLOAD_ACCESS_KEY_ID=minio +Environment=LANGFUSE_S3_MEDIA_UPLOAD_SECRET_ACCESS_KEY=miniosecret +Environment=LANGFUSE_S3_MEDIA_UPLOAD_ENDPOINT=http://localhost:9090 +Environment=LANGFUSE_S3_MEDIA_UPLOAD_FORCE_PATH_STYLE=true +Environment=LANGFUSE_S3_MEDIA_UPLOAD_PREFIX=media/ +Environment=LANGFUSE_S3_BATCH_EXPORT_ENABLED=false +Environment=LANGFUSE_S3_BATCH_EXPORT_BUCKET=langfuse +Environment=LANGFUSE_S3_BATCH_EXPORT_PREFIX=exports/ +Environment=LANGFUSE_S3_BATCH_EXPORT_REGION=auto +Environment=LANGFUSE_S3_BATCH_EXPORT_ENDPOINT=http://localhost:9000 +Environment=LANGFUSE_S3_BATCH_EXPORT_EXTERNAL_ENDPOINT=http://localhost:9090 +Environment=LANGFUSE_S3_BATCH_EXPORT_ACCESS_KEY_ID=minio +Environment=LANGFUSE_S3_BATCH_EXPORT_SECRET_ACCESS_KEY=miniosecret +Environment=LANGFUSE_S3_BATCH_EXPORT_FORCE_PATH_STYLE=true +Environment=LANGFUSE_INGESTION_QUEUE_DELAY_MS= +Environment=LANGFUSE_INGESTION_CLICKHOUSE_WRITE_INTERVAL_MS= +Environment=REDIS_HOST=localhost +Environment=REDIS_PORT=6379 +Environment=REDIS_AUTH=myredissecret +Environment=REDIS_TLS_ENABLED=false +Environment=REDIS_TLS_CA=/certs/ca.crt +Environment=REDIS_TLS_CERT=/certs/redis.crt +Environment=REDIS_TLS_KEY=/certs/redis.key +Environment=EMAIL_FROM_ADDRESS= +Environment=SMTP_CONNECTION_URL= +Image=docker.io/langfuse/langfuse-worker:3 + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/active/container_gitea/quadlets/gitea.network b/active/container_langfuse/quadlets/langfuse.network similarity index 100% rename from active/container_gitea/quadlets/gitea.network rename to active/container_langfuse/quadlets/langfuse.network diff --git a/active/container_langfuse/quadlets/langfuse.pod b/active/container_langfuse/quadlets/langfuse.pod new file mode 100644 index 0000000..02654ea --- /dev/null +++ b/active/container_langfuse/quadlets/langfuse.pod @@ -0,0 +1,12 @@ +[Pod] +PublishPort=3000:3000/tcp +PublishPort=127.0.0.1:3030:3030/tcp +PublishPort=127.0.0.1:8123:8123/tcp +PublishPort=127.0.0.1:9000:9000/tcp +PublishPort=9090:9000/tcp +PublishPort=127.0.0.1:9091:9001/tcp +PublishPort=127.0.0.1:6379:6379/tcp +PublishPort=127.0.0.1:5432:5432/tcp + +[Install] +WantedBy=default.target diff --git a/active/container_litellm/quadlets/litellm-web.container b/active/container_litellm/quadlets/litellm-web.container index 4fa82b2..64cbdf9 100644 --- a/active/container_litellm/quadlets/litellm-web.container +++ b/active/container_litellm/quadlets/litellm-web.container @@ -11,7 +11,7 @@ HealthInterval=30s HealthRetries=3 HealthStartPeriod=40s HealthTimeout=10s -Image=ghcr.io/berriai/litellm-database:v1.83.14-stable.patch.3 +Image=ghcr.io/berriai/litellm-database:main-stable Volume=/home/ai/litellm-config.yaml:/app/config.yaml:z Exec=--config=/app/config.yaml diff --git a/active/container_ntfy/ntfy.md b/active/container_ntfy/ntfy.md new file mode 100644 index 0000000..a66d04a --- /dev/null +++ b/active/container_ntfy/ntfy.md @@ -0,0 +1,24 @@ +# NTFY + +## Running ntfy + +```bash +podman run \ +-v ntfy:/var/cache/ntfy \ +-v ./active/container_ntfy/server.yml:/etc/ntfy/server.yml:z \ +-p 8001:80 \ +-it \ +docker.io/binwiederhier/ntfy:latest \ +serve \ +--cache-file /var/cache/ntfy/cache.db +``` + +## Testing with Curl + +```bash +# Publish a topic +curl -d "Backup successful" localhost:8001/mytopic + +# Listen on a topic +curl -s localhost:8001/mytopic +``` diff --git a/active/container_ntfy/server.yml b/active/container_ntfy/server.yml new file mode 100644 index 0000000..1940f23 --- /dev/null +++ b/active/container_ntfy/server.yml @@ -0,0 +1 @@ +# base-url: diff --git a/active/device_esphome/6w1gdHC9EQ4ROIMSlKS9Kw.wav b/active/device_esphome/6w1gdHC9EQ4ROIMSlKS9Kw.wav new file mode 100644 index 0000000..366d48c Binary files /dev/null and b/active/device_esphome/6w1gdHC9EQ4ROIMSlKS9Kw.wav differ diff --git a/active/device_esphome/O_ZfoOtCoa-SAmnVSyacIg.wav b/active/device_esphome/O_ZfoOtCoa-SAmnVSyacIg.wav new file mode 100644 index 0000000..57facf7 Binary files /dev/null and b/active/device_esphome/O_ZfoOtCoa-SAmnVSyacIg.wav differ diff --git a/active/device_esphome/alexa.json b/active/device_esphome/alexa.json new file mode 100644 index 0000000..dff427a --- /dev/null +++ b/active/device_esphome/alexa.json @@ -0,0 +1,16 @@ +{ + "type": "micro", + "wake_word": "Alexa", + "author": "Kevin Ahrendt", + "website": "https://www.kevinahrendt.com/", + "model": "alexa.tflite", + "trained_languages": ["en"], + "version": 2, + "micro": { + "probability_cutoff": 0.9, + "sliding_window_size": 5, + "feature_step_size": 10, + "tensor_arena_size": 22348, + "minimum_esphome_version": "2024.7.0" + } +} diff --git a/active/device_esphome/alexa.tflite b/active/device_esphome/alexa.tflite new file mode 100644 index 0000000..b9a87dd Binary files /dev/null and b/active/device_esphome/alexa.tflite differ diff --git a/active/device_esphome/tab2.yaml b/active/device_esphome/dickhead.yaml similarity index 68% rename from active/device_esphome/tab2.yaml rename to active/device_esphome/dickhead.yaml index d066fe8..2550dae 100644 --- a/active/device_esphome/tab2.yaml +++ b/active/device_esphome/dickhead.yaml @@ -1,9 +1,12 @@ esphome: - name: tab2 - friendly_name: M5Stack Tab5 2 + name: dickhead + friendly_name: Dickhead on_boot: - # Set the charing icon to the correct state on boot + # Set the charging icon to the correct state on boot - then: + - logger.log: "Delaying backlight initialization" + - delay: 2s + - logger.log: "End delay" - if: condition: lambda: return id(charging).state; @@ -17,6 +20,7 @@ esphome: esp32: board: esp32-p4-evboard flash_size: 16MB + cpu_frequency: 360MHz framework: type: esp-idf advanced: @@ -36,6 +40,7 @@ esp32_hosted: logger: hardware_uart: USB_SERIAL_JTAG + level: DEBUG psram: mode: hex @@ -53,8 +58,8 @@ ota: platform: esphome wifi: - ssid: !secret wifi_ssid - password: !secret wifi_password + ssid: !secret mobile_wifi_ssid + password: !secret mobile_wifi_pass fast_connect: true on_connect: - lvgl.label.update: @@ -63,15 +68,50 @@ wifi: - select.set: id: dac_output option: "LINE1" + - lvgl.label.update: + id: lbl_ip + text: !lambda return id(ip_addr).state; + - lvgl.label.update: + id: lbl_ap + text: !lambda return id(ssid).state; on_disconnect: - lvgl.label.update: id: lbl_status text: "DISCONNECTED" -# ap: -# password: !secret hotspot_password -# ap_timeout: 90s + ap: + password: !secret hotspot_password + ap_timeout: 30s -# captive_portal: +captive_portal: + +text_sensor: + - platform: wifi_info + ip_address: + id: ip_addr + name: Device IP Address + address_0: + name: Device IP Address 0 + address_1: + name: Device IP Address 1 + address_2: + name: Device IP Address 2 + address_3: + name: Device IP Address 3 + address_4: + name: Device IP Address 4 + ssid: + id: ssid + name: Device Connected SSID + bssid: + name: Device Connected BSSID + mac_address: + name: Device Mac Wifi Address + scan_results: + name: Device Latest Scan Results + dns_address: + name: Device DNS Address + power_save_mode: + name: Device Wifi Power Save Mode time: - platform: sntp @@ -82,14 +122,13 @@ time: - 1.pool.ntp.org - 2.pool.ntp.org -# wireguard: -# address: !secret tab1_wg_ip -# private_key: !secret tab1_wg_pk -# peer_endpoint: !secret wg_host -# peer_public_key: !secret wg_pubkey - -# # Optional keepalive (disabled by default) -# peer_persistent_keepalive: 25s +wireguard: + address: !secret tab1_wg_ip + private_key: !secret tab1_wg_pk + peer_endpoint: !secret wg_host + peer_public_key: !secret wg_pubkey + netmask: 0.0.0.0 + peer_persistent_keepalive: 25s i2c: - id: bsp_bus @@ -196,6 +235,39 @@ binary_sensor: pin: pi4ioe5v6408: pi4ioe1 number: 7 + - platform: lvgl + widget: volume_up_widget + name: Volume Up Button + on_press: + then: + - logger.log: "Button pressed" + - media_player.volume_up: + id: tab5_media_player + - delay: 100ms + - lvgl.label.update: + id: lbl_volume + text: !lambda return to_string(int(id(tab5_media_player).volume * 100)); + - light.turn_on: + id: backlight + brightness: !lambda |- + float current_value = id(backlight).current_values.get_brightness(); + return current_value < 0.6 ? 0.6 : current_value + 0.2; + - platform: lvgl + widget: volume_down_widget + name: Volume Down Button + on_press: + then: + - logger.log: "Button pressed" + - media_player.volume_down: + id: tab5_media_player + - lvgl.label.update: + id: lbl_volume + text: !lambda return to_string(int(id(tab5_media_player).volume * 100)); + - light.turn_on: + id: backlight + brightness: !lambda |- + float current_value = id(backlight).current_values.get_brightness(); + return current_value < 0.6 ? 0.6 : current_value - 0.2; sensor: - platform: ina226 @@ -255,8 +327,8 @@ touchscreen: - logger.log: "LVGL resuming" - lvgl.resume: - light.turn_on: backlight - on_release: - - media_player.stop: + # on_release: + # - media_player.stop: esp_ldo: - voltage: 2.5V @@ -287,7 +359,7 @@ light: restore_mode: ALWAYS_ON default_transition_length: 250ms initial_state: - brightness: "100%" + brightness: "50%" image: defaults: @@ -318,20 +390,41 @@ lvgl: transition_length: 15s - lvgl.pause: widgets: + - obj: + align: TOP_MID + width: 100% + height: 100% + layout: + type: flex + flex_flow: column + flex_align_main: START + flex_align_track: center + flex_align_cross: center + widgets: + - label: + align: TOP_MID + id: lbl_status + text_font: montserrat_48 + text: "CONNECTING..." + - label: + align: TOP_MID + id: lbl_ap + text_font: montserrat_22 + text: "CONNECTING..." + - label: + align: TOP_MID + id: lbl_ip + text_font: montserrat_22 + text: "CONNECTING..." - image: id: listen_icon_widget src: va_idle align: CENTER - - label: - align: TOP_MID - id: lbl_status - text_font: montserrat_48 - text: "CONNECTING..." - label: align: BOTTOM_LEFT id: lbl_version text_font: montserrat_12 - text: "v0.5" + text: "v0.6" - label: align: BOTTOM_RIGHT id: lbl_battery @@ -341,21 +434,38 @@ lvgl: id: charging_icon_widget src: charging_icon align: TOP_RIGHT - - slider: - id: backlight_slider + - button: + id: volume_up_widget + widgets: + - label: + text: "\uF028" + text_font: montserrat_48 + text_align: CENTER + align: CENTER x: 20 - y: 50 - width: 30 - height: 220 + y: 20 + width: 100 + height: 100 pad_all: 8 - min_value: 0 - max_value: 255 - on_release: - - homeassistant.action: - action: light.turn_on - data: - entity_id: light.backlight - brightness: !lambda return int(x); + - button: + id: volume_down_widget + widgets: + - label: + text: "\uF027" + text_font: montserrat_48 + text_align: CENTER + align: CENTER + x: 20 + y: 140 + width: 100 + height: 100 + pad_all: 8 + - label: + x: 20 + y: 260 + id: lbl_volume + text_font: montserrat_28 + text: !lambda return "%.1f",to_string(id(tab5_media_player).volume); # The DAC Output select needs to be manually (or with an automation) changed to `LINE1` for the onboard speaker select: @@ -414,7 +524,7 @@ speaker: audio_dac: es8388_dac dac_type: external channel: mono - buffer_duration: 100ms + buffer_duration: 60000ms bits_per_sample: 16bit sample_rate: 48000 @@ -429,9 +539,12 @@ media_player: micro_wake_word: id: mww models: - - okay_nabu - - hey_mycroft - - hey_jarvis + - model: + type: local + path: alexa.json + id: alexa + probability_cutoff: 0.97 + sliding_window_size: 5 on_wake_word_detected: - voice_assistant.start: wake_word: !lambda return wake_word; diff --git a/active/device_esphome/esphome.md b/active/device_esphome/esphome.md index 8fc33ce..b517081 100644 --- a/active/device_esphome/esphome.md +++ b/active/device_esphome/esphome.md @@ -56,6 +56,13 @@ Make sure your permissions are set correctly sudo usermod -a -G dialout ducoterra ``` +or on Kinoite + +```bash +grep -E '^dialout:' /usr/lib/group | sudo tee -a /etc/group +sudo usermod -aG dialout $USER +``` + Then "run" your config file ```bash diff --git a/active/device_esphome/hey_dick_head.json b/active/device_esphome/hey_dick_head.json new file mode 100644 index 0000000..dacb964 --- /dev/null +++ b/active/device_esphome/hey_dick_head.json @@ -0,0 +1,16 @@ +{ + "type": "micro", + "wake_word": "hey dick head", + "author": "peenfart", + "website": "https://www.kevinahrendt.com/", + "model": "./hey_dick_head.tflite", + "trained_languages": ["en"], + "version": 2, + "micro": { + "probability_cutoff": 0.97, + "sliding_window_size": 5, + "feature_step_size": 10, + "tensor_arena_size": 22860, + "minimum_esphome_version": "2024.7" + } +} diff --git a/active/device_esphome/hey_dick_head.tflite b/active/device_esphome/hey_dick_head.tflite new file mode 100644 index 0000000..5d81944 Binary files /dev/null and b/active/device_esphome/hey_dick_head.tflite differ diff --git a/active/device_esphome/pelqhWNwESkNYMimKY55Dw.wav b/active/device_esphome/pelqhWNwESkNYMimKY55Dw.wav new file mode 100644 index 0000000..95f6dd0 Binary files /dev/null and b/active/device_esphome/pelqhWNwESkNYMimKY55Dw.wav differ diff --git a/active/device_esphome/w-r2uv3WyGSbJfJy9NdTvg.wav b/active/device_esphome/w-r2uv3WyGSbJfJy9NdTvg.wav new file mode 100644 index 0000000..6ff898b Binary files /dev/null and b/active/device_esphome/w-r2uv3WyGSbJfJy9NdTvg.wav differ diff --git a/active/device_home_assistant/config b/active/device_home_assistant/config new file mode 160000 index 0000000..5a3ce1d --- /dev/null +++ b/active/device_home_assistant/config @@ -0,0 +1 @@ +Subproject commit 5a3ce1da521092c2691f06b5893bb05a66c7805e diff --git a/active/device_reesework/reesework.md b/active/device_reesework/reesework.md new file mode 100644 index 0000000..f3a7d0e --- /dev/null +++ b/active/device_reesework/reesework.md @@ -0,0 +1,48 @@ +# Reese's Workstation + +- [Reese's Workstation](#reeses-workstation) + - [Virtual Machines](#virtual-machines) + +## Virtual Machines + +1. Blueprints are stored in active/software_osbuild +2. Builds are stored in ${HOME}/Images/builds + +```bash +# These are saved in ~/.bashrc.d/osbuild.sh +export OSBUILD_BLUEPRINT_DIR=active/software_osbuild +export OSBUILD_CLOUD_DIR=${HOME}/Images/cloud +export OSBUILD_BUILD_DIR=${HOME}/Images/builds +export OSBUILD_IMAGE_DIR=${HOME}/Images/disks + +# Push a blueprint +composer-cli blueprints push ${OSBUILD_BLUEPRINT_DIR}/fedora43-base.toml + +# Start a build +composer-cli compose start fedora43-base qcow2 + +# Watch the build +watch composer-cli compose list running + +# Extract the build +composer-cli compose image \ +--filename ${OSBUILD_BUILD_DIR}/fedora-43-base.qcow2 \ +92a2f1dd-9238-4d50-ab1d-cd7c8e355cac + +# Convert the build to raw +qemu-img convert -f qcow2 -O raw \ +${OSBUILD_BUILD_DIR}/fedora-43-base.qcow2 \ +${OSBUILD_IMAGE_DIR}/fedora-boot.raw + +# Install (Change password for default user ducoterra!) +virt-install \ +--boot uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no \ +--cpu host-passthrough --vcpus sockets=1,cores=8,threads=2 \ +--ram=8192 \ +--os-variant=fedora43 \ +--network default \ +--graphics none \ +--console pty,target.type=virtio \ +--name "fedora" \ +--import --disk "path=${OSBUILD_IMAGE_DIR}/fedora-boot.raw,bus=virtio" +``` diff --git a/active/os_fedora/fedora-kde.md b/active/os_fedora/fedora-kde.md new file mode 100644 index 0000000..bac40a0 --- /dev/null +++ b/active/os_fedora/fedora-kde.md @@ -0,0 +1,89 @@ +# KDE Notes + +## Fixing alt tab or taskbar icons + +1. System Settings - Window Management - Window Rules - Add New... +2. Bring the buggy app window side-by-side with system settings. +3. Click "Detect Window Properties..." in the bottom. +4. Move the crosshair to the buggy window and click. +5. Click "Window class (application)". +6. Click "Add Property..." in the bottm left. +7. Click "Desktop file name". +8. Enter the base name of the desktop file + + For flatpak, check `/var/lib/flatpak/exports/share/applications/` + + For system apps, check `/usr/share/applications/` + + For user apps, check `~/.local/share/applications/` + +9. Click "Apply". +10. The icon should be fixed immediately. + +## KDE Connect + +Allow KDE Connect via 1714-1764 tcp/udp + +```bash +# Set source address to allow connections +sudo firewall-cmd \ +--zone=drop \ +--permanent \ +--add-port=1714-1764/udp \ +--add-port=1714-1764/tcp + +sudo firewall-cmd --reload +``` + +## Automatic Display Switching + +```bash +# List displays +# Builtin: eDP-2 +# Roku: DP-13 +# Lenovo: DP-11 +kscreen-doctor -o + +# Put the builtin display back to normal +kscreen-doctor \ +output.eDP-2.enable \ +output.eDP-2.position.0,0 \ +output.eDP-2.primary \ +output.eDP-2.mode.2560x1600@165 \ +output.eDP-2.scale.1.25 + +# Show on Roku TV and Monitor +kscreen-doctor \ +output.DP-11.enable \ +output.DP-11.position.0,0 \ +output.DP-11.mode.2560x1440@60 \ +output.DP-11.scale.1 \ +output.DP-13.enable \ +output.DP-13.position.0,0 \ +output.DP-13.mode.2560x1440@100 \ +output.DP-13.scale.1 \ +output.DP-13.primary \ +output.eDP-2.disable + +# Show only on the roku TV +kscreen-doctor \ +output.DP-11.enable \ +output.DP-11.position.0,0 \ +output.DP-11.primary \ +output.DP-11.mode.3840x2160@60 \ +output.DP-11.scale.2 \ +output.DP-13.disable \ +output.eDP-2.disable + +# Mirror the builtin display to the roku tv +kscreen-doctor \ +output.DP-11.enable \ +output.DP-11.position.0,0 \ +output.DP-11.primary \ +output.DP-11.mode.3840x2160@60 \ +output.DP-11.scale.2 \ +output.eDP-2.enable \ +output.eDP-2.mode.1920x1080@60 \ +output.eDP-2.scale.1 \ +output.eDP-2.position.0,0 +``` \ No newline at end of file diff --git a/active/os_fedora/fedora-kinoite.md b/active/os_fedora/fedora-kinoite.md index b88e156..bd04d00 100644 --- a/active/os_fedora/fedora-kinoite.md +++ b/active/os_fedora/fedora-kinoite.md @@ -1,114 +1,204 @@ # Fedora Kinoite - [Fedora Kinoite](#fedora-kinoite) - - [TPM2 Luks Decryption](#tpm2-luks-decryption) - - [Podman](#podman) - - [Docker Compose and Docker Buildkit with Rootless Podman](#docker-compose-and-docker-buildkit-with-rootless-podman) - - [rpm-ostree](#rpm-ostree) - - [Git, Vim, etc](#git-vim-etc) - - [Libvirt, Qemu, KVM](#libvirt-qemu-kvm) - - [ROCM](#rocm) - - [Ollama](#ollama) - - [Network](#network) - - [Hostname](#hostname) - - [VLAN Setup with nmcli](#vlan-setup-with-nmcli) - - [GPU Support in Distrobox](#gpu-support-in-distrobox) - - [Automatic Display Switching](#automatic-display-switching) + - [Adding users to groups (usermod replacement)](#adding-users-to-groups-usermod-replacement) + - [System Prereqs](#system-prereqs) + - [Toolbox](#toolbox) + - [Clipboard Sharing](#clipboard-sharing) + - [Running Host Commands](#running-host-commands) + - [Saving a Toolbox](#saving-a-toolbox) + - [Apps](#apps) + - [Gear Lever](#gear-lever) + - [VSCodium](#vscodium) + - [Ansible](#ansible) + - [OpenCode](#opencode) + - [Nextcloud](#nextcloud) + - [UV](#uv) + - [NVM](#nvm) + - [Set Hostname](#set-hostname) + - [rpm-ostree Notes](#rpm-ostree-notes) + - [Accent keys when holding a key](#accent-keys-when-holding-a-key) -## TPM2 Luks Decryption - -Mostly taken from here: - - -PCR reference for `--tpm2-pcrs` args - -```text -0: System firmware executable -2: Kernel -4: Bootloader -7: Secure boot state -8: Cmdline -9: Initrd -``` - -Basic commands: +## Adding users to groups (usermod replacement) ```bash -# Show tpm2 devices -systemd-cryptenroll --tpm2-device=list -# Show crypto luks block devices -blkid -t TYPE=crypto_LUKS - -# Enroll the tpm2 device with systemd-cryptenroll -systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0,2,4,7,8,9 /dev/nvme0n1p3 - -# Reenroll -systemd-cryptenroll /dev/nvme0n1p3 --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0,2,4,7,8,9 - -# Append to command line args -rpm-ostree kargs --append=rd.luks.options=tpm2-device=auto +grep -E '^weldr:' /usr/lib/group | sudo tee -a /etc/group +sudo usermod -aG weldr $USER ``` -When you update you'll need to reenroll. Add this to your ~/.bashrc +## System Prereqs ```bash -# LUKS TPM2 commands -alias tpm2-reenroll='sudo systemd-cryptenroll /dev/nvme0n1p3 --wipe-slot=tpm2 --tpm2-device=auto --tpm2-pcrs=0,2,4,7,8,9' +# Appimage support +rpm-ostree install fuse fuse-libs + +# Editor +rpm-ostree install vim + +# Qemu/KVM +rpm-ostree install virt-manager libvirt virt-install + +# OSBuild +rpm-ostree install -y osbuild-composer composer-cli openscap-scanner scap-security-guide + +# Distrobox +rpm-ostree install -y distrobox + +# BTRFS Snapshots +rpm-ostree install -y snapper + +# Kitty Terminal +# terminfo prevents the "WARNING: terminal is not fully functional" error +rpm-ostree install -y kitty kitty-terminfo ``` -## Podman +## Toolbox -Since you'll be using podman for most container-based services, you'll want to set the -the podman auth file to somewhere persistent, otherwise it'll get deleted every time you -reboot. +### Clipboard Sharing -Add this to your `.bashrc`: +Inside the toolbox run: ```bash -# Podman auth file -export REGISTRY_AUTH_FILE=$HOME/.podman-auth.json +sudo dnf install -y wl-clipboard ``` -Source that and then run `podman login` to create the file. +### Running Host Commands -### Docker Compose and Docker Buildkit with Rootless Podman +You'll want to use toolbox for development. Unfortunately, toolbox doesn't +integrate well with host system operations. -Allows you to use podman with full docker-compose compatibility. - - +I create a `toolbox.sh` in `~/.bashrc.d` with functions that map to host +commands like so: ```bash -rpm-ostree install docker-compose docker-buildx -reboot -systemctl --user enable --now podman.socket -docker context create podman --docker host=unix://$XDG_RUNTIME_DIR/podman/podman.sock -docker context use podman +# Use flatpak-spawn --host podman inside toolbox, regular podman outside +podman() { + if [ -f /run/.containerenv ]; then + flatpak-spawn --host podman "$@" + else + command podman "$@" + fi +} -### Autostarting services with quadlets - -If you want to run something as your user at boot (like a systemd process, think ollama) you can -create a user quadlets like so: - -```bash -# Generate the .container file -podman run --rm ghcr.io/containers/podlet --install --description "Local AI" \ - podman run \ - -d \ - -v ollama:/root/.ollama \ - -p 11434:11434 \ - --name ollama \ - --restart always \ - docker.io/ollama/ollama > ~/.config/containers/systemd/ollama.container - -# Verify the service (Note the filename:service, this is required! You will get "Failed to prepare filename" without it) -systemd-analyze verify ~/.config/containers/systemd/ollama.container:ollama.service - -# Start the service -systemctl --user daemon-reload -systemctl --user start ollama +# Notify the user if they're trying to run a command that doesn't work in a toolbox +rpm-ostree() { + if [ -f /run/.containerenv ]; then + echo "Command must be run outside toolbox." + else + command rpm-ostree "$@" + fi +} ``` -## rpm-ostree +I have the following functions mapped: + +1. podman +2. rpm-ostree +3. flatpak +4. firewall-cmd + +### Saving a Toolbox + +After setting up a base toolbox you'll want to save it for future use. This is easy with podman commit. + +```bash +# 1. Commit the running container to a new image +podman commit fedora-toolbox-44 my-toolbox-snapshot + +# 2. Save the image to a tar file for later use +podman save -o ~/my-toolbox-snapshot.tar my-toolbox-snapshot + +# Load the image +podman load -i ~/my-toolbox-snapshot.tar + +# Create a new toolbox from it +toolbox create --image my-toolbox-snapshot +``` + +## Apps + +Anything not mentioned here is installed via flatpak from flathub. + +### Gear Lever + +```bash +flatpak install it.mijorus.gearlever +``` + +### VSCodium + +Install in toolbox. + + + +```bash +sudo tee -a /etc/yum.repos.d/vscodium.repo << 'EOF' +[gitlab.com_paulcarroty_vscodium_repo] +name=gitlab.com_paulcarroty_vscodium_repo +baseurl=https://paulcarroty.gitlab.io/vscodium-deb-rpm-repo/rpms/ +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg +metadata_expire=1h +EOF + +sudo dnf install codium +``` + +### Ansible + +```bash +# Install pipx +python3 -m ensurepip --user +python3 -m pip install --user pipx +pipx ensurepath + +# Install ansible +pipx install --include-deps ansible +``` + +### OpenCode + +```bash +curl -fsSL https://opencode.ai/install | bash + +# Install pyright LSP +python -m pip install --upgrade pip +python -m pip install --user pyright +``` + +### Nextcloud + +Download the AppImage. Install via Gear Lever. + + + +| Source | Repo | Release file name | +| ------ | -------------------------- | ----------------- | +| Github | nextcloud-releases/desktop | *x86_64.AppImage | + +### UV + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +### NVM + + + +```bash +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash +``` + +## Set Hostname + +```bash +sudo hostnamectl hostname reesework16 +``` + +## rpm-ostree Notes Note: if you don't need them, remove extra repos @@ -133,169 +223,6 @@ rpm-ostree apply-live rpm-ostree status ``` -### Git, Vim, etc +## Accent keys when holding a key -Some packages are nice to have at the system level. - -```bash -rpm-ostree install git vim -``` - -### Libvirt, Qemu, KVM - -```bash -rpm-ostree install virt-manager libvirt -systemctl enable --now libvirtd -``` - -### ROCM - -Credit to: - - -Here are most of the ROCM packages that you'll need. - -Fedora is currently working on a ROCM meta package here: - - -```bash -rpm-ostree install \ -hipblas-devel \ -hipblaslt-devel \ -hipcc \ -hipcc-libomp-devel \ -hipcub-devel \ -hipfft-devel \ -hipfort-devel \ -hiprand-devel \ -hiprt-devel \ -hipsolver-devel \ -hipsparse-devel \ -rocalution-devel \ -rocblas-devel \ -rocfft-devel \ -rocm-clang-devel \ -rocm-clang-tools-extra-devel \ -rocm-cmake \ -rocm-comgr-devel \ -rocm-core-devel \ -rocm-hip-devel \ -rocm-libc++-devel \ -rocm-libc++-static \ -rocm-llvm-devel \ -rocm-omp-devel \ -rocm-runtime-devel \ -rocm-rpp-devel \ -rocm-smi-devel \ -rocminfo \ -rocdecode-devel \ -rocjpeg-devel \ -rocprim-devel \ -rocrand-devel \ -rocsolver-devel \ -rocsparse-devel \ -rocthrust-devel \ -roctracer-devel \ -miopen -``` - -### Ollama - - - -Run the installation script as normal. Make sure you have the [ROCM](#rocm) drivers installed -for GPU acceleration. The script *should* automatically pull the ROCM drivers after installing -the base packages. If not, you should install them manually. - -For starting ollama as a service, follow the link below with one exception. - - - -```bash -# Change the recommended home dir to /var/home/ollama -sudo useradd -r -s /bin/false -U -m -d /var/home/ollama ollama -``` - -## Network - -### Hostname - -```bash -sudo hostnamectl hostname reesework16 -``` - -### VLAN Setup with nmcli - -```bash -# VLAN 2 -nmcli conn -export NMCLI_DEVICE=enp195s0f4u1u3 -nmcli connection add type VLAN con-name $NMCLI_DEVICE.2 dev $NMCLI_DEVICE id 2 -``` - -## GPU Support in Distrobox - -Fix for `error="failed to check permission on /dev/kfd: open /dev/kfd: invalid argument"` - -```bash -# You have to create the video and render group to /etc/group before you can use it -sudo grep -E '^video:' /usr/lib/group | sudo tee -a /etc/group -sudo grep -E '^render:' /usr/lib/group | sudo tee -a /etc/group -sudo usermod -aG video $USER -sudo usermod -aG render $USER -``` - -Logout and log back in to adopt new groups. - -## Automatic Display Switching - -```bash -# List displays -# Builtin: eDP-2 -# Roku: DP-13 -# Lenovo: DP-11 -kscreen-doctor -o - -# Put the builtin display back to normal -kscreen-doctor \ -output.eDP-2.enable \ -output.eDP-2.position.0,0 \ -output.eDP-2.primary \ -output.eDP-2.mode.2560x1600@165 \ -output.eDP-2.scale.1.25 - -# Show on Roku TV and Monitor -kscreen-doctor \ -output.DP-11.enable \ -output.DP-11.position.0,0 \ -output.DP-11.mode.2560x1440@60 \ -output.DP-11.scale.1 \ -output.DP-13.enable \ -output.DP-13.position.0,0 \ -output.DP-13.mode.2560x1440@100 \ -output.DP-13.scale.1 \ -output.DP-13.primary \ -output.eDP-2.disable - -# Show only on the roku TV -kscreen-doctor \ -output.DP-11.enable \ -output.DP-11.position.0,0 \ -output.DP-11.primary \ -output.DP-11.mode.3840x2160@60 \ -output.DP-11.scale.2 \ -output.DP-13.disable \ -output.eDP-2.disable - -# Mirror the builtin display to the roku tv -kscreen-doctor \ -output.DP-11.enable \ -output.DP-11.position.0,0 \ -output.DP-11.primary \ -output.DP-11.mode.3840x2160@60 \ -output.DP-11.scale.2 \ -output.eDP-2.enable \ -output.eDP-2.mode.1920x1080@60 \ -output.eDP-2.scale.1 \ -output.eDP-2.position.0,0 -``` +Settings -> keyboard -> virtual keyboard -> disable virtual keyboard \ No newline at end of file diff --git a/active/os_fedora/fedora-server.md b/active/os_fedora/fedora-server.md index 565360d..5f843e4 100644 --- a/active/os_fedora/fedora-server.md +++ b/active/os_fedora/fedora-server.md @@ -4,6 +4,7 @@ - [Installation](#installation) - [Setup SSH](#setup-ssh) - [DNF](#dnf) + - [Version lock](#version-lock) - [Power Profiles with Tuned](#power-profiles-with-tuned) - [Fail2Ban](#fail2ban) - [BTRFS Parent Volumes](#btrfs-parent-volumes) @@ -84,6 +85,7 @@ - [Sound Devices](#sound-devices) - [Install ffmpegthumbnailer, remove totem](#install-ffmpegthumbnailer-remove-totem) - [Add compatibility for HEIC to mogrify](#add-compatibility-for-heic-to-mogrify) + - [Redirecting ports for rootless services](#redirecting-ports-for-rootless-services) @@ -127,6 +129,16 @@ dnf update --refresh -y dnf install -y git glances tmux vim python3-libdnf5 borgbackup tpm2-tools ``` +### Version lock + +```bash +sudo dnf install dnf-plugin-versionlock +sudo dnf list --showduplicates +sudo dnf versionlock add -.x86_64 +sudo dnf versionlock list +sudo dnf versionlock delete +``` + ## Power Profiles with Tuned 1. `dnf install tuned` @@ -1486,3 +1498,29 @@ sudo dnf install ffmpegthumbnailer ```bash sudo dnf install libheic-freeworld ``` + +## Redirecting ports for rootless services + +```bash +# ipv4 +sudo firewall-cmd \ +--permanent \ +--add-forward-port=port=80:proto=tcp:toport=8080 + +sudo firewall-cmd --add-rich-rule='forward-port port=80 protocol=tcp to-port=8080 +``` + +Or in ansible + +```yaml +- name: Forward ports + ansible.posix.firewalld: + port_forward: + - port: "{{ item.from }}" + proto: tcp + toport: "{{ item.to }}" + permanent: true + state: enabled + immediate: true + loop: "{{ forward_ports }}" +``` \ No newline at end of file diff --git a/active/software_ai_stack/ai-internal.network b/active/software_ai_stack/ai-internal.network deleted file mode 100644 index d386500..0000000 --- a/active/software_ai_stack/ai-internal.network +++ /dev/null @@ -1,2 +0,0 @@ -[Network] -Internal=true \ No newline at end of file diff --git a/active/software_ai_stack/ai-internal.pod b/active/software_ai_stack/ai-internal.pod deleted file mode 100644 index afc4492..0000000 --- a/active/software_ai_stack/ai-internal.pod +++ /dev/null @@ -1,14 +0,0 @@ -[Pod] -Network=ai-internal.network -# llama.cpp server -PublishPort=8000:8000/tcp -# llama.cpp embed -PublishPort=8001:8001/tcp -# llama.cpp instruct -PublishPort=8002:8002/tcp -# llama.cpp tool -PublishPort=8003:8003/tcp -# stable-diffusion.cpp gen -PublishPort=1234:1234/tcp -# stable-diffusion.cpp edit -PublishPort=1235:1235/tcp \ No newline at end of file diff --git a/active/software_ai_stack/ai_stack.md b/active/software_ai_stack/ai_stack.md index 9ecb659..879722f 100644 --- a/active/software_ai_stack/ai_stack.md +++ b/active/software_ai_stack/ai_stack.md @@ -3,10 +3,6 @@ - [Self Hosted AI Stack](#self-hosted-ai-stack) - [Notes](#notes) - [Podman Volume Locations](#podman-volume-locations) - - [List of Internal Links](#list-of-internal-links) - - [Quick Install](#quick-install) - - [Text Stack](#text-stack) - - [Image Stack](#image-stack) - [Setup](#setup) - [Create the AI user](#create-the-ai-user) - [Helper aliases](#helper-aliases) @@ -36,16 +32,7 @@ - [open-webui](#open-webui) - [Upgrade Open Webui](#upgrade-open-webui) - [lite-llm](#lite-llm) - - [Install Services with Quadlets](#install-services-with-quadlets) - - [API Keys](#api-keys) - - [Internal and External Pods](#internal-and-external-pods) - - [Llama CPP Server (Port 8000)](#llama-cpp-server-port-8000) - - [Llama CPP Embedding Server (Port 8001)](#llama-cpp-embedding-server-port-8001) - - [Llama CPP Instruct Server (Port 8002)](#llama-cpp-instruct-server-port-8002) - - [Stable Diffusion CPP (Port 1234 and 1235)](#stable-diffusion-cpp-port-1234-and-1235) - - [Open Webui (Port 8080)](#open-webui-port-8080) - - [Install the update script](#install-the-update-script) - - [Install Guest Open Webui with Start/Stop Services](#install-guest-open-webui-with-startstop-services) + - [Install the update script](#install-the-update-script) - [Benchmark Results](#benchmark-results) - [Testing with Curl](#testing-with-curl) - [OpenAI API](#openai-api) @@ -77,33 +64,6 @@ hf-download () `~/.local/share/containers/storage/volumes/` -### List of Internal Links - -- llama-cpp -- llama-embed -- llama-instruct -- image-gen -- image-edit -- openwebui - -## Quick Install - -### Text Stack - -```bash -ansible-playbook \ --i ansible/inventory.yaml \ -active/software_ai_stack/install_ai_text_stack.yaml -``` - -### Image Stack - -```bash -ansible-playbook \ --i ansible/inventory.yaml \ -active/software_ai_stack/install_ai_image_stack.yaml -``` - ## Setup ### Create the AI user @@ -186,8 +146,7 @@ rsync -av --progress /home/ai/models/ /srv/models/ In my completely subjective opinion: 5 bit quant is usually the sweet spot for unsloth models. Q5_K_S is usually just fine. -I usually download the F16 mmproj files. This is also completely subjective. -BF16 is fine. F32 is overkill. +I usually download the BF16 mmproj files. This is also completely subjective. #### Text models @@ -391,20 +350,43 @@ podman run \ --name llama-server-demo \ --device=/dev/kfd \ --device=/dev/dri \ --v /home/ai/models/text:/models:z \ +-v $(pwd):/models:z \ -p 8010:8000 \ --ipc host \ localhost/llama-cpp-vulkan:latest \ --host 0.0.0.0 \ --port 8000 \ --c 128000 \ --perf \ --n-gpu-layers all \ --jinja \ ---models-max 1 \ ---models-dir /models \ ---chat-template-kwargs '{"enable_thinking": false}' \ --m /models/qwen3.5-35b-a3b +--model /models/gemma-4-E4B-it-qat-UD-Q4_K_XL.gguf \ +--mmproj /models/mmproj-F16.gguf \ +--temp 1.0 \ +--top-p 0.95 \ +--top-k 64 \ +--chat-template-kwargs '{"enable_thinking":true}' + +podman run \ +--rm \ +--name llama-server-demo \ +--device=/dev/kfd \ +--device=/dev/dri \ +-v $(pwd):/models:z \ +-p 8010:8000 \ +--ipc host \ +localhost/llama-cpp-vulkan:latest \ +--host 0.0.0.0 \ +--port 8000 \ +--perf \ +--n-gpu-layers all \ +--jinja \ +--model /models/Qwen3.5-9B-UD-Q4_K_XL.gguf \ +--mmproj /models/mmproj-BF16.gguf \ +--temp 0.6 \ +--top-p 0.95 \ +--top-k 20 \ +--min-p 0.00 \ +--chat-template-kwargs '{"enable_thinking":true}' ``` Embedding models @@ -595,96 +577,7 @@ podman run \ -p 4000:4000 ``` -## Install Services with Quadlets - -### API Keys - -```bash -mkdir -p /home/ai/.llama-api -touch /home/ai/.llama-api/keys.env -chmod 600 /home/ai/.llama-api/keys.env -vim /home/ai/.llama-api/keys.env - -LLAMA_API_KEY= - -# Generate keys and append to file, then comma separate the keys -openssl rand -base64 48 >> keys.env -openssl rand -base64 48 >> keys.env -openssl rand -base64 48 >> keys.env -``` - -### Internal and External Pods - -These will be used to restrict internet access to our llama.cpp and -stable-diffusion.cpp services while allowing the frontend services to -communicate with those containers. - -```bash -scp -r active/software_ai_stack/ai-internal.* deskwork-ai:.config/containers/systemd/ -ssh deskwork-ai -systemctl --user daemon-reload -systemctl --user start ai-internal-pod.service -``` - -### Llama CPP Server (Port 8000) - -Installs the llama.cpp server to run our text models. - -```bash -scp -r active/software_ai_stack/llama-think.container deskwork-ai:.config/containers/systemd/ -ssh deskwork-ai -systemctl --user daemon-reload -systemctl --user restart ai-internal-pod.service -``` - -### Llama CPP Embedding Server (Port 8001) - -Installs the llama.cpp server to run our embedding models - -```bash -scp -r active/software_ai_stack/llama-embed.container deskwork-ai:.config/containers/systemd/ -ssh deskwork-ai -systemctl --user daemon-reload -systemctl --user restart ai-internal-pod.service -``` - -### Llama CPP Instruct Server (Port 8002) - -Installs the llama.cpp server to run a constant instruct (no thinking) model for quick replies - -```bash -scp -r active/software_ai_stack/llama-instruct.container deskwork-ai:.config/containers/systemd/ -ssh deskwork-ai -systemctl --user daemon-reload -systemctl --user restart ai-internal-pod.service -``` - -### Stable Diffusion CPP (Port 1234 and 1235) - -Installs the stable-diffusion.cpp server to run our image models. - -```bash -scp -r active/software_ai_stack/quadlets_stable_diffusion/* deskwork-ai:.config/containers/systemd/ -ssh deskwork-ai -systemctl --user daemon-reload -systemctl --user restart ai-internal-pod.service -``` - -### Open Webui (Port 8080) - -Installs the open webui frontend. - -```bash -scp -r active/software_ai_stack/quadlets_openwebui/* deskwork-ai:.config/containers/systemd/ -ssh deskwork-ai -systemctl --user daemon-reload -systemctl --user restart ai-external-pod.service -``` - -Note, all services will be available at `host.containers.internal`. So llama.cpp -will be up at `http://host.containers.internal:8000`. - -### Install the update script +## Install the update script ```bash # 1. Builds the latest llama.cpp and stable-diffusion.cpp @@ -696,18 +589,6 @@ chmod +x update-script.sh ./update-script.sh ``` -### Install Guest Open Webui with Start/Stop Services - -Optionally install a guest openwebui service. - -```bash -scp -r active/software_ai_stack/systemd/. deskwork-ai:.config/systemd/user/ -ssh deskwork-ai -systemctl --user daemon-reload -systemctl --user enable open-webui-guest-start.timer -systemctl --user enable open-webui-guest-stop.timer -``` - ## Benchmark Results Benchmarks are run with [unsloth gpt-oss-20b Q8_0](https://huggingface.co/unsloth/gpt-oss-20b-GGUF/blob/main/gpt-oss-20b-Q8_0.gguf) diff --git a/active/software_ai_stack/install_ai_image_stack.yaml b/active/software_ai_stack/install_ai_image_stack.yaml deleted file mode 100644 index 9e164b7..0000000 --- a/active/software_ai_stack/install_ai_image_stack.yaml +++ /dev/null @@ -1,23 +0,0 @@ -- name: Create Deskwork AI Stack - hosts: deskwork-ai - tasks: - - name: Create /home/ai/.config/containers/systemd - ansible.builtin.file: - path: /home/ai/.config/containers/systemd - state: directory - mode: "0755" - - name: Copy Quadlets - template: - src: "{{ item }}" - dest: "/home/ai/.config/containers/systemd/{{ item }}" - loop: - - ai-internal.network - - ai-internal.pod - - stable-diffusion-gen-server.container - - stable-diffusion-edit-server.container - - name: Reload and start the ai-internal-pod service - ansible.builtin.systemd_service: - state: restarted - name: ai-internal-pod.service - daemon_reload: true - scope: user diff --git a/active/software_ai_stack/install_ai_instruct_stack.yaml b/active/software_ai_stack/install_ai_instruct_stack.yaml deleted file mode 100644 index 56d275c..0000000 --- a/active/software_ai_stack/install_ai_instruct_stack.yaml +++ /dev/null @@ -1,24 +0,0 @@ -- name: Create Deskwork AI Stack - hosts: deskwork-ai - tasks: - - name: Create /home/ai/.config/containers/systemd - ansible.builtin.file: - path: /home/ai/.config/containers/systemd - state: directory - mode: "0755" - - name: Copy Quadlets - template: - src: "{{ item }}" - dest: "/home/ai/.config/containers/systemd/{{ item }}" - loop: - - ai-internal.network - - ai-internal.pod - - llama-embed.container - - llama-instruct.container - - llama-tool.container - - name: Reload and start the ai-internal-pod service - ansible.builtin.systemd_service: - state: restarted - name: ai-internal-pod.service - daemon_reload: true - scope: user diff --git a/active/software_ai_stack/install_ai_turbo_stack.yaml b/active/software_ai_stack/install_ai_turbo_stack.yaml deleted file mode 100644 index 07b45c0..0000000 --- a/active/software_ai_stack/install_ai_turbo_stack.yaml +++ /dev/null @@ -1,22 +0,0 @@ -- name: Create Deskwork AI Stack - hosts: driveripper-ai - tasks: - - name: Create /home/ai/.config/containers/systemd - ansible.builtin.file: - path: /home/ai/.config/containers/systemd - state: directory - mode: "0755" - - name: Copy Quadlets - template: - src: "{{ item }}" - dest: "/home/ai/.config/containers/systemd/{{ item }}" - loop: - - ai-internal.network - - ai-internal.pod - - llama-turbo.container - - name: Reload and start the ai-internal-pod service - ansible.builtin.systemd_service: - state: restarted - name: ai-internal-pod.service - daemon_reload: true - scope: user diff --git a/active/software_ai_stack/llama-embed.container b/active/software_ai_stack/llama-embed.container deleted file mode 100644 index 91510de..0000000 --- a/active/software_ai_stack/llama-embed.container +++ /dev/null @@ -1,46 +0,0 @@ -[Unit] -Description=A Llama CPP Server For Embedding Models - -[Container] -# Shared AI internal pod without internet access -Pod=ai-internal.pod - -# Image is built locally via podman build -Image=localhost/llama-cpp-vulkan:latest - -# Downloaded models volume -Volume=/home/ai/models/embedding:/models:z - -# GPU Device -AddDevice=/dev/kfd -AddDevice=/dev/dri - -# Server command -Exec=--port 8001 \ - -c 0 \ - -b 1024 \ - -ub 1024 \ - --perf \ - --n-gpu-layers all \ - --embedding \ - -m /models/emebeddinggemma-300m/embeddinggemma-300M-BF16.gguf \ - --alias embed - -# Health Check -HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8001/health || exit 1 -HealthInterval=10s -HealthRetries=3 -HealthStartPeriod=10s -HealthTimeout=30s -HealthOnFailure=kill - -# EnvironmentFile=/home/ai/.llama-api/keys.env - -[Service] -Restart=always -# Extend Timeout to allow time to pull the image -TimeoutStartSec=900 - -[Install] -# Start by default on boot -WantedBy=multi-user.target default.target \ No newline at end of file diff --git a/active/software_ai_stack/llama-instruct.container b/active/software_ai_stack/llama-instruct.container deleted file mode 100644 index 6f063ef..0000000 --- a/active/software_ai_stack/llama-instruct.container +++ /dev/null @@ -1,54 +0,0 @@ -[Unit] -Description=A Llama CPP Server Running a Non-Reasoning Model - -[Container] -# Shared AI internal pod without internet access -Pod=ai-internal.pod - -# Image is built locally via podman build -Image=localhost/llama-cpp-vulkan:latest - -# Downloaded models volume -Volume=/home/ai/models/text:/models:z - -# GPU Device -AddDevice=/dev/kfd -AddDevice=/dev/dri - -# Server command -Exec=--port 8002 \ - -c 262144 \ - -n 32768 \ - --temp 0.7 \ - --top-p 0.8 \ - --top-k 20 \ - --presence-penalty 1.5 \ - --min-p 0.00 \ - --perf \ - --n-gpu-layers all \ - -fa on \ - --spec-type draft-mtp --spec-draft-n-max 2 \ - --jinja \ - --chat-template-kwargs '{"preserve_thinking": false, "enable_thinking": false}' \ - -m /models/qwen3.6-35b-a3b-mtp/Qwen3.6-35B-A3B-UD-Q5_K_M.gguf \ - --mmproj /models/qwen3.6-35b-a3b-mtp/mmproj-F16.gguf \ - --alias instruct - -# Health Check -HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8002/health || exit 1 -HealthInterval=10s -HealthRetries=3 -HealthStartPeriod=10s -HealthTimeout=30s -HealthOnFailure=kill - -# EnvironmentFile=/home/ai/.llama-api/keys.env - -[Service] -Restart=always -# Extend Timeout to allow time to pull the image -TimeoutStartSec=900 - -[Install] -# Start by default on boot -WantedBy=multi-user.target default.target \ No newline at end of file diff --git a/active/software_ai_stack/llama-tool.container b/active/software_ai_stack/llama-tool.container deleted file mode 100644 index 241da93..0000000 --- a/active/software_ai_stack/llama-tool.container +++ /dev/null @@ -1,57 +0,0 @@ -[Unit] -Description=A Llama CPP Server Running a Reasoning Model - -[Container] -# Shared AI internal pod without internet access -Pod=ai-internal.pod - -# Image is built locally via podman build -# latest-mtp is for mtp testing -# latest is main branch -Image=localhost/llama-cpp-vulkan:latest - -# Downloaded models volume -Volume=/home/ai/models/text:/models:z - -# GPU Device -AddDevice=/dev/kfd -AddDevice=/dev/dri - -# Server command -Exec=--port 8003 \ - -c 262144 \ - -n 32768 \ - --temp 0.6 \ - --top-p 0.95 \ - --top-k 20 \ - --min-p 0.0 \ - --presence-penalty 0.0 \ - --repeat-penalty 1.0 \ - --reasoning-budget 4096 \ - --perf \ - --n-gpu-layers all \ - -fa on \ - --spec-type draft-mtp --spec-draft-n-max 2 \ - --jinja \ - --chat-template-kwargs '{"preserve_thinking": false}' \ - --chat-template-file /models/qwen3.6-35b-a3b-mtp/chat_template.jinja \ - -m /models/qwen3.6-35b-a3b-mtp/Qwen3.6-35B-A3B-UD-Q5_K_M.gguf \ - --mmproj /models/qwen3.6-35b-a3b-mtp/mmproj-F16.gguf \ - --alias tool - -# Health Check -HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8003/health || exit 1 -HealthInterval=10s -HealthRetries=3 -HealthStartPeriod=10s -HealthTimeout=30s -HealthOnFailure=kill - -[Service] -Restart=always -# Extend Timeout to allow time to pull the image -TimeoutStartSec=900 - -[Install] -# Start by default on boot -WantedBy=multi-user.target default.target \ No newline at end of file diff --git a/active/software_ai_stack/llama-turbo.container b/active/software_ai_stack/llama-turbo.container deleted file mode 100644 index 496c042..0000000 --- a/active/software_ai_stack/llama-turbo.container +++ /dev/null @@ -1,57 +0,0 @@ -[Unit] -Description=A Llama CPP Server Running a Reasoning Model - -[Container] -# Shared AI internal pod without internet access -Pod=ai-internal.pod - -# Image is built locally via podman build -# latest-mtp is for mtp testing -# latest is main branch -Image=localhost/llama-cpp-vulkan:latest - -# Downloaded models volume -Volume=/home/ai/models/text:/models:z - -# GPU Device -AddDevice=/dev/kfd -AddDevice=/dev/dri - -# Server command -Exec=--port 8000 \ - -c 262144 \ - -n 32768 \ - --temp 0.6 \ - --top-p 0.95 \ - --top-k 20 \ - --min-p 0.0 \ - --presence-penalty 0.0 \ - --repeat-penalty 1.0 \ - --reasoning-budget 4096 \ - --perf \ - --n-gpu-layers all \ - -fa on \ - --spec-type draft-mtp --spec-draft-n-max 2 \ - --jinja \ - --chat-template-kwargs '{"preserve_thinking": true}' \ - --chat-template-file /models/qwen3.6-35b-a3b-mtp/chat_template.jinja \ - -m /models/qwen3.6-35b-a3b-mtp/Qwen3.6-35B-A3B-UD-Q5_K_M.gguf \ - --mmproj /models/qwen3.6-35b-a3b-mtp/mmproj-F16.gguf \ - --alias turbo - -# Health Check -HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8000/health || exit 1 -HealthInterval=10s -HealthRetries=3 -HealthStartPeriod=10s -HealthTimeout=30s -HealthOnFailure=kill - -[Service] -Restart=always -# Extend Timeout to allow time to pull the image -TimeoutStartSec=900 - -[Install] -# Start by default on boot -WantedBy=multi-user.target default.target \ No newline at end of file diff --git a/active/software_ai_stack/open-webui.container b/active/software_ai_stack/open-webui.container deleted file mode 100644 index 3153fc8..0000000 --- a/active/software_ai_stack/open-webui.container +++ /dev/null @@ -1,24 +0,0 @@ -[Unit] -Description=An Open Webui Frontend for Local AI Services - -[Container] -# Shared AI external pod -PublishPort=8080:8080 - -# Open Webui base image -Image=ghcr.io/open-webui/open-webui:main - -# Nothing too complicated here. Open Webui will basically configure itself. -Volume=open-webui-data:/app/backend/data - -# WEBUI_SECRET_KEY is required to prevent logout on Restart -EnvironmentFile=/home/ai/.env/open-webui-env - -[Service] -Restart=always -# Extend Timeout to allow time to pull the image -TimeoutStartSec=900 - -[Install] -# Start by default on boot -WantedBy=multi-user.target default.target \ No newline at end of file diff --git a/active/software_ai_stack/stable-diffusion-edit-server.container b/active/software_ai_stack/stable-diffusion-edit-server.container deleted file mode 100644 index 702d7e5..0000000 --- a/active/software_ai_stack/stable-diffusion-edit-server.container +++ /dev/null @@ -1,44 +0,0 @@ -[Unit] -Description=A Stable Diffusion CPP Server for Editing Images - -[Container] -# Shared AI internal pod without internet access -Pod=ai-internal.pod - -# Vulkan image for AMD GPU -Image=localhost/stable-diffusion-cpp:latest - -# Shared models directory -Volume=/home/ai/models:/models:z - -# GPU Device -AddDevice=/dev/kfd -AddDevice=/dev/dri - -# Override entrypoint to use server -Entrypoint=/sd-server - -# Server args -Exec=-l 0.0.0.0 \ - --listen-port 1235 \ - --diffusion-model /models/image/flux2-klein/flux-2-klein-9b-Q8_0.gguf \ - --vae /models/image/flux2-klein/ae.safetensors \ - --llm /models/image/flux2-klein/Qwen3-8B-Q4_K_M.gguf \ - -v \ - --sampling-method euler \ - --cfg-scale 1.0 \ - --vae-conv-direct \ - --diffusion-conv-direct \ - --fa \ - --mmap \ - --seed -1 \ - --steps 5 - -[Service] -Restart=always -# Extend Timeout to allow time to pull the image -TimeoutStartSec=900 - -[Install] -# Start by default on boot -WantedBy=multi-user.target default.target diff --git a/active/software_ai_stack/stable-diffusion-gen-server.container b/active/software_ai_stack/stable-diffusion-gen-server.container deleted file mode 100644 index bc54a05..0000000 --- a/active/software_ai_stack/stable-diffusion-gen-server.container +++ /dev/null @@ -1,43 +0,0 @@ -[Unit] -Description=A Stable Diffusion CPP Server for Generating Images - -[Container] -# Shared AI internal pod without internet access -Pod=ai-internal.pod - -# Vulkan image for AMD GPU -Image=localhost/stable-diffusion-cpp:latest - -# Shared models directory -Volume=/home/ai/models:/models:z - -# GPU Device -AddDevice=/dev/kfd -AddDevice=/dev/dri - -# Override entrypoint to use server -Entrypoint=/sd-server - -# Server args -Exec=-l 0.0.0.0 \ - --listen-port 1234 \ - --diffusion-model /models/image/z-turbo/z_image_turbo-Q8_0.gguf \ - --vae /models/image/z-turbo/ae.safetensors \ - --llm /models/image/z-turbo/Qwen3-4B-Instruct-2507-Q4_K_M.gguf \ - -v \ - --cfg-scale 1.0 \ - --vae-conv-direct \ - --diffusion-conv-direct \ - --fa \ - --mmap \ - --seed -1 \ - --steps 8 - -[Service] -Restart=always -# Extend Timeout to allow time to pull the image -TimeoutStartSec=900 - -[Install] -# Start by default on boot -WantedBy=multi-user.target default.target diff --git a/active/software_ai_stack/systemd/open-webui-guest-start.service b/active/software_ai_stack/systemd/open-webui-guest-start.service deleted file mode 100644 index cff0b14..0000000 --- a/active/software_ai_stack/systemd/open-webui-guest-start.service +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Start open-webui-guest on schedule -Wants=open-webui-guest.service -After=open-webui-guest.service - -[Service] -Type=oneshot -ExecStart=/bin/systemctl --user start open-webui-guest.service \ No newline at end of file diff --git a/active/software_ai_stack/systemd/open-webui-guest-start.timer b/active/software_ai_stack/systemd/open-webui-guest-start.timer deleted file mode 100644 index 029e46d..0000000 --- a/active/software_ai_stack/systemd/open-webui-guest-start.timer +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Timer to start open-webui-guest at 08:00 Mon‑Fri - -[Timer] -OnCalendar=Mon..Fri *-*-* 08:00:00 -Persistent=true -Unit=open-webui-guest-start.service - -[Install] -WantedBy=timers.target \ No newline at end of file diff --git a/active/software_ai_stack/systemd/open-webui-guest-stop.service b/active/software_ai_stack/systemd/open-webui-guest-stop.service deleted file mode 100644 index 572f44a..0000000 --- a/active/software_ai_stack/systemd/open-webui-guest-stop.service +++ /dev/null @@ -1,7 +0,0 @@ -[Unit] -Description=Stop open-webui-guest on schedule -After=open-webui-guest.service - -[Service] -Type=oneshot -ExecStart=/bin/systemctl --user stop open-webui-guest.service \ No newline at end of file diff --git a/active/software_ai_stack/systemd/open-webui-guest-stop.timer b/active/software_ai_stack/systemd/open-webui-guest-stop.timer deleted file mode 100644 index 11a8138..0000000 --- a/active/software_ai_stack/systemd/open-webui-guest-stop.timer +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Timer to stop open-webui-guest at 18:00 Mon‑Fri - -[Timer] -OnCalendar=Mon..Fri *-*-* 18:00:00 -Persistent=true -Unit=open-webui-guest-stop.service - -[Install] -WantedBy=timers.target \ No newline at end of file diff --git a/active/software_konsole/konsole.md b/active/software_konsole/konsole.md new file mode 100644 index 0000000..37a41e5 --- /dev/null +++ b/active/software_konsole/konsole.md @@ -0,0 +1,5 @@ +# Konsole + +## Tips + +- To open new tabs with the same profile as the current tab, assign the "clone tab" keyboard shortcut to ++. \ No newline at end of file diff --git a/active/software_osbuild/fedora44-base.toml b/active/software_osbuild/fedora44-base.toml new file mode 100644 index 0000000..1bd49ff --- /dev/null +++ b/active/software_osbuild/fedora44-base.toml @@ -0,0 +1,79 @@ +name = "fedora44-base" +description = "Fedora Base Installation" +version = "0.2.3" +distro = "fedora-44" +modules = [] +groups = [] + +[customizations] +hostname = "fedora44-base" + +[customizations.timezone] +timezone = "America/New_York" + +[[customizations.disk.partitions]] +type = "plain" +label = "root" +mountpoint = "/" +fs_type = "ext4" +minsize = "128 GiB" + +[customizations.firewall.services] +enabled = ["ssh", "cockpit"] + +[customizations.services] +enabled = ["sshd", "cockpit.socket"] + +[[packages]] +name = "bash-completion" +version = "*" + +[[packages]] +name = "tmux" +version = "*" + +[[packages]] +name = "openssh-server" +version = "*" + +[[packages]] +name = "vim" +version = "*" + +[[packages]] +name = "git" +version = "*" + +[[packages]] +name = "cockpit" +version = "*" + +[[packages]] +name = "borgbackup" +version = "*" + +[[packages]] +name = "policycoreutils-python-utils" +version = "*" + +[[packages]] +name = "systemd-container" +version = "*" + +[[customizations.files]] +path = "/root/.inputrc" +mode = "0644" +user = "root" +group = "root" +data = """ +"\\C-h": backward-kill-word +""" + +[[customizations.files]] +path = "/root/.ssh/authorized_keys" +mode = "0600" +user = "root" +group = "root" +data = """ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQa781Qj8mNlUdRquFFqg0O2ornG9SBHe705y4+1vPI ssh@ducoterra.net +""" diff --git a/active/software_osbuild/image_builder.md b/active/software_osbuild/image_builder.md index 2f83ba3..b2ed5aa 100644 --- a/active/software_osbuild/image_builder.md +++ b/active/software_osbuild/image_builder.md @@ -29,6 +29,8 @@ dnf install -y osbuild-composer composer-cli systemctl enable --now osbuild-composer.socket # Optional: add your user to the weldr group +# On fedora kinoite (atomic) you'll need to add the group manually for now +grep -E '^weldr:' /usr/lib/group | sudo tee -a /etc/group sudo usermod -aG weldr $USER # Optional: cockpit dependency diff --git a/active/software_podman/podman.md b/active/software_podman/podman.md new file mode 100644 index 0000000..f3ab0f9 --- /dev/null +++ b/active/software_podman/podman.md @@ -0,0 +1,48 @@ +# Podman + +## Networks + +```bash +# Create a podman network to group shared containers +podman network create test1 +# Create an iperf3 container running on that network +podman run -d --name iperf3 --network test1 docker.io/networkstatic/iperf3 -s +# A container running in the same network can reference another pod by its name +podman run --rm -it --network test1 docker.io/networkstatic/iperf3 -c iperf3 +# A container outside the created network can't talk to other containers +# This will fail +podman run --rm -it docker.io/networkstatic/iperf3 -c iperf3 +``` + +## Healthchecks + +TCP Healthchecks can be accomplished with netcat (netcat-openbsd) + +```bash +podman run -d --name iperf3 \ +--health-cmd='CMD-SHELL iperf3 -n 1 -c localhost' \ +--health-interval=10s --health-timeout=5s --health-retries=3 \ +docker.io/networkstatic/iperf3 iperf3 -s +``` + +HTTP Healthchecks can be accomplished with curl + +```bash +podman run -d --name nginx \ +--health-cmd='CMD-SHELL curl --fail http://127.0.0.1:80 || exit 1' \ +--health-interval=10s --health-timeout=5s --health-retries=3 \ +docker.io/nginx +``` + +Check healthchecks with + +```bash +podman --log-level debug healthcheck run +``` + +## Volume Import and Export + +```bash +podman volume export myvol --output myvol.tar +podman volume import myvol test.tar +``` diff --git a/active/software_smb/smb.md b/active/software_smb/smb.md index bad5759..3fe28c3 100644 --- a/active/software_smb/smb.md +++ b/active/software_smb/smb.md @@ -30,11 +30,17 @@ sudo smbpasswd -a sambauser ## Create a SMB Share ```bash +# Add the share user +sudo useradd -r -M -s /usr/sbin/nologin sambauser +sudo smbpasswd -a sambauser + # Create share -mkdir /srv/smb/sambauser +sudo mkdir /srv/smb/sambauser +sudo chown -R sambauser:sambauser /srv/smb/sambauser +sudo chmod -R 2770 /srv/samba/sambauser # Set proper selinux labels for samba -sudo semanage fcontext --add --type "samba_share_t" "/srv/smb(/.*)?" +sudo semanage fcontext --add --type "samba_share_t" "/srv/smb/sambauser" # Run restorecon at the root of the btrfs subvolume sudo restorecon -FRv /srv diff --git a/active/software_toolbx/Containerfile b/active/software_toolbx/Containerfile new file mode 100644 index 0000000..5e545a6 --- /dev/null +++ b/active/software_toolbx/Containerfile @@ -0,0 +1,33 @@ +FROM registry.fedoraproject.org/fedora-toolbox:44 + +COPY vscodium.repo /etc/yum.repos.d/vscodium.repo + +RUN dnf install -y \ +vim git btop \ +virt-manager virt-install \ +podman podman-compose \ +wl-clipboard \ +jq yq rg \ +kubectl helm \ +pinentry \ +pandoc \ +iperf3 \ +nethogs \ +ansible \ +tmux \ +wine \ +unzip \ +ImageMagick \ +go \ +rust rustup \ +git git-lfs \ +net-tools \ +texlive-latex texlive-scheme-full \ +pwgen \ +reptyr \ +netcat \ +p7zip \ +make \ +gcc \ +codium \ +pass diff --git a/active/software_toolbx/toolbx.md b/active/software_toolbx/toolbx.md new file mode 100644 index 0000000..b9d915d --- /dev/null +++ b/active/software_toolbx/toolbx.md @@ -0,0 +1,7 @@ +# Toolbox + +## How to Use + +```bash +podman build -t localhost/fedora-toolbox:44 active/software_toolbx +``` diff --git a/active/software_toolbx/vscodium.repo b/active/software_toolbx/vscodium.repo new file mode 100644 index 0000000..a90671d --- /dev/null +++ b/active/software_toolbx/vscodium.repo @@ -0,0 +1,9 @@ +[gitlab.com_paulcarroty_vscodium_repo] +name=gitlab.com_paulcarroty_vscodium_repo +baseurl=https://paulcarroty.gitlab.io/vscodium-deb-rpm-repo/rpms/ +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg +metadata_expire=1h + diff --git a/active/software_virsh/virsh.md b/active/software_virsh/virsh.md index aabf513..49423d9 100644 --- a/active/software_virsh/virsh.md +++ b/active/software_virsh/virsh.md @@ -40,7 +40,6 @@ Virtual Machine Management ### Configuring Aliases - 1. Edit `~/.config/libvirt/libvirt.conf` 2. Add your aliases diff --git a/active/vibe_system_report/agent_disk_report.py b/active/vibe_system_report/agent_disk_report.py index fcfb7fc..60e7be6 100644 --- a/active/vibe_system_report/agent_disk_report.py +++ b/active/vibe_system_report/agent_disk_report.py @@ -1,24 +1,19 @@ import json import os import smtplib -import ssl import subprocess from email.message import EmailMessage from pathlib import Path from typing import Iterable, TypedDict, cast +import requests from dotenv import dotenv_values, load_dotenv from openai import OpenAI from openai.types.chat import ChatCompletionMessageParam, ChatCompletionToolUnionParam -class AWS_SES_DOTENV(TypedDict): - USER: str - PASSWORD: str - ENDPOINT: str - TLS_PORT: str - SENDER: str - RECEIVER: str +class NTFY_DOTENV(TypedDict): + TOKEN: str class ToolCallController: @@ -209,39 +204,30 @@ def smartctl(device_path: str) -> str: return output -def load_ses_creds() -> AWS_SES_DOTENV: +def load_ntfy_creds() -> NTFY_DOTENV: ses_dotenv_location = Path(os.getenv("HOME", "/root"), ".env/aws_ses") print(f"Loading env from {ses_dotenv_location}") raw_values = dotenv_values(ses_dotenv_location) if raw_values: - aws_ses_config = cast(AWS_SES_DOTENV, raw_values) + ntfy_config = cast(NTFY_DOTENV, raw_values) # print(f"AWS SES Credentials loaded: {aws_ses_config}") - return aws_ses_config - print("No email credentials supplied. Exiting.") + return ntfy_config + print("No ntfy credentials supplied. Exiting.") exit(1) def alert_user(message: str) -> str: - ses_config = load_ses_creds() - port = int(ses_config["TLS_PORT"]) - user = ses_config["USER"] - password = ses_config["PASSWORD"] - sender = ses_config["SENDER"] - receiver = ses_config["RECEIVER"] + ntfy_creds = load_ntfy_creds() - # Create a secure SSL context - context = ssl.create_default_context() - - msg = EmailMessage() - msg["Subject"] = "Agent Disk Report" - msg["From"] = sender - msg["To"] = receiver - msg.set_content(message) - - with smtplib.SMTP_SSL(ses_config["ENDPOINT"], port, context=context) as server: - server.login(user, password) - result = server.send_message(msg) - print(result) + requests.post( + "https://ntfy.reeseapps.com/servers", + data=message, + headers={ + "Authorization": f"Bearer {ntfy_creds['TOKEN']}", + "Title": "Agent Disk Report", + "Priority": "default", + }, + ) return "Sent." @@ -322,13 +308,14 @@ def run_conversation(user_message: str, max_tool_calls=10): if __name__ == "__main__": load_dotenv() api_key = os.getenv("OPENAI_API_KEY", "") - client = OpenAI(base_url="https://llama-think.reeselink.com", api_key=api_key) + client = OpenAI(base_url="https://aipi.reeseapps.com", api_key=api_key) # Example usage print( run_conversation( "Check the btrfs pools on this system. Take the appropriate action if any pools aren't " "healthy. Don't run scrubs unless necessary. Also check the btrfs pool space and report " "if any are getting full. At the very end of performing your checks send a single, " - "concise message to the user explaining what you did and what concerns you might have." + "concise message to the user via the alert_user tool explaining what you did and " + "what concerns you might have." ) ) diff --git a/automations/proxy/README.md b/automations/proxy/README.md deleted file mode 100644 index 5946d2f..0000000 --- a/automations/proxy/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Proxy Automation - -1. Create or update the reeselink.com address in Unifi -2. Create or update the entry in the ddns values -3. Create or update the entry in the caddy values \ No newline at end of file diff --git a/automations/proxy/proxy.sh b/automations/proxy/proxy.sh deleted file mode 100644 index e69de29..0000000 diff --git a/automations/updates/.python-version b/automations/updates/.python-version deleted file mode 100644 index 3a4f41e..0000000 --- a/automations/updates/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.13 \ No newline at end of file diff --git a/automations/updates/README.md b/automations/updates/README.md deleted file mode 100644 index 1bd0618..0000000 --- a/automations/updates/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Update - -Updates every server in the fleet - -## Usage - -```bash -uv run automations/updates/main.py -``` diff --git a/automations/updates/main.py b/automations/updates/main.py deleted file mode 100644 index 64ebe6b..0000000 --- a/automations/updates/main.py +++ /dev/null @@ -1,64 +0,0 @@ -import subprocess -import time - -import yaml -from tqdm import tqdm - - -def main(): - print("Retrieving hosts") - with open("ansible/inventory.yaml", "r") as f: - all_hosts = yaml.load(f, yaml.SafeLoader) - fedora_hosts = all_hosts["fedora"]["hosts"].keys() - - with open("update.log", "w") as log_file: - for _, host in enumerate(tqdm(fedora_hosts, desc="Running system updates")): - log_file.write(f"Updating {host}\n") - log_file.flush() - try: - subprocess.run( - ["ssh", host, "dnf", "upgrade", "-y"], - stdout=log_file, - stderr=log_file, - check=True, - ) - except Exception as e: - log_file.write(f"Couldn't connect to {host}. Skipping...\n") - continue - log_file.flush() - log_file.write(f"Rebooting {host}\n") - log_file.flush() - subprocess.run( - ["ssh", host, "reboot"], - stdout=log_file, - stderr=log_file, - check=True, - ) - time.sleep(5) # wait for reboot to take effect - booted = False - max_attempts = 10 - cur_attempts = 0 - while max_attempts > cur_attempts and not booted: - try: - subprocess.run( - ["ssh", host, "echo"], - stdout=log_file, - stderr=log_file, - check=True, - timeout=2, - ) - log_file.write(f"{host} booted!\n") - log_file.flush() - booted = True - except Exception as e: - cur_attempts += 1 - log_file.write(f"Waiting for {host} to reboot\n") - log_file.flush() - time.sleep(5) - - if cur_attempts >= max_attempts: - exit(1) - - -if __name__ == "__main__": - main() diff --git a/automations/updates/pyproject.toml b/automations/updates/pyproject.toml deleted file mode 100644 index a0474ca..0000000 --- a/automations/updates/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name = "updates" -version = "0.1.0" -description = "Update servers based on ansible inventory" -readme = "README.md" -requires-python = ">=3.13" -dependencies = [ - "pyyaml>=6.0.3", - "tqdm>=4.67.3", - "types-pyyaml>=6.0.12.20250915", - "types-tqdm>=4.67.3.20260205", -] diff --git a/automations/updates/uv.lock b/automations/updates/uv.lock deleted file mode 100644 index 6f9fffe..0000000 --- a/automations/updates/uv.lock +++ /dev/null @@ -1,121 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.13" - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - -[[package]] -name = "tqdm" -version = "4.67.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, -] - -[[package]] -name = "types-pyyaml" -version = "6.0.12.20250915" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, -] - -[[package]] -name = "types-requests" -version = "2.32.4.20260107" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, -] - -[[package]] -name = "types-tqdm" -version = "4.67.3.20260205" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "types-requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/46/790b9872523a48163bdda87d47849b4466017640e5259d06eed539340afd/types_tqdm-4.67.3.20260205.tar.gz", hash = "sha256:f3023682d4aa3bbbf908c8c6bb35f35692d319460d9bbd3e646e8852f3dd9f85", size = 17597, upload-time = "2026-02-05T04:03:19.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/da/7f761868dbaa328392356fab30c18ab90d14cce86b269e7e63328f29d4a3/types_tqdm-4.67.3.20260205-py3-none-any.whl", hash = "sha256:85c31731e81dc3c5cecc34c6c8b2e5166fafa722468f58840c2b5ac6a8c5c173", size = 23894, upload-time = "2026-02-05T04:03:18.48Z" }, -] - -[[package]] -name = "updates" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "pyyaml" }, - { name = "tqdm" }, - { name = "types-pyyaml" }, - { name = "types-tqdm" }, -] - -[package.metadata] -requires-dist = [ - { name = "pyyaml", specifier = ">=6.0.3" }, - { name = "tqdm", specifier = ">=4.67.3" }, - { name = "types-pyyaml", specifier = ">=6.0.12.20250915" }, - { name = "types-tqdm", specifier = ">=4.67.3.20260205" }, -] - -[[package]] -name = "urllib3" -version = "2.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, -] diff --git a/pyproject.toml b/pyproject.toml index b04af5f..a475002 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ dependencies = [ "pytest>=9.0.2", "pyyaml>=6.0.3", "requests>=2.32.5", + "rich>=15.0.0", "sse-starlette>=3.3.4", "tqdm>=4.67.3", "types-pyyaml>=6.0.12.20250915", diff --git a/retired/kubernetes_userspace b/retired/kubernetes_userspace new file mode 160000 index 0000000..18f62b5 --- /dev/null +++ b/retired/kubernetes_userspace @@ -0,0 +1 @@ +Subproject commit 18f62b5fb0185549d7c6be07f7a2d61489442ce8 diff --git a/uv.lock b/uv.lock index ce874d4..3f34cdc 100644 --- a/uv.lock +++ b/uv.lock @@ -285,6 +285,7 @@ dependencies = [ { name = "pytest" }, { name = "pyyaml" }, { name = "requests" }, + { name = "rich" }, { name = "sse-starlette" }, { name = "tqdm" }, { name = "types-pyyaml" }, @@ -307,6 +308,7 @@ requires-dist = [ { name = "pytest", specifier = ">=9.0.2" }, { name = "pyyaml", specifier = ">=6.0.3" }, { name = "requests", specifier = ">=2.32.5" }, + { name = "rich", specifier = ">=15.0.0" }, { name = "sse-starlette", specifier = ">=3.3.4" }, { name = "tqdm", specifier = ">=4.67.3" }, { name = "types-pyyaml", specifier = ">=6.0.12.20250915" }, @@ -612,6 +614,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -689,6 +703,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/46/f6b4ad632c67ef35209a66127e4bddc95759649dd595f71f13fba11bdf9a/mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741", size = 215967, upload-time = "2026-04-02T14:48:07.24Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + [[package]] name = "mergedeep" version = "1.3.4" @@ -1259,6 +1282,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + [[package]] name = "rpds-py" version = "0.30.0"