├── .gitignore ├── apps ├── matchbox │ ├── data │ │ └── config │ │ │ ├── assets │ │ │ └── .gitignore │ │ │ ├── groups │ │ │ └── .gitignore │ │ │ └── profiles │ │ │ └── .gitignore │ └── matchbox.container ├── Taskfile.yaml ├── node-exporter │ └── node-exporter.container ├── smartctl-exporter │ └── smartctl-exporter.container ├── README.md ├── podman-exporter │ └── podman-exporter.container └── dhcp-relay │ └── dhcp-relay.container ├── .vscode └── extensions.json ├── .github ├── labels.yaml ├── Taskfile.yaml └── workflows │ ├── label-sync.yaml │ └── renovate.yaml ├── .mise.toml ├── .editorconfig ├── .sops.yaml ├── .gitattributes ├── hack ├── ipvlanaddrs.sh └── unifi │ └── ipxe.conf ├── .renovaterc.json5 ├── Taskfile.yaml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .private/ 2 | .task/ 3 | age.key 4 | -------------------------------------------------------------------------------- /apps/matchbox/data/config/assets/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /apps/matchbox/data/config/groups/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /apps/matchbox/data/config/profiles/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /apps/Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: '3' 4 | 5 | includes: 6 | :dhcp-relay: 7 | taskfile: ./dhcp-relay 8 | optional: true 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EdwinKofler.vscode-assorted-languages", 4 | "hangxingliu.vscode-systemd-support", 5 | "mikestead.dotenv", 6 | "redhat.vscode-yaml", 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.github/labels.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: renovate/container 3 | color: "027fa0" 4 | - name: type/patch 5 | color: "ffeC19" 6 | - name: type/minor 7 | color: "ff9800" 8 | - name: type/major 9 | color: "f6412d" 10 | - name: hold 11 | color: "ee0701" 12 | -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | SOPS_AGE_KEY_FILE = "{{config_root}}/age.key" 3 | 4 | [tools] 5 | "aqua:cli/cli" = "2.68.1" 6 | "aqua:FiloSottile/age" = "1.2.1" 7 | "aqua:getsops/sops" = "3.9.4" 8 | "aqua:go-task/task" = "3.42.0" 9 | "aqua:mikefarah/yq" = "4.45.1" 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | indent_size = 4 15 | trim_trailing_whitespace = false 16 | 17 | [{*.sh,*.conf}] 18 | indent_style = space 19 | indent_size = 4 20 | -------------------------------------------------------------------------------- /.sops.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | creation_rules: 3 | - path_regex: containers/.+\.sops\.env 4 | input_type: dotenv 5 | key_groups: 6 | - age: 7 | - age1nz0njhy3v78hfwu7cx330ngu5eafvu5wtfufe9r9rqu9hhqltpxs26ukw7 8 | - path_regex: containers/.+\.sops\.json 9 | input_type: json 10 | key_groups: 11 | - age: 12 | - age1nz0njhy3v78hfwu7cx330ngu5eafvu5wtfufe9r9rqu9hhqltpxs26ukw7 13 | stores: 14 | yaml: 15 | indent: 2 16 | -------------------------------------------------------------------------------- /apps/node-exporter/node-exporter.container: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=node-exporter 3 | 4 | [Container] 5 | ContainerName=node-exporter 6 | Exec=--path.rootfs=/host 7 | Image=quay.io/prometheus/node-exporter:v1.9.0@sha256:c99d7ee4d12a38661788f60d9eca493f08584e2e544bbd3b3fca64749f86b848 8 | Network=host 9 | Volume=/:/host:ro,rslave 10 | 11 | [Service] 12 | Restart=always 13 | TimeoutStartSec=900 14 | 15 | [Install] 16 | WantedBy=multi-user.target default.target 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.container linguist-detectable linguist-language=desktop 3 | *.env linguist-detectable linguist-language=SHELL 4 | *.json linguist-detectable linguist-language=JSON 5 | *.json5 linguist-detectable linguist-language=JSON5 6 | *.md linguist-detectable linguist-language=MARKDOWN 7 | *.sh linguist-detectable linguist-language=SHELL 8 | *.toml linguist-detectable linguist-language=TOML 9 | *.yml linguist-detectable linguist-language=YAML 10 | *.yaml linguist-detectable linguist-language=YAML 11 | *.yaml.j2 linguist-detectable linguist-language=YAML 12 | -------------------------------------------------------------------------------- /apps/smartctl-exporter/smartctl-exporter.container: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=smartctl-exporter 3 | 4 | [Container] 5 | ContainerName=smartctl-exporter 6 | Exec=--smartctl.path=/usr/sbin/smartctl --smartctl.interval=120s --web.listen-address=0.0.0.0:9633 --web.telemetry-path=/metrics 7 | Image=quay.io/prometheuscommunity/smartctl-exporter:v0.13.0@sha256:1fc7f4a40ab0d3c56edb6cdfe37f0cd97731ea56a154a538afe2eca7b96b82ba 8 | Network=host 9 | Volume=/dev:/dev 10 | 11 | [Service] 12 | Restart=always 13 | TimeoutStartSec=900 14 | 15 | [Install] 16 | WantedBy=multi-user.target default.target 17 | -------------------------------------------------------------------------------- /apps/matchbox/matchbox.container: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=matchbox 3 | After=network-online.target 4 | Wants=network-online.target 5 | 6 | [Container] 7 | AddCapability=NET_BIND_SERVICE 8 | ContainerName=matchbox 9 | Exec=-address=0.0.0.0:80 -log-level=debug 10 | Image=quay.io/poseidon/matchbox:v0.11.0@sha256:06bcdae85335fd00e8277b007b55cfb49d96a0114628c0f70db2b92b079d246a 11 | IP=192.168.1.128 12 | Network=containernet 13 | Volume=./matchbox/config:/var/lib/matchbox 14 | 15 | [Service] 16 | Restart=always 17 | TimeoutStartSec=900 18 | 19 | [Install] 20 | WantedBy=multi-user.target default.target 21 | -------------------------------------------------------------------------------- /apps/README.md: -------------------------------------------------------------------------------- 1 | # apps 2 | 3 | ## dhcp-proxy 4 | 5 | 6 | 7 | ## matchbox 8 | 9 | 10 | 11 | ## node-exporter 12 | 13 | 14 | 15 | ## podman-exporter 16 | 17 | 18 | 19 | ### podman-exporter configuration 20 | 21 | 1. Enable the `podman.socket` service 22 | 23 | ```sh 24 | sudo systemctl enable --now podman.socket 25 | ``` 26 | 27 | 2. Start `podman-exporter` 28 | 29 | ```sh 30 | task start-podman-exporter 31 | ``` 32 | -------------------------------------------------------------------------------- /apps/podman-exporter/podman-exporter.container: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=podman-exporter 3 | After=podman.socket 4 | Requires=podman.socket 5 | 6 | [Container] 7 | ContainerName=podman-exporter 8 | Environment=CONTAINER_HOST=unix:///run/podman/podman.sock 9 | Exec=--collector.enhance-metrics 10 | Image=quay.io/navidys/prometheus-podman-exporter:v1.15.0@sha256:9f88003b6e5ef2de7bdcc1e576ca1e90d217667b83ba761721719c3d7273ee0a 11 | Network=host 12 | SecurityLabelDisable=true 13 | Volume=/run/podman/podman.sock:/run/podman/podman.sock 14 | 15 | [Service] 16 | Restart=always 17 | TimeoutStartSec=900 18 | 19 | [Install] 20 | WantedBy=multi-user.target default.target 21 | -------------------------------------------------------------------------------- /.github/Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: "3" 4 | 5 | vars: 6 | LABELS_CONFIG_FILE: '{{.ROOT_DIR}}/labels.yaml' 7 | 8 | x-preconditions: 9 | - &force 10 | sh: '[[ -z {{.CLI_FORCE}} ]]' 11 | - ¬-root 12 | sh: '[[ $LOGNAME != "root" ]]' 13 | 14 | tasks: 15 | 16 | append-app-labels: 17 | desc: Append container labels to the labels config file 18 | cmds: 19 | - for: { var: apps } 20 | cmd: | 21 | yq --inplace '. += [{"name": "app/{{.ITEM}}", "color": "0e8a16"}]' {{.LABELS_CONFIG_FILE}} 22 | vars: 23 | apps: 24 | sh: ls --directory {{.ROOT_DIR}}/../apps/*/ | xargs --max-args=1 basename 25 | preconditions: 26 | - *force 27 | - *not-root 28 | -------------------------------------------------------------------------------- /hack/ipvlanaddrs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare -A container_ips 4 | 5 | for file in apps/*/*.container; do 6 | ip=$(grep -oP '(?<=IP=).*' "$file") 7 | if [ -n "$ip" ]; then 8 | container_name=$(basename "${file%%.*}") 9 | container_ips["$container_name"]=$ip 10 | fi 11 | done 12 | 13 | # Sort the container names by IP address 14 | sorted_container_names=($(for ip in "${container_ips[@]}"; do echo "$ip"; done | sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4)) 15 | 16 | # Print the sorted results 17 | for ip in "${sorted_container_names[@]}"; do 18 | for container_name in "${!container_ips[@]}"; do 19 | if [[ "${container_ips[$container_name]}" == "$ip" ]]; then 20 | echo "Container Name: $container_name" 21 | echo "IP: $ip" 22 | echo "-----------------------------" 23 | fi 24 | done 25 | done 26 | -------------------------------------------------------------------------------- /hack/unifi/ipxe.conf: -------------------------------------------------------------------------------- 1 | # Unifi hack for getting conditional PXE/iPXE boot working 2 | 3 | # Add this file to the dnsmasq config directory 4 | # /run/dnsmasq.conf.d/ipxe.conf 5 | # Restart dnsmasq so it sees the new conf file 6 | # pkill dnsmasq 7 | 8 | # Legacy PXE 9 | dhcp-match=set:bios,option:client-arch,0 10 | dhcp-boot=tag:bios,undionly.kpxe,,192.168.1.42 11 | 12 | # UEFI 13 | dhcp-match=set:efi32,option:client-arch,6 14 | dhcp-boot=tag:efi32,ipxe.efi,,192.168.1.42 15 | dhcp-match=set:efibc,option:client-arch,7 16 | dhcp-boot=tag:efibc,ipxe.efi,,192.168.1.42 17 | dhcp-match=set:efi64,option:client-arch,9 18 | dhcp-boot=tag:efi64,ipxe.efi,,192.168.1.42 19 | dhcp-match=set:arm64,option:client-arch,11 20 | dhcp-boot=tag:arm64,ipxe-arm64.efi,,192.168.1.42 21 | 22 | # iPXE 23 | dhcp-userclass=set:ipxe,iPXE 24 | dhcp-boot=tag:ipxe,http://192.168.1.128/boot.ipxe,,192.168.1.42 25 | 26 | # TFTP 27 | dhcp-option=66,192.168.1.42 28 | -------------------------------------------------------------------------------- /apps/dhcp-relay/dhcp-relay.container: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=dhcp-relay 3 | 4 | [Container] 5 | AddCapability=NET_BIND_SERVICE 6 | AddCapability=NET_ADMIN 7 | AddCapability=NET_RAW 8 | ContainerName=dhcp-relay 9 | Exec=--log-dhcp --log-queries --no-daemon \ 10 | --port=0 \ 11 | --dhcp-range=192.168.1.1,proxy,255.255.255.0 \ 12 | --enable-tftp \ 13 | --tftp-root=/var/lib/tftpboot \ 14 | --pxe-service=net:#ipxe,x86PC,,undionly.kpxe \ 15 | --pxe-service=net:#ipxe,X86-64_EFI,,ipxe.efi \ 16 | --dhcp-match=set:bios,option:client-arch,0 \ 17 | --dhcp-boot=tag:bios,undionly.kpxe \ 18 | --dhcp-match=set:efi32,option:client-arch,6 \ 19 | --dhcp-boot=tag:efi32,ipxe.efi \ 20 | --dhcp-match=set:efibc,option:client-arch,7 \ 21 | --dhcp-boot=tag:efibc,ipxe.efi \ 22 | --dhcp-match=set:efi64,option:client-arch,9 \ 23 | --dhcp-boot=tag:efi64,ipxe.efi \ 24 | --dhcp-match=set:arm64,option:client-arch,11 \ 25 | --dhcp-boot=tag:arm64,ipxe-arm64.efi \ 26 | --dhcp-userclass=set:ipxe,iPXE \ 27 | --dhcp-boot=tag:ipxe,http://192.168.1.128/boot.ipxe 28 | Image=quay.io/poseidon/dnsmasq:v0.5.0-41-g0212fd2@sha256:303aec739ff1aa419d2623a898ce8efaa6b05ca059165f531432b114b5639ef1 29 | Network=host 30 | 31 | [Service] 32 | Restart=always 33 | TimeoutStartSec=900 34 | 35 | [Install] 36 | WantedBy=multi-user.target default.target 37 | -------------------------------------------------------------------------------- /.github/workflows/label-sync.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: "Label Sync" 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: ["main"] 9 | paths: 10 | - .github/labels.yaml 11 | - .github/workflows/label-sync.yaml 12 | - apps/** 13 | schedule: 14 | - cron: "0 0 * * *" # Every day at midnight 15 | 16 | jobs: 17 | label-sync: 18 | name: Label Sync 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Generate Token 22 | uses: actions/create-github-app-token@v1 23 | id: app-token 24 | with: 25 | app-id: "${{ secrets.BOT_APP_ID }}" 26 | private-key: "${{ secrets.BOT_APP_PRIVATE_KEY }}" 27 | 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | with: 31 | token: "${{ steps.app-token.outputs.token }}" 32 | 33 | - name: Setup Homebrew 34 | uses: Homebrew/actions/setup-homebrew@master 35 | 36 | - name: Setup Workflow Tools 37 | shell: bash 38 | run: brew install go-task yq 39 | 40 | - name: Append app labels to the labels config file 41 | shell: bash 42 | working-directory: .github 43 | run: task append-app-labels --force 44 | 45 | - name: Sync Labels 46 | uses: EndBug/label-sync@v2 47 | with: 48 | token: "${{ steps.app-token.outputs.token }}" 49 | config-file: .github/labels.yaml 50 | delete-other-labels: true 51 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Renovate 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | dryRun: 9 | description: Dry Run 10 | default: "false" 11 | required: false 12 | logLevel: 13 | description: Log Level 14 | default: debug 15 | required: false 16 | version: 17 | description: Renovate version 18 | default: latest 19 | required: false 20 | schedule: 21 | - cron: "0 * * * *" 22 | push: 23 | branches: ["main"] 24 | paths: 25 | - .renovaterc.json5 26 | - .renovate/**.json5 27 | 28 | concurrency: 29 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 30 | cancel-in-progress: true 31 | 32 | env: 33 | LOG_LEVEL: "${{ inputs.logLevel || 'debug' }}" 34 | RENOVATE_AUTODISCOVER: true 35 | RENOVATE_AUTODISCOVER_FILTER: "${{ github.repository }}" 36 | RENOVATE_DRY_RUN: "${{ inputs.dryRun == true }}" 37 | RENOVATE_PLATFORM: github 38 | RENOVATE_PLATFORM_COMMIT: true 39 | WORKFLOW_RENOVATE_VERSION: "${{ inputs.version || 'latest' }}" 40 | 41 | jobs: 42 | main: 43 | name: Renovate 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Generate Token 47 | uses: actions/create-github-app-token@v1 48 | id: app-token 49 | with: 50 | app-id: "${{ secrets.BOT_APP_ID }}" 51 | private-key: "${{ secrets.BOT_APP_PRIVATE_KEY }}" 52 | 53 | - name: Checkout 54 | uses: actions/checkout@v4 55 | with: 56 | token: "${{ steps.app-token.outputs.token }}" 57 | 58 | - name: Run Renovate 59 | uses: renovatebot/github-action@v41.0.14 60 | with: 61 | token: "${{ steps.app-token.outputs.token }}" 62 | renovate-version: "${{ env.WORKFLOW_RENOVATE_VERSION }}" 63 | -------------------------------------------------------------------------------- /.renovaterc.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "docker:enableMajor", 6 | ":automergeBranch", 7 | ":disableRateLimiting", 8 | ":dependencyDashboard", 9 | ":semanticCommits", 10 | ":skipStatusChecks", 11 | ":timezone(America/New_York)" 12 | ], 13 | "dependencyDashboardTitle": "Renovate Dashboard 🤖", 14 | "suppressNotifications": ["prEditedNotification", "prIgnoreNotification"], 15 | "customManagers": [ 16 | { 17 | "customType": "regex", 18 | "description": ["Process container versions"], 19 | "fileMatch": ["(^|/)apps/.+\\.container$"], 20 | "matchStrings": [ 21 | "Image=(?[^:]+):(?[^\\s@]+)(@(?sha256:[0-9a-f]+))?" 22 | ], 23 | "datasourceTemplate": "docker", 24 | "versioningTemplate": "docker" 25 | } 26 | ], 27 | "packageRules": [ 28 | { 29 | "addLabels": ["renovate/container", "type/major"], 30 | "additionalBranchPrefix": "{{parentDir}}-", 31 | "commitMessageExtra": " ( {{currentVersion}} → {{newVersion}} )", 32 | "commitMessagePrefix": "feat({{parentDir}})!: ", 33 | "commitMessageTopic": "{{depName}}", 34 | "labels": ["app/{{parentDir}}"], 35 | "matchDatasources": ["docker"], 36 | "matchFileNames": ["apps/**/*.container"], 37 | "matchUpdateTypes": ["major"] 38 | }, 39 | { 40 | "addLabels": ["renovate/container", "type/minor"], 41 | "additionalBranchPrefix": "{{parentDir}}-", 42 | "commitMessageExtra": "( {{currentVersion}} → {{newVersion}} )", 43 | "commitMessageTopic": "{{depName}}", 44 | "labels": ["app/{{parentDir}}"], 45 | "matchDatasources": ["docker"], 46 | "matchFileNames": ["apps/**/*.container"], 47 | "matchUpdateTypes": ["minor"], 48 | "semanticCommitScope": "{{parentDir}}", 49 | "semanticCommitType": "feat" 50 | }, 51 | { 52 | "addLabels": ["renovate/container", "type/patch"], 53 | "additionalBranchPrefix": "{{parentDir}}-", 54 | "commitMessageExtra": "( {{currentVersion}} → {{newVersion}} )", 55 | "commitMessageTopic": "{{depName}}", 56 | "labels": ["app/{{parentDir}}"], 57 | "matchDatasources": ["docker"], 58 | "matchFileNames": ["apps/**/*.container"], 59 | "matchUpdateTypes": ["patch"], 60 | "semanticCommitScope": "{{parentDir}}", 61 | "semanticCommitType": "fix" 62 | } 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: '3' 4 | 5 | env: 6 | SOPS_AGE_KEY_FILE: '{{.ROOT_DIR}}/age.key' 7 | 8 | includes: 9 | app: 10 | taskfile: ./apps 11 | optional: true 12 | 13 | tasks: 14 | 15 | default: 16 | cmd: task -l 17 | silent: true 18 | 19 | # https://github.com/coreos/rpm-ostree/issues/2127 20 | deps: 21 | desc: Install System Deps 22 | cmds: 23 | - sudo rpm-ostree install --idempotent --assumeyes bind-utils expect fastfetch fish fzf gron htop moreutils nano net-tools netcat nmap rsync smartmontools starship systemd-networkd tcpdump telnet tree wget zoxide 24 | # - sudo rpm-ostree install --idempotent --assumeyes https://downloads.1password.com/linux/rpm/stable/x86_64/1password-cli-latest.x86_64.rpm --uninstall 1password-cli 25 | - sudo rpm-ostree status -v 26 | # - sudo systemctl reboot 27 | 28 | start-*: 29 | desc: Start an app 30 | cmds: 31 | - task: reload-{{.APP}} 32 | - sudo systemctl start {{.APP}} 33 | - sudo systemctl is-active --quiet {{.APP}} || exit 1 34 | - sudo podman ps --all --filter name={{.APP}} --format "{{ `{{.Names}} - {{.Image}} - {{.Status}}` }}" 35 | vars: 36 | APP: '{{index .MATCH 0}}' 37 | 38 | stop-*: 39 | desc: Stop an app 40 | cmd: sudo systemctl stop {{.APP}} 41 | vars: 42 | APP: '{{index .MATCH 0}}' 43 | preconditions: 44 | - sh: systemctl status {{.APP}} 45 | 46 | restart-*: 47 | desc: Restart an app 48 | cmds: 49 | - task: reload-{{.APP}} 50 | - sudo systemctl restart {{.APP}} 51 | - sudo systemctl is-active --quiet {{.APP}} || exit 1 52 | - sudo podman ps --all --filter name={{.APP}} --format "{{ `{{.Names}} - {{.Image}} - {{.Status}}` }}" 53 | vars: 54 | APP: '{{index .MATCH 0}}' 55 | preconditions: 56 | - sh: systemctl status {{.APP}} 57 | 58 | status-*: 59 | desc: Status of an app 60 | cmd: sudo systemctl status {{.APP}} 61 | vars: 62 | APP: '{{index .MATCH 0}}' 63 | preconditions: 64 | - sh: systemctl status {{.APP}} 65 | 66 | reload-*: 67 | desc: Reload an app 68 | cmds: 69 | - sudo rsync -rv {{.ROOT_DIR}}/apps/{{.APP}}/{{.APP}}.container /etc/containers/systemd/ 70 | - | 71 | if test -d {{.ROOT_DIR}}/apps/{{.APP}}/data; then 72 | sudo rsync -rv --mkpath --delete {{.ROOT_DIR}}/apps/{{.APP}}/data/{{- if eq .CLI_FORCE false }}config/{{ end }} /etc/containers/systemd/{{.APP}}{{- if eq .CLI_FORCE false }}/config{{ end }} 73 | sudo --preserve-env bash -c "find /etc/containers/systemd/{{.APP}}/config -type f -name "*.sops.*" -print0 | xargs -0 -I {} sops --config {{.ROOT_DIR}}/.sops.yaml --decrypt --in-place {}" 74 | fi 75 | - sudo systemctl daemon-reload 76 | vars: 77 | APP: '{{index .MATCH 0}}' 78 | label: reload-{{.APP}} 79 | sources: 80 | - '{{.ROOT_DIR}}/apps/{{.APP}}/{{.APP}}.container' 81 | - '{{.ROOT_DIR}}/apps/{{.APP}}/data/config/**/**' 82 | generates: 83 | - /etc/containers/systemd/{{.APP}}.container 84 | - /etc/containers/systemd/{{.APP}}/config/**/** 85 | - /run/systemd/generator/{{.APP}}.service 86 | preconditions: 87 | - sh: test -f {{.ROOT_DIR}}/apps/{{.APP}}/{{.APP}}.container 88 | 89 | remove-*: 90 | desc: Remove an app 91 | prompt: Remove the '{{.APP}}' container ... continue? 92 | cmds: 93 | - task: stop-{{.APP}} 94 | - sudo rm /etc/containers/systemd/{{.APP}}.container 95 | - sudo rm -rf /etc/containers/systemd/{{.APP}} 96 | - sudo rm -rf /run/systemd/generator/{{.APP}}.service 97 | - sudo systemctl daemon-reload 98 | vars: 99 | APP: '{{index .MATCH 0}}' 100 | 101 | logs-*: 102 | desc: Tail logs of a app 103 | cmd: sudo podman logs -f {{.APP}} 104 | vars: 105 | APP: '{{index .MATCH 0}}' 106 | preconditions: 107 | - sh: sudo podman inspect {{.APP}} 108 | 109 | list: 110 | desc: List all apps 111 | cmd: sudo podman ps --all --format '{{ `{{.Names}}\t{{.Status}}\t{{.Networks}}` }}' --sort names | column -s$'\t' --table 112 | 113 | dotfiles: 114 | desc: Setup dotfiles 115 | cmds: 116 | - | # Nano 117 | git -C ~/.nano pull || git clone https://github.com/galenguyer/nano-syntax-highlighting ~/.nano 118 | - | # Fish Hooks 119 | tee /home/$LOGNAME/.config/fish/conf.d/hooks.fish > /dev/null < /dev/null < ~/.ssh/authorized_keys 32 | sudo install -d -o $(logname) -g $(logname) -m 755 /var/opt/home-service 33 | git clone git@github.com:$GITHUB_USER/home-service.git /var/opt/home-service/. 34 | ``` 35 | 36 | 3. Install additional system deps and reboot 37 | 38 | ```sh 39 | cd /var/opt/home-service 40 | task deps 41 | sudo systemctl reboot 42 | ``` 43 | 44 | 4. Create an Age public/private key pair for use with sops 45 | 46 | ```sh 47 | age-keygen -o /var/opt/home-service/age.key 48 | ``` 49 | 50 | ### Network configuration 51 | 52 | > [!NOTE] 53 | > _I am using [ipvlan](https://docs.docker.com/network/drivers/ipvlan) to expose most containers on their own IP addresses on the same network as this here device, the available addresses are mentioned in the `--ip-range` flag below. **Beware** of **IP addressing** and **interface names**._ 54 | 55 | 1. Create the podman `containernet` network 56 | 57 | ```sh 58 | sudo podman network create \ 59 | --driver=ipvlan \ 60 | --ipam-driver=host-local \ 61 | --subnet=192.168.1.0/24 \ 62 | --gateway=192.168.1.1 \ 63 | --ip-range=192.168.1.121-192.168.1.149 \ 64 | containernet 65 | ``` 66 | 67 | 2. Setup the currently used interface with `systemd-networkd` 68 | 69 | 📍 _Setting the DNS server to a DNS container used on this system might make dragons appear 🐉._ 70 | 71 | ```sh 72 | sudo bash -c 'cat << EOF > /etc/systemd/network/enp1s0.network 73 | [Match] 74 | Name = enp1s0 75 | [Network] 76 | DHCP = yes 77 | DNS = 192.168.1.1 78 | IPVLAN = containernet 79 | [DHCPv4] 80 | UseDNS = false' 81 | ``` 82 | 83 | 3. Setup `containernet` with `systemd-networkd` 84 | 85 | ```sh 86 | sudo bash -c 'cat << EOF > /etc/systemd/network/containernet.netdev 87 | [NetDev] 88 | Name = containernet 89 | Kind = ipvlan' 90 | sudo bash -c 'cat << EOF > /etc/systemd/network/containernet.network 91 | [Match] 92 | Name = containernet 93 | [Network] 94 | IPForward = yes 95 | Address = 192.168.1.120/24' 96 | ``` 97 | 98 | 4. Disable `networkmanager`, the enable and start `systemd-networkd` 99 | 100 | ```sh 101 | sudo systemctl disable --now NetworkManager 102 | sudo systemctl enable systemd-networkd 103 | sudo systemctl start systemd-networkd 104 | ``` 105 | 106 | ### Container configuration 107 | 108 | > [!TIP] 109 | > _To encrypt files with sops **replace** the **public key** in the `.sops.yaml` file with **your Age public key**. The format should look similar to the one already present._ 110 | 111 | View the [apps](./apps) directory for documentation on configuring an app container used here, or setup your own by reviewing the structure of this repository. 112 | 113 | Using the included [Taskfile](./Taskfile.yaml) there are helper commands to start, stop, restart containers and more. Run the command below to view all available tasks. 114 | 115 | ```sh 116 | go-task --list 117 | ``` 118 | 119 | ### Optional configuration 120 | 121 | #### Fish shell 122 | 123 | > [!TIP] 124 | > _🐟 [fish](https://fishshell.com/) is awesome, you should try fish!_ 125 | 126 | ```sh 127 | chsh -s /usr/bin/fish 128 | # IMPORTANT: Log out and log back in 129 | task dotfiles 130 | ``` 131 | 132 | #### Enable Chrony as a NTP server 133 | 134 | > [!TIP] 135 | > _⌚ You can also update `/etc/chrony.conf` with custom NTP servers._ 136 | 137 | ```sh 138 | sudo sed -i 's/^#allow .*/allow all/g' /etc/chrony.conf 139 | sudo systemctl restart chronyd 140 | ``` 141 | 142 | #### Tune selinux 143 | 144 | ```sh 145 | sudo sed -i 's/SELINUX=enforcing/SELINUX=permissive/g' /etc/selinux/config 146 | sudo systemctl reboot 147 | ``` 148 | 149 | #### Disable firewalld 150 | 151 | ```sh 152 | sudo systemctl disable --now firewalld.service 153 | ``` 154 | 155 | ## Network topology 156 | 157 | | Name | Subnet | DHCP range | ARP reserved | 158 | |------|--------|------------|--------------| 159 | | LAN | 192.168.1.0/24 | 150-254 | 120-149 | 160 | | TRUSTED | 192.168.10.0/24 | 150-254 | - | 161 | | SERVERS | 192.168.42.0/24 | 150-254 | 120-149 | 162 | | GUESTS | 192.168.50.0/24 | 150-254 | - | 163 | | IOT | 192.168.70.0/24 | 150-254 | - | 164 | | WIREGUARD | 192.168.80.0/28 | - | - | 165 | 166 | ## Related Projects 167 | 168 | - [bjw-s/nix-config](https://github.com/bjw-s/nix-config/): NixOS driven configuration for running a home service machine, a nas or [nix-darwin](https://github.com/LnL7/nix-darwin) using [deploy-rs](https://github.com/serokell/deploy-rs) and [home-manager](https://github.com/nix-community/home-manager). 169 | - [truxnell/nix-config](https://github.com/truxnell/nix-config): NixOS driven configuration for running your entire homelab. 170 | - [joryirving/home-service](https://github.com/joryirving/home-service/): Docker-compose implementation of this repository. 171 | --------------------------------------------------------------------------------