Initial commit

This commit is contained in:
2024-12-30 11:42:12 -07:00
commit 09ba4114c1
86 changed files with 7522 additions and 0 deletions

43
home-server/dns/update-dns.sh Executable file
View File

@@ -0,0 +1,43 @@
#!/bin/bash
# curl -X GET https://api.cloudflare.com/client/v4/zones/bf7a05315be9bf7a39d50dd4001e7a97/dns_records -H "X-Auth-Email: alexmickelson96@gmail.com" -H "X-Auth-Key: jo7GntHEEBtANFsuteAM8EJ-stLUqyNbOk2x4Czr" | python -m json.tool
source /home/alex/actions-runner/_work/infrastructure/infrastructure/home-pi/dns/cloudflare.env
NETWORK_INTERFACE=wlan0
IP=$(ip a s $NETWORK_INTERFACE | awk '/inet / {print$2}' | cut -d/ -f1)
EMAIL="alexmickelson96@gmail.com";
ZONE_ID="bf7a05315be9bf7a39d50dd4001e7a97";
update_record() {
LOCAL_NAME=$1
LOCAL_RECORD_ID=$2
echo "UPDATING RECORD FOR $LOCAL_NAME TO $IP"
curl -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$LOCAL_RECORD_ID" \
-H "X-Auth-Email: alexmickelson96@gmail.com" \
-H "X-Auth-Key: $CLOUDFLARE_TOKEN" \
-H "Content-Type: application/json" \
--data '{"type":"A","name":"'"$LOCAL_NAME"'","content":"'"$IP"'","ttl":1}' \
| python3 -m json.tool;
echo
echo "------------------------------------"
echo
}
NAME="ha.alexmickelson.guru";
RECORD_ID="09eac5a17fa4302091532dabdbe73a68"
update_record $NAME $RECORD_ID
NAME="jellyfin.alexmickelson.guru";
RECORD_ID="577293ab0488913308fda78010a7483b"
update_record $NAME $RECORD_ID
NAME="next.alexmickelson.guru";
RECORD_ID="cc686333d2421a4e558a04589b375ded"
update_record $NAME $RECORD_ID

View File

@@ -0,0 +1,256 @@
services:
jellyfin:
image: jellyfin/jellyfin
container_name: jellyfin
user: 1000:1000
network_mode: "host"
volumes:
- /data/jellyfin/config:/config
- /data/jellyfin/cache:/cache
- /data/media/music/tagged:/music
- /data/media/movies:/movies
- /data/media/tvshows:/tvshows
restart: "unless-stopped"
environment:
- JELLYFIN_PublishedServerUrl=https://jellyfin.alexmickelson.guru
nextcloud:
build:
context: nextcloud
container_name: nextcloud
environment:
- TZ=America/Denver
- OVERWRITEPROTOCOL=https
- MYSQL_PASSWORD=slkdnflksnelkfnsdweoinv
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_HOST=nextcloud-db
volumes:
- /data/nextcloud/html:/var/www/html
- /data/media/music:/music
- /data/media/movies:/movies
- /data/media/tvshows:/tvshows
- /data/media/shared:/shared
- /data/media/audiobooks:/audiobooks
restart: unless-stopped
networks:
- proxy
nextcloud-cron:
build:
context: nextcloud
container_name: nextcloud-cron
environment:
- TZ=America/Denver
- OVERWRITEPROTOCOL=https
- MYSQL_PASSWORD=slkdnflksnelkfnsdweoinv
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_HOST=nextcloud-db
volumes:
- /data/nextcloud/html:/var/www/html
- /data/media/music:/music
- /data/media/movies:/movies
- /data/media/tvshows:/tvshows
- /data/media/shared:/shared
- /data/media/audiobooks:/audiobooks
restart: unless-stopped
entrypoint: /cron.sh
depends_on:
- nextcloud
networks:
- proxy
nextcloud-db:
image: mariadb:10.6
container_name: nextcloud_db
# mysql -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE
restart: always
command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
volumes:
- /data/nextcloud-db/:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=klsdnofinsodkflksen34tesrg
- MYSQL_PASSWORD=slkdnflksnelkfnsdweoinv
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
networks:
- proxy
homeassistant:
container_name: homeassistant
image: homeassistant/home-assistant:stable
volumes:
- /data/homeAssistant/config:/config
- /etc/localtime:/etc/localtime:ro
- /dev/serial/by-id:/dev/serial/by-id
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
- /dev/ttyUSB1:/dev/ttyUSB1
environment:
- TZ=America/Denver
restart: always
network_mode: host
# octoprint:
# image: octoprint/octoprint
# container_name: octoprint
# restart: unless-stopped
# # ports:
# # - 80:80
# # devices:
# # # use `python -m serial.tools.miniterm` to see what the name is of the printer, this requires pyserial
# # - /dev/ttyACM0:/dev/ttyACM0
# # - /dev/video0:/dev/video0
# volumes:
# - /data/octoprint:/octoprint
# # uncomment the lines below to ensure camera streaming is enabled when
# # you add a video device
# environment:
# - ENABLE_MJPG_STREAMER=true
# - MJPG_SREAMER_INPUT=-n -r 1280x720 -f 30
prometheus:
image: bitnami/prometheus:2
container_name: prometheus
restart: unless-stopped
environment:
- HOMEASSISTANT_TOKEN=${HOMEASSISTANT_TOKEN}
volumes:
- ./prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml
- /data/prometheus:/opt/bitnami/prometheus/data
# command:
# - '--config.file=/etc/prometheus/prometheus.yml'
# - '--storage.tsdb.path=/prometheus'
# - '--web.console.libraries=/etc/prometheus/console_libraries'
# - '--web.console.templates=/etc/prometheus/consoles'
# - '--web.enable-lifecycle'
# expose:
# - 9090
networks:
- proxy
grafana:
image: grafana/grafana:main
container_name: grafana
restart: always
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
volumes:
- /data/grafana:/var/lib/grafana
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/robots.txt"]
interval: 10s
timeout: 5s
retries: 3
start_period: 3s
networks:
- proxy
# acpupsd_exporter:
# image: sfudeus/apcupsd_exporter:master_1.19
# container_name: apcupsd_exporter
# restart: always
# extra_hosts:
# - host.docker.internal:host-gateway
# command: -apcupsd.addr host.docker.internal:3551
# ports:
# - 0.0.0.0:9162:9162
# docker run -it --rm -p 9162:9162 --net=host sfudeus/apcupsd_exporter:master_1.19
reverse-proxy:
image: ghcr.io/linuxserver/swag
container_name: reverse-proxy
restart: unless-stopped
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=America/Denver
- URL=alexmickelson.guru
- SUBDOMAINS=wildcard
- VALIDATION=dns
- DNSPLUGIN=cloudflare
volumes:
- ./nginx.conf:/config/nginx/site-confs/default.conf
- /data/swag:/config
- /data/cloudflare/cloudflare.ini:/config/dns-conf/cloudflare.ini
ports:
- 0.0.0.0:80:80
- 0.0.0.0:443:443
extra_hosts:
- host.docker.internal:host-gateway
networks:
- proxy
audiobookshelf:
image: ghcr.io/advplyr/audiobookshelf:latest
restart: unless-stopped
ports:
- 13378:80
volumes:
- /data/media/audiobooks:/audiobooks
# - </path/to/podcasts>:/podcasts
- /data/audiobookshelf/config:/config
- /data/audiobookshelf/metadata:/metadata
networks:
- proxy
docker-registry:
image: registry:2
container_name: docker-registry
restart: unless-stopped
ports:
- "5000:5000"
environment:
REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /data
REGISTRY_HTTP_TLS_CERTIFICATE: /etc/docker/certs.d/server.alexmickelson.guru/cert.pem
REGISTRY_HTTP_TLS_KEY: /etc/docker/certs.d/server.alexmickelson.guru/key.pem
volumes:
- /data/docker-registry:/data
- /data/swag/keys/letsencrypt/fullchain.pem:/etc/docker/certs.d/server.alexmickelson.guru/cert.pem
- /data/swag/keys/letsencrypt/privkey.pem:/etc/docker/certs.d/server.alexmickelson.guru/key.pem
depends_on:
- reverse-proxy
networks:
- proxy
# github-actions-exporter:
# # ports:
# # - 9999:9999
# image: ghcr.io/labbs/github-actions-exporter
# environment:
# - GITHUB_REPOS=alexmickelson/infrastructure
# - GITHUB_TOKEN=${MY_GITHUB_TOKEN}
# pihole:
# container_name: pihole
# image: pihole/pihole:latest
# # For DHCP it is recommended to remove these ports and instead add: network_mode: "host"
# ports:
# # - "0.0.0.0:53:53/tcp"
# # - "0.0.0.0:53:53/udp"
# # - "127.0.0.1:53:53/tcp"
# # - "127.0.0.1:53:53/udp"
# - "100.122.128.107:53:53/tcp"
# - "100.122.128.107:53:53/udp"
# # # - "67:67/udp" # Only required if you are using Pi-hole as your DHCP server
# - "8580:80"
# environment:
# TZ: 'America/Denver'
# # WEBPASSWORD: 'set a secure password here or it will be random'
# volumes:
# - '/data/pihole/etc-pihole:/etc/pihole'
# - '/data/pihole/etc-dnsmasq.d:/etc/dnsmasq.d'
# # https://github.com/pi-hole/docker-pi-hole#note-on-capabilities
# cap_add:
# - NET_ADMIN # Required if you are using Pi-hole as your DHCP server, else not needed
# restart: unless-stopped
networks:
proxy:
external:
name: proxy

View File

@@ -0,0 +1,4 @@
FROM nextcloud:production
RUN usermod -u 1000 www-data
RUN groupmod -g 1000 www-data

202
home-server/nginx.conf Normal file
View File

@@ -0,0 +1,202 @@
# include mime.types;
# default_type application/octet-stream;
# log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
# access_log /var/log/nginx/access.log main;
# sendfile on;
# keepalive_timeout 65;
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ha.alexmickelson.guru;
include /config/nginx/ssl.conf;
include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf;
location / {
proxy_pass http://host.docker.internal:8123;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name next.alexmickelson.guru;
include /config/nginx/ssl.conf;
include /config/nginx/proxy.conf;
include /config/nginx/resolver.conf;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
location /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
location /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
location / {
proxy_pass http://nextcloud:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Port $server_port;
client_max_body_size 1G;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name plex.alexmickelson.guru;
location / {
proxy_pass http://host.docker.internal:32400;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name jellyfin.alexmickelson.guru;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "0"; # Do NOT enable. This is obsolete/dangerous
add_header X-Content-Type-Options "nosniff";
client_max_body_size 20M;
location / {
# Proxy main Jellyfin traffic
proxy_pass http://host.docker.internal:8096;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_buffering off;
}
location /socket {
# Proxy Jellyfin Websockets traffic
proxy_pass http://host.docker.internal:8096;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name audiobook.alexmickelson.guru;
location / {
proxy_pass http://audiobookshelf:80;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
}
}
# server {
# listen 443 ssl;
# listen [::]:443 ssl;
# server_name octoprint.alexmickelson.guru;
# location / {
# proxy_pass http://octoprint:80;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# }
# }
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name prometheus.alexmickelson.guru;
location / {
proxy_pass http://prometheus:9090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name grafana.alexmickelson.guru;
location / {
proxy_pass http://grafana:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name photos.alexmickelson.guru;
# allow large file uploads
client_max_body_size 50000M;
# Set headers
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# enable websockets: http://nginx.org/en/docs/http/websocket.html
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
# set timeout
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
location / {
proxy_pass http://immich_server:2283;
}
}

View File

@@ -0,0 +1,340 @@
# Edit this configuration file to define what should be installed on
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running nixos-help).
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
<home-manager/nixos>
];
# Bootloader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
networking.hostName = "home-server"; # Define your hostname.
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Enable networking
networking.networkmanager.enable = true;
networking.nat.enable = true;
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
# Set your time zone.
time.timeZone = "America/Denver";
# Select internationalisation properties.
i18n.defaultLocale = "en_US.UTF-8";
i18n.extraLocaleSettings = {
LC_ADDRESS = "en_US.UTF-8";
LC_IDENTIFICATION = "en_US.UTF-8";
LC_MEASUREMENT = "en_US.UTF-8";
LC_MONETARY = "en_US.UTF-8";
LC_NAME = "en_US.UTF-8";
LC_NUMERIC = "en_US.UTF-8";
LC_PAPER = "en_US.UTF-8";
LC_TELEPHONE = "en_US.UTF-8";
LC_TIME = "en_US.UTF-8";
};
# Configure keymap in X11
services.xserver.xkb = {
layout = "us";
variant = "";
};
users.users.github = {
isNormalUser = true;
description = "github";
extraGroups = [ "docker" ];
shell = pkgs.fish;
};
users.users.alex = {
isNormalUser = true;
description = "alex";
extraGroups = [ "networkmanager" "wheel" "docker" "users" "libvirtd" "cdrom" ];
shell = pkgs.fish;
};
home-manager.users.alex = { pgks, ...}: {
home.stateVersion = "24.05";
home.packages = with pkgs; [
openldap
k9s
jwt-cli
thefuck
fish
kubectl
lazydocker
btop
nix-index
usbutils
makemkv
mbuffer
lzop
lsof
code-server
];
programs.fish = {
enable = true;
shellAliases = {
dang="fuck";
};
shellInit = ''
function commit
git add --all
git commit -m "$argv"
git push
end
# have ctrl+backspace delete previous word
bind \e\[3\;5~ kill-word
# have ctrl+delete delete following word
bind \b backward-kill-word
set -U fish_user_paths ~/.local/bin $fish_user_paths
#set -U fish_user_paths ~/.dotnet $fish_user_paths
#set -U fish_user_paths ~/.dotnet/tools $fish_user_paths
export VISUAL=vim
export EDITOR="$VISUAL"
export DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
set -x LIBVIRT_DEFAULT_URI qemu:///system
thefuck --alias | source
'';
};
home.file = {
".config/lazydocker/config.yml".text = ''
gui:
returnImmediately: true
'';
".config/k9s/config.yaml".text = ''
k9s:
liveViewAutoRefresh: true
screenDumpDir: /home/alexm/.local/state/k9s/screen-dumps
refreshRate: 2
maxConnRetry: 5
readOnly: false
noExitOnCtrlC: false
ui:
enableMouse: false
headless: false
logoless: false
crumbsless: false
reactive: false
noIcons: false
defaultsToFullScreen: false
skipLatestRevCheck: false
disablePodCounting: false
shellPod:
image: busybox:1.35.0
namespace: default
limits:
cpu: 100m
memory: 100Mi
imageScans:
enable: false
exclusions:
namespaces: []
labels: {}
logger:
tail: 1000
buffer: 5000
sinceSeconds: -1
textWrap: false
showTime: false
thresholds:
cpu:
critical: 90
warn: 70
memory:
critical: 90
warn: 70
namespace:
lockFavorites: false
'';
};
home.sessionVariables = {
EDITOR = "vim";
};
};
home-manager.useGlobalPkgs = true;
# Allow unfree packages
nixpkgs.config.allowUnfree = true;
# List packages installed in system profile. To search, run:
# $ nix search wget
environment.systemPackages = with pkgs; [
vim
wget
curl
docker
fish
git
zfs
gcc-unwrapped
github-runner
sanoid
virtiofsd
tmux
];
services.openssh.enable = true;
programs.fish.enable = true;
virtualisation.docker.enable = true;
#virtualisation.docker.extraOptions = "--dns 1.1.1.1 --dns 8.8.8.8 --dns 100.100.100.100";
services.tailscale.enable = true;
services.tailscale.extraSetFlags = [
"--stateful-filtering=false"
];
services.envfs.enable = true;
# printing
services.printing = {
enable = true;
drivers = [ pkgs.brgenml1lpr pkgs.brgenml1cupswrapper pkgs.brlaser];
listenAddresses = [ "*:631" ];
extraConf = ''
ServerAlias server.alexmickelson.guru
'';
allowFrom = [ "all" ];
browsing = true;
defaultShared = true;
openFirewall = true;
};
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
publish = {
enable = true;
userServices = true;
};
};
systemd.services.printing-server = {
description = "Web Printing Server Service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.nix}/bin/nix run .#fastapi-server";
Restart = "always";
WorkingDirectory = "/home/alex/infrastructure/home-server/printing/server";
User = "alex";
};
};
# virtualization stuff
virtualisation.libvirtd.enable = true;
# zfs stuff
boot.supportedFilesystems = [ "zfs" ];
boot.zfs.forceImportRoot = false;
networking.hostId = "eafe9551";
boot.zfs.extraPools = [ "data-ssd" "backup" ];
services.sanoid = {
enable = true;
templates.production = {
hourly = 24;
daily = 14;
monthly = 5;
autoprune = true;
autosnap = true;
};
datasets."data-ssd/data" = {
useTemplate = [ "production" ];
};
datasets."data-ssd/media" = {
useTemplate = [ "production" ];
};
templates.backup = {
hourly = 24;
daily = 14;
monthly = 5;
autoprune = true;
autosnap = false;
};
datasets."backup/data" = {
useTemplate = [ "backup" ];
};
datasets."backup/media" = {
useTemplate = [ "backup" ];
};
};
services.github-runners = {
infrastructure = {
enable = true;
name = "infrastructure-runner";
user = "github";
tokenFile = "/data/runner/github-infrastructure-token.txt";
url = "https://github.com/alexmickelson/infrastructure";
extraLabels = [ "home-server" ];
#workDir = "/data/runner/infrastructure/";
replace = true;
serviceOverrides = {
ReadWritePaths = [
"/data/cloudflare/"
"/data/runner/infrastructure"
"/data/runner"
"/home/github/infrastructure"
];
PrivateDevices = false;
DeviceAllow = "/dev/zfs rw";
ProtectProc = false;
ProtectSystem = false;
PrivateMounts = false;
PrivateUsers = false;
#DynamicUser = true;
#NoNewPrivileges = false;
ProtectHome = false;
#RuntimeDirectoryPreserve = "yes";
};
extraPackages = with pkgs; [
docker
git-secret
zfs
sanoid
mbuffer
lzop
];
};
};
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
networking.firewall.enable = false;
# networking.firewall.trustedInterfaces = [ "docker0" ];
# This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions
# on your system were taken. Its perfectly fine and recommended to leave
# this value at the release version of the first install of this system.
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "24.05"; # Did you read the comment?
}

View File

@@ -0,0 +1,198 @@
#
# Configuration file for the CUPS scheduler. See "man cupsd.conf" for a
# complete description of this file.
#
# Log general information in error_log - change "warn" to "debug"
# for troubleshooting...
LogLevel warn
PageLogFormat
# Specifies the maximum size of the log files before they are rotated. The value "0" disables log rotation.
MaxLogSize 0
# Default error policy for printers
ErrorPolicy retry-job
# Allow remote access
Listen *:631 # important
ServerAlias * # important
# Show shared printers on the local network.
Browsing Yes
BrowseLocalProtocols dnssd
# Default authentication type, when authentication is required...
DefaultAuthType Basic
DefaultEncryption IfRequested
# Web interface setting...
WebInterface Yes
# Timeout after cupsd exits if idle (applied only if cupsd runs on-demand - with -l)
IdleExitTimeout 60
# Restrict access to the server...
<Location />
Order allow,deny
Allow all
</Location>
# Restrict access to the admin pages...
<Location /admin>
Order allow,deny
Allow all
</Location>
# Restrict access to configuration files...
<Location /admin/conf>
AuthType Default
Require user @SYSTEM
Order allow,deny
Allow all
</Location>
# Restrict access to log files...
<Location /admin/log>
AuthType Default
Require user @SYSTEM
Order allow,deny
Allow all
</Location>
# Set the default printer/job policies...
<Policy default>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
Order deny,allow
# Allow all # mine...
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default CUPS-Get-Devices>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
<Limit All>
Order deny,allow
# Allow all # mine
</Limit>
</Policy>
# Set the authenticated printer/job policies...
<Policy authenticated>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
AuthType Default
Order deny,allow
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
AuthType Default
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
AuthType Default
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
<Limit All>
Order deny,allow
</Limit>
</Policy>
# Set the kerberized printer/job policies...
<Policy kerberos>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
AuthType Negotiate
Order deny,allow
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
AuthType Negotiate
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
AuthType Negotiate
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
<Limit All>
Order deny,allow
</Limit>
</Policy>

View File

@@ -0,0 +1,19 @@
version: "3.8"
services:
cups:
image: olbat/cupsd:stable-2024-01-19 # admin user/password: print/print
container_name: cups
privileged: true
volumes:
- "/dev/bus/usb:/dev/bus/usb" # keep this under volumes, not devices
- "/run/dbus:/run/dbus"
- "./cupsd.conf:/etc/cups/cupsd.conf:ro"
#- "./data/printers.conf:/etc/cups/printers.conf:ro"
ports:
- "631:631/tcp" # CUPS
restart: "always"
cups-webpage:
buid: server
ports:
- 6311:6311

View File

@@ -0,0 +1,24 @@
# Printer configuration file for CUPS v2.4.2
# Written by cupsd
# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING
NextPrinterId 2
<Printer Brother_HL-L2300D_series>
PrinterId 1
UUID urn:uuid:8ac038d7-8659-3de9-57d0-0a7f97b956cc
Info Brother HL-L2300D series
Location
MakeModel Brother HL-L2300D series, using brlaser v6
DeviceURI usb://Brother/HL-L2300D%20series?serial=U63878J0N375067
State Idle
StateTime 1714021523
ConfigTime 1714021523
Type 4180
Accepting Yes
Shared Yes
JobSheets none none
QuotaPeriod 0
PageLimit 0
KLimit 0
OpPolicy default
ErrorPolicy retry-job
</Printer>

View File

@@ -0,0 +1,13 @@
## what I am running on office server
```bash
sudo apt install python3-pip cups python3-cups hplip
pip install pycups fastapi "uvicorn[standard]" python-multipart
sudo hp-setup -i # manually configure printer...
python -m uvicorn print_api:app --reload --host 0.0.0.0
```
url: http://100.103.75.97:8000/

View File

@@ -0,0 +1,11 @@
FROM python:3
RUN apt-get update \
&& apt-get install -y libcups2-dev python3-pip cups python3-cups gcc \
&& pip install pycups fastapi "uvicorn[standard]" python-multipart
WORKDIR /app
COPY ./src .
CMD python -m uvicorn print_api:app --reload --host 0.0.0.0 --port 6311

27
home-server/printing/server/flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1725634671,
"narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -0,0 +1,38 @@
{
description = "Printer Server Flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
myPython = pkgs.python3.withPackages (python-pkgs: with pkgs; [
python312Packages.fastapi
python312Packages.fastapi-cli
python312Packages.pycups
python312Packages.python-multipart
python312Packages.uvicorn
]);
in
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
python312Packages.fastapi
python312Packages.fastapi-cli
python312Packages.pycups
python312Packages.python-multipart
python312Packages.uvicorn
];
};
packages.${system} = rec {
fastapi-server = pkgs.writeShellScriptBin "start-server" ''
${myPython}/bin/fastapi run ${self}/src/print_api.py
'';
default = fastapi-server;
};
};
}

View File

@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html>
<head>
<title>Document Upload</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
height: 100vh;
background: #09373e;
color: #85bfc8;
}
#form-container {
display: flex;
justify-content: center;
align-items: center;
}
form {
border: 1px solid #ccc;
padding: 20px;
background: #05252a;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h2 {
margin-bottom: 20px;
text-align: center;
}
input[type="file"] {
display: none; /* Hide the file input */
}
input[type="submit"] {
padding: 10px 20px;
border: none;
background: #007bff;
color: #fff;
font-size: 16px;
cursor: pointer;
}
input[type="submit"]:hover {
background: #0056b3;
}
#dropzone {
border: 2px dashed #ccc;
padding: 20px;
width: 300px;
text-align: center;
color: #ccc;
cursor: pointer;
}
#dropzone.dragover {
border-color: #000;
color: #000;
}
</style>
</head>
<body>
<h2>Upload Document</h2>
<br />
<section id="form-container">
<form
id="printForm"
action="/print/"
method="post"
enctype="multipart/form-data"
>
<div id="dropzone">Drop file to upload or click to select</div>
<input type="file" id="fileInput" />
<br />
<input type="submit" value="Upload Document" name="submit" />
</form>
</section>
<script>
var stagedFile = undefined;
const formElement = document.getElementById("printForm");
const fileInputElement = document.getElementById("fileInput");
formElement.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("file", stagedFile);
const response = await fetch("/print/", {
method: "POST",
body: formData,
});
const data = await response.json();
console.log(data);
});
document
.getElementById("dropzone")
.addEventListener("dragover", function (event) {
event.preventDefault(); // Prevent default behavior (Prevent file from being opened)
event.stopPropagation();
event.target.classList.add("dragover"); // Optional: add a style change
});
document
.getElementById("dropzone")
.addEventListener("dragleave", function (event) {
event.preventDefault();
event.stopPropagation();
event.target.classList.remove("dragover"); // Optional: revert style change
});
document
.getElementById("dropzone")
.addEventListener("drop", function (event) {
event.preventDefault();
event.stopPropagation();
event.target.classList.remove("dragover"); // Optional: revert style change
// Process files
var files = event.dataTransfer.files;
handleFiles(files);
});
// Handle file selection when clicked
document
.getElementById("dropzone")
.addEventListener("click", function () {
fileInputElement.click(); // Trigger the hidden file input's click
});
fileInputElement.addEventListener("change", function (event) {
const files = event.target.files;
handleFiles(files);
});
const handleFiles = (files) => {
stagedFile = files[0];
renderStagedFile();
};
const renderStagedFile = () => {
const element = document.getElementById("dropzone");
if (!stagedFile) {
element.textContent = "Drop file to upload or click to select";
} else {
element.textContent = `FILE: ${stagedFile.name}`;
}
};
</script>
</body>
</html>

View File

@@ -0,0 +1,69 @@
import os
from pprint import pprint
import tempfile
from fastapi import FastAPI, File, UploadFile, Request
import cups
from fastapi.responses import HTMLResponse
app = FastAPI()
# @app.post("/print/")
# async def print_document(file: UploadFile = File(...)):
# temp_file = tempfile.NamedTemporaryFile(delete=False)
# temp_file.write(await file.read())
# temp_file.close()
# conn = cups.Connection(host="server.alexmickelson.guru")
# printers = conn.getPrinters()
# print(file.filename)
# print(temp_file.name)
# pprint(printers)
# for printer in printers:
# print(printer, printers[printer]["device-uri"])
# default_printer = list(printers.keys())[0]
# job_id = conn.printFile(default_printer, temp_file.name, f"FastAPI Print Job for {temp_file.name}", {})
# os.unlink(temp_file.name)
# return {"job_id": job_id, "file_name": file.filename}
@app.post("/print/")
async def print_document(file: UploadFile = File(...)):
# Save the uploaded file to a temporary file
temp_file = tempfile.NamedTemporaryFile(delete=False)
temp_file.write(await file.read())
temp_file.close()
# Connect to the CUPS server on the host (use default CUPS connection)
conn = cups.Connection() # This will connect to localhost CUPS
# Get the list of available printers
printers = conn.getPrinters()
print(file.filename)
print(temp_file.name)
pprint(printers)
for printer in printers:
print(printer, printers[printer]["device-uri"])
# Use the default printer (first one in the list)
default_printer = list(printers.keys())[0]
# Print the file
job_id = conn.printFile(default_printer, temp_file.name, f"FastAPI Print Job for {temp_file.name}", {})
# Clean up the temporary file
os.unlink(temp_file.name)
return {"job_id": job_id, "file_name": file.filename}
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
with open('src/index.html', 'r') as f:
html_content = f.read()
return HTMLResponse(content=html_content, status_code=200)

View File

@@ -0,0 +1,52 @@
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
- job_name: "node"
static_configs:
- targets:
- 100.119.183.105:9100 # desktop
- 100.122.128.107:9100 # home server
- 100.64.229.40:9100 # linode
- job_name: "docker"
static_configs:
- targets:
# - 100.119.183.105:9323 # desktop
- 100.122.128.107:9323 # home server
- 100.64.229.40:9323 # linode
- job_name: ups
static_configs:
- targets:
- 100.122.128.107:9162 # home server
- job_name: homeassistant
scrape_interval: 60s
metrics_path: /api/prometheus
authorization:
credentials: '%{HOMEASSITANT_TOKEN}'
scheme: https
static_configs:
- targets: ['ha.alexmickelson.guru']