Compare commits
272 Commits
da669efee2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| d728722274 | |||
| 10fb8e88ce | |||
| 5f300d9e7b | |||
| 99ac2ea3b0 | |||
| 2caa36c0ab | |||
| 55c15c790d | |||
| 450d9d6457 | |||
| e08bf42eaa | |||
| 8e0e5c0d16 | |||
| ada9db7942 | |||
| 5995612407 | |||
| 5762916970 | |||
| dd456eab69 | |||
| 18a8fde66e | |||
| f97c9185c1 | |||
| 8bf4b185a1 | |||
| 8424d771f6 | |||
| 840f99a7e9 | |||
| 1b27bafd41 | |||
| 4770d671c0 | |||
| 28a7bed3b9 | |||
| 170becceb0 | |||
| 6e8f530537 | |||
| 209bafd70f | |||
| 1d182437db | |||
| 6c046a549e | |||
| 0a30a2ac23 | |||
| 82c15e8d26 | |||
| f277d089bd | |||
| 7ed345b8e8 | |||
| bd6b15b617 | |||
| 3282b7d634 | |||
| 21ed381d18 | |||
| 4500f41983 | |||
| 1d30eeb939 | |||
| 537f144885 | |||
| dbada3c509 | |||
| c8be707420 | |||
| fdba2c75c7 | |||
| 55d600c0c0 | |||
| 71c5bd5e6c | |||
| 998f04713f | |||
| 6935fbea8b | |||
| 301e090251 | |||
| 58d8ef050c | |||
| 41cb2ec791 | |||
| 1faec5b2d1 | |||
| 111b8cec97 | |||
| 3aaebdb1c4 | |||
| 3e7b8c93e3 | |||
| 3e2f46377e | |||
| 38bead3dc8 | |||
| 351d36b217 | |||
| 59a37c9b46 | |||
| d7d3722ce7 | |||
| 6475524d23 | |||
| 1a70ca9564 | |||
| d6f206f0bb | |||
| b3c5366f31 | |||
| fab06ca4d5 | |||
| bd1d04943d | |||
| 8305d1b0c5 | |||
| 2d812c03eb | |||
| 156e63fd6c | |||
| a912c4dc55 | |||
| 8a2a68a91c | |||
| 01d3ab1357 | |||
| 20c5af7a69 | |||
| 865311bf49 | |||
| 9fab06795a | |||
| 038fb7ae76 | |||
| 3775e0dd7b | |||
| 66a5d69846 | |||
| 8747f887f8 | |||
| 39f4460e0a | |||
| 6f8626ca8a | |||
| 04c08bf419 | |||
| 709a24366a | |||
| 63dad8c626 | |||
| b57342f53e | |||
| 7cefa3a650 | |||
| 5d54ae898e | |||
| 794d5c2dad | |||
| 04cdf1bd2f | |||
| b4c4e31437 | |||
| 56bb321e4a | |||
| c0d868088e | |||
| df5c89f071 | |||
| b73bc3e80a | |||
| db25b2bfbb | |||
| 819bfc1531 | |||
| cfdb8d8474 | |||
| d50ed9858c | |||
| 7af4b6a5d1 | |||
| ca04f5d8c3 | |||
| a02cefc62a | |||
| 28974e9688 | |||
| aaf5f79895 | |||
| eccac4d4a2 | |||
| 399f67ba25 | |||
| 439a580dfe | |||
| bfae290927 | |||
| 1eeb0b7102 | |||
| f49ac19af1 | |||
| 27c85ff9d0 | |||
| b6d44b5a20 | |||
| b8b7574536 | |||
| 5ee2cb2b56 | |||
| 6c88bc1790 | |||
| 6be832b012 | |||
| fc9ef6b9ff | |||
| ec19103a81 | |||
| 7499a21cbd | |||
| 5758b3a320 | |||
| 7d2f818fca | |||
| 19d0946e06 | |||
| 7d5294e7b9 | |||
| 28ed3fcf74 | |||
| 5a35cd04a6 | |||
| 5648224062 | |||
| bbb9cacd71 | |||
| 40743442e9 | |||
| 7564c5d740 | |||
| 3a6d5bb8c4 | |||
| f256ca7fad | |||
| cb18e436ca | |||
| 019b1166ec | |||
| cc15f27205 | |||
| 356c049aaf | |||
| df6465fa8a | |||
| a05c33ad87 | |||
| 67906cbf16 | |||
| 09e381ecc4 | |||
| 7fd35b79c4 | |||
| c9900e4314 | |||
| 5ea3bac570 | |||
| eae7bb0e09 | |||
| 465daec0ab | |||
| f516f46b06 | |||
| 742d0172cf | |||
| e0568ddfdc | |||
| 9941dfa61f | |||
| aac9e9f38f | |||
| bdda87778c | |||
| fccec6d87c | |||
| 5e259e0b42 | |||
| 496c483050 | |||
| 1433f88d53 | |||
| 506c4f9357 | |||
| a4ed475237 | |||
| de43e917c5 | |||
| be515979cf | |||
| cc03069d57 | |||
| fe7aaadf64 | |||
| 0b6549a359 | |||
| af60555eea | |||
| 64334192de | |||
| 4751fb5582 | |||
| 34e56a13ea | |||
| c3f2603702 | |||
| 15f6b2edd0 | |||
| 6339b733c4 | |||
| 305ce21e41 | |||
| 8ab1c91b38 | |||
| bf5c7a74cb | |||
| b48ec98cb3 | |||
| 58089e558e | |||
| 97b6874258 | |||
| 8ad0c4d336 | |||
| 536fc2b463 | |||
| b7287b0d51 | |||
| a0ffb52f98 | |||
| eb40b7ff06 | |||
| b3a71cb9bc | |||
| 16594b3e7d | |||
| 7937e00018 | |||
| 0e91e1e7f5 | |||
| 99b387fe8b | |||
| fe53ea7551 | |||
| 541f9b3776 | |||
| 1c9302c773 | |||
| ba9ef3913d | |||
| 79b4a615f0 | |||
| 7225a5e787 | |||
| 467ade9340 | |||
| 619136674e | |||
| 3990566fe5 | |||
| 7f01dc4cac | |||
| da95b2fa71 | |||
| 9b628caaef | |||
| 4ec924b736 | |||
| 2712bd2197 | |||
| 03d3ff5712 | |||
| 6aeb0c9f89 | |||
| 91394ef68a | |||
| a7d304cc5b | |||
| 0b8619bf64 | |||
| 30e75d0ad5 | |||
| a18a0e913d | |||
| ecf3e03e81 | |||
| 934471bd88 | |||
| 0fff2f87a5 | |||
| e8bf13275e | |||
| 436903543b | |||
| c47f678220 | |||
| d4438c8585 | |||
| 0df4a4c1ec | |||
| 365d15767b | |||
| 4969520222 | |||
| 9cfd7f5052 | |||
| faad280aa0 | |||
| f1ea4b9b20 | |||
| b6b90bca7d | |||
| 39b9726be7 | |||
| 7fc3c3db63 | |||
| 89b2a1cf45 | |||
| 94ee6bc9a4 | |||
| 81f04c6c51 | |||
| d0c67baeb8 | |||
| 35fa61ef34 | |||
| 8b5fb0861d | |||
| 17a3602d3c | |||
| fa42667c2a | |||
| d161d5f421 | |||
| a36b1e8310 | |||
| 640ad93684 | |||
| 51a3a10701 | |||
| cf340ca277 | |||
| c0d51ee06d | |||
| 53d73142ae | |||
| 8e52274edd | |||
| cbde498ae8 | |||
| 9feace9558 | |||
| 2a5496118b | |||
| f362b2ab77 | |||
| 7a8cd490d5 | |||
| 5a20d97084 | |||
| 8def0af08f | |||
| 348d8e1d03 | |||
| a078503a89 | |||
| bc57914131 | |||
| 3b01625f7d | |||
| 8cf4762a65 | |||
| 6f9b384caa | |||
| 7ac54dd987 | |||
| 57684db260 | |||
| c20998d365 | |||
| 2f1d88b001 | |||
| b8453eaf43 | |||
| 87d22fba6d | |||
| e4eb5c80fc | |||
| c8e3542fe8 | |||
| 0ac30a5190 | |||
| c02651e65a | |||
| 9a5a28098c | |||
| 9cfc423a38 | |||
| 3b043eaf6d | |||
| 386b70314d | |||
| e2add63337 | |||
| 7de9b583d5 | |||
| 406c0f539e | |||
| 1651b8a550 | |||
| ff2fdd3c08 | |||
| 35ad68fbbe | |||
| bd4503c035 | |||
| c423af5498 | |||
| a2d482e16d | |||
| 12ef36af33 | |||
| 6ae6c5e0e5 | |||
| 44b47ce18c | |||
| c96c24f864 | |||
| df50e70f3e |
106
.chatgpt_config.yaml
Normal file
106
.chatgpt_config.yaml
Normal file
@@ -0,0 +1,106 @@
|
||||
project_name: "cloonar-nixos"
|
||||
default_prompt_blocks:
|
||||
- "basic-prompt"
|
||||
- "secure-coding"
|
||||
initial_prompt: |
|
||||
You are helping me build or refine a NixOS configuration (potentially with Nix Flakes). Please keep the following points in mind when generating or explaining code:
|
||||
|
||||
1. **Project & Directory Structure**
|
||||
- For single-host configurations, you may have a simple structure like:
|
||||
```
|
||||
/etc/nixos/
|
||||
├── configuration.nix
|
||||
├── hardware-configuration.nix
|
||||
└── other-module.nix
|
||||
```
|
||||
- For multi-host setups or more complex deployments, consider **modules** in a dedicated folder:
|
||||
```
|
||||
my-nix-config/
|
||||
├── flake.nix # (if using Flakes)
|
||||
├── hosts/
|
||||
│ ├── hostname1/
|
||||
│ │ └── configuration.nix
|
||||
│ └── hostname2/
|
||||
│ └── configuration.nix
|
||||
├── modules/
|
||||
│ ├── networking.nix
|
||||
│ ├── services.nix
|
||||
│ ├── users.nix
|
||||
│ └── ...
|
||||
└── hardware/
|
||||
└── hardware-configuration-<machine>.nix
|
||||
```
|
||||
- Split large configurations into multiple `.nix` files or modules for clarity. Import them in a top-level `configuration.nix` or `flake.nix`.
|
||||
|
||||
2. **Nix Flakes (Optional)**
|
||||
- If using Flakes, include a top-level `flake.nix` defining your outputs:
|
||||
- `outputs.nixosConfigurations.<hostname> = { ... }`
|
||||
- Reference your system with something like `nixos-rebuild switch --flake .#<hostname>`.
|
||||
- Keep pinned inputs (e.g., `nixpkgs` at a particular commit) in your `flake.lock` to ensure reproducibility.
|
||||
|
||||
3. **System Configuration & Modules**
|
||||
- Place typical NixOS settings (e.g., `networking.hostName`, `time.timeZone`, `environment.systemPackages`, etc.) in `configuration.nix` or a modular file structure.
|
||||
- Use [NixOS modules](https://nixos.org/manual/nixos/stable/index.html#sec-writing-modules) to separate concerns. For example:
|
||||
- `networking.nix` for network settings,
|
||||
- `users.nix` for user/group management,
|
||||
- `services.nix` for enabling/configuring system services.
|
||||
- If you have custom logic or package overlays, keep them in separate files (e.g., `overlays.nix`).
|
||||
|
||||
4. **Home Manager Integration (Optional)**
|
||||
- For user-level configuration (e.g., dotfiles, user-specific packages), consider integrating [Home Manager](https://nix-community.github.io/home-manager/) either as a standalone or via Flakes.
|
||||
- Keep Home Manager configs in a separate `home.nix` file, referencing it in your main configuration or flake outputs.
|
||||
|
||||
5. **Security & Secrets Management**
|
||||
- Avoid committing plain-text secrets (passwords, tokens) to version control.
|
||||
- Consider using [sops-nix](https://github.com/Mic92/sops-nix) or other secret management solutions to encrypt sensitive files.
|
||||
- Enable recommended security settings, such as:
|
||||
- `security.sudo.wheelNeedsPassword = true`
|
||||
- `security.rtkit.enable = true`
|
||||
- `users.users.<name>.extraGroups` to limit privileges.
|
||||
- Regularly update your `nixpkgs` channel or flake inputs for the latest security patches.
|
||||
|
||||
6. **System Services & Daemons**
|
||||
- Use built-in NixOS modules for services (e.g., `services.nginx`, `services.postgresql`, etc.) instead of manual configuration whenever possible.
|
||||
- For each service, ensure you:
|
||||
- Set `enable = true;` if it’s needed,
|
||||
- Provide configuration in the same module file or a dedicated file if it’s complex.
|
||||
- Keep service-specific secrets (e.g., database passwords) out of the main config by referencing environment variables or a secret management solution.
|
||||
|
||||
7. **Package Management & Overlays**
|
||||
- Place packages you need system-wide into `environment.systemPackages`.
|
||||
- For overriding or extending packages from `nixpkgs`, use the [overlays](https://nixos.wiki/wiki/Overlays) mechanism:
|
||||
```nix
|
||||
self: super: {
|
||||
myPackage = super.callPackage ./pkgs/my-package { };
|
||||
}
|
||||
```
|
||||
- Maintain a dedicated `overlays/` folder if you have multiple custom overlays.
|
||||
|
||||
8. **Customization & Extensions**
|
||||
- Use `environment.etc` or NixOS options to create or manage custom config files in `/etc/`.
|
||||
- For advanced use cases, you can define your own modules to unify logic for related settings or services.
|
||||
- Document each module with comments about what it configures and why.
|
||||
|
||||
9. **Testing & Deployment**
|
||||
- Use the `nixos-rebuild test` command to evaluate changes without fully switching.
|
||||
- If using Flakes, run `nixos-rebuild test --flake .#<hostname>`.
|
||||
- Test critical services after switching (e.g., `systemctl status service-name`).
|
||||
- Consider building virtual machines via `nixos-rebuild build-vm` or [NixOS tests](https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests) to validate complex changes.
|
||||
|
||||
10. **Output Format**
|
||||
- Present any generated Nix configuration as well-structured `.nix` files, referencing them in a central place (`configuration.nix` or `flake.nix`).
|
||||
- When explaining your reasoning, describe which modules or options you chose and why (e.g., “I separated `networking.nix` to isolate network settings from system services.”).
|
||||
- If you modify existing files, specify precisely which lines or sections have changed, and why you made those changes.
|
||||
|
||||
Please follow these guidelines to ensure the generated or explained NixOS configuration adheres to best practices for maintainability, modularity, and security.
|
||||
|
||||
debug: false
|
||||
improved_debug: false
|
||||
|
||||
preview_changes: false
|
||||
interactive_file_selection: false
|
||||
partial_acceptance: false
|
||||
|
||||
enable_debug_commands: false
|
||||
prompt_char_limit: 300000
|
||||
enable_step_by_step: true
|
||||
1
.github/copilot-instructions.md
vendored
Symbolic link
1
.github/copilot-instructions.md
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../.roo/rules/rules.md
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ raspberry/.env
|
||||
raspberry/result
|
||||
|
||||
esphome/trash
|
||||
esphome/.esphome
|
||||
|
||||
8
.mcp.json
Normal file
8
.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"nixos": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-nixos"]
|
||||
}
|
||||
}
|
||||
}
|
||||
84
.roo/rules/rules.md
Normal file
84
.roo/rules/rules.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# RULES.md
|
||||
|
||||
## Overview
|
||||
|
||||
This repository manages NixOS configurations for multiple systems, structured to promote modularity, security, and maintainability.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Each host has its own directory under `hosts/`, containing:
|
||||
|
||||
```
|
||||
|
||||
hosts/
|
||||
└── hostname/
|
||||
├── configuration.nix
|
||||
├── modules/
|
||||
└── secrets.yaml
|
||||
```
|
||||
|
||||
|
||||
|
||||
* `configuration.nix`: Main configuration file for the host.
|
||||
* `modules/`: Custom NixOS modules specific to the host.
|
||||
* `secrets.yaml`: Encrypted secrets file (see [Secrets Management](#secrets-management)).
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Modularization
|
||||
|
||||
* Break down configurations into reusable modules placed in the `modules/` directory.
|
||||
* Use the `imports` directive in `configuration.nix` to include necessary modules.
|
||||
* Avoid monolithic configurations; modularity enhances clarity and reusability.
|
||||
|
||||
### Version Control
|
||||
|
||||
* Track all configuration files using Git.
|
||||
* Exclude sensitive files like `secrets.yaml` from version control.
|
||||
* Use descriptive commit messages to document changes.
|
||||
|
||||
## Deployment with Bento
|
||||
|
||||
Bento is utilized for deploying configurations across systems.
|
||||
|
||||
* Centralize configurations on a management server.
|
||||
* Ensure each host accesses only its specific configuration files.
|
||||
* Leverage Bento's features to manage deployments efficiently.([NixOS Discourse][1], [Reddit][2], [cbiit.github.io][3])
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Secrets Management
|
||||
|
||||
* Never store plain-text secrets in the Nix store or configuration files.
|
||||
* Use tools like [sops-nix](https://github.com/Mic92/sops-nix) to encrypt `secrets.yaml`.
|
||||
* Restrict access to decrypted secrets using appropriate file permissions.([Reddit][4], [dade][5])
|
||||
|
||||
### System Hardening
|
||||
|
||||
* Disable unnecessary services to minimize attack surfaces.
|
||||
* Configure firewalls to allow only essential traffic.
|
||||
* Regularly update systems to apply security patches.
|
||||
|
||||
### User Management
|
||||
|
||||
* Implement the principle of least privilege for user accounts.
|
||||
* Use SSH keys for authentication; disable password-based logins.
|
||||
* Monitor user activities and access logs for suspicious behavior.
|
||||
|
||||
## Maintenance Guidelines
|
||||
|
||||
* Regularly review and refactor modules for efficiency and clarity.
|
||||
* Document all modules and configurations for future reference.
|
||||
* Test configurations in a controlled environment before deploying to production systems.([NixOS & Flakes][6])
|
||||
* After developing a feature, delete the corresponding development plan.
|
||||
|
||||
---
|
||||
|
||||
Adhering to these guidelines will help maintain a secure, organized, and efficient NixOS configuration across multiple systems.
|
||||
|
||||
[1]: https://discourse.nixos.org/t/introducing-bento-a-nixos-deployment-framework/21446?utm_source=chatgpt.com "Introducing bento, a NixOS deployment framework"
|
||||
[2]: https://www.reddit.com/r/NixOS/comments/1e95b69/how_do_you_guys_organize_your_nix_config_files_i/?utm_source=chatgpt.com "How do you guys organize your .nix config files? I have a ... - Reddit"
|
||||
[3]: https://cbiit.github.io/bento-docs/master/installation/bento-quick-start.html?utm_source=chatgpt.com "1. Quick Start Tutorial — Bento release-4.1.0 documentation"
|
||||
[4]: https://www.reddit.com/r/NixOS/comments/1cnhx6z/best_security_practices_for_nixos_devices_exposed/?utm_source=chatgpt.com "Best Security practices for NixOS devices exposed to the Internet"
|
||||
[5]: https://0xda.de/blog/2024/07/framework-and-nixos-sops-nix-secrets-management/?utm_source=chatgpt.com "Framework and NixOS - Sops-nix Secrets Management - dade"
|
||||
[6]: https://nixos-and-flakes.thiscute.world/nixos-with-flakes/modularize-the-configuration?utm_source=chatgpt.com "Modularize Your NixOS Configuration | NixOS & Flakes Book"
|
||||
109
.sops.yaml
109
.sops.yaml
@@ -4,22 +4,20 @@
|
||||
# for a more complex example.
|
||||
keys:
|
||||
- &bitwarden age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7 # nixos age key
|
||||
- &dominik age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
- &dominik age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
- &dominik2 age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
- &git-server age106n5n3rrrss45eqqzz8pq90la3kqdtnw63uw0sfa2mahk5xpe30sxs5x58
|
||||
- &web-01-server age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
|
||||
- &web-02 age1gjm4c3swt8u88e36gf2qlg3syxfc0ly94u64c42f2tsf24npw4csa6e4fw
|
||||
- &web-arm age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw
|
||||
- &home-assistant-server age1ezq2j34qngky22enhnslx6hzh4ekwk8dtmn6c9us0uqxqpn7hgpsspjz58
|
||||
- &ldap-server-test age1azmxsw5llmp2nnsv3yc2l8paelmq9rfepxd8jvmswgsmax0qyyxqdnsc7t
|
||||
- &testmodules age1zkzpnfeakyvg3fqtyay32sushjx2hqe28y6hs6ss7plemzqjqa5s6s5yu3
|
||||
- &ldap-server-arm age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
||||
- &fw age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
||||
- &fw-new age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2
|
||||
- &netboot age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
|
||||
- &gpd-win4 age1ceg548u5ma6rgu3xgvd254y5xefqrdqfqhcjsjp3255q976fgd2qaua53d
|
||||
- &nb age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
- &amzebs-01 age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||
- &nas age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
|
||||
|
||||
- &mail-social-grow-tech age1gtulvdj4aclpfhk3mmzvpz9xysccxhvu99x6ayaqlj8m44ehffgq6zuc5u
|
||||
- &web-social-grow-tech age1md4kkdf08zmagqv0yzza8h75f80c9j8np2p6eqea6fpa94szd5lsltz9va
|
||||
creation_rules:
|
||||
- path_regex: ^[^/]+\.yaml$
|
||||
key_groups:
|
||||
@@ -27,24 +25,29 @@ creation_rules:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- path_regex: hosts/nb-01.cloonar.com/[^/]+\.yaml$
|
||||
- *nb
|
||||
- path_regex: hosts/nb/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- path_regex: hosts/nb-new.cloonar.com/[^/]+\.yaml$
|
||||
- *nb
|
||||
- path_regex: hosts/gpd-win4/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- path_regex: hosts/fw.cloonar.com/[^/]+\.yaml$
|
||||
- *gpd-win4
|
||||
- *nb
|
||||
- path_regex: hosts/fw/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *fw
|
||||
- path_regex: hosts/fw-new/[^/]+\.yaml$
|
||||
key_groups:
|
||||
@@ -52,118 +55,102 @@ creation_rules:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *fw
|
||||
- *fw-new
|
||||
- path_regex: hosts/fw.cloonar.com/modules/web/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *web-02
|
||||
- path_regex: hosts/web-01.cloonar.com/[^/]+\.yaml$
|
||||
- path_regex: hosts/fw-new/modules/web/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *web-01-server
|
||||
- *web-02
|
||||
- path_regex: hosts/web-arm/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *web-arm
|
||||
- path_regex: hosts/mail.cloonar.com/[^/]+\.yaml$
|
||||
- path_regex: hosts/amzebs-01/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *amzebs-01
|
||||
- path_regex: hosts/nas/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *nas
|
||||
- path_regex: hosts/mail/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *ldap-server-arm
|
||||
- *ldap-server-test
|
||||
- path_regex: hosts/mail.social-grow.tech/[^/]+\.yaml$
|
||||
- path_regex: hosts/fw/modules/web/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *mail-social-grow-tech
|
||||
- path_regex: hosts/web.social-grow.tech/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *web-social-grow-tech
|
||||
- *nb
|
||||
- *web-02
|
||||
- path_regex: utils/modules/lego/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *git-server
|
||||
- *web-01-server
|
||||
- *web-02
|
||||
- *web-arm
|
||||
- *home-assistant-server
|
||||
- *ldap-server-arm
|
||||
- *ldap-server-test
|
||||
- *testmodules
|
||||
- *netboot
|
||||
- *fw
|
||||
- *fw-new
|
||||
- *mail-social-grow-tech
|
||||
- *web-social-grow-tech
|
||||
- path_regex: hosts/web-01.cloonar.com/modules/bitwarden/[^/]+\.yaml$
|
||||
- path_regex: utils/modules/attic-cache/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *web-01-server
|
||||
- path_regex: hosts/web-01.cloonar.com/modules/zammad/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *web-01-server
|
||||
- path_regex: utils/modules/plausible/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *web-01-server
|
||||
- *nb
|
||||
- path_regex: utils/modules/promtail/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *git-server
|
||||
- *web-01-server
|
||||
- *nb
|
||||
- *web-arm
|
||||
- *home-assistant-server
|
||||
- *ldap-server-arm
|
||||
- *ldap-server-test
|
||||
- *testmodules
|
||||
- *netboot
|
||||
- *fw
|
||||
- *fw-new
|
||||
- *nas
|
||||
- *amzebs-01
|
||||
- path_regex: utils/modules/victoriametrics/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *git-server
|
||||
- *web-01-server
|
||||
- *nb
|
||||
- *web-arm
|
||||
- *home-assistant-server
|
||||
- *ldap-server-arm
|
||||
- *ldap-server-test
|
||||
- *testmodules
|
||||
- *netboot
|
||||
- *fw
|
||||
- *fw-new
|
||||
- *nas
|
||||
- *amzebs-01
|
||||
|
||||
31
AGENTS.md
Normal file
31
AGENTS.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `hosts/<host>/configuration.nix` defines each machine; host modules, packages, and site configs live alongside for composability.
|
||||
- Shared building blocks sit in `utils/` (`modules/`, `overlays/`, `pkgs/`, `bento.nix`), while `fleet.nix` centralizes cross-host user provisioning.
|
||||
- Provisioning assets (ISO profiles, Raspberry Pi imaging, helper scripts) live under `iso/`, `raspberry*/`, and `scripts/`—refer to them before reinventing steps.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- Enter the dev shell via `nix-shell` (uses `shell.nix`) to populate MCP helper configs and standard tooling.
|
||||
- Dry-run any change with `./scripts/test-configuration <host>`; append `-v` to mirror `nixos-rebuild --show-trace` for deeper diagnostics.
|
||||
- Deployment relies on the Git runner—once reviewed changes merge to main, the runner rebuilds and switches the relevant host automatically; treat a clean dry-run as the gate before pushing.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Format Nix files with two-space indentation; run `nixpkgs-fmt` (via `nix run nixpkgs#nixpkgs-fmt .`) before committing complex edits.
|
||||
- Keep module and derivation names in lower kebab-case (`web-arm`, `home-assistant.nix`) and align attribute names with actual host or service identifiers.
|
||||
- Use comments sparingly to justify non-obvious decisions (open ports, unusual service options) and prefer explicit imports over wildcard includes.
|
||||
|
||||
## Testing Guidelines
|
||||
- Always run `./scripts/test-configuration <host>` before raising a PR; it ensures evaluation succeeds and secrets are present.
|
||||
- For service changes, confirm activation with `nixos-rebuild test` (or `switch`) on a staging machine and capture any notable logs.
|
||||
- Document manual smoke checks (e.g., URLs defined in `hosts/web-arm/sites/`) in the PR so reviewers can repeat them quickly.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Follow the Conventional Commits pattern used in `git log` (`fix:`, `chore:`, `update:`) and scope by host when helpful (`fix(mail):`).
|
||||
- Split refactors, secrets rotations, and package bumps into distinct commits to simplify review and rollback.
|
||||
- PRs should call out affected hosts, link dry-build output (and confirm the runner result after merge), and tag the owners noted in `hosts/<host>/users/*.nix`; attach screenshots for UI-facing updates.
|
||||
|
||||
## Security & Configuration Tips
|
||||
- Configure `config.sh` before provisioning SFTP users so the values consumed by `fleet.nix` stay in sync with the chroot layout.
|
||||
- Store API keys referenced in `shell.nix` (such as the Brave Search token) under `~/.config/mcp-servers/` and keep real secrets out of version control.
|
||||
- Rotate and edit encrypted `hosts/<host>/secrets.yaml` via `nix-shell -p sops --run 'sops hosts/<host>/secrets.yaml'`; commit only the encrypted output.
|
||||
100
CLAUDE.md
Normal file
100
CLAUDE.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Repository Overview
|
||||
|
||||
This is a NixOS infrastructure repository managing multiple hosts (servers and personal machines) using a modular Nix configuration approach with SOPS for secrets management and Bento for deployment.
|
||||
|
||||
## Build and Test Commands
|
||||
|
||||
```bash
|
||||
# Enter development shell (sets up MCP configs)
|
||||
nix-shell
|
||||
|
||||
# Test configuration before deployment (required before PRs)
|
||||
./scripts/test-configuration <hostname>
|
||||
./scripts/test-configuration -v <hostname> # with --show-trace
|
||||
|
||||
# Edit encrypted secrets
|
||||
nix-shell -p sops --run 'sops hosts/<hostname>/secrets.yaml'
|
||||
|
||||
# Update secrets keys after adding new age keys
|
||||
./scripts/update-secrets-keys
|
||||
|
||||
# Format Nix files
|
||||
nix run nixpkgs#nixpkgs-fmt .
|
||||
|
||||
# Compute hash for new packages
|
||||
nix hash to-sri --type sha256 $(nix-prefetch-url https://example.com/file.tar.gz)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Host Structure
|
||||
Each host in `hosts/<hostname>/` contains:
|
||||
- `configuration.nix` - Main entry point importing modules
|
||||
- `hardware-configuration.nix` - Machine-specific hardware config
|
||||
- `secrets.yaml` - SOPS-encrypted secrets
|
||||
- `modules/` - Host-specific service configurations
|
||||
- `fleet.nix` → symlink to root `fleet.nix` (SFTP user provisioning)
|
||||
- `utils/` → symlink to root `utils/` (shared modules)
|
||||
|
||||
Current hosts: `fw` (firewall/router), `nb` (notebook), `web-arm`, `mail`, `amzebs-01`, `nas`
|
||||
|
||||
### Shared Components (`utils/`)
|
||||
- `modules/` - Reusable NixOS modules (nginx, sops, borgbackup, lego, promtail, etc.)
|
||||
- `overlays/` - Nixpkgs overlays
|
||||
- `pkgs/` - Custom package derivations
|
||||
- `bento.nix` - Deployment helper module
|
||||
|
||||
### Secrets Management
|
||||
- SOPS with age encryption; keys defined in `.sops.yaml`
|
||||
- Each host has its own age key derived from SSH host key
|
||||
- Host secrets in `hosts/<hostname>/secrets.yaml`
|
||||
- Shared module secrets in `utils/modules/<module>/secrets.yaml`
|
||||
|
||||
**IMPORTANT: Never modify secrets files directly.** Instead, tell the user which secrets need to be added and where, so they can edit the encrypted files themselves using:
|
||||
```bash
|
||||
nix-shell -p sops --run 'sops hosts/<hostname>/secrets.yaml'
|
||||
```
|
||||
|
||||
### Deployment
|
||||
The Git runner handles deployment automatically when changes merge to main. A successful `./scripts/test-configuration <host>` dry-build is the gate before pushing.
|
||||
|
||||
## Custom Packages
|
||||
|
||||
When creating a new package in `utils/pkgs/`, always include an `update.sh` script to automate version updates. See `utils/pkgs/claude-code/update.sh` for the pattern:
|
||||
|
||||
1. Fetch latest version from upstream (npm, GitHub, etc.)
|
||||
2. Update version string in `default.nix`
|
||||
3. Update source hash using `nix-prefetch-url`
|
||||
4. Update dependency hashes (e.g., `npmDepsHash`) by triggering a build with a fake hash
|
||||
5. Verify the final build succeeds
|
||||
|
||||
Example structure:
|
||||
```
|
||||
utils/pkgs/<package-name>/
|
||||
├── default.nix
|
||||
├── update.sh # Always include this
|
||||
└── (other files like patches, lock files)
|
||||
```
|
||||
|
||||
**IMPORTANT: When modifying a custom package** (patches, version updates, etc.), always test by building the package directly, not just running `test-configuration`. The configuration test only checks that the Nix expression evaluates, but doesn't verify the package actually builds:
|
||||
|
||||
```bash
|
||||
# Build a custom package directly to verify it works
|
||||
nix-build -E 'with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; config.allowUnfree = true; }; <package-name>'
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
**IMPORTANT: Always run `./scripts/test-configuration <hostname>` after making any changes** to verify the NixOS configuration builds successfully. This is required before committing.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Nix files: two-space indentation, lower kebab-case naming
|
||||
- Commits: Conventional Commits format (`fix:`, `feat:`, `chore:`), scope by host when relevant (`fix(mail):`). Do not add "Generated with Claude Code" or "Co-Authored-By: Claude" footers.
|
||||
- Modules import via explicit paths, not wildcards
|
||||
- Comments explain non-obvious decisions (open ports, unusual service options)
|
||||
- **Never update `system.stateVersion`** - it should remain at the original installation version. To upgrade NixOS, update the `channel` file instead.
|
||||
65
README.md
65
README.md
@@ -2,8 +2,8 @@
|
||||
- install ubuntu 20.04
|
||||
- get age key from SSH
|
||||
```console
|
||||
curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | PROVIDER=hetznercloud NIX_CHANNEL=nixos-24.05 bash 2>&1 | tee /tmp/infect.log
|
||||
nix-shell -p ssh-to-age --run 'ssh-keyscan example.com | ssh-to-age'
|
||||
curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | PROVIDER=hetznercloud NIX_CHANNEL=nixos-25.05 bash 2>&1 | tee /tmp/infect.log
|
||||
nix-shell -p ssh-to-age --run 'ssh-keyscan install.cloonar.com | ssh-to-age'
|
||||
```
|
||||
- fix secrets files
|
||||
```console
|
||||
@@ -39,7 +39,7 @@ cat ~/.ssh/id_rsa.pub | ssh -p23 u149513-subx@u149513-subx.your-backup.de instal
|
||||
|
||||
# 4. Add new Host
|
||||
```console
|
||||
sftp host.cloonar.com@git.cloonar.com:/config/bootstrap.sh ./
|
||||
sftp host@git.cloonar.com:/config/bootstrap.sh ./
|
||||
```
|
||||
|
||||
# 5. Yubikey
|
||||
@@ -58,3 +58,62 @@ umask 0077; wg genpsk > psk
|
||||
```console
|
||||
nix hash to-sri --type sha256 $(nix-prefetch-url https://tar.gz)
|
||||
```
|
||||
|
||||
# 8. Fingerprint Reader Setup (e.g., on Framework Laptop with Goodix reader)
|
||||
|
||||
This section assumes you have configured fingerprint support in your NixOS configuration, for example, by creating and importing a module like `hosts/nb/modules/fingerprint.nix` with the following content:
|
||||
|
||||
```nix
|
||||
# hosts/nb/modules/fingerprint.nix
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.fprintd.enable = true;
|
||||
|
||||
security.pam.services.login.fprintAuth = true;
|
||||
security.pam.services.sudo.fprintAuth = true;
|
||||
# Add other services like swaylock if needed
|
||||
# security.pam.services.swaylock.fprintAuth = true;
|
||||
}
|
||||
```
|
||||
|
||||
After rebuilding your NixOS configuration (`sudo nixos-rebuild switch`), you can enroll fingerprints for a user.
|
||||
|
||||
## Enrolling Fingerprints
|
||||
|
||||
To enroll a fingerprint for the current user:
|
||||
```console
|
||||
fprintd-enroll
|
||||
```
|
||||
Or for a specific user (e.g., `dominik`):
|
||||
```console
|
||||
fprintd-enroll dominik
|
||||
```
|
||||
Follow the on-screen prompts to scan your fingerprint multiple times.
|
||||
|
||||
## Verifying Enrollment
|
||||
You can verify enrolled fingerprints:
|
||||
```console
|
||||
fprintd-verify
|
||||
```
|
||||
|
||||
## Listing Enrolled Fingerprints
|
||||
To see which fingers are enrolled for the current user:
|
||||
```console
|
||||
fprintd-list $(whoami)
|
||||
```
|
||||
Or for a specific user:
|
||||
```console
|
||||
fprintd-list dominik
|
||||
```
|
||||
|
||||
## Deleting Fingerprints
|
||||
To delete all fingerprints for the current user:
|
||||
```console
|
||||
fprintd-delete $(whoami)
|
||||
```
|
||||
Or for a specific user:
|
||||
```console
|
||||
fprintd-delete dominik
|
||||
```
|
||||
You can also delete specific fingerprints by their ID if you know it.
|
||||
|
||||
2
buchhaltung.md
Normal file
2
buchhaltung.md
Normal file
@@ -0,0 +1,2 @@
|
||||
Bei EU Rechnungen das Hakerl machen bei "Nicht im Inland steuerbare Leistung (außerhalb EU, z.B. Schweiz)"
|
||||
VXEhGveIHdSj7JKq6zof48vLhKaCo0RJea6DhVqopA8=
|
||||
@@ -6,4 +6,4 @@ https://github.com/tasmota/mgos32-to-tasmota32/releases
|
||||
In Tasmota make OTA Update to minimal:
|
||||
http://ota.tasmota.com/tasmota/release/tasmota-minimal.bin.gz
|
||||
Make ESPHome Configuration in Dashboard:
|
||||
docker run --rm -p 6052:6052 -e ESPHOME_DASHBOARD_USE_PING=true -v "${PWD}":/config -it ghcr.io/esphome/esphome
|
||||
docker run --rm --network host -e ESPHOME_DASHBOARD_USE_PING=true -v "${PWD}":/config -it ghcr.io/esphome/esphome:latest
|
||||
|
||||
19
esphome/archive/install.yaml
Normal file
19
esphome/archive/install.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
substitutions:
|
||||
device_name: "install"
|
||||
friendly_name: "Esphome Install"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: Cloonar-Smart
|
||||
password: 0m6sY7Ue3G31
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
@@ -23,6 +21,9 @@ esphome:
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -37,6 +38,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
340
esphome/hallway-light-switch.yaml
Normal file
340
esphome/hallway-light-switch.yaml
Normal file
@@ -0,0 +1,340 @@
|
||||
substitutions:
|
||||
# Default name
|
||||
name: "hallway-light-switch"
|
||||
# Default friendly name
|
||||
friendly_name: "Hallway Light Switch"
|
||||
# Allows ESP device to be automatically linked to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc
|
||||
room: "Hallway"
|
||||
# Description as appears in ESPHome & top of webserver page
|
||||
device_description: "Hallway Light Switch"
|
||||
# Project Name
|
||||
project_name: "Athom Technology.Mini Relay V2"
|
||||
# Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
|
||||
project_version: "v2.0.4"
|
||||
# Restore the relay (GPO switch) upon reboot to state:
|
||||
light_restore_mode: RESTORE_DEFAULT_OFF
|
||||
# Set the update interval for sensors
|
||||
sensor_update_interval: 10s
|
||||
# Current Limit in Amps.
|
||||
current_limit : "10"
|
||||
# Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
|
||||
dns_domain: ".cloonar.smart"
|
||||
# Set timezone of the smart plug. Useful if the plug is in a location different to the HA server. Can be entered in unix Country/Area format (i.e. "Australia/Sydney")
|
||||
timezone: ""
|
||||
# Set the duration between the sntp service polling ntp.org servers for an update
|
||||
sntp_update_interval: 6h
|
||||
# Network time servers for your region, enter from lowest to highest priority. To use local servers update as per zones or countries at: https://www.ntppool.org/zone/@
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
# Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
|
||||
wifi_fast_connect: "false"
|
||||
# Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||
log_level: "WARN"
|
||||
# Hide the ENERGY sensor that shows kWh consumed, but with no time period associated with it. Resets when device restarted and reflashed.
|
||||
hide_energy_sensor: "true"
|
||||
# Enable or disable the use of IPv6 networking on the device
|
||||
ipv6_enable: "false"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.5.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
platformio_options:
|
||||
board_build.mcu: esp32c3
|
||||
board_build.variant: esp32c3
|
||||
board_build.flash_mode: dio
|
||||
|
||||
esp32:
|
||||
board: esp32-c3-devkitm-1
|
||||
flash_size: 4MB
|
||||
variant: ESP32C3
|
||||
framework:
|
||||
type: arduino
|
||||
version: recommended
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 5min
|
||||
|
||||
api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
mdns:
|
||||
disabled: false
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
network:
|
||||
enable_ipv6: ${ipv6_enable}
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: ${dns_domain}
|
||||
|
||||
esp32_improv:
|
||||
authorizer: none
|
||||
|
||||
uart:
|
||||
rx_pin: GPIO20
|
||||
baud_rate: 4800
|
||||
data_bits: 8
|
||||
stop_bits: 1
|
||||
parity: EVEN
|
||||
|
||||
globals:
|
||||
- id: total_energy
|
||||
type: float
|
||||
restore_value: yes
|
||||
initial_value: '0.0'
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO3
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
name: "Power Button"
|
||||
disabled_by_default: true
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at most 1s
|
||||
- OFF for at least 0.2s
|
||||
then:
|
||||
- light.toggle: mini_relay
|
||||
- timing:
|
||||
- ON for at least 4s
|
||||
then:
|
||||
- button.press: Reset
|
||||
|
||||
- platform: gpio
|
||||
id: the_switch
|
||||
name: "Power Switch"
|
||||
pin:
|
||||
number: GPIO4
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at most 1s
|
||||
then:
|
||||
- light.toggle: mini_relay
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: "diagnostic"
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: "diagnostic"
|
||||
device_class: ""
|
||||
|
||||
- platform: cse7766
|
||||
id: athom_cse7766
|
||||
current:
|
||||
name: "Current"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
- lambda: if (x < 0.060) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected
|
||||
on_value_range:
|
||||
- above: ${current_limit}
|
||||
then:
|
||||
- light.turn_off: mini_relay
|
||||
|
||||
voltage:
|
||||
name: "Voltage"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
|
||||
power:
|
||||
name: "Power"
|
||||
id: power_sensor
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
- lambda: if (x < 3.0) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected
|
||||
|
||||
energy:
|
||||
name: "Energy"
|
||||
id: energy
|
||||
unit_of_measurement: kWh
|
||||
filters:
|
||||
- throttle: ${sensor_update_interval}
|
||||
# Multiplication factor from W to kW is 0.001
|
||||
- multiply: 0.001
|
||||
on_value:
|
||||
then:
|
||||
- lambda: |-
|
||||
static float previous_energy_value = 0.0;
|
||||
float current_energy_value = id(energy).state;
|
||||
id(total_energy) += current_energy_value - previous_energy_value;
|
||||
previous_energy_value = current_energy_value;
|
||||
id(total_energy_sensor).update();
|
||||
|
||||
apparent_power:
|
||||
name: "Apparent Power"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
reactive_power:
|
||||
name: "Reactive Power"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
power_factor:
|
||||
name: "Power Factor"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
|
||||
- platform: template
|
||||
name: "Total Energy"
|
||||
id: total_energy_sensor
|
||||
unit_of_measurement: kWh
|
||||
device_class: "energy"
|
||||
state_class: "total_increasing"
|
||||
icon: "mdi:lightning-bolt"
|
||||
accuracy_decimals: 3
|
||||
lambda: |-
|
||||
return id(total_energy);
|
||||
update_interval: ${sensor_update_interval}
|
||||
|
||||
- platform: total_daily_energy
|
||||
name: "Total Energy Today"
|
||||
restore: true
|
||||
power_id: power_sensor
|
||||
unit_of_measurement: kWh
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
button:
|
||||
- platform: restart
|
||||
name: "Restart"
|
||||
entity_category: config
|
||||
|
||||
- platform: factory_reset
|
||||
name: "Factory Reset"
|
||||
id: Reset
|
||||
entity_category: config
|
||||
|
||||
- platform: safe_mode
|
||||
name: "Safe Mode"
|
||||
internal: false
|
||||
entity_category: config
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: relay_output
|
||||
pin: GPIO6
|
||||
|
||||
light:
|
||||
- platform: status_led
|
||||
id: led
|
||||
name: "Blue LED"
|
||||
disabled_by_default: true
|
||||
pin:
|
||||
number: GPIO7
|
||||
inverted: true
|
||||
|
||||
- platform: binary
|
||||
id: mini_relay
|
||||
output: relay_output
|
||||
name: "Mini Switch"
|
||||
restore_mode: ${light_restore_mode}
|
||||
on_turn_on:
|
||||
- light.turn_on: led
|
||||
on_turn_off:
|
||||
- light.turn_off: led
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
# Creates a sensor showing when the device was last restarted
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
# device_class: timestamp
|
||||
|
||||
# Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds = seconds % (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds = seconds % 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds = seconds % 60;
|
||||
if ( days > 3650 ) {
|
||||
return { "Starting up" };
|
||||
} else if ( days ) {
|
||||
return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
||||
} else if ( hours ) {
|
||||
return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
||||
} else if ( minutes ) {
|
||||
return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) +"s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
# Define the timezone of the device
|
||||
timezone: "${timezone}"
|
||||
# Change sync interval from default 5min to 6 hours (or as set in substitutions)
|
||||
update_interval: ${sntp_update_interval}
|
||||
# Set specific sntp servers to use
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
# Publish the time the device was last restarted
|
||||
on_time_sync:
|
||||
then:
|
||||
# Update last restart time, but only once.
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
@@ -1,78 +1,214 @@
|
||||
substitutions:
|
||||
device_name: "livingroom-bulb-1"
|
||||
name: "livingroom-bulb-1"
|
||||
friendly_name: "Living Room Bulb 1"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
domain: "${dns_domain}"
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.11
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
|
||||
@@ -1,78 +1,214 @@
|
||||
substitutions:
|
||||
device_name: "livingroom-bulb-2"
|
||||
name: "livingroom-bulb-2"
|
||||
friendly_name: "Living Room Bulb 2"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
domain: "${dns_domain}"
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.12
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
|
||||
@@ -1,78 +1,214 @@
|
||||
substitutions:
|
||||
device_name: "livingroom-bulb-3"
|
||||
name: "livingroom-bulb-3"
|
||||
friendly_name: "Living Room Bulb 3"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
domain: "${dns_domain}"
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.13
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
|
||||
@@ -1,78 +1,214 @@
|
||||
substitutions:
|
||||
device_name: "livingroom-bulb-4"
|
||||
name: "livingroom-bulb-4"
|
||||
friendly_name: "Living Room Bulb 4"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
domain: "${dns_domain}"
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.14
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
|
||||
@@ -1,78 +1,214 @@
|
||||
substitutions:
|
||||
device_name: "livingroom-bulb-5"
|
||||
name: "livingroom-bulb-5"
|
||||
friendly_name: "Living Room Bulb 5"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
domain: "${dns_domain}"
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.15
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
|
||||
@@ -1,78 +1,214 @@
|
||||
substitutions:
|
||||
device_name: "livingroom-bulb-6"
|
||||
name: "livingroom-bulb-6"
|
||||
friendly_name: "Living Room Bulb 6"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
domain: "${dns_domain}"
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.16
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
|
||||
@@ -5,8 +5,6 @@ substitutions:
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
@@ -20,7 +18,8 @@ esphome:
|
||||
id: my_light
|
||||
color_temperature: 2700 K
|
||||
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
@@ -40,6 +39,7 @@ interval:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
28
fleet.nix
28
fleet.nix
@@ -19,42 +19,42 @@
|
||||
};
|
||||
|
||||
users = [
|
||||
{
|
||||
username = "web-01.cloonar.com";
|
||||
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCl7cvUGMmtpVfJ3PB4Rco7V8z83nivst77SgBn+Z3cHgcDJDu9l3L4Q6rv9b6thmEX+Xf0ri6UwDI8UuJro4F9qpCXsTkHres3f/pDZokgfO7bvU2l7ujq6NnAx0qJWdB6oku36x3t2wBnvkDijXLtGPeQbd6c33hECEwA7QszvoBbGi0yFiGsqR5W7o0kiju/LMzCkExeaspFV6DBtEW0qZVMYx+lBIK5Hi/g3vBjbhFdWGz8T2AITcAnGI9n6f+dg3dlMPEHXnF9KRod1EVDnYMxbEp49i98m65F1xAFwOo35WSg48LlV1PK1VusboE3pHgE2VEFmW1J+PVQZ+z0JAaRBv/wSVN0YzuCLfLtUr10K1W23YbT1UVm7FusKpT1KElZ9adfbk6SXVhXnru40VcwqgYfw7naQJzT8aDI9Tnci+z4xCCxrdUF/psDBPD5sfjMPbjdPbt6Jnx1H9ZodiC/sQUtbn6MMbenMSf/AmuUC9xzpXlqCtPmN1dSC+8= root@web-01";
|
||||
}
|
||||
{
|
||||
username = "web-arm";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGzJRWe8hsqAVnGSjPrcheloteWMzORoQ5Gj4IfhCROF";
|
||||
}
|
||||
{
|
||||
username = "mail.cloonar.com";
|
||||
username = "mail";
|
||||
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCfEuRazRv8zKWJSq+T3SssgOrkBFu6y/t6uoMNrD3P9WHowRDejo2rBsWFgPszhfgxLpWHiuSZFMG8z+07k5fVTdmbUwx0vXI1lmQ7AxB/CPwBef2Vpb7b8Rq6geejvP8X6UjQWP0rsCMtoX2SeBDTG8bDlyq1U3vYxVY4hery6a9Wu57OI5VbSIHhqQvExo7euz8V7ORsLyT8gi9x3r8gNaKJmvssB6QXXZ7U2sJaAUjhV/BmrZJD5qR9EwqwiMPJ2+SkZ0Vz6CFG6GLyB/ngXPEfclLKK7AzookJy7WepqojjFTzmOBMH903oR+MIpjDECKxgaFtW4xY0A/tj8ZDCBPtP8AKjediOASkAi7eUMPseQKDE0BNLSidC0hlQUe0aPaMeA8b1U86PblzpgF8ntkUPbxhO0AgHKq9fPN+f58f75fryNbhgPRRkeLet1q3hxguEMg2MIg/EqIw862YPWPtGRk0wJHwQU7jx+9BbjdptAVTJo/Cj9vM7mpZphE= root@mail";
|
||||
}
|
||||
{
|
||||
username = "nb-01.cloonar.com";
|
||||
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7";
|
||||
username = "nb";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ6g/lXONzSW1JbyXnj+/0QPWtaiNxu9A0GOCbi96603";
|
||||
}
|
||||
{
|
||||
username = "nb-new.cloonar.com";
|
||||
username = "nb-new";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC1dDoAJUY58I+4SSfDAkO5kInsMcJT/r/mW+MYXLQVR";
|
||||
}
|
||||
{
|
||||
username = "fw.cloonar.com";
|
||||
username = "fw";
|
||||
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtxpJAFohRtBaET9e7EE4I6UmeUT/h1ZTD1zeOHFiWB/AT71ooDT4/QukJOA3LqklDjtDQHH+qjGY50Wa8/oGTA/X3aBDPg5GAHN+U+kYO2UTC69VVjh4TTS35ijg+AdgegtMI4c0VIUMZB24tthV9KEbD20w6XnTzy2Q6PjbBrwsOeHYr9pkygJZDU65ZeKmLyR6yLaadHzXX1I7V2SwiakPEebhQaGipm540d+tAbirKCHcmiORkpd++e3dfwi25hC9bCQ7b3bdaFPAmuhhFEid4jpCt79X+l0qqpClgRLziBjYykNJDFKAljFBJA11/3ofPCuaBCDUuJVhAH044gtT3sbvJq1prd8ElZy6L1yc5YbfFgDMwi71Y2hef780NmDs5Opk9xUCKqdl1YfLyUDgdiiaZ8uhUMd2Ai9BAxJAXtcz/V41ngt3YkUVyGTZdTAODIKk44blGIkgs7JO4yam4UB1curbD0faIZnWLyS5pdFQ+FI05YVjoHXJdme8=";
|
||||
}
|
||||
{
|
||||
username = "fw-new";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILnb9todh2b+c3iCmEz72smRwL37aZf3Xs3voT7+PLTP";
|
||||
}
|
||||
|
||||
{
|
||||
username = "mail.social-grow.tech";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH1K4mhBji1kMGnO55OOFaDknBf2Q6wgm7DaMYKip+S5";
|
||||
username = "gpd-win4";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILjfS2DtS8PQgkf86dU+EVu5t+r/QlCWmY7+RPYprQrO";
|
||||
}
|
||||
{
|
||||
username = "web.social-grow.tech";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIw4lHUd/+rHIWP2WBAj9smo2CkeHEOHhTqZzacmxMcC";
|
||||
username = "nas";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICS6b97LPUpr7/kWvOcI40s5e+gfbfz0I2/hAPL6zTmU";
|
||||
}
|
||||
|
||||
{
|
||||
username = "amzebs-01";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMkFZ60SPl8pzEtGrFq1+n6ZkDuNe3xJaccJMjr3y/q";
|
||||
}
|
||||
];
|
||||
in {
|
||||
|
||||
57
gpd-win-4.md
Normal file
57
gpd-win-4.md
Normal file
@@ -0,0 +1,57 @@
|
||||
I want a wall-mounted docking solution for my GPD Win 4, designed in OpenSCAD 2021.1. Here are the requirements and clarifications:
|
||||
|
||||
Orientation & Fit
|
||||
|
||||
The GPD Win 4 should be inserted upside down (top facing down), with the screen facing the wall.
|
||||
It slides in from the top and is guided by side rails.
|
||||
There should be a small clearance so the GPD Win 4 can be easily inserted/removed without excessive friction.
|
||||
Front Rail (Lip)
|
||||
|
||||
Side Rails:
|
||||
The dock should have two side rails that run from top to bottom, guiding the GPD Win 4.
|
||||
|
||||
The front is open for airflow.
|
||||
However, there should be a small lip (front rail) on each side, running from top to bottom and connected to the side rails. This lip prevents the GPD Win 4 from falling out forward.
|
||||
Back Plate / Wall Mount
|
||||
|
||||
The dock has a solid back plate that mounts to the wall with two countersunk screws.
|
||||
The default spacing and size of these screws can be parameterized (e.g., an M4 or M3 countersunk hole).
|
||||
The back plate thickness should be sufficient for strength (e.g., 3–4 mm).
|
||||
No special side or back vents are needed.
|
||||
Cable Brackets
|
||||
|
||||
At the bottom, back, inside the dock, there are two brackets, one for a 90° USB-C cable (standard USB-C power) and one for a 90° Oculink flat cable.
|
||||
The back plate should be open where these two brackets are, so the cables can exit the dock.
|
||||
Each bracket should have:
|
||||
An opening on the side facing the wall, to allow the cable to pass behind (i.e., into or through the wall).
|
||||
A hole for an M3 screw that presses against the cable from the side to lock it in place.
|
||||
Enough space to seat a 90° connector so it points upwards to plug into the GPD Win 4.
|
||||
Parametric Design
|
||||
|
||||
The design should be fully parameterized in OpenSCAD, including (but not limited to) the following parameters:
|
||||
device_width, device_thickness, device_length (for the GPD Win 4)
|
||||
clearance_x, clearance_y, clearance_z (how much extra space around the device)
|
||||
wall_plate_thickness
|
||||
rail_thickness
|
||||
front_rail_lip_width or front_rail_lip_thickness
|
||||
wall_mount_screw_hole_diameter, wall_mount_screw_spacing (for countersunk screws)
|
||||
bracket_inner_width_usbC, bracket_inner_height_usbC (for the USB-C connector dimensions)
|
||||
bracket_inner_width_oculink, bracket_inner_height_oculink (for the Oculink connector dimensions)
|
||||
m3_side_screw_hole_diameter (the hole that lets an M3 screw clamp the cable from the side)
|
||||
Any other geometry parameters (openings for cables, bracket thickness, etc.)
|
||||
Defaults
|
||||
|
||||
Please choose default dimensions that accurately reflect:
|
||||
Approximate GPD Win 4 size (if not exact, then close estimates).
|
||||
Standard 90° USB-C and 90° Oculink connector sizes.
|
||||
Typical M3 screws for cable clamps.
|
||||
Countersunk holes for M3 or M4 wall screws (whichever you prefer).
|
||||
Version
|
||||
|
||||
This must render successfully in OpenSCAD 2021.1.
|
||||
Summary
|
||||
|
||||
The final output should be an OpenSCAD file that, when the parameters are set to their defaults, produces the described wall-mounted docking station for the GPD Win 4 with side rails, minimal front lip, bracket cutouts for cables, and properly sized holes for screws.
|
||||
|
||||
If any additional measurements or details are needed, please ask.
|
||||
|
||||
471
hosts/amzebs-01/EMAIL_SETUP.md
Normal file
471
hosts/amzebs-01/EMAIL_SETUP.md
Normal file
@@ -0,0 +1,471 @@
|
||||
# Email Setup for amzebs-01 (amz.at)
|
||||
|
||||
This host is configured to send emails via Laravel with DKIM signing.
|
||||
|
||||
## Configuration Overview
|
||||
|
||||
- **Postfix**: Localhost-only SMTP server (no external access)
|
||||
- **Rspamd**: DKIM signing with host-specific key
|
||||
- **Domain**: amz.at
|
||||
- **DKIM Selector**: amzebs-01
|
||||
- **Secret Management**: DKIM private key stored in sops
|
||||
|
||||
## Initial Setup (Before First Deployment)
|
||||
|
||||
### 1. Generate DKIM Key Pair
|
||||
|
||||
You need to generate a DKIM key pair locally first. You'll need `rspamd` package installed.
|
||||
|
||||
#### Option A: Using rspamd (if installed locally)
|
||||
|
||||
```bash
|
||||
# Create a temporary directory
|
||||
mkdir -p /tmp/dkim-gen
|
||||
|
||||
# Generate the key pair
|
||||
rspamadm dkim_keygen -s amzebs-01 -d amz.at -k /tmp/dkim-gen/amz.at.amzebs-01.key
|
||||
```
|
||||
|
||||
This will output:
|
||||
- **Private key** saved to `/tmp/dkim-gen/amz.at.amzebs-01.key`
|
||||
- **Public key** printed to stdout (starts with `v=DKIM1; k=rsa; p=...`)
|
||||
|
||||
#### Option B: Using OpenSSL (alternative)
|
||||
|
||||
```bash
|
||||
# Create temporary directory
|
||||
mkdir -p /tmp/dkim-gen
|
||||
|
||||
# Generate private key (2048-bit RSA)
|
||||
openssl genrsa -out /tmp/dkim-gen/amz.at.amzebs-01.key 2048
|
||||
|
||||
# Extract public key in the correct format for DNS
|
||||
openssl rsa -in /tmp/dkim-gen/amz.at.amzebs-01.key -pubout -outform PEM | \
|
||||
grep -v '^-----' | tr -d '\n' > /tmp/dkim-gen/public.txt
|
||||
|
||||
# Display the DNS record value
|
||||
echo "v=DKIM1; k=rsa; p=$(cat /tmp/dkim-gen/public.txt)"
|
||||
```
|
||||
|
||||
**Save the public key output!** You'll need it for DNS configuration later.
|
||||
|
||||
### 2. Add DKIM Private Key to Sops Secrets
|
||||
|
||||
Now you need to encrypt and add the private key to your secrets file.
|
||||
|
||||
#### Step 1: View the private key
|
||||
|
||||
```bash
|
||||
cat /tmp/dkim-gen/amz.at.amzebs-01.key
|
||||
```
|
||||
|
||||
#### Step 2: Edit the secrets file
|
||||
|
||||
```bash
|
||||
cd /home/dominik/projects/cloonar/cloonar-nixos/hosts/amzebs-01
|
||||
sops secrets.yaml
|
||||
```
|
||||
|
||||
#### Step 3: Add the key to secrets.yaml
|
||||
|
||||
In the sops editor, add a new key called `rspamd-dkim-key` with the **entire private key content** including the BEGIN/END markers:
|
||||
|
||||
```yaml
|
||||
rspamd-dkim-key: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
|
||||
(paste the entire key content here)
|
||||
...
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- Make sure to use the pipe `|` character for multiline content
|
||||
- Keep the proper indentation (2 spaces before each line of the key)
|
||||
- Include the full BEGIN/END markers
|
||||
|
||||
#### Step 4: Save and exit
|
||||
|
||||
Save the file in sops (it will be encrypted automatically).
|
||||
|
||||
#### Step 5: Clean up temporary files
|
||||
|
||||
```bash
|
||||
rm -rf /tmp/dkim-gen
|
||||
```
|
||||
|
||||
### 3. Verify Secret is Encrypted
|
||||
|
||||
Check that the secret is properly encrypted:
|
||||
|
||||
```bash
|
||||
cat hosts/amzebs-01/secrets.yaml
|
||||
```
|
||||
|
||||
You should see encrypted content, not the plain private key.
|
||||
|
||||
### 4. Extract Public Key for DNS (if needed later)
|
||||
|
||||
If you didn't save the public key earlier, you can extract it after deployment:
|
||||
|
||||
```bash
|
||||
# On the server after deployment
|
||||
sudo cat /var/lib/rspamd/dkim/amz.at.amzebs-01.key | \
|
||||
openssl rsa -pubout -outform PEM 2>/dev/null | \
|
||||
grep -v '^-----' | tr -d '\n'
|
||||
```
|
||||
|
||||
Then format it as:
|
||||
```
|
||||
v=DKIM1; k=rsa; p=<output_from_above>
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### 1. Deploy Configuration
|
||||
|
||||
After adding the DKIM private key to sops, deploy the configuration:
|
||||
|
||||
```bash
|
||||
# Build and switch on the remote host
|
||||
nixos-rebuild switch --flake .#amzebs-01 --target-host amzebs-01 --use-remote-sudo
|
||||
```
|
||||
|
||||
Or if deploying locally on the server:
|
||||
|
||||
```bash
|
||||
sudo nixos-rebuild switch
|
||||
```
|
||||
|
||||
### 2. Verify Deployment
|
||||
|
||||
Check that the services are running:
|
||||
|
||||
```bash
|
||||
# Check rspamd-dkim-setup service
|
||||
systemctl status rspamd-dkim-setup
|
||||
|
||||
# Check that rspamd is running
|
||||
systemctl status rspamd
|
||||
|
||||
# Check that postfix is running
|
||||
systemctl status postfix
|
||||
|
||||
# Verify DKIM key was deployed
|
||||
ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key
|
||||
```
|
||||
|
||||
## DNS Configuration
|
||||
|
||||
Add the following DNS records to ensure proper email delivery and avoid spam classification.
|
||||
|
||||
### Critical: PTR Record (Reverse DNS)
|
||||
|
||||
**This is CRITICAL for email deliverability!** Without a proper PTR record, most mail servers will reject or spam your emails.
|
||||
|
||||
#### What is a PTR Record?
|
||||
A PTR (pointer) record is a reverse DNS entry that maps your IP address back to your hostname. Mail servers use this to verify you're a legitimate mail server.
|
||||
|
||||
#### Required PTR Record
|
||||
```
|
||||
IP Address: 23.88.38.1
|
||||
Points to: amzebs-01.amz.at
|
||||
```
|
||||
|
||||
#### How to Configure PTR Record
|
||||
|
||||
**Step 1: Contact Your Hosting Provider**
|
||||
|
||||
PTR records MUST be configured through your hosting provider (e.g., Hetzner, OVH, AWS, etc.). You cannot set PTR records through your domain registrar.
|
||||
|
||||
1. Log into your hosting provider's control panel
|
||||
2. Find the "Reverse DNS" or "PTR Record" section
|
||||
3. Set the PTR record for IP `23.88.38.1` to point to `amzebs-01.amz.at`
|
||||
|
||||
**Common Provider Links:**
|
||||
- **Hetzner**: Robot panel → IPs → Edit reverse DNS
|
||||
- **OVH**: Network → IP → ... → Modify reverse
|
||||
- **AWS EC2**: Select instance → Networking → Request reverse DNS
|
||||
|
||||
**Step 2: Verify Forward DNS First**
|
||||
|
||||
Before setting the PTR record, ensure your forward DNS is correct:
|
||||
|
||||
```bash
|
||||
# This should return 23.88.38.1
|
||||
dig +short amzebs-01.amz.at A
|
||||
host amzebs-01.amz.at
|
||||
```
|
||||
|
||||
**Step 3: Verify PTR Record**
|
||||
|
||||
After configuring, verify the PTR record is working:
|
||||
|
||||
```bash
|
||||
# Method 1: Using dig
|
||||
dig +short -x 23.88.38.1
|
||||
|
||||
# Method 2: Using host
|
||||
host 23.88.38.1
|
||||
|
||||
# Method 3: Using nslookup
|
||||
nslookup 23.88.38.1
|
||||
```
|
||||
|
||||
All commands should return: `amzebs-01.amz.at`
|
||||
|
||||
**Step 4: Verify FCrDNS (Forward-Confirmed Reverse DNS)**
|
||||
|
||||
This ensures forward and reverse DNS match properly:
|
||||
|
||||
```bash
|
||||
# Forward lookup
|
||||
dig +short amzebs-01.amz.at
|
||||
# Should output: 23.88.38.1
|
||||
|
||||
# Reverse lookup
|
||||
dig +short -x 23.88.38.1
|
||||
# Should output: amzebs-01.amz.at.
|
||||
```
|
||||
|
||||
If both work correctly, FCrDNS passes! ✓
|
||||
|
||||
**Why PTR Records Matter:**
|
||||
- Gmail, Microsoft, Yahoo require valid PTR records
|
||||
- Missing PTR = automatic spam classification or rejection
|
||||
- Can add 5-10 points to spam score alone
|
||||
- Required for professional email delivery
|
||||
|
||||
### Domain DNS Records (amz.at)
|
||||
|
||||
Add these records through your domain registrar's DNS management:
|
||||
|
||||
#### SPF Record
|
||||
```
|
||||
Type: TXT
|
||||
Name: @
|
||||
Value: v=spf1 mx a:amzebs-01.amz.at ~all
|
||||
```
|
||||
|
||||
#### DKIM Record
|
||||
```
|
||||
Type: TXT
|
||||
Name: amzebs-01._domainkey
|
||||
Value: [Your public key from step 1 above]
|
||||
```
|
||||
|
||||
The DKIM record will look something like:
|
||||
```
|
||||
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
||||
```
|
||||
|
||||
#### DMARC Record
|
||||
```
|
||||
Type: TXT
|
||||
Name: _dmarc
|
||||
Value: v=DMARC1; p=quarantine; rua=mailto:postmaster@amz.at; ruf=mailto:postmaster@amz.at; fo=1
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
- `p=quarantine`: Failed messages should be quarantined (you can change to `p=reject` after testing)
|
||||
- `rua=mailto:...`: Aggregate reports sent to this address
|
||||
- `ruf=mailto:...`: Forensic reports sent to this address
|
||||
- `fo=1`: Generate forensic reports for any failure
|
||||
|
||||
## Laravel Configuration
|
||||
|
||||
Update your Laravel application's `.env` file:
|
||||
|
||||
#### Option A: Using sendmail (Recommended)
|
||||
```env
|
||||
MAIL_MAILER=sendmail
|
||||
MAIL_FROM_ADDRESS=noreply@amz.at
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
#### Option B: Using SMTP
|
||||
```env
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=25
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=noreply@amz.at
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
**Note**: Laravel can use ANY email address with @amz.at domain. All will be DKIM signed automatically.
|
||||
|
||||
## Testing Email
|
||||
|
||||
### Test from Command Line
|
||||
|
||||
```bash
|
||||
# Send a test email
|
||||
echo "Test email body" | mail -s "Test Subject" test@example.com -aFrom:test@amz.at
|
||||
```
|
||||
|
||||
### Check Postfix Queue
|
||||
|
||||
```bash
|
||||
# View mail queue
|
||||
mailq
|
||||
|
||||
# View logs
|
||||
journalctl -u postfix -f
|
||||
```
|
||||
|
||||
### Check Rspamd Logs
|
||||
|
||||
```bash
|
||||
# View rspamd logs
|
||||
journalctl -u rspamd -f
|
||||
```
|
||||
|
||||
### Test DKIM Signature and Deliverability
|
||||
|
||||
Send an email to test your complete email configuration:
|
||||
|
||||
#### Email Testing Services
|
||||
1. **Mail Tester** (https://www.mail-tester.com/)
|
||||
- Provides a temporary email address
|
||||
- Shows comprehensive spam score (0-10, higher is better)
|
||||
- Checks DKIM, SPF, DMARC, PTR, blacklists, content
|
||||
- **Target: 9/10 or higher**
|
||||
|
||||
2. **MXToolbox Email Health** (https://mxtoolbox.com/emailhealth/)
|
||||
- Comprehensive deliverability check
|
||||
- Checks DNS records, blacklists, configuration
|
||||
|
||||
3. **Google Admin Toolbox** (https://toolbox.googleapps.com/apps/messageheader/)
|
||||
- Paste email headers to see how Gmail scored your email
|
||||
- Shows SPF, DKIM, DMARC results
|
||||
|
||||
#### What to Check
|
||||
- ✓ DKIM signature is valid
|
||||
- ✓ SPF passes
|
||||
- ✓ DMARC passes
|
||||
- ✓ PTR record (reverse DNS) matches
|
||||
- ✓ Not on any blacklists
|
||||
- ✓ Spam score < 2.0 (lower is better)
|
||||
|
||||
#### Common Issues & Fixes
|
||||
|
||||
**High Spam Score (> 5.0)**
|
||||
- Check: PTR record configured correctly? (Critical!)
|
||||
- Check: HELO name matches hostname?
|
||||
- Check: All headers present (To:, From:, Subject:)?
|
||||
- Check: IP not blacklisted?
|
||||
|
||||
**Missing "To:" Header**
|
||||
Your Laravel app must set a recipient. In your code:
|
||||
```php
|
||||
Mail::to('recipient@example.com')
|
||||
->send(new YourMailable());
|
||||
```
|
||||
|
||||
**HELO/EHLO Mismatch**
|
||||
After applying this configuration, HELO should be `amzebs-01.amz.at`, not `localhost`
|
||||
|
||||
**Check Current HELO Name**
|
||||
```bash
|
||||
# On the server
|
||||
echo "HELO test" | nc localhost 25
|
||||
# Should see: 250 amzebs-01.amz.at
|
||||
```
|
||||
|
||||
## Verification Commands
|
||||
|
||||
```bash
|
||||
# Check if Postfix is running
|
||||
systemctl status postfix
|
||||
|
||||
# Check if Rspamd is running
|
||||
systemctl status rspamd
|
||||
|
||||
# Check if Postfix is listening on localhost only
|
||||
ss -tlnp | grep master
|
||||
|
||||
# View DKIM public key again
|
||||
systemctl start rspamd-show-dkim
|
||||
journalctl -u rspamd-show-dkim
|
||||
|
||||
# Check if DKIM key exists
|
||||
ls -la /var/lib/rspamd/dkim/
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. **Localhost-only**: Postfix is configured to listen ONLY on 127.0.0.1
|
||||
2. **No authentication**: Not needed since only local processes can connect
|
||||
3. **No firewall changes**: No external ports opened for email
|
||||
4. **DKIM signing**: All outgoing emails are automatically signed with DKIM
|
||||
5. **Host-specific key**: Using selector "amzebs-01" allows multiple hosts to send for amz.at
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Email not being sent
|
||||
|
||||
1. Check Postfix status: `systemctl status postfix`
|
||||
2. Check queue: `mailq`
|
||||
3. Check logs: `journalctl -u postfix -n 100`
|
||||
|
||||
### DKIM not signing
|
||||
|
||||
1. Check Rspamd status: `systemctl status rspamd`
|
||||
2. Check if key exists: `ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key`
|
||||
3. Check Rspamd logs: `journalctl -u rspamd -n 100`
|
||||
|
||||
### Permission errors
|
||||
|
||||
```bash
|
||||
# Ensure proper ownership
|
||||
chown -R rspamd:rspamd /var/lib/rspamd/dkim/
|
||||
chmod 600 /var/lib/rspamd/dkim/*.key
|
||||
```
|
||||
|
||||
### Rotate DKIM key
|
||||
|
||||
```bash
|
||||
# 1. Generate new key pair locally (follow "Initial Setup" steps)
|
||||
# 2. Update the rspamd-dkim-key in secrets.yaml with new key
|
||||
# 3. Deploy the configuration
|
||||
nixos-rebuild switch
|
||||
|
||||
# 4. Restart the setup service to copy new key
|
||||
systemctl restart rspamd-dkim-setup
|
||||
|
||||
# 5. Restart rspamd to use new key
|
||||
systemctl restart rspamd
|
||||
|
||||
# 6. Update DNS with new public key
|
||||
# 7. Wait for DNS propagation before removing old DNS record
|
||||
```
|
||||
|
||||
## Related Files
|
||||
|
||||
- Postfix config: `hosts/amzebs-01/modules/postfix.nix`
|
||||
- Rspamd config: `hosts/amzebs-01/modules/rspamd.nix`
|
||||
- Main config: `hosts/amzebs-01/configuration.nix`
|
||||
- Secrets file: `hosts/amzebs-01/secrets.yaml` (encrypted)
|
||||
|
||||
## Sops Secret Configuration
|
||||
|
||||
The DKIM private key is stored as a sops secret with the following configuration:
|
||||
|
||||
```nix
|
||||
sops.secrets.rspamd-dkim-key = {
|
||||
owner = "rspamd";
|
||||
group = "rspamd";
|
||||
mode = "0400";
|
||||
};
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- Only the rspamd user can read the key
|
||||
- The key is decrypted at boot time by sops-nix
|
||||
- The key is encrypted in version control
|
||||
- The key persists across rebuilds
|
||||
|
||||
The key is automatically copied from the sops secret path to `/var/lib/rspamd/dkim/amz.at.amzebs-01.key` by the `rspamd-dkim-setup.service` on every boot.
|
||||
1
hosts/amzebs-01/channel
Normal file
1
hosts/amzebs-01/channel
Normal file
@@ -0,0 +1 @@
|
||||
https://channels.nixos.org/nixos-25.11
|
||||
81
hosts/amzebs-01/configuration.nix
Normal file
81
hosts/amzebs-01/configuration.nix
Normal file
@@ -0,0 +1,81 @@
|
||||
{ config, lib, pkgs, ... }: {
|
||||
imports = [
|
||||
./utils/bento.nix
|
||||
./utils/modules/sops.nix
|
||||
./utils/modules/nginx.nix
|
||||
./utils/modules/set-nix-channel.nix
|
||||
|
||||
./modules/mysql.nix
|
||||
./modules/web/stack.nix
|
||||
./modules/laravel-storage.nix
|
||||
./modules/laravel-scheduler.nix
|
||||
./modules/blackbox-exporter.nix
|
||||
./modules/postfix.nix
|
||||
./modules/rspamd.nix
|
||||
|
||||
./utils/modules/autoupgrade.nix
|
||||
./utils/modules/promtail
|
||||
./utils/modules/victoriametrics
|
||||
./utils/modules/borgbackup.nix
|
||||
|
||||
./hardware-configuration.nix
|
||||
|
||||
./sites
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
vim
|
||||
screen
|
||||
php82
|
||||
];
|
||||
|
||||
time.timeZone = "Europe/Vienna";
|
||||
|
||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
sops.defaultSopsFile = ./secrets.yaml;
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
options = "--delete-older-than 60d";
|
||||
};
|
||||
|
||||
boot.tmp.cleanOnBoot = true;
|
||||
zramSwap.enable = true;
|
||||
|
||||
networking.hostName = "amzebs-01";
|
||||
networking.domain = "cloonar.com";
|
||||
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFshMhXwS0FQFPlITipshvNKrV8sA52ZFlnaoHd1thKg"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
];
|
||||
|
||||
programs.ssh = {
|
||||
knownHosts = {
|
||||
"git.cloonar.com" = {
|
||||
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDlUj7eEfS/4+z/3IhFhOTXAfpGEpNv6UWuYSL5OAhus";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# backups - adjust repo for this host
|
||||
borgbackup.repo = "u149513-sub10@u149513-sub10.your-backup.de:borg";
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt (not DNS)
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.defaults.email = "admin+acme@cloonar.com";
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 22 80 443 3306 ];
|
||||
|
||||
# Allow MariaDB access only from specific IP
|
||||
extraCommands = ''
|
||||
iptables -A nixos-fw -p tcp --dport 3306 -s 77.119.230.30 -j nixos-fw-accept
|
||||
'';
|
||||
};
|
||||
|
||||
system.stateVersion = "25.11";
|
||||
}
|
||||
27
hosts/amzebs-01/hardware-configuration.nix
Normal file
27
hosts/amzebs-01/hardware-configuration.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
# Hardware configuration for amzebs-01
|
||||
# This is a template - update with actual hardware configuration after installation
|
||||
{ modulesPath, ... }:
|
||||
{
|
||||
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
|
||||
|
||||
boot.loader.grub = {
|
||||
efiSupport = true;
|
||||
efiInstallAsRemovable = true;
|
||||
device = "nodev";
|
||||
configurationLimit = 2;
|
||||
};
|
||||
|
||||
# Update these with actual device UUIDs and paths after installation
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/sda15";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/sda1";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" ];
|
||||
boot.initrd.kernelModules = [ "nvme" ];
|
||||
}
|
||||
83
hosts/amzebs-01/modules/blackbox-exporter.nix
Normal file
83
hosts/amzebs-01/modules/blackbox-exporter.nix
Normal file
@@ -0,0 +1,83 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
hostname = config.networking.hostName;
|
||||
|
||||
cfg = config.services.blackbox-exporter;
|
||||
nginxVHosts = config.services.nginx.virtualHosts or {};
|
||||
allDomains = lib.attrNames nginxVHosts;
|
||||
filteredDomains = builtins.filter (d: !builtins.elem d cfg.blacklistDomains) allDomains;
|
||||
httpsDomains = lib.map (d: "https://${d}") filteredDomains;
|
||||
domainsString = builtins.concatStringsSep "\n "
|
||||
(map (d: "\"${d}\",") httpsDomains);
|
||||
in {
|
||||
options.services.blackbox-exporter.blacklistDomains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "List of domains to exclude from Blackbox Exporter monitoring";
|
||||
};
|
||||
|
||||
config = {
|
||||
services.blackbox-exporter = {
|
||||
blacklistDomains = [
|
||||
# Currently no domains blacklisted - monitoring all nginx virtualHosts
|
||||
];
|
||||
};
|
||||
|
||||
# Systemd service for Blackbox Exporter
|
||||
systemd.services.blackbox-exporter = {
|
||||
description = "Blackbox Exporter";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.ExecStart = ''
|
||||
${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
|
||||
--config.file=/etc/blackbox_exporter/blackbox.yml
|
||||
'';
|
||||
};
|
||||
|
||||
# Configuration file for Blackbox Exporter
|
||||
environment.etc."blackbox_exporter/blackbox.yml".text = ''
|
||||
modules:
|
||||
http_200_final:
|
||||
prober: http
|
||||
http:
|
||||
method: GET
|
||||
follow_redirects: true
|
||||
preferred_ip_protocol: "ip4" # avoid blanket IPv6 failures
|
||||
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
|
||||
valid_status_codes: [200]
|
||||
'';
|
||||
|
||||
# Add scrape config for VictoriaMetrics agent
|
||||
services.victoriametrics.extraScrapeConfigs = [
|
||||
''
|
||||
- job_name: "blackbox_http_all_domains"
|
||||
metrics_path: "/probe"
|
||||
params:
|
||||
module: ["http_200_final"]
|
||||
|
||||
static_configs:
|
||||
- targets:
|
||||
[
|
||||
${domainsString}
|
||||
]
|
||||
|
||||
relabel_configs:
|
||||
- source_labels: ["__address__"]
|
||||
target_label: "__param_target"
|
||||
regex: '(.*)'
|
||||
replacement: "$1"
|
||||
- source_labels: ["__param_target"]
|
||||
target_label: "instance"
|
||||
- target_label: "__address__"
|
||||
replacement: "127.0.0.1:9115"
|
||||
- source_labels: ["__address__"]
|
||||
regex: "127\\.0\\.0\\.1:9115"
|
||||
target_label: "__scheme__"
|
||||
replacement: "http"
|
||||
''
|
||||
];
|
||||
};
|
||||
}
|
||||
51
hosts/amzebs-01/modules/laravel-scheduler.nix
Normal file
51
hosts/amzebs-01/modules/laravel-scheduler.nix
Normal file
@@ -0,0 +1,51 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# Daily scheduled Laravel artisan jobs
|
||||
# Runs artisan finish:reports at 01:00 for production and staging APIs
|
||||
|
||||
let
|
||||
php = pkgs.php82;
|
||||
|
||||
sites = [
|
||||
{
|
||||
domain = "api.ebs.amz.at";
|
||||
user = "api_ebs_amz_at";
|
||||
}
|
||||
{
|
||||
domain = "api.stage.ebs.amz.at";
|
||||
user = "api_stage_ebs_amz_at";
|
||||
}
|
||||
];
|
||||
|
||||
mkArtisanService = site: {
|
||||
name = "artisan-finish-reports-${site.domain}";
|
||||
value = {
|
||||
description = "Laravel artisan finish:reports for ${site.domain}";
|
||||
after = [ "network.target" "mysql.service" "phpfpm-${site.domain}.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = site.user;
|
||||
Group = "nginx";
|
||||
WorkingDirectory = "/var/www/${site.domain}";
|
||||
ExecStart = "${php}/bin/php artisan finish:reports";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mkArtisanTimer = site: {
|
||||
name = "artisan-finish-reports-${site.domain}";
|
||||
value = {
|
||||
description = "Daily timer for artisan finish:reports on ${site.domain}";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 01:00:00";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
systemd.services = builtins.listToAttrs (map mkArtisanService sites);
|
||||
systemd.timers = builtins.listToAttrs (map mkArtisanTimer sites);
|
||||
}
|
||||
30
hosts/amzebs-01/modules/laravel-storage.nix
Normal file
30
hosts/amzebs-01/modules/laravel-storage.nix
Normal file
@@ -0,0 +1,30 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Create Laravel storage directories for all API instances
|
||||
# These directories are required for Laravel to function properly
|
||||
systemd.tmpfiles.rules = [
|
||||
# api.ebs.cloonar.dev
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/cache 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/testing 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/sessions 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/views 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/logs 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/bootstrap/cache 0775 api_ebs_cloonar_dev nginx -"
|
||||
|
||||
# api.ebs.amz.at
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/cache 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/testing 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/sessions 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/views 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/logs 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/bootstrap/cache 0775 api_ebs_amz_at nginx -"
|
||||
|
||||
# api.stage.ebs.amz.at
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/cache 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/testing 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/sessions 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/views 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/logs 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/bootstrap/cache 0775 api_stage_ebs_amz_at nginx -"
|
||||
];
|
||||
}
|
||||
43
hosts/amzebs-01/modules/mysql.nix
Normal file
43
hosts/amzebs-01/modules/mysql.nix
Normal file
@@ -0,0 +1,43 @@
|
||||
{ pkgs, config, ... }:
|
||||
{
|
||||
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb;
|
||||
settings = {
|
||||
mysqld = {
|
||||
max_allowed_packet = "64M";
|
||||
transaction_isolation = "READ-COMMITTED";
|
||||
binlog_format = "ROW";
|
||||
# Allow remote connections
|
||||
bind-address = "0.0.0.0";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Create read-only user for remote access after MySQL starts
|
||||
systemd.services.mysql-setup-readonly-user = {
|
||||
description = "Setup MySQL read-only user";
|
||||
after = [ "mysql.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = "root";
|
||||
};
|
||||
script = ''
|
||||
PASSWORD=$(cat ${config.sops.secrets.mysql-readonly-password.path})
|
||||
${pkgs.mariadb}/bin/mysql -u root <<EOF
|
||||
CREATE USER IF NOT EXISTS 'api_ebs_amz_at_ro'@'%' IDENTIFIED BY '$PASSWORD';
|
||||
GRANT SELECT ON api_ebs_amz_at.* TO 'api_ebs_amz_at_ro'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
EOF
|
||||
'';
|
||||
};
|
||||
|
||||
services.mysqlBackup.enable = true;
|
||||
|
||||
sops.secrets.mysql-readonly-password = {
|
||||
owner = "mysql";
|
||||
};
|
||||
}
|
||||
56
hosts/amzebs-01/modules/postfix.nix
Normal file
56
hosts/amzebs-01/modules/postfix.nix
Normal file
@@ -0,0 +1,56 @@
|
||||
{ pkgs
|
||||
, lib
|
||||
, config
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
headerChecksFile = pkgs.writeText "header_checks" ''
|
||||
# Warn about missing critical headers (but don't reject from localhost)
|
||||
# These help identify misconfigured applications
|
||||
/^$/ WARN Missing headers detected
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.postfix = {
|
||||
mapFiles."header_checks" = headerChecksFile;
|
||||
enable = true;
|
||||
hostname = "amzebs-01.amz.at";
|
||||
domain = "amz.at";
|
||||
|
||||
config = {
|
||||
# Explicitly set hostname to prevent "localhost" HELO issues
|
||||
myhostname = "amzebs-01.amz.at";
|
||||
|
||||
# Set proper HELO name for outgoing SMTP connections
|
||||
smtp_helo_name = "amzebs-01.amz.at";
|
||||
|
||||
# Professional SMTP banner (prevents appearing as default/misconfigured)
|
||||
smtpd_banner = "$myhostname ESMTP";
|
||||
|
||||
# Listen only on localhost for security
|
||||
# Laravel will send via localhost, no external access needed
|
||||
inet_interfaces = "loopback-only";
|
||||
|
||||
# Compatibility
|
||||
compatibility_level = "2";
|
||||
|
||||
# Only accept mail from localhost
|
||||
mynetworks = [ "127.0.0.0/8" "[::1]/128" ];
|
||||
|
||||
# Larger message size limits for attachments
|
||||
mailbox_size_limit = 202400000; # ~200MB
|
||||
message_size_limit = 51200000; # ~50MB
|
||||
|
||||
# Ensure proper header handling
|
||||
# Reject mail that's missing critical headers
|
||||
header_checks = "regexp:/var/lib/postfix/conf/header_checks";
|
||||
|
||||
# Rate limiting to prevent spam-like behavior
|
||||
# Allow reasonable sending rates for applications
|
||||
smtpd_client_message_rate_limit = 100;
|
||||
smtpd_client_recipient_rate_limit = 200;
|
||||
|
||||
# Milter configuration is handled automatically by rspamd.postfix.enable
|
||||
};
|
||||
};
|
||||
}
|
||||
84
hosts/amzebs-01/modules/rspamd.nix
Normal file
84
hosts/amzebs-01/modules/rspamd.nix
Normal file
@@ -0,0 +1,84 @@
|
||||
{ pkgs
|
||||
, config
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
domain = "amz.at";
|
||||
selector = "amzebs-01";
|
||||
|
||||
localConfig = pkgs.writeText "local.conf" ''
|
||||
logging {
|
||||
level = "notice";
|
||||
}
|
||||
|
||||
# DKIM signing configuration with host-specific selector
|
||||
dkim_signing {
|
||||
path = "/var/lib/rspamd/dkim/${domain}.${selector}.key";
|
||||
selector = "${selector}";
|
||||
allow_username_mismatch = true;
|
||||
}
|
||||
|
||||
# ARC signing (Authenticated Received Chain)
|
||||
arc {
|
||||
path = "/var/lib/rspamd/dkim/${domain}.${selector}.key";
|
||||
selector = "${selector}";
|
||||
allow_username_mismatch = true;
|
||||
}
|
||||
|
||||
# Add authentication results to headers
|
||||
milter_headers {
|
||||
use = ["authentication-results"];
|
||||
authenticated_headers = ["authentication-results"];
|
||||
}
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
.include(priority=1,duplicate=merge) "${localConfig}"
|
||||
'';
|
||||
|
||||
# Enable Postfix milter integration
|
||||
postfix.enable = true;
|
||||
};
|
||||
|
||||
# Copy DKIM key from sops secret to rspamd directory
|
||||
systemd.services.rspamd-dkim-setup = {
|
||||
description = "Setup DKIM key from sops secret for ${domain}";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "rspamd.service" ];
|
||||
after = [ "sops-nix.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = ''
|
||||
DKIM_DIR="/var/lib/rspamd/dkim"
|
||||
DKIM_KEY="$DKIM_DIR/${domain}.${selector}.key"
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
mkdir -p "$DKIM_DIR"
|
||||
|
||||
# Copy key from sops secret
|
||||
if [ -f "${config.sops.secrets.rspamd-dkim-key.path}" ]; then
|
||||
cp "${config.sops.secrets.rspamd-dkim-key.path}" "$DKIM_KEY"
|
||||
chown rspamd:rspamd "$DKIM_KEY"
|
||||
chmod 600 "$DKIM_KEY"
|
||||
echo "DKIM key deployed successfully from sops secret"
|
||||
else
|
||||
echo "ERROR: DKIM key not found in sops secrets!"
|
||||
echo "Please ensure rspamd-dkim-key is defined in secrets.yaml"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
sops.secrets.rspamd-dkim-key = {
|
||||
owner = "rspamd";
|
||||
group = "rspamd";
|
||||
mode = "0400";
|
||||
};
|
||||
}
|
||||
@@ -53,6 +53,7 @@ let
|
||||
|
||||
enableMysql = mkEnableOption (lib.mdDoc "MySQL Database");
|
||||
enableDefaultLocations = mkEnableOption (lib.mdDoc "Create default nginx location directives") // { default = true; };
|
||||
enablePhp = mkEnableOption (lib.mdDoc "PHP-FPM support") // { default = true; };
|
||||
|
||||
authorizedKeys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
@@ -138,12 +139,12 @@ in
|
||||
BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}";
|
||||
};
|
||||
}
|
||||
) cfg.instances;
|
||||
) (lib.filterAttrs (name: opts: opts.enablePhp) cfg.instances);
|
||||
|
||||
services.phpfpm.pools = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
@@ -166,7 +167,7 @@ in
|
||||
phpPackage = instanceOpts.phpPackage;
|
||||
phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ];
|
||||
}
|
||||
) cfg.instances;
|
||||
) (lib.filterAttrs (name: opts: opts.enablePhp) cfg.instances);
|
||||
|
||||
};
|
||||
|
||||
@@ -216,7 +217,7 @@ in
|
||||
'';
|
||||
|
||||
# Cache Media: images, icons, video, audio, HTC
|
||||
"~* \\.(?:jpg|jpeg|gif|png|webp|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
"~* \\.(?:css|js|jpg|jpeg|gif|png|webp|avif|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
@@ -228,19 +229,12 @@ in
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Cache CSS, Javascript, Images, Icons, Video, Audio, HTC, Fonts
|
||||
"~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
"/".extraConfig = ''
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
'';
|
||||
})
|
||||
{
|
||||
(mkIf instanceOpts.enablePhp {
|
||||
"~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
@@ -256,7 +250,7 @@ in
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
|
||||
fastcgi_index index.php;
|
||||
'';
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
extraConfig = instanceOpts.extraConfig;
|
||||
@@ -325,4 +319,3 @@ config.users.groups = mapAttrs' (instance: instanceOpts:
|
||||
mkIf instanceOpts.enableMysql user
|
||||
) cfg.instances;
|
||||
}
|
||||
|
||||
46
hosts/amzebs-01/secrets.yaml
Normal file
46
hosts/amzebs-01/secrets.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
borg-passphrase: ENC[AES256_GCM,data:Q2GvEat5EHmshFiya3yNqFTVS+oJv0al+bYMRwysb0yu7F2gCJd000Y3ibA+tUPSL9iSlMSy0cTkesGVEGBt9w==,iv:/kUJXgibF1cyaCPB55/0nKYq9sSva6psxu2P/l7iRN4=,tag:velr9LTfoj7gEWhUmvPtQg==,type:str]
|
||||
borg-ssh-key: ENC[AES256_GCM,data:0YEvv7QDmGsur0PFMmz5HqDgDCEk0kRaOu1n7GGWAwmmr0K0bVbpKzHw5wMiMnXEBrvj0izo4P4LYGAlAAYn22Bhgi2eN/vdvSO5V5uDV1ep2dV/TN2m3oYgTIgot47YgwBhcNunIUtEsbZuhAsTGL6LFBPJ3OCLKvhXTNTDaajgH/e4CvyxHHl63MBzr0i1ajigl1IKCk2hhZF4Kd1YGBCVZRoNyyNXywihlcFeskNfldW/sd5Qn2nowVf1MEV9n6Il6Zc1FX69WUVy1k+kOT7HJZGq3uDmgwXQgQhqKm1wh5uOlLkGUX6fz/nz+YFzLFMuUVvs34CzbbEFuWmGU+aNQrfCfI1hqwB5s6wVNdpUmigX9AQMQklu85tHFJg1AaRvhA24Cp/GrptggrTThcjwVFoe9NSQouNYn+ImTvlsE4HuDRRFE6YUounGd2lpRd40LsEjwKiLtwBwqG94u4ZOI91+LG6ZqHftRehE9r/CtedLyqtluNyyQyUNKPraUOm9Rrapewsj0ZCZgGQU,iv:xdRUBQlZlwVIog5KgZRmGNxdmhFE9HgnK3Ahfo+zT9k=,tag:McsJKUEGnKXxiv8Tg5zA4A==,type:str]
|
||||
mysql-readonly-password: ENC[AES256_GCM,data:k2RplkUZPGZlh29KXXdtwe+MCqKzTI/bLdyuEeicdkbGlBk1SGyLF8vW4t8=,iv:a14IrXYVCDqPKGfJSEPP8g19sPvRTx5NT8IVJJeL48s=,tag:GWgU3oa/+u21/L2y3+vOsw==,type:str]
|
||||
rspamd-dkim-key: ENC[AES256_GCM,data:maOnsx8AQUIjXqHEzHLxtSvAkr9+YCZid9xWaflkffS0gHd/hoHrozHy+rHSjU7Mz7QHYhjUjFY7Hp7wdKQnHpQLJRV96iNPXTXXYtBr7oDL51cq8ozd094FuMeLNSPitV89OHDcM+9h1F4dsdDWPUiw7eoijQeZ8vx1/VCVAp4FVxTFX3qhoMhXlFabiyM85eKMwJG4BdSwqS624f2Z4tvECRp0pBGtd/3r4/EVRDV1qNsiFvH8mi8eyg9xiWDLDrePq4TuWSu1Xc7z0qpDy0o8iAwGhPu9egIyzHEPk07j9U7PpK56C2UCSY0JBm0hkBGbqLXyRklSMytxoKgw4GJykMwNPNXmA9yuLPanxagJB/z8b7X4HTuYhExzQcC6ke/y8xKcxU4qGt8Ayy5v+QoNpdqXIPsZkIuw9uWm6RIgDt2dCaOdI06lesZKjqU/T6EhDfGoGZX7DwQ7uV9xNDM4NW2jsKpUdFKnzPCCe7/jO/ck4P4i8V+6NWDjj4+/BXDNnKJbMcHIHoSvckCGZiginJsbGvSWd0HfbpR7GQAnL3uKB5/HuFAaUkx+dPHmmP2tOBv6vNt+tq+V9i4kQmwAdl8a9KI456tw9vLwXcBDZOO7n4X5H0jc4afoYCnvLxahvbIXm2QNcBYVKxkqBCvoYEMrBjrnujwbQdEfDKQf3g5p8LQwAfCQ3ng+XH/BDF3qMBdsN1u5Di0FQpCDaGKX8pJ1gg+il76fJgSU8ftoaT32hJnLAjal4cgNIxbta2UYQLixUqaWZ8xqvxrSopkWYrlBBUyQh9jMEoTzpxwCsEPQ72qgVcQfJYlMl4WUBwcasfJnySR+qZ22g3fhStpAQ2HuTGhLjTG1QOewYdwDXDhNmcbqZ478Sp1t7qbBx0R7vWFSyYCMlbmLmvzPm6Z3ET7lkfCrMjMNXaQ8cWSF19QaHAfqRwQooLL93yx7U0KHCilEg4bUjsw8MLQNa4A0ohpq6CG4s5O1+di7W4/h71/moggIebFb2eGLJ8BvbkwiVozXI9L77IGd9RswlEjZed18u7fqetS7dyDthhVG2pvya4zZI/cxIq6oJkNr2RIt3NgYChOh0I/17DuSJJ1jAmPB0Evj8QtCCo49ENnyO5cGWn12DZWybwYkg2jQC4aDFA/u5ajTo3wOKdwHj5hgMz/z05Bn2vAdhCGl6uWWNzcNnDiu3/rjqsjOkfkp0hCP7Q==,iv:FORxJ8htcoLIEJihUN7im3dN4jhnigB70InTohtpWwU=,tag:e2DHBd2dn3piCkEdkbHdoA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhQUpWNUgxVnhuTXd2TkF0
|
||||
SVVHemFKRWlYczZ0TnBESVNRczhuRUNnUG1BCmJKQ2JZbHhFcXJidHJzci9OaFBm
|
||||
ZTd0MGhsaVBic3dMb3psUHRCRnR3ODQKLS0tIERrSG1GVTRHdkJpVWpqdTZ4Yytq
|
||||
OHhlZjV6MjRVbXFsWjlQSU03ZDNwYm8KAswHRSdV0BW/oJyZx63iZRHsF7SZ6PO+
|
||||
hajQqmEyfcVfEu39zZzxQ2mtWlOr69I++irOhE3NeiFeJ1yIRQDJEQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUaUNnY0hpdDAzMTNIUS9D
|
||||
RmdKbmplUk9DRXlLRXEvSnVjT05sQjcvTnpJCkd6bGRINm5yYUZOUTVzWEdjRmtG
|
||||
Mmx0ci93N2wvTWV5MzlRVnlYdUxoUWsKLS0tIEVHUlNWYStWTG01RzRrVnNXc3BW
|
||||
VkRkUXROU3plNmwvTUVhYmhCS2syQkEKKgC0EmUu1u2vZ/SZTnam+h846gZSyY4V
|
||||
JyMzkws8O5TY9juWdDzXJIU67mIgc4qrWWN3uh8k28JBZGc078b5bg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoK2JUNVdYTzkvM1BBWXRm
|
||||
citCNlE4Z1NLdEZ2R0tNZTVSMlFSeGxGOURnClJnYURYa0JZaVprQWdBcmVnOWVj
|
||||
TGVCK1JWMVlueHJUaTZZYmROM0E5aDAKLS0tIEJxYkdadGtZM250d2d6Ujl2UU9C
|
||||
YUpkVll2S2RpT0I1UVZiZFRKS1prMEEKp/bGImanJ/58vTQG/gUun/Y2QdmOEi3h
|
||||
hVS0V2QcfuGgi0/YofLOM3+M6k6ViXw07XfXmR+puvLIHKr2y11x1Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDSGdEZnZEaDRpWUJVcnds
|
||||
VGFSQklvczBZdEdEbXhodW8vME9wMUpVRENjClFZcnVqYkJxdlBiZFhma0tmZjgz
|
||||
YXlIdlRDTDU4MHg1dzhGVDRJb2FGYVUKLS0tIDBXSWZ2NkxzdEk0ZlFRM00ybFNy
|
||||
M0doaWl5R2cwU2RxQm5DbWxXeTZ5S2MKwrB3SysmgzCThQOhEVx18dxIfko0+oZY
|
||||
9BSZOoFbfuwiLbtpL4J8bzxDvxn6sXxB8EBJH1hbpID53AquWDsxSw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2025-11-19T11:16:25Z"
|
||||
mac: ENC[AES256_GCM,data:x4yor9G+QirceSYSX1K9GdfyGellT4JCkE09Tl9/mOX8HMOKFAQGknuwwU6SNGg+ciBFk4TdjQnDmVai4T8JQo9W/DLiZ+GKnWO3s+ZLDX30sEF0aMjKa43R5CCPO/Fl2XH96TaPC+8itTJQ6TpBSg51QLPcpqrMljiBNWvEoTU=,iv:Zi9rglAwgsejUmIpLN/1QlL80BSp3HP32k1xkWt2b+o=,tag:2ADk8d2G4OezkQjcV3CZuA==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.11.0
|
||||
@@ -1,16 +1,16 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
{
|
||||
services.webstack.instances."api.optiprot.eu" = {
|
||||
services.webstack.instances."api.ebs.amz.at" = {
|
||||
enableDefaultLocations = false;
|
||||
enableMysql = true;
|
||||
authorizedKeys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDBGlzrg6NP5ezRFVu1CV8r0uCcS2oIgYG2/u6Cit++ARWQRO5Y0+9qC1Y2RNUaLPbvTmXg7ShskolUeuLryqvp10K2kXQ4E9NlmJ3BNLiAfWCzfe6gAgr6u5unVlXHttnP0leYpGGMUCKuiJpzy/bR6rMIUrCQC6W/MeXkwysNWKvL+ZD0IeQbogtfMFZmag9PO04RKZZvuUn9YvlgkTEK97g5dtyP1NxdtE9dDYf0G+0HcHITcw+lVmGNNwi43nAoUHieQd1kWc8YmxFB+y5O+vRH2O6pZBSdr0tdK6bPcezxd3Gk6i3a54yZfbvSislWA+o7s6uw/qExocpZb7xWa5ymPrGlEPbpYdT1y3hFO25+L1lR4QdG9oUNtJ974bL+EmYmHU+j32K3f8fxDg6BRo8FuriLtAzP7/2/7W8K4nIdMoosS+Ond2JE6XFkg1kSrXCivDBQoetZLO2y+ZPYcsQwIZsdjOnZqVr76nTepqCGIKYCuNM/9sl4AWCsyU="
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBTsA1z6/vOshSqmEUGO6vFbAYCrucgNORMKyoQ5/9/l"
|
||||
];
|
||||
extraConfig = ''
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
phpPackage = pkgs.php82.withExtensions ({ enabled, all }:
|
||||
enabled ++ [ all.imagick ]);
|
||||
};
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
services.nginx.virtualHosts."api.ebs.amz.at".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
{
|
||||
services.webstack.instances."api.paraclub.at" = {
|
||||
services.webstack.instances."api.ebs.cloonar.dev" = {
|
||||
enableDefaultLocations = false;
|
||||
enableMysql = true;
|
||||
authorizedKeys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCmLPJoHwL+d7dnc3aFLbRCDshxRSQ0dtAVv/LYBn2/PBlZcIyVO9drjr702GL9QuS5DQyjtoZjSOvv1ykBKedUwY3XDyyZgtqjleojKIFMXkdXtD5iG+RUraUfzcFCZU12BYXSeAXK1HmIjSDUtDOlp6lVVWxNpz1vWSRtA/+PULhP+n5Cj7232Wf372+EPfQPntOlcMbyrDLFtj7cUz+E6BH0qdX0l3QtIVnK/C1iagPAwLcwPJd9Sfs8lj5C4g8T9uBJa6OX+87lE4ySYY+Cik9BN59S0ctjXvWCFsPO3udQSC1mf33XdDenc2mbi+lZWTfrN8S2K5CsbxRsVBlbapFBRwufEpN4iQnaTu1QmzDrmktBFAPJ2jvjBJPIx6W3KOy3kUwh9WNhzd/ubf9dFTHzkTzgluo/Zk6/S8fTJiA4rbYKSkLw9Y265bvtR1kfUBLKSa/Axe5dkKysX1RNKfTJEwbh2TfIS3apQPZZc5kIEWfeK/6kbQX7WJZFtTs="
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqpF703JmLTBpBjTSvC0bnYu+lSYdmaGPHxMnHEbMmp"
|
||||
];
|
||||
extraConfig = ''
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
phpPackage = pkgs.php82.withExtensions ({ enabled, all }:
|
||||
enabled ++ [ all.imagick ]);
|
||||
};
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
services.nginx.virtualHosts."api.ebs.cloonar.dev".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
{
|
||||
services.webstack.instances."api.optiprot.cloonar.dev" = {
|
||||
services.webstack.instances."api.stage.ebs.amz.at" = {
|
||||
enableDefaultLocations = false;
|
||||
enableMysql = true;
|
||||
authorizedKeys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDBGlzrg6NP5ezRFVu1CV8r0uCcS2oIgYG2/u6Cit++ARWQRO5Y0+9qC1Y2RNUaLPbvTmXg7ShskolUeuLryqvp10K2kXQ4E9NlmJ3BNLiAfWCzfe6gAgr6u5unVlXHttnP0leYpGGMUCKuiJpzy/bR6rMIUrCQC6W/MeXkwysNWKvL+ZD0IeQbogtfMFZmag9PO04RKZZvuUn9YvlgkTEK97g5dtyP1NxdtE9dDYf0G+0HcHITcw+lVmGNNwi43nAoUHieQd1kWc8YmxFB+y5O+vRH2O6pZBSdr0tdK6bPcezxd3Gk6i3a54yZfbvSislWA+o7s6uw/qExocpZb7xWa5ymPrGlEPbpYdT1y3hFO25+L1lR4QdG9oUNtJ974bL+EmYmHU+j32K3f8fxDg6BRo8FuriLtAzP7/2/7W8K4nIdMoosS+Ond2JE6XFkg1kSrXCivDBQoetZLO2y+ZPYcsQwIZsdjOnZqVr76nTepqCGIKYCuNM/9sl4AWCsyU="
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqpF703JmLTBpBjTSvC0bnYu+lSYdmaGPHxMnHEbMmp"
|
||||
];
|
||||
extraConfig = ''
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
@@ -31,4 +31,7 @@
|
||||
phpPackage = pkgs.php82.withExtensions ({ enabled, all }:
|
||||
enabled ++ [ all.imagick ]);
|
||||
};
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
services.nginx.virtualHosts."api.stage.ebs.amz.at".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
}
|
||||
14
hosts/amzebs-01/sites/default.nix
Normal file
14
hosts/amzebs-01/sites/default.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
{ ... }: {
|
||||
imports = [
|
||||
# Enabled vhosts (cloonar.dev)
|
||||
./api.ebs.cloonar.dev.nix
|
||||
./ebs.cloonar.dev.nix
|
||||
./ebs-mobile.cloonar.dev.nix
|
||||
|
||||
# Disabled vhosts (amz.at) - uncomment to enable
|
||||
./api.ebs.amz.at.nix
|
||||
./api.stage.ebs.amz.at.nix
|
||||
./ebs.amz.at.nix
|
||||
./stage.ebs.amz.at.nix
|
||||
];
|
||||
}
|
||||
49
hosts/amzebs-01/sites/ebs-mobile.cloonar.dev.nix
Normal file
49
hosts/amzebs-01/sites/ebs-mobile.cloonar.dev.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "ebs-mobile.cloonar.dev";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErjoADQK5SJ5si/iezzwQn5xH1RkgnTIlbeE4BRU1FN"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
49
hosts/amzebs-01/sites/ebs.amz.at.nix
Normal file
49
hosts/amzebs-01/sites/ebs.amz.at.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "ebs.amz.at";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIInwmhTIPw7NnR3LDn2T5N6by0ZPXdL3r2O/8oRUc/ki"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
49
hosts/amzebs-01/sites/ebs.cloonar.dev.nix
Normal file
49
hosts/amzebs-01/sites/ebs.cloonar.dev.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "ebs.cloonar.dev";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErjoADQK5SJ5si/iezzwQn5xH1RkgnTIlbeE4BRU1FN"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
49
hosts/amzebs-01/sites/stage.ebs.amz.at.nix
Normal file
49
hosts/amzebs-01/sites/stage.ebs.amz.at.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "stage.ebs.amz.at";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErjoADQK5SJ5si/iezzwQn5xH1RkgnTIlbeE4BRU1FN"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
https://channels.nixos.org/nixos-24.05
|
||||
@@ -1,83 +0,0 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
domain = "sync.cloonar.com";
|
||||
in {
|
||||
sops.secrets.firefox-sync = { };
|
||||
|
||||
security.acme.certs."${domain}" = {
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
containers."firefox-sync" = {
|
||||
autoStart = true;
|
||||
ephemeral = false; # because of ssh key
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "10.42.97.1";
|
||||
localAddress = "10.42.97.51/24";
|
||||
bindMounts = {
|
||||
"/run/secrets/firefox-sync" = {
|
||||
hostPath = "/run/secrets/firefox-sync";
|
||||
isReadOnly = true;
|
||||
};
|
||||
"/var/lib/acme/${domain}/" = {
|
||||
hostPath = "${config.security.acme.certs.${domain}.directory}";
|
||||
isReadOnly = true;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networking = {
|
||||
hostName = "firefox-sync";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "10.42.97.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nameservers = [ "10.42.97.1" ];
|
||||
};
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
sslCertificate = "/var/lib/acme/${domain}/fullchain.pem";
|
||||
sslCertificateKey = "/var/lib/acme/${domain}/key.pem";
|
||||
sslTrustedCertificate = "/var/lib/acme/${domain}/chain.pem";
|
||||
listen = [
|
||||
{
|
||||
addr = "0.0.0.0";
|
||||
ssl = true;
|
||||
port = 5000;
|
||||
}
|
||||
];
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:5001/";
|
||||
recommendedProxySettings = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.mysql.package = pkgs.mariadb;
|
||||
services.firefox-syncserver = {
|
||||
enable = true;
|
||||
singleNode = {
|
||||
enable = true;
|
||||
enableNginx = false;
|
||||
hostname = domain;
|
||||
};
|
||||
settings = {
|
||||
port = 5001;
|
||||
tokenserver.enable = true;
|
||||
};
|
||||
secrets = "/run/secrets/firefox-sync";
|
||||
logLevel = "trace";
|
||||
};
|
||||
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
];
|
||||
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
foundry-vtt = pkgs.callPackage ../pkgs/foundry-vtt {};
|
||||
cids = import ../modules/staticids.nix;
|
||||
in {
|
||||
users.users.foundry-vtt = {
|
||||
isSystemUser = true;
|
||||
uid = cids.uids.foundry-vtt;
|
||||
home = "/var/lib/foundry-vtt";
|
||||
group = "foundry-vtt";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.foundry-vtt = {
|
||||
gid = cids.gids.foundry-vtt;
|
||||
};
|
||||
|
||||
|
||||
containers.foundry-vtt = {
|
||||
autoStart = true;
|
||||
ephemeral = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "10.42.97.1";
|
||||
localAddress = "10.42.97.21/24";
|
||||
bindMounts = {
|
||||
"/var/lib/foundry-vtt" = {
|
||||
hostPath = "/var/lib/foundry-vtt";
|
||||
isReadOnly = false;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networking = {
|
||||
hostName = "foundry-vtt";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "10.42.97.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
nameservers = [ "10.42.97.1" ];
|
||||
};
|
||||
systemd.services.foundry-vtt = {
|
||||
description = "Foundry VTT Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
NODE_ENV = "production";
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.nodejs}/bin/node ${foundry-vtt}/share/foundry-vtt/resources/app/main.js --dataPath=${config.users.users.foundry-vtt.home}";
|
||||
Restart = "always";
|
||||
User = "foundry-vtt";
|
||||
WorkingDirectory = "${config.users.users.foundry-vtt.home}";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.foundry-vtt = {
|
||||
isSystemUser = true;
|
||||
uid = cids.uids.foundry-vtt;
|
||||
home = "/var/lib/foundry-vtt";
|
||||
group = "foundry-vtt";
|
||||
};
|
||||
|
||||
users.groups.foundry-vtt = {
|
||||
gid = cids.gids.foundry-vtt;
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 30000 ];
|
||||
};
|
||||
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
unstable = import
|
||||
(builtins.fetchTarball https://github.com/nixos/nixpkgs/tarball/nixpkgs-unstable)
|
||||
# reuse the current configuration
|
||||
{ config = config.nixpkgs.config; };
|
||||
in {
|
||||
services.home-assistant.customComponents = with unstable.home-assistant-custom-components; [
|
||||
epex_spot
|
||||
];
|
||||
|
||||
services.home-assistant.config = {
|
||||
sensor = [
|
||||
{
|
||||
platform = "template";
|
||||
sensors = {
|
||||
electricity_price = {
|
||||
friendly_name = "Current Price of electricity";
|
||||
unit_of_measurement = "EUR/kWh";
|
||||
value_template = ''
|
||||
{{ (((states('sensor.epex_spot_data_price') | int ) / 1000) + (0.0149 + 0.053 + 0.00866)) | float }}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
services.home-assistant.config = {
|
||||
"automation toilet music" = {
|
||||
alias = "toilet music";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "light.toilett_lights";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "media_player.volume_mute";
|
||||
target = {
|
||||
entity_id = "media_player.music_toilet_snapcast_client";
|
||||
};
|
||||
data = {
|
||||
is_volume_muted = "{{ trigger.to_state.state == 'off' }}";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
services.home-assistant = {
|
||||
extraComponents = [ "snapcast" ];
|
||||
config = {
|
||||
"automation piano" = {
|
||||
alias = "piano";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "media_player.music_piano_snapcast_client";
|
||||
attribute = "is_volume_muted";
|
||||
};
|
||||
condition = [
|
||||
{
|
||||
condition = "template";
|
||||
value_template = "{{ trigger.from_state.state != 'unavailable' }}";
|
||||
}
|
||||
{
|
||||
condition = "template";
|
||||
value_template = "{{ state_attr('media_player.music_piano_snapcast_client', 'is_volume_muted') == true or state_attr('media_player.music_piano_snapcast_client', 'is_volume_muted') == false }}";
|
||||
}
|
||||
];
|
||||
action = {
|
||||
service = "switch.turn_on";
|
||||
target = {
|
||||
entity_id = "switch.piano_switch_power";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
{ pkgs, config, python3Packages, ... }:
|
||||
let
|
||||
domain = "snapcast.cloonar.com";
|
||||
|
||||
snapweb = pkgs.stdenv.mkDerivation {
|
||||
pname = "snapweb";
|
||||
version = "0.8";
|
||||
|
||||
src = pkgs.fetchzip {
|
||||
url = "https://github.com/badaix/snapweb/releases/download/v0.8.0/snapweb.zip";
|
||||
sha256 = "sha256-IpT1pcuzcM8kqWJUX3xxpRQHlfPNsrwhemLmY0PyzjI=";
|
||||
stripRoot = false;
|
||||
};
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r $src/* $out/
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
security.acme.certs."${domain}" = {
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
containers.snapcast = {
|
||||
autoStart = true;
|
||||
ephemeral = false; # because of ssh key
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "10.42.97.1";
|
||||
localAddress = "10.42.97.21/24";
|
||||
bindMounts = {
|
||||
"/var/lib/acme/snapcast/" = {
|
||||
hostPath = "${config.security.acme.certs.${domain}.directory}";
|
||||
isReadOnly = true;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, python3Packages, ... }:
|
||||
let
|
||||
shairport-sync = pkgs.shairport-sync.overrideAttrs (_: {
|
||||
configureFlags = [
|
||||
"--with-alsa" "--with-pipe" "--with-pa" "--with-stdout"
|
||||
"--with-avahi" "--with-ssl=openssl" "--with-soxr"
|
||||
"--without-configfiles"
|
||||
"--sysconfdir=/etc"
|
||||
"--with-metadata"
|
||||
];
|
||||
});
|
||||
in
|
||||
{
|
||||
networking = {
|
||||
hostName = "snapcast";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "10.42.96.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
nameservers = [ "10.42.97.1" ];
|
||||
firewall.enable = false;
|
||||
};
|
||||
environment.etc = {
|
||||
# Creates /etc/nanorc
|
||||
shairport = {
|
||||
text = ''
|
||||
whatever you want to put in the file goes here.
|
||||
metadata =
|
||||
{
|
||||
enabled = "yes"; // set this to yes to get Shairport Sync to solicit metadata from the source and to pass it on via a pipe
|
||||
include_cover_art = "yes"; // set to "yes" to get Shairport Sync to solicit cover art from the source and pass it via the pipe. You must also set "enabled" to "yes".
|
||||
cover_art_cache_directory = "/tmp/shairport-sync/.cache/coverart"; // artwork will be stored in this directory if the dbus or MPRIS interfaces are enabled or if the MQTT client is in use. Set it to "" to prevent caching, which may be useful on some systems
|
||||
pipe_name = "/tmp/shairport-sync-metadata";
|
||||
pipe_timeout = 5000; // wait for this number of milliseconds for a blocked pipe to unblock before giving up
|
||||
};
|
||||
'';
|
||||
|
||||
# The UNIX file mode bits
|
||||
mode = "0440";
|
||||
};
|
||||
};
|
||||
|
||||
services.snapserver = {
|
||||
enable = true;
|
||||
codec = "flac";
|
||||
http.enable = true;
|
||||
http.docRoot = "${snapweb}/";
|
||||
streams.mopidy = {
|
||||
type = "pipe";
|
||||
location = "/run/snapserver/mopidy";
|
||||
};
|
||||
streams.airplay = {
|
||||
type = "airplay";
|
||||
location = "${shairport-sync}/bin/shairport-sync";
|
||||
query = {
|
||||
devicename = "Multi Room New";
|
||||
port = "5000";
|
||||
params = "--mdns=avahi";
|
||||
};
|
||||
};
|
||||
streams.mixed = {
|
||||
type = "meta";
|
||||
location = "/airplay/mopidy";
|
||||
};
|
||||
};
|
||||
|
||||
services.avahi.enable = true;
|
||||
services.avahi.publish.enable = true;
|
||||
services.avahi.publish.userServices = true;
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."snapcast.cloonar.com" = {
|
||||
sslCertificate = "/var/lib/acme/snapcast/fullchain.pem";
|
||||
sslCertificateKey = "/var/lib/acme/snapcast/key.pem";
|
||||
sslTrustedCertificate = "/var/lib/acme/snapcast/chain.pem";
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
proxy_buffering off;
|
||||
'';
|
||||
locations."/".extraConfig = ''
|
||||
proxy_pass http://127.0.0.1:1780;
|
||||
proxy_set_header Host $host;
|
||||
proxy_redirect http:// https://;
|
||||
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;
|
||||
'';
|
||||
};
|
||||
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{ ... }: {
|
||||
services.nginx.virtualHosts."git.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
locations."/" = {
|
||||
proxyPass = "https://git.cloonar.com/";
|
||||
};
|
||||
};
|
||||
services.nginx.virtualHosts."foundry-vtt.cloonar.com" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
locations."/" = {
|
||||
proxyPass = "http://10.42.97.21:30000";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
borg-passphrase: ENC[AES256_GCM,data:2WjoqMRmXvW9EGMmpMYhrC0Qt0Dk7QWlbEncZPdK2SxVljEoFibjVEr6jeYdAx6UkaXdjk9pD3PBbls2tWt0TiNQdh8=,iv:bHzASNjqqfPsQ/1w/oM7x0FubAzzRkn+iWrZlenU9rs=,tag:ektqi0rqEywg9YGybPQesw==,type:str]
|
||||
borg-ssh-key: ENC[AES256_GCM,data:b/xZnUTfi85IG1s897CBF1HD7BTswQUatbotyZfLmbhxXxEyffUeaiGsT9Gh9yQqOKTstTihA48nVk/4ekAPD/ZGDQ189V1BwKkQ5chN9TSULofekfmemhUhVGjnx8OFl6hYYpTttQSTLHtczmfE2iX1JyrZy2Z+H+w6dbZjkYDayRUt/4+5wCtQJ1Nt7bjzwLWhjdVtwDeBLm/kCywVguZLCgyiuqmXMr1h9jpUS7URZegGz1lFs34Ismu1LtaRjFGRyd8aKaTU6PSxDbjE4dQ3Lh1Hm3nhtOrSkswBZLp8OTP6emrQ7c3oJp1zqO5zQHXxD2V5hkPw6ln0Ee1aQp1rvLD8shRXzRbHG+mySvjKLJvLypnNuYfQklqlnhbG+M1/NN13oVF13nHpKwP5q33sRr49mfHw8YHdRhHuhYHVrpy8ep0AmPXiDYCDM4cnlOMnzlH/toF0fq0YRny6QoqKNpaYhmA61MXRPTZCqoAcE1N+oo7HymjJetzL9b2FkPCoDOx989IJ8SUaBJpzR+agNsFi87htVllRp4ozms/m56dI0AdwqeAre00iMBzpVS0hXURE7fqvAnLHQD1goW9XB2mztqcJ09YafrOgTA3oyazWcAjxgV33GupxxIDmwRdLmavvr4qrHfddYctYLPI7VolqT9JmKN6iVG9vYsDutgoyRlhzbGASKPLgcYn9sGG+LBgTHfZyABnYOaUetVP72mhSN30ZZixcCskVlGg5C53wrW5o6mBv+PyG8PimxLmQylbvHUdGGVLQfMpJaaXgpUjBX1MWdQAVa+Nyjm7QwYdRKoCb3suQ6bOq5O9eotel3GPB8gpKzInhNA/0xiB4UyCGp1i21iRS9+Rc7yufo5s3t56k0643K2DhBUVgssiTsG15BbQdX4c1O28i9zwEZ+wVci1yvLX38M0a3tDDt9iW1BIOWehShS7dpyJR2/OgWLFagw9hYP5h24t5k6Gz2ODhPouaFccYDRUBR6UECxA+gDS+trN8iNSX1oWa0ys0XvgwWpJ2CrdSArNqe1BdhM47BQwudiA3RwaEN3wRh5PeykSk/3BUXK+ZdAr0BZ8ij2q4F8zQexLxnrV6xRqofNcVs62iJAjx6g86InSv0nNjLQ9U/fBTL66u1iRZFJhuxPjDNfLJZqT0TvRR7KBcNWTwTuMCGNp5s9TngMUF4uhHx8qGxtjfH58WjixOhC9lgUt7cYEFIeefcwIO9VVnKoiXK5sPIvIsjtLRzGvejYSd0ZwSF3Ly9FkWLkr+o5rs5bXtGMsSQ+BUFg5nM1BqrHIGv9M+F4kPxhnqm9/JXuMSQ+JUzix5N0vHuSTphCayDpMHJYRUEkDEmwPXMyB9zWVmvMb0ByUnfs/n/jmL4WRuggYqchIR3/xuco5HUqLbEKXiJ39wVgy+i3/biWOOEu5BmMx3qbgQ1+6nlxY+f1qpXZ8br0RlXLOQ6L/O9Qa9gKZaxLm/5GCiFZ+SeU/c5OgUndYqTk6FsbDlNurA69IqjwubG345lpdB9VPoGP7dLsx3VaGKW0bvr06oRaeasMx90SN5bGQJH+0iQFkGPhp0m2v31zpBk1IibXi5Qb1OWGXGYd+iNt1ZQF0HVuEqQEXI62x92QkaR7eHowR4tCRF1xH1ZrBkyjtdofUU2wPqsRrOWqGIZWUh/JpfXkSAZQo9yJKnHcp9d3BPEvWpLWS9g1Jfej5XG497aP6crWw5XawOyzi+PEgz2Y3Q0R/MM3S1W2R7Z+21nekbCfghpNylwIX4UYkeX8YorheiumkUfFXjktPSkFCTuUrYAA89WZjIIqd4/gt3tS7keCsjEiTkW2KdDPlzNItKnC8xWnpRc+Wh6ghA/nt3j4POb880j3scFoDjgOv5lNk2Q84S/IW+DQ3U8o4JrKiXsxchDvmgGbU4FbXZTGLXeM1CybmbZKogIHdwJkhC425oqA1PMiq5tDPLKpl2214JuaV4Xd8R0bwCSHYjQp9gqJT9j1Wg/3P0M3/VGZGoJEVriiBl6PBHP2CcvxK1NADDmMHgGQwwfROoSijAzzPKCy9sgzsquTkqzq8q4aChjGKShxs+52dpnmmuygSlxjyVQCEW9kLERf1Nm1arsLkHJ4ZsWgSrskGvjsPEvyEnpY33gGB7fpy90NW0GtELgGzEw/1nfLcFbRBJ7gH+4Dby2fBTxoV2ks9m0Fv6OWsfIe6H54zWLmqB1RkQaskb1wDKU3HATOmuYo/fByLIsMyR5l3P7LXWF5CJOprzp41rGts/ybJEG1EUtmVCs2epTwbeG/Waq1DB3TFa639ETjxOfGQ65PXp5aT1d5v+ko87LiR+0us6xwlfZ6NMRRZuPt4wycFgPUAAmpdmguwDKifHKA258g9kzotT25JeFFEMVhsMi1PoXEqA+sFomdsLt+Vtpr2aGMUWyHD/E2fgAtybLwxbjqDINi8vXWJxv/UZdH8wBOlWLtaeGg5/jRsMuL/hSSZ84Q2zfRVvV7/BZ7wnxfoXmAwRdTZijAvc9TxWszP6E5mAix7s/znU+1vnseJdxWa4Ff1wOGVL/Tem2K0J/mp75XuzSP7nCYDMgqhnvfzlD8vv6QpxtDUAbdTBDyPkQ4U9L6+y5ul5Aegpui+p0G9/0UHdBYhJiFd90omnhSmyHx2pvgUTfbL/Kv/pk7nwTv89a87NXNA9K6AATwx0kUPgIWs/5FGi8leCXGSsgBbJogL1htC72pKzVH6ckEzKeBzRADmwFLhnPIvp37ZkQPj0rrWRhkd5RqsFcN0166N+M4lPD0hzPd2+nEXDAOHoCK7U+BcRcJ3GUlyPU91dbWfo9otPd3naTvGVZuFDxOihLtBXaLTsxmS4STk6DVRjwNmX8YC9FwXkED19xEeH6KkaFs1nVXnmDqpvi2BcueT96t6TOeu5HcA9fAgFTpOKVT6cK2PcHTtJhjrPkfSYr0/ksJdV7r9N4JgAEfiASMMHS5uQWJlyJKWo92rJ2IvSCQx4lcK3gasgcTsVaYmuRORM+6263r4NKS8W8r55XvVyW/C7vvsVq6wF3xUkQadBkxIUQUVWxxCc1pWOlfWwMs0i+ZssoaWopbs7x45z86i+3HsHmfS6GuXUpQfgvXe9Bn7mOj7VQWaG9NIFUpIxisGfdY9L8+RXobo7etD3da7TNMs40BT+34tijcX53FzKwvG3ESNPB2hjOAITDta6LDOHhJrlVqn90p1DicThHOaT3fxt6ST287EhWqK9S1gpkLrp0gNSA9v+K9mBvWaWYNDXY7sGxOIMzCEIdFT18Pra92NhGTJtC0XizHDMUfGx5WAaard1Iy/PYXvavoAwp30qDCQGF+PgwSProa+JtQQPzoEgtSXNVhUWIzz10TACuo+vHt8sHvFG3VuU7jSOr9sqVrN36KMDUlwo0gavHKsjRxHf2OGh552q7AP+sM6Y5WhA4KhmQSUKCVxYVQ==,iv:U3+fjacm8+gZAjPQNz2mjFYTUbLyltTaPiSKb3lvCmk=,tag:ZR6zI1UijDayIvH3v35Hqg==,type:str]
|
||||
zammad-key-base: ENC[AES256_GCM,data:HO9MuwcwjryuXr5No8sCPfso5bpLtQCoczrC/R214ecVIFwwH1uhMeNO8Tlh6EjRLPo7aVTSz87Vx5yaNVezvHCs55G6TT9mcNS/v/V7sbFz9dNIgbFblY3gFIAa4cViioYc71wdb7d4Tta7qhse5zQ41KhAqCWuGDgFErQA4Oc=,iv:b1wY8fW0psircSlNXwDjPzNWK8NyAMNqegitNcqV6U4=,tag:oQ7nyO9TKOOu6IF7ODzpPA==,type:str]
|
||||
dendrite-private-key: ENC[AES256_GCM,data:ZHDIa/iYSZGofE67JU63fHRdKbs/ZyEJY45tV6H8WZAOcduGafPYBo2NCZ7nqLbc2Z9dUUgsrpzvkQ3+VaWqFUv7YsE+CbCx4CeiLGMkj8EAGzX4rkJGHMzkkc2UT7v9znCnKACS3fZtU69trqVMcf1PzgqepOHMBku37dzpwOQC/Tc3UTuO72M=,iv:Ljun1/ruY9cDBm9vu62riUrpGjrWtFFx90GeE7uc3Yo=,tag:FF4xPb1SDhK/4ITr/idvYg==,type:str]
|
||||
matrix-shared-secret: ENC[AES256_GCM,data:HeS4PT0R+TRU6Htwa5TChjK1VAjAdgSS8tSnva+ga3f+mEfJPTQ02pEvS2WFvcnchmEjNYy39zL/rbtX,iv:4yR+VgdJY3VcvLg18v+5jbJDSkFzaeyLNAZ0k8ivjdQ=,tag:RA96iSFDUdlXq30c/vkvpA==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoUWdTYlRjWDJvemF5Q2sr
|
||||
VCtrS2dTTGRwUlNIWHd0WkVCRkRMcGhuTzE0ClNic1FmQ05UNWQwbGc4TUFMNGlI
|
||||
K0RhK2pqUGY3UElmK1pNUEkxV2xGUTQKLS0tIFRORE9JTDRZK0MwZUJoc2xlcHFH
|
||||
bmp3ZW14TVdCMHhkSi84NE5neDdrY3cKYfgu7aqvG6wQmEFhmzieXFGoQpyffPXj
|
||||
jiHrAPjBBFy21wdYf0nQXNMzekqOMJwOj0oNA2b5omprPxjB9uns4Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1gjm4c3swt8u88e36gf2qlg3syxfc0ly94u64c42f2tsf24npw4csa6e4fw
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUUjQxWnBMQXo3QmF1STUw
|
||||
bHh1NDhvQXZIQ2RiOUx5OU5Wc3BVSEJDUEZVCmVzeFk5SWpMbVV4VUdsRmhiaWwz
|
||||
bTJDY1pJRXJvNUdCSXJqQ3Byd3lWN2sKLS0tIHRKdXRNc1BYcURBRVNlenk1OEl3
|
||||
Q05BN0VnQ0haeHBobWhRV0EzL3dLSEkKWlALiX5mvG8y0WUc8yFWMbcpSRrSGoQx
|
||||
SHaOlDCjYvViZ7GPRLqnSwDGZ1clC6JsTbwKXrMsWdZBKvSO/VIWQw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2024-10-14T16:53:41Z"
|
||||
mac: ENC[AES256_GCM,data:DUi6zUrZBMVaYZ/BvWny7RwPgXe+vQ+odO30fGe8iZHj9d3gzB95F75CqIgENi4gVOA4CQDADE+p45z/mtl04HAh7RiT0/k21RSdQcH2W9AX525fOzeqbxbPA/tXJOctwGrytFwlK9UdJULXkJCwYrJnwNc0XPnBk1FodTykXWs=,iv:q/eapgTVL/rifrrZeIcXT5VO9bEoS4EmmEhYJ2xHvQ4=,tag:xb0Qj/wu17cLTkvefsDqiw==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.8.1
|
||||
@@ -1,64 +0,0 @@
|
||||
borg-passphrase: ENC[AES256_GCM,data:jHb+yXK0RqNdVYtWiueztZFlHC/xQ6ZiAOUcLt6BxmZQewuL3mh4AZ+lQdmA/4EaaTTIhVMR3xFx5fU6b2CtNLiGb/0=,iv:IW09B1EE1OupMCOvv13MXRYiMsD4VmIfyYONUyrPX1c=,tag:3ankeLOaDJkwRUGCd72DuA==,type:str]
|
||||
borg-ssh-key: ENC[AES256_GCM,data:ir25XfzLBb/H/YWzxP501hCaLBB4jpiLW7WUcnvguzosT9QeOtBdJ0WB1IndEMtiEgQyE9kyGOJ3QJwzbQNkX6CG96Uzt2mKw8gw8ayUqC+B9zR8eIRYiDKOYs+YREVo7nA5pLLzIc/9jaRicDFMmw1Thmk7UUJKB1DNV49nU9K+nAfrCzk7ZQieY8oaasFD0cvNb4Ndj6f9PWSXkNBwKK52ig4hDeNBs1bdy8nDE8VqlwOo8H2DcYMzdMjKCZDBRccy8NofHEhakCW5OdliFyIHsLkcBHca3Bp46JN7wbo8avPPd9bXGuRiOSWYq50RcyZUovnB3g7Dk3swCyuiFztnStN63+g7ZnGFdYLYDYfuDSPN1W2HCkknmaoT910VNE8sEAMyfXk4tqJv4eW4qmFk2UwPlRCrsk9GtdRQ5wm8muNPHEZ8s2dGkn4WDcjy7SUpgF4UJJZV8iJe74W9BK1Ef+AWWNsNjYfZde3iw1+8Fz1u65u4seFWqQMok/noADpszbpk+YYRoM+5D/YVMx+KeDtoFqnZfULM/BqvAqdYYZtRzojndeNW6Ea4sxDE+XQ5b1OwGFlNAlnuS1fYYPvKojrKNgT9KMwbsvPijU5vFddY8Qpz2h6GKEv/OW87j5UeyDW4l32lvyawBuzczBfiFgCElggGSZHM5rjE4Deb06eQleTioZ79EDXTv5UsPQ6Bc1v5Wvnu8DvxJe4B10vxH70JIGIlmjwo0yhMkxDTN7BkAGQC0QAPhwtURDq+XVufQNjlTUjjH1Q1E4u0Vy19clMs8SStqFeMN02BfWZdS9mbueF5Ehc+8wTfAs43CQFublJ4wfG1PzEbqj9LZdimFe4hCnE2y6Gbf591shugVSAMA3UXQUuvFQmm69i9gz88YSYrkLlVStM+dtXCugZho72xgHtnI+5o19wuoZPRoxe47W0T2kJZZeomtqoAsSo5yr5JeYzYdaHYcK2fgRY0HWgWzOxnVEfX/gRPR3b20Tko6yp9lIDECkXVDQSxptxqIYk+VuETnD9YF2OpYeHZLGoo9OLdEHVZRcuy1S74aAOJGO9SAHLw3eukxG//AZlwcOYjOsYDVt3BjhYZEkYCLg8GkAqV/7bGsxT7pgckNEB2NRYQI9ckqEcEw9CdkYre67HwfPCvAble68VnRzgp+v5s0koVjTURF9FTxvVOXQEbvSpY828idyx6nOaAIHoqpIOFz4jsGE9L4FKamqnlnjzj2Ri/MboT9JQBj8bnIF/ej+dQGpfqZo7zqtu3d0B/9e0xuVTcqI9Bxlqn3D4108I8R37Ctr5OFKloeOZ8HHMsHcBUAzZC6/fWrOspru14YHW2YNj8nBxHve/P3oiTQ/nlXLcBGLoFfI+hOpofccQB8FnkKfTbLSRUGrGY6NJt9RCnZgm2+RUgel77XpsCsT/Q5ZGclBdyk8mSaqVjiNyHCbCV5tF/tWnuvf859S0tcmqbJ0FhIRAvwxFucmfi6FSPX5HEMdRbNV7szrHKSX60u7YA2DBBzv3c/+C2bxq70vhwFelqz7FqpVKwebbE4/a59lZpibzefCoji/TPDJB62/ox5NHHE5qenv7IPcEj3dEmdasbrApAw1UFsFlRCnlg4JIYley/AQx7OzUSImqkG8JWvSJ4JXijhsr9dPFR/cb0srUO88aFNh/ZUQhELZCVnzAsF81Y4w6LTGApMfUVN/yx9MqENGvObywzMls1UJphvzDZzvb+Ue6eqELogN1QcEI/WOirwVtJO6E7IevEtK4xxWsLfRHVjtbLc4QjCWuiyszAPTTttKJ+iC2h14Wj1XoiMpWRiVnj+jI9iWRen96P4glYEfuCYQS6vbGkNDEoZt/FnkLJDbLdjXatmhUoRpvExOtp26ULR/f1lwzLMJBt1qPvhuGur1ru2B1e8+AVte1Cfjmk+xrnxNwkTFLGe89Qjd77wPyQv9h0YrhZ6uDi2zLemhZs2LjW5ZvzV5P4thMDxkhezJHatPHAGa8OfclJOyrRTyW2azdz2A45MNzZtCQcnQdQxBXf+XRskLnhquZfgv66hFITjuF/HeI9cq4HJcrgaOcVj+tBdK1bTCyL2kqKkCpSCbh/Pv6FuAlDXgLjsWwZgOKz8gfTIfXMapPLDYVTbS/PPPABylZflN98FFyeFDHB3Fwn1a6qAJ0mC7+4sowVZ1DIAoflaHqNs5TXyb3KeZGgXj5ZQwhv1z6NySvOS6cHxx0PvkFo99T1NHztxCRERNvBdWSwsr32DTwEvZo5iNPy3lvKI5A+rXc7jlQkUbufbddtLw2iPtt29XyMDOysK010fXzzQRjaz4R8ZaDtHNjqPrynvqFPXRB0VSIrwXS2utU7bmD+0dGX26t9k5qRBi7Gm+iZNKGMnSRsm17bVk5o8q0tb1P1eGL9mexZJJvxolfXVFJJtR8m6vLmUX1LSht/JhoWFElrINl0hviwd1dehmTqdQqWz5/imjF+pVOasrt7XVZ+7T/rDpuwNl375qSZptM1pMUExJ3CvzigpnarXXQxEBYkf0haGvQwPWNVHe/bR/1VooSQkH/mGg1g+rcTqp4yB5hsFu1lNK4ph04WQOqaafg40HBv6e5cOjLkFdEtYNpjyd6sRS+WHk7zzFlfPVlzijq8f+oDH9ALRzNnL1Y2DrX53wx4dBBWvxE1Yhb6Kj6Er4ZDiRLLXo+wJOGCpnNTPJMVaYskZ+LN2e9nS2/ZwbsNBnPHxSqCc1oP4d3yXH0j90VKnWg79aIEOagRvTF/9F6SkkGL9zVuUnoVSPwq97etWWtjGoEORMGY7jkGOK+U391p7Z69Hrv2AejS1BoSDeGcxXasFvINpmc+Hl2c+zOlFBySu2zA39cVlcStUFICA5GCmE5Eum4ED9DXP6RAuicD7YE0qSKbMkfLxIWMCZ6wBcwVUjdt43SI/ZqdpDm3E1kTRg07dE0R091rtfzEiIwBM4xFPJBafOx0L/Do61YMOHGzi6wgIQO7P7wIslv62M8MD1KKa/eH0tE2vhG/GyEGtKkg3P9vZRJwioifyshS1hvrt5pLinuCaDYyqMAl8Ro0OOm8di7+mBvXib0nRLfW7wBGDA4ADTipizNWAmbspQQl89kH5gdxgXO5U+N/qc0zXbpB+qeHVkPIK1DmrJ8pHLOE8mOpLy7eHUsSku/WtTt/RP4pcDbBU/43MCbk7NXKu/LjKjkQBjAL49LxnYmhEU7X//jtwSPE3gdx0x+wRJxzlbehM6rpfDRV5WQGSFf7yjLc/Ga1KwsgVdAstJEzDdv2vWSsjNzfJvHVBLrQPIC9fggi3DeLiHTAryCUcLUhNj4xtZWhSS1qmx07E4VzfjDJLMOsLY0vlimgngZ3YYCjC3Sw0frfQH2SZvmbLd3XfBdud67ZaMUobcRhnKzQnilldyD1jWVWLdVTup4RVxT4GYek9nmYflzpWWmwbXatz9Sgcw==,iv:9E1uiPqM3Hh4KWtL8haxm6PRm2VPc+DggrA135FvfB8=,tag:QSOgzVH9IBMgZxJvUhvY2w==,type:str]
|
||||
ddclient: ENC[AES256_GCM,data:EaXjXS/bwL3S/Fr+rzQ7dXA1eIzeFpHH7H+SvoNhVSg=,iv:3BzjnJG5yT1W8ob2nm0oUlr+sSJ73W/ctl48xyxeeWM=,tag:TqKSwfxF0V1v5T8VT/qblw==,type:str]
|
||||
wrwks_vpn_key: ENC[AES256_GCM,data:gGipXC8JJO59b4KWMSo0+r761raQl7RzgBuUbXmPEKlZR21bs5XRAQalzDCFNtjcpNkXiGqAHCLkDTtjPagMsw==,iv:MH1EBJEOdQDEgm9E0F884fynhsH8KiS5QSc605XbASQ=,tag:FUM1eptHS0rpt6ILyQjGOg==,type:str]
|
||||
wg_cloonar_key: ENC[AES256_GCM,data:Dtp6I5J0jU5LLVwEFU4DFCpUngPRmFMebGXnk2oSwsKtsir/DtRBFG7ictM=,iv:1Abx/EAZRJrRQURljofzUYDgJpuREriX0nSrFbH5Npw=,tag:l4uFl9Uc+W0XeLVfLGmgZA==,type:str]
|
||||
wg_epicenter_works_key: ENC[AES256_GCM,data:LeLjfwfaz+loWyHYRgIMIPzHzlOnhl9tluKcQFgdes6r+deft1JfnUzDuF0=,iv:DKrc3I+U2hWDH8nnc8ZQeaVtA1eVXu7SXdTn1fxHoH4=,tag:V0PL0GrL2NEPVslAZa801A==,type:str]
|
||||
wg_epicenter_works_psk: ENC[AES256_GCM,data:Den3NDWdP013Or6/2Vll1igUahuRSNW4hu+nDa5vkr93bbveQTaWFT4TD4U=,iv:r3UsD3+3lUIP2X3Grti7wpXTQBXtu1/MdrycEmpZfsI=,tag:ghbAcxmjGVOe9jCZsmFzjA==,type:str]
|
||||
wg_ghetto_at_key: ENC[AES256_GCM,data:OIHmoy3SpIi9aefZnZ1PzpyHbEso18ceoTULf2eQkx1rJbaxC6PD1lma7eQ=,iv:u0eFjHHOBzPTmBvBEQsYY5flcBayiAQKd6e7RyiPwJI=,tag:731C9wvv8bA5fuuQq+weVQ==,type:str]
|
||||
gitea-mailer-password: ENC[AES256_GCM,data:M4qCWNt1oQVJzxThIjocm2frwuVMyx+69TBpke25RwxJxEQnvHL1CM579OVroTm7+gGE/oOJqAwDIepfiDtyM1xm,iv:jayFZMbu3uDimS/rIKZSeoU0MsYwWp880iEMs1oQE4k=,tag:qGDncRkyuCWaELhcxUrqtQ==,type:str]
|
||||
gitea-runner: ENC[AES256_GCM,data:NYG3qRLiMjmfA+oHYBXBbxpuX2ZjB/VgvLaS7yr5kJeDN/NukB/B3OZcEfsUWgbBS5IsLENESngWTFmK4W3htN4lSqdg/g4UsUr20beNov+pbyPN05rkBYmSCZZFwZ1L9POEE4GF4LuuoNpDlWIw0mrA8oV8MoI4W5QS2IGranBTIQQaYXU5TEGYa4XMVo4oC75iuH6DIq1KD6OgFAfMhm/wlbP8CP/Iaw2K8CNPxktk93pm3OSmggf22Z4JPEnvV25sc9iBkxLkDk9FXYFys0g=,iv:UzL5ncVOC/loJwcFSG1QJHnzLp3il4Hf3qDwLWxrIlo=,tag:w0Zn/E+02KyAsPXZdOLrew==,type:str]
|
||||
gitea-runner-token: ENC[AES256_GCM,data:HpBjLS10w78ihbnAUrlCRGvwrXLBYKH5v/P7XggoUSWLoAazSVQArABxaK7PJas=,iv:q3Y6jV0gmug06O0EYqGVyIJ4AvMGr2ydwY17YKxo0Qw=,tag:Ws5HLbdaeYGGXzDZW/FX4w==,type:str]
|
||||
home-assistant-ldap: ENC[AES256_GCM,data:uZEPbSnkgQYSd8ev6FD8TRHWWr+vusadtMcvP7KKL2AZAV0h1hga5fODN6I5u0DNL9hq2pNM+FwU0E/svWLRww==,iv:IhmUgSu34NaAY+kUZehx40uymydUYYAyte1aGqQ33/8=,tag:BKFCJPr7Vz4EG78ry/ZD7g==,type:str]
|
||||
home-assistant-secrets.yaml: ENC[AES256_GCM,data:m7uOVo7hPk/RmqqRS6y7NKoMKsR9Bdi1ntatsZdDOAbJMjZmZL2FgPEHi/zF73zCfRfTOca3dwpulR3WXZ9Ic1sbUIggmusJMg4Gellw1CUhx7SbQN5nieAbPbB9GVxMuV4OakD1u7Swz8JggDT6IwojSnuD5omCRCyUH1wvKB+Re59q6EStderlm5MJNVFlVrbKVbLKLcw4yRgTh34BGnTTjcJmgSlQjO1ciu2B7YQmdl0Fw6d8AdbEzgB5TFG5ONc85UhJDE8Wlw==,iv:GCtpcVChN2UMWtfnWURozCfVj2YbRPqp/bH4Jjntybs=,tag:pcxP7gTBtXMNT5iyW5YXTw==,type:str]
|
||||
matrix-shared-secret: ENC[AES256_GCM,data:67imd3m6WBeGP/5Msmjy8B6sP983jMyWzRIzWgNVV5jZslX+GBJyEYzm3OTDs1iTZf4ScvuYheTH0QFPfw==,iv:7ElCpESWumbIHmmFaedcpkFm5M58ZT3vW9wb9e1Sbh4=,tag:wr4FIymtJBtCerVqae+Xlw==,type:str]
|
||||
palworld: ENC[AES256_GCM,data:rdqChPt4gSJHS1D60+HJ+4m5mg35JbC+pOmevK21Y95QyAIeyBLVGhRYlOaUcqdZM2e4atyTTSf6z4nHsm539ddCbW7J2DCdF5PQkrAGDmmdTVq+jyJAT8gTrbXXCglT1wvFYY5dbf2NKA4ASJIA8bdVNuwRZU0CtFiishzLuc9m8ZcGCNwQ/+xkMZgkUAHYRlEJAZyMpXR6KkFftiR05JRAFczD4N7GXPPe+vyvgXg7QBGtf20Qd4SGBUw0zI/SNTRmifHUuc4Z6+Fe9JHgvTc3uFcTMVnty0fEuL+a29liaVdAFq8BnqJfc5CNV401ZSUeMbG41lCn1cegP/WChs9J6HXNrhWDgiXa6ln++NoKcfOHIfZVbYOCoOxFR6+YWeBU2+sHmdwI9j5XQf5Ly2hmg12j0Ds2Cn8k4PG5aQP+HT2bedqyxwSt6fi97A0Osnh4ig7+DzYAjSNLewbYLzVdK39VdvB9hqLto+yFS3gAaeYOHwPwtqa+COI85c55lHiyKHlSwPhBqYaaiDu00lQTUzq9R5vz6F/l+T3bUjuna5RryUu8yhnk5DyK834KycTOg4ETcZTqro6prfiEBxc+Utsc9JvEtZgwFv6fsVLOu7nHxuiYuvseZ4YA8LlYdwPJboMPO2XsuhwWtT1uz/rh2orH7/vsXvzA/kF8NFemWBEMVLYA8byC5ze8doiGDYp4T5AAf10nJB1ceQ==,iv:gs78fxhvo9KlTaR5nzs12/LdgPChSFPHD2k4VQp3ARo=,tag:lpWBOi9xh2cWkS+71KD/UQ==,type:str]
|
||||
ark: ENC[AES256_GCM,data:YYGyzoVIKI9Ac1zGOr0BEpd3fgBsvp1hSwAvfO07/EQdg8ufMWUkNvqNHDKN62ZK5A1NnY3JTA1p4gyZ4ryQeAOsbwqU1GSk2YKHFyPeEnpLz/Ml82KMsv7XPGXuKRXZ4v3UcLu0R8k1Q0gQsMWo4FjCs3FF5mVtJG/YWxxbCYHoBLJ/di5p0DgjuFgJBQknYBpuLzr+yIoeqEyN7XcGYAJO53trEJuOOxLILULifkqISHjZ66i5F1fHW0iUdRbmeWV4aOAeOrsQqXYv,iv:gJwV5ip84zHqpU0l0uESfWWOtcgihMvEEdLaeI+twcU=,tag:sy8udVQsKxV/jOqwhJmWAg==,type:str]
|
||||
firefox-sync: ENC[AES256_GCM,data:uAJAdyKAuXRuqCFl8742vIejU5RnAPpUxUFCC0s0QeXZR5oH2YOrDh+3vKUmckW4V1cIhSHoe+4+I4HuU5E73DDrJThfIzBEw+spo4HXwZf5KBtu3ujgX6/fSTlPWV7pEsDDsZ0y6ziKPADBDym8yEk0bU9nRedvTBUhVryo3aolzF/c+gJvdeDvKUYa8+8=,iv:yuvE4KG7z7Rp9ZNlLiJ2rh0keed3DuvrELzsfJu4+bs=,tag:HFo1A53Eva31NJ8fRE7TlA==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsNzNjZ1o1dXFxalFiRXUx
|
||||
U3NQK0gvQWVRbnAxam8yZmJTTmRTaVVZdkdrCnQ0R1ZBWEVmcE12NWNuaDFtRGlj
|
||||
UFRManh2VFgwUFJaNFpVZFNqc01oSkEKLS0tIHA5UDlHY1lDWUtwTk10RHZoQWQ1
|
||||
bzZ6MzhQQmYrZ3JKUDZoa1lDZXRHRDAKHtzHnt+zHgMsuyX0vP6xapvJ8796/vkn
|
||||
u9U56OdFlqthTy870vMMoJWW3wAFfj/QV124bG63lJ02gAHEr/PGJw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLc0ZsVlNzQ0d1dGJlSzN6
|
||||
bzB0bnhHTzlodWJveFBmdVVCdjJ5c2V0dkM4Cmt1cHhJa2U4NmJZSUFGYzhCQmdH
|
||||
eVJDUjc0LzdIOHo4TWlCeVEvQUg1b1EKLS0tIGRpTFA4TkgvU2ZLOXM3NktMbjRP
|
||||
aGM2aVdRSUpsRXRCZE02MXJ3MVpxK00KO2dZUNZ1KQFg4bnNp1PEntL2fY1h+JCK
|
||||
l7CnGwotydc9NybwYtisv9XVrz3QoiD09OiLvg7VkmfzEaGmqmja/g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjQTBxNkV2REdrRS9MaUxa
|
||||
YWxNOFBKQlAwOW5qSk9hM1Q5c0tjZTdWUjBjCkM5TmtwR2RBRER3Uzc4dWtGOVM2
|
||||
bjZFZVc3V0t0enhyam1DWVM3b0h5WlEKLS0tIGNPUzFJUGRYZStMRTMwV3pWTW1t
|
||||
V003cnFtYVNEbERiRDV4bmVXVlBaUTAK7pLGaixTRCg5lKhN8CN95cdr7X8X1oDY
|
||||
LX2t+SPvb8hqsssLf/mqVxPsgAXl0L9lfsYtRsuMWONmaJsOleVE4A==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDbDA5U0xnUDNXYUtRVVN3
|
||||
YW5aTFg1T0pOZWc4cXFDRDlrRmxZWWw1MUdRCjdlUVg0S0IxTXM4ZXcydGR0aldu
|
||||
WnU3ZnUydUh4em02TWFVamx6a0xpQmMKLS0tIEdpWFg1UEVGNHIzY2VZZk40NlBG
|
||||
WXJpUUxadERyYUExRFMzNzBXaUVET3cKG9ZwWy5YvTr/BAw/i+ZJos5trwRvaW5j
|
||||
eV/SHiEteZZtCuCVFAp3iolE/mJyu97nA2yFwWaLN86h+/xkOJsdqA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2024-11-20T21:39:00Z"
|
||||
mac: ENC[AES256_GCM,data:JCFvFwSqnAQCOB76n5pfQsdsaod8bBiVZ2VY+WWBDWi84gQByhqy808E2ZZJSJ1/amUi8dNBeOPNWZIGdieuWJyatrqjWziAl7gXx5u35i77sS6hAD+G/Fc/elgRbjc0VIbplZ7UxBmwo3vkVpI4RqQiQv63MvKHI+TkoY8vFUM=,iv:uy50x8FqqDW7hCLZeHfhFB/dxa3N6kM2Vj9waAZJngg=,tag:Wt1FG0kW4VFZ2fvvAC0T4A==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.8.1
|
||||
1
hosts/fw/channel
Normal file
1
hosts/fw/channel
Normal file
@@ -0,0 +1 @@
|
||||
https://channels.nixos.org/nixos-25.11
|
||||
@@ -4,19 +4,23 @@
|
||||
./utils/bento.nix
|
||||
./utils/modules/sops.nix
|
||||
./utils/modules/lego/lego.nix
|
||||
|
||||
./utils/modules/nginx.nix
|
||||
|
||||
./utils/modules/autoupgrade.nix
|
||||
./utils/modules/victoriametrics
|
||||
./utils/modules/promtail
|
||||
./utils/modules/borgbackup.nix
|
||||
# ./utils/modules/netdata.nix
|
||||
./utils/modules/set-nix-channel.nix
|
||||
|
||||
# fw
|
||||
./modules/network-prefix.nix
|
||||
./modules/networking.nix
|
||||
./modules/setupnetwork.nix
|
||||
./modules/firewall.nix
|
||||
./modules/dhcp4.nix
|
||||
./modules/unbound.nix
|
||||
# ./modules/dhcp4.nix
|
||||
# ./modules/unbound.nix
|
||||
|
||||
./modules/dnsmasq.nix
|
||||
./modules/avahi.nix
|
||||
./modules/openconnect.nix
|
||||
./modules/wireguard.nix
|
||||
@@ -25,25 +29,36 @@
|
||||
./modules/ddclient.nix
|
||||
# ./modules/wol.nix
|
||||
|
||||
|
||||
# microvm
|
||||
./modules/microvm.nix
|
||||
./modules/gitea-vm.nix
|
||||
# ./modules/vscode-server.nix # Add VS Code Server microvm
|
||||
|
||||
./modules/ai-mailer.nix
|
||||
# ./modules/wazuh.nix
|
||||
|
||||
# web
|
||||
./modules/web
|
||||
|
||||
# git
|
||||
./modules/gitea.nix
|
||||
./modules/fwmetrics.nix
|
||||
# ./modules/fwmetrics.nix
|
||||
|
||||
# ha customers
|
||||
./modules/ha-customers
|
||||
|
||||
./modules/firefox-sync.nix
|
||||
./modules/fivefilters.nix
|
||||
# ./modules/pyload
|
||||
|
||||
# ./modules/firefox-sync.nix
|
||||
|
||||
# home assistant
|
||||
./modules/home-assistant
|
||||
./modules/deconz.nix
|
||||
# ./modules/mopidy.nix
|
||||
# ./modules/mosquitto.nix
|
||||
./modules/snapserver.nix
|
||||
# ./modules/snapserver.nix
|
||||
./modules/lms.nix
|
||||
|
||||
# gaming
|
||||
# ./modules/palworld.nix
|
||||
@@ -52,11 +67,15 @@
|
||||
|
||||
# setup network
|
||||
./modules/setupnetwork.nix
|
||||
|
||||
|
||||
./modules/set-nix-channel.nix # Automatically manage nix-channel from /var/bento/channel
|
||||
./modules/grafana-monitor.nix # Grafana online status monitor
|
||||
|
||||
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
networkPrefix = "10.42";
|
||||
|
||||
nixpkgs.overlays = [
|
||||
(import ./utils/overlays/packages.nix)
|
||||
];
|
||||
@@ -67,11 +86,25 @@
|
||||
|
||||
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||
"mongodb"
|
||||
"ai-mailer"
|
||||
"filebot"
|
||||
];
|
||||
|
||||
# Intel N100 Graphics Support for hardware transcoding
|
||||
hardware.graphics = {
|
||||
enable = true;
|
||||
extraPackages = with pkgs; [
|
||||
intel-media-driver # VAAPI driver (iHD) for modern Intel GPUs
|
||||
vpl-gpu-rt # Intel VPL/QSV runtime for Gen 12+ (N100)
|
||||
intel-compute-runtime # OpenCL support for tone-mapping
|
||||
];
|
||||
};
|
||||
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
time.timeZone = "Europe/Vienna";
|
||||
|
||||
services.logind.extraConfig = "RuntimeDirectorySize=2G";
|
||||
services.logind.settings.Login.RuntimeDirectorySize = "2G";
|
||||
|
||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
sops.defaultSopsFile = ./secrets.yaml;
|
||||
@@ -89,7 +122,21 @@
|
||||
];
|
||||
|
||||
nix = {
|
||||
settings.auto-optimise-store = true;
|
||||
settings = {
|
||||
auto-optimise-store = true;
|
||||
# Build performance optimizations
|
||||
max-jobs = 4;
|
||||
cores = 4;
|
||||
# Enable eval caching for faster rebuilds
|
||||
eval-cache = true;
|
||||
# Use binary caches to avoid unnecessary rebuilds
|
||||
substituters = [
|
||||
"https://cache.nixos.org"
|
||||
];
|
||||
trusted-public-keys = [
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||
];
|
||||
};
|
||||
gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
@@ -105,8 +152,8 @@
|
||||
services.tlp = {
|
||||
enable = true;
|
||||
settings = {
|
||||
CPU_SCALING_GOVERNOR_ON_AC = "powersave"; # powersave or performance
|
||||
CPU_ENERGY_PERF_POLICY_ON_AC = "power"; # power or performance
|
||||
CPU_SCALING_GOVERNOR_ON_AC = "performance"; # powersave or performance
|
||||
CPU_ENERGY_PERF_POLICY_ON_AC = "performance"; # power or performance
|
||||
# CPU_MIN_PERF_ON_AC = 0;
|
||||
# CPU_MAX_PERF_ON_AC = 100; # max 100
|
||||
};
|
||||
@@ -137,6 +184,9 @@
|
||||
|
||||
# backups
|
||||
borgbackup.repo = "u149513-sub2@u149513-sub2.your-backup.de:borg";
|
||||
services.borgbackup.jobs.default.paths = lib.mkAfter [
|
||||
"/var/lib/microvms/persist/web-02/var/backup"
|
||||
];
|
||||
|
||||
system.stateVersion = "22.05";
|
||||
}
|
||||
111
hosts/fw/modules/ai-mailer.nix
Normal file
111
hosts/fw/modules/ai-mailer.nix
Normal file
@@ -0,0 +1,111 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
users.users.ai-mailer = {
|
||||
isSystemUser = true;
|
||||
group = "ai-mailer";
|
||||
home = "/var/lib/ai-mailer";
|
||||
createHome = true;
|
||||
description = "AI Mailer service user";
|
||||
};
|
||||
users.groups.ai-mailer = { };
|
||||
|
||||
environment.etc."ai-mailer/config.yaml" = {
|
||||
mode = "0400";
|
||||
user = "ai-mailer";
|
||||
group = "ai-mailer";
|
||||
text = ''
|
||||
imap:
|
||||
server: "imap.gmail.com"
|
||||
port: 993
|
||||
username: "it@paraclub.at"
|
||||
password: "file://${config.sops.secrets.ai-mailer-imap-password.path}"
|
||||
mailbox_in: "INBOX"
|
||||
draft_box: "[Gmail]/Entwürfe"
|
||||
processed_box: "INBOX/Done"
|
||||
use_tls: true
|
||||
|
||||
ai:
|
||||
openrouter_api_key: "file://${config.sops.secrets.ai-mailer-openrouter-key.path}"
|
||||
model: "openai/gpt-5-mini"
|
||||
temperature: 0.3
|
||||
max_tokens: 200000
|
||||
|
||||
context:
|
||||
urls:
|
||||
- "https://paraclub.cloonar.dev/de/tandemfallschirmspringen/faq/"
|
||||
- "https://paraclub.at/de/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/alle-infos/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/kosten-tandemsprung/"
|
||||
- "https://paraclub.at/de/ueber-uns/anfahrt/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/faq/"
|
||||
- "https://paraclub.at/de/ausbildung/uebersicht/"
|
||||
- "https://paraclub.at/de/ausbildung/aff-ablauf/"
|
||||
- "https://paraclub.at/de/ausbildung/kurstermine/"
|
||||
- "https://paraclub.at/de/ausbildung/anmeldung/"
|
||||
- "https://paraclub.at/de/ausbildung/kosten/"
|
||||
|
||||
polling:
|
||||
interval: "300s"
|
||||
|
||||
processing:
|
||||
max_tokens: 30000
|
||||
skip_junk_emails: false
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
file_path: "/var/log/ai-mailer/ai-mailer.log"
|
||||
'';
|
||||
};
|
||||
|
||||
sops.secrets.ai-mailer-imap-password = {
|
||||
owner = "ai-mailer";
|
||||
};
|
||||
|
||||
sops.secrets.ai-mailer-openrouter-key = {
|
||||
owner = "ai-mailer";
|
||||
};
|
||||
|
||||
systemd.services.ai-mailer = {
|
||||
description = "AI Mail Assistant Service";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "ai-mailer";
|
||||
Group = "ai-mailer";
|
||||
WorkingDirectory = "/var/lib/ai-mailer";
|
||||
ExecStart = "${pkgs.ai-mailer}/bin/ai-mailer -config /etc/ai-mailer/config.yaml";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
StateDirectory = "ai-mailer";
|
||||
LogsDirectory = "ai-mailer";
|
||||
RuntimeDirectory = "ai-mailer";
|
||||
|
||||
# Security settings
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
CapabilityBoundingSet = "";
|
||||
};
|
||||
|
||||
restartTriggers = [
|
||||
"/etc/ai-mailer/config.yaml"
|
||||
config.sops.secrets.ai-mailer-imap-password.path
|
||||
config.sops.secrets.ai-mailer-openrouter-key.path
|
||||
];
|
||||
};
|
||||
}
|
||||
94
hosts/fw/modules/allywatch.nix
Normal file
94
hosts/fw/modules/allywatch.nix
Normal file
@@ -0,0 +1,94 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
domain = "a11ywatch.cloonar.com";
|
||||
confDir = "/var/lib/a11ywatch";
|
||||
|
||||
json = pkgs.formats.json { };
|
||||
in {
|
||||
# 1) Enable Podman (daemonless, drop-in for docker)
|
||||
virtualisation.podman.enable = true; # :contentReference[oaicite:0]{index=0}
|
||||
virtualisation.podman.dockerCompat = true; # :contentReference[oaicite:1]{index=1}
|
||||
virtualisation.podman.defaultNetwork.settings.dns_enabled = true;# :contentReference[oaicite:2]{index=2}
|
||||
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:3000/";
|
||||
};
|
||||
};
|
||||
|
||||
environment.etc."containers/networks/a11ywatch-net.json" = {
|
||||
source = json.generate "a11ywatch-net.json" ({
|
||||
name = "a11ywatch-net";
|
||||
id = "ccb4b7fb90d2df26db27ef0995765b04f52d318db752c9474b470c5ef4d7978d";
|
||||
driver = "bridge";
|
||||
network_interface = "podman1";
|
||||
subnets = [
|
||||
{
|
||||
subnet = "10.89.0.0/24";
|
||||
gateway = "10.89.0.1";
|
||||
}
|
||||
];
|
||||
ipv6_enabled = false;
|
||||
internal = false;
|
||||
dns_enabled = true;
|
||||
ipam_options = {
|
||||
driver = "host-local";
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
users.users.a11ywatch = {
|
||||
isSystemUser = true;
|
||||
group = "a11ywatch";
|
||||
home = "/var/lib/a11ywatch";
|
||||
createHome = true;
|
||||
};
|
||||
users.groups.a11ywatch = { };
|
||||
users.groups.docker.members = [ "a11ywatch" ];
|
||||
|
||||
# 2) Create the bridge network on boot via a oneshot systemd service
|
||||
systemd.services.a11ywatch-net = {
|
||||
description = "Ensure a11ywatch-net Podman network exists";
|
||||
wants = [ "podman.service" ];
|
||||
after = [ "podman.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = ''
|
||||
${pkgs.podman}/bin/podman network inspect a11ywatch-net >/dev/null 2>&1 \
|
||||
|| ${pkgs.podman}/bin/podman network create a11ywatch-net
|
||||
'';
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
wantedBy = [
|
||||
"multi-user.target"
|
||||
];
|
||||
};
|
||||
|
||||
# 3) Declare your two containers using the podman backend
|
||||
virtualisation.oci-containers = {
|
||||
backend = "podman"; # :contentReference[oaicite:3]{index=3}
|
||||
containers = {
|
||||
a11ywatch-backend = {
|
||||
image = "docker.io/a11ywatch/a11ywatch:latest";
|
||||
autoStart = true;
|
||||
ports = [ "3280:3280" ];
|
||||
volumes = [ "${confDir}:/a11ywatch/conf" ];
|
||||
environment = { SUPER_MODE = "true"; };
|
||||
extraOptions = [ "--network=a11ywatch-net" ];
|
||||
};
|
||||
a11ywatch-frontend = {
|
||||
image = "docker.io/a11ywatch/web:latest";
|
||||
autoStart = true;
|
||||
ports = [ "3000:3000" ];
|
||||
volumes = [ "${confDir}:/a11ywatch/conf" ];
|
||||
environment = { SUPER_MODE = "true"; };
|
||||
extraOptions = [
|
||||
"--network=a11ywatch-net"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=server"
|
||||
"--ip=10.42.97.201"
|
||||
"--ip=${config.networkPrefix}.97.201"
|
||||
];
|
||||
};
|
||||
};
|
||||
54
hosts/fw/modules/cloonar-assistant-config-server.nix
Normal file
54
hosts/fw/modules/cloonar-assistant-config-server.nix
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
users = [
|
||||
{
|
||||
username = "ca-test";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDglSLU9AUtbU0fCN0eByi/EHyo1QiPPLiscN5RAR+wq";
|
||||
}
|
||||
];
|
||||
|
||||
userList = lib.concatStringsSep "," (map (u: u.username) users);
|
||||
in {
|
||||
environment.etc = {
|
||||
# our single user+key file
|
||||
"cloonar_assistant_ssh/sftp_users_keys" = {
|
||||
text = lib.concatStringsSep "\n"
|
||||
(map (u: "${u.username} ${u.key}") users);
|
||||
mode = "0600";
|
||||
user = "root";
|
||||
group = "root";
|
||||
};
|
||||
|
||||
# the little awk script to extract the key for $1
|
||||
"cloonar_assistant_ssh/sftp-fetch-key.sh" = {
|
||||
text = ''
|
||||
#!/usr/bin/env bash
|
||||
awk -v u="$1" '$1==u { $1=""; sub(/^ +/, ""); print }' /etc/cloonar_assistant_ssh/sftp_users_keys
|
||||
'';
|
||||
mode = "0700";
|
||||
user = "root";
|
||||
group = "root";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = map (u:
|
||||
# Type 'd' = create directory if missing
|
||||
# Mode 0755, owner root:root
|
||||
"d /home/cloonar-assistant-configs/${u.username} 0755 root root -"
|
||||
) users;
|
||||
|
||||
services.openssh.extraConfig = ''
|
||||
Match User ${userList}
|
||||
X11Forwarding no
|
||||
AllowTcpForwarding no
|
||||
ChrootDirectory /home/cloonar-assistant-configs/%u
|
||||
ForceCommand internal-sftp
|
||||
|
||||
# ← only for those matched users:
|
||||
AuthorizedKeysCommand /etc/cloonar_assistant_ssh/sftp-fetch-key.sh %u
|
||||
AuthorizedKeysCommandUser root
|
||||
'';
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
services.ddclient = {
|
||||
enable = true;
|
||||
use = "if, if=wan";
|
||||
usev4 = "if, if=wan";
|
||||
protocol = "hetzner";
|
||||
# server = "https://dns.hetzner.com/api/v1/";
|
||||
username = "dominik.polakovics@cloonar.com";
|
||||
@@ -15,6 +15,7 @@
|
||||
"palworld.cloonar.com"
|
||||
"matrix.cloonar.com"
|
||||
"element.cloonar.com"
|
||||
"tinder.cloonar.com"
|
||||
];
|
||||
};
|
||||
|
||||
@@ -2,21 +2,33 @@
|
||||
virtualisation = {
|
||||
oci-containers.containers = {
|
||||
deconz = {
|
||||
autoStart = false;
|
||||
autoStart = true;
|
||||
image = "marthoc/deconz";
|
||||
volumes = [
|
||||
"/etc/localtime:/etc/localtime:ro"
|
||||
"/var/lib/deconz:/root/.local/share/dresden-elektronik/deCONZ"
|
||||
"/dev/bus/usb:/dev/bus/usb:ro"
|
||||
"/run/udev:/run/udev:ro"
|
||||
];
|
||||
environment = {
|
||||
DECONZ_DEVICE = "/dev/ttyACM0";
|
||||
TZ = "Europe/Vienna";
|
||||
DECONZ_UID = "0";
|
||||
DECONZ_GID = "0";
|
||||
DECONZ_START_VERBOSE = "1";
|
||||
};
|
||||
extraOptions = [
|
||||
"--network=server"
|
||||
"--ip=10.42.97.22"
|
||||
"--ip=${config.networkPrefix}.97.22"
|
||||
"--device=/dev/ttyACM0"
|
||||
"--hostname=deconz"
|
||||
"--mac-address=1a:c4:04:6e:29:bd"
|
||||
"--cap-add=CAP_MKNOD"
|
||||
"--cap-add=CAP_NET_RAW"
|
||||
"--cap-add=CAP_NET_ADMIN"
|
||||
"--device-cgroup-rule=c 166:* rmw"
|
||||
"--device-cgroup-rule=c 188:* rmw"
|
||||
"--security-opt=label=disable"
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
{ ... }: {
|
||||
{ config, ... }:
|
||||
{
|
||||
services.kea.dhcp4 = {
|
||||
enable = true;
|
||||
settings = {
|
||||
@@ -21,17 +22,18 @@
|
||||
renew-timer = 1000;
|
||||
subnet4 = [
|
||||
{
|
||||
id = 96;
|
||||
pools = [
|
||||
{
|
||||
pool = "10.42.96.100 - 10.42.96.240";
|
||||
pool = "${config.networkPrefix}.96.100 - ${config.networkPrefix}.96.240";
|
||||
}
|
||||
];
|
||||
subnet = "10.42.96.0/24";
|
||||
subnet = "${config.networkPrefix}.96.0/24";
|
||||
interface = "lan";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "10.42.96.1";
|
||||
data = "${config.networkPrefix}.96.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
@@ -43,40 +45,41 @@
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "10.42.96.1";
|
||||
data = "${config.networkPrefix}.96.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "04:7c:16:d5:63:5e";
|
||||
ip-address = "10.42.96.5";
|
||||
ip-address = "${config.networkPrefix}.96.5";
|
||||
server-hostname = "omada.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "30:05:5c:56:62:37";
|
||||
ip-address = "10.42.96.100";
|
||||
ip-address = "${config.networkPrefix}.96.100";
|
||||
server-hostname = "brn30055c566237.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "24:df:a7:b1:1b:74";
|
||||
ip-address = "10.42.96.101";
|
||||
ip-address = "${config.networkPrefix}.96.101";
|
||||
server-hostname = "rmproplus-b1-1b-74.cloonar.com";
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
{
|
||||
id = 97;
|
||||
pools = [
|
||||
{
|
||||
pool = "10.42.97.100 - 10.42.97.240";
|
||||
pool = "${config.networkPrefix}.97.100 - ${config.networkPrefix}.97.240";
|
||||
}
|
||||
];
|
||||
subnet = "10.42.97.0/24";
|
||||
subnet = "${config.networkPrefix}.97.0/24";
|
||||
interface = "server";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "10.42.97.1";
|
||||
data = "${config.networkPrefix}.97.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
@@ -84,54 +87,55 @@
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "10.42.97.1";
|
||||
data = "${config.networkPrefix}.97.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "1a:c4:04:6e:29:bd";
|
||||
ip-address = "10.42.97.2";
|
||||
ip-address = "${config.networkPrefix}.97.2";
|
||||
server-hostname = "omada.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "02:00:00:00:00:03";
|
||||
ip-address = "10.42.97.5";
|
||||
ip-address = "${config.networkPrefix}.97.5";
|
||||
server-hostname = "web-02.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "02:00:00:00:00:04";
|
||||
ip-address = "10.42.97.6";
|
||||
ip-address = "${config.networkPrefix}.97.6";
|
||||
server-hostname = "matrix.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "ea:db:d4:c1:18:ba";
|
||||
ip-address = "10.42.97.50";
|
||||
ip-address = "${config.networkPrefix}.97.50";
|
||||
server-hostname = "git.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "c2:4f:64:dd:13:0c";
|
||||
ip-address = "10.42.97.20";
|
||||
ip-address = "${config.networkPrefix}.97.20";
|
||||
server-hostname = "home-assistant.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "1a:c4:04:6e:29:02";
|
||||
ip-address = "10.42.97.25";
|
||||
ip-address = "${config.networkPrefix}.97.25";
|
||||
server-hostname = "deconz.cloonar.com";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 101;
|
||||
pools = [
|
||||
{
|
||||
pool = "10.42.101.100 - 10.42.101.240";
|
||||
pool = "${config.networkPrefix}.101.100 - ${config.networkPrefix}.101.240";
|
||||
}
|
||||
];
|
||||
subnet = "10.42.101.0/24";
|
||||
subnet = "${config.networkPrefix}.101.0/24";
|
||||
interface = "infrastructure";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "10.42.101.1";
|
||||
data = "${config.networkPrefix}.101.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
@@ -139,29 +143,30 @@
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "10.42.101.1";
|
||||
data = "${config.networkPrefix}.101.1";
|
||||
}
|
||||
{
|
||||
name = "capwap-ac-v4";
|
||||
code = 138;
|
||||
data = "10.42.97.2";
|
||||
data = "${config.networkPrefix}.97.2";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 99;
|
||||
pools = [
|
||||
{
|
||||
pool = "10.42.99.100 - 10.42.99.240";
|
||||
pool = "${config.networkPrefix}.99.100 - ${config.networkPrefix}.99.240";
|
||||
}
|
||||
];
|
||||
subnet = "10.42.99.0/24";
|
||||
subnet = "${config.networkPrefix}.99.0/24";
|
||||
interface = "multimedia";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "10.42.99.1";
|
||||
data = "${config.networkPrefix}.99.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
@@ -169,59 +174,60 @@
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "10.42.99.1";
|
||||
data = "${config.networkPrefix}.99.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "c4:a7:2b:c7:ea:30";
|
||||
ip-address = "10.42.99.10";
|
||||
ip-address = "${config.networkPrefix}.99.10";
|
||||
hostname = "metz.cloonar.multimedia";
|
||||
}
|
||||
{
|
||||
hw-address = "f0:2f:9e:d4:3b:21";
|
||||
ip-address = "10.42.99.11";
|
||||
ip-address = "${config.networkPrefix}.99.11";
|
||||
hostname = "firetv-living";
|
||||
}
|
||||
{
|
||||
hw-address = "bc:33:29:ed:24:f0";
|
||||
ip-address = "10.42.99.12";
|
||||
ip-address = "${config.networkPrefix}.99.12";
|
||||
hostname = "ps5";
|
||||
}
|
||||
{
|
||||
hw-address = "e4:2a:ac:32:3f:79";
|
||||
ip-address = "10.42.99.13";
|
||||
ip-address = "${config.networkPrefix}.99.13";
|
||||
hostname = "xbox";
|
||||
}
|
||||
{
|
||||
hw-address = "98:b6:e9:b6:ef:f4";
|
||||
ip-address = "10.42.99.14";
|
||||
ip-address = "${config.networkPrefix}.99.14";
|
||||
hostname = "switch";
|
||||
}
|
||||
{
|
||||
hw-address = "f0:2f:9e:c1:74:72";
|
||||
ip-address = "10.42.99.21";
|
||||
ip-address = "${config.networkPrefix}.99.21";
|
||||
hostname = "firetv-bedroom";
|
||||
}
|
||||
{
|
||||
hw-address = "30:05:5c:56:62:37";
|
||||
ip-address = "10.42.99.100";
|
||||
ip-address = "${config.networkPrefix}.99.100";
|
||||
server-hostname = "brn30055c566237";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 254;
|
||||
pools = [
|
||||
{
|
||||
pool = "10.42.254.10 - 10.42.254.254";
|
||||
pool = "${config.networkPrefix}.254.10 - ${config.networkPrefix}.254.254";
|
||||
}
|
||||
];
|
||||
subnet = "10.42.254.0/24";
|
||||
subnet = "${config.networkPrefix}.254.0/24";
|
||||
interface = "guest";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "10.42.254.1";
|
||||
data = "${config.networkPrefix}.254.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
@@ -230,17 +236,18 @@
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 100;
|
||||
pools = [
|
||||
{
|
||||
pool = "10.42.100.100 - 10.42.100.240";
|
||||
pool = "${config.networkPrefix}.100.100 - ${config.networkPrefix}.100.240";
|
||||
}
|
||||
];
|
||||
subnet = "10.42.100.0/24";
|
||||
subnet = "${config.networkPrefix}.100.0/24";
|
||||
interface = "smart";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "10.42.100.1";
|
||||
data = "${config.networkPrefix}.100.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
@@ -248,29 +255,29 @@
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "10.42.100.1";
|
||||
data = "${config.networkPrefix}.100.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "fc:ee:28:03:63:e9";
|
||||
ip-address = "10.42.100.148";
|
||||
ip-address = "${config.networkPrefix}.100.148";
|
||||
server-hostname = "k1c";
|
||||
}
|
||||
{
|
||||
hw-address = "cc:50:e3:bc:27:64";
|
||||
ip-address = "10.42.100.112";
|
||||
ip-address = "${config.networkPrefix}.100.112";
|
||||
server-hostname = "Nuki_Bridge_1A753F72";
|
||||
}
|
||||
|
||||
{
|
||||
hw-address = "34:6f:24:f3:af:ad";
|
||||
ip-address = "10.42.100.137";
|
||||
ip-address = "${config.networkPrefix}.100.137";
|
||||
server-hostname = "daikin86604";
|
||||
}
|
||||
{
|
||||
hw-address = "34:6f:24:c1:f8:54";
|
||||
ip-address = "10.42.100.139";
|
||||
ip-address = "${config.networkPrefix}.100.139";
|
||||
server-hostname = "daikin53800";
|
||||
}
|
||||
];
|
||||
176
hosts/fw/modules/dnsmasq.nix
Normal file
176
hosts/fw/modules/dnsmasq.nix
Normal file
@@ -0,0 +1,176 @@
|
||||
{ config, ... }: {
|
||||
services.resolved.enable = false;
|
||||
|
||||
services.dnsmasq = {
|
||||
enable = true;
|
||||
settings = {
|
||||
port = "53";
|
||||
bind-interfaces = true; # force dnsmasq to bind immediately
|
||||
expand-hosts = true;
|
||||
|
||||
log-dhcp = true;
|
||||
|
||||
server = [
|
||||
"/epicenter.works/10.50.60.1"
|
||||
"/akvorrat.at/10.50.60.1"
|
||||
"9.9.9.9"
|
||||
"149.112.112.11"
|
||||
];
|
||||
|
||||
interface = [
|
||||
"lan"
|
||||
"server"
|
||||
"infrastructure"
|
||||
"multimedia"
|
||||
"guest"
|
||||
"smart"
|
||||
];
|
||||
|
||||
domain = [
|
||||
"cloonar.com,lan"
|
||||
"cloonar.com,server"
|
||||
"cloonar.com,infrastructure"
|
||||
"cloonar.multimedia,multimedia"
|
||||
"cloonar.smart,smart"
|
||||
"cloonar.guest,guest"
|
||||
];
|
||||
|
||||
dhcp-option = [
|
||||
"lan,15,cloonar.com" # domain name
|
||||
"lan,3,${config.networkPrefix}.96.1" # Gateway
|
||||
"lan,6,${config.networkPrefix}.96.1" # DNS
|
||||
"server,15,cloonar.com"
|
||||
"server,3,${config.networkPrefix}.97.1"
|
||||
"server,6,${config.networkPrefix}.97.1"
|
||||
"infrastructure,15,cloonar.com"
|
||||
"infrastructure,3,${config.networkPrefix}.101.1"
|
||||
"infrastructure,6,${config.networkPrefix}.101.1"
|
||||
"multimedia,15,cloonar.multimedia"
|
||||
"multimedia,3,${config.networkPrefix}.99.1"
|
||||
"multimedia,6,${config.networkPrefix}.99.1"
|
||||
"smart,15,cloonar.smart"
|
||||
"smart,3,${config.networkPrefix}.100.1"
|
||||
"smart,6,${config.networkPrefix}.100.1"
|
||||
"guest,15,cloonar.guest"
|
||||
"guest,3,${config.networkPrefix}.254.1"
|
||||
"guest,6,9.9.9.9"
|
||||
];
|
||||
|
||||
dhcp-range = [
|
||||
"lan,${config.networkPrefix}.96.100,${config.networkPrefix}.96.200,24h"
|
||||
"server,${config.networkPrefix}.97.100,${config.networkPrefix}.97.200,24h"
|
||||
"infrastructure,${config.networkPrefix}.101.100,${config.networkPrefix}.101.200,24h"
|
||||
"multimedia,${config.networkPrefix}.99.100,${config.networkPrefix}.99.200,24h"
|
||||
"smart,${config.networkPrefix}.100.100,${config.networkPrefix}.100.200,24h"
|
||||
"guest,${config.networkPrefix}.254.100,${config.networkPrefix}.254.200,24h"
|
||||
];
|
||||
|
||||
dhcp-host = [
|
||||
"24:df:a7:b1:1b:74,${config.networkPrefix}.96.101,rmproplus-b1-1b-74"
|
||||
|
||||
"30:05:5c:56:62:37,${config.networkPrefix}.99.100,brn30055c566237"
|
||||
"1a:c4:04:6e:29:bd,${config.networkPrefix}.97.2,omada"
|
||||
"02:00:00:00:00:04,${config.networkPrefix}.97.6,matrix"
|
||||
"ea:db:d4:c1:18:ba,${config.networkPrefix}.97.50,git"
|
||||
"c2:4f:64:dd:13:0c,${config.networkPrefix}.97.20,home-assistant"
|
||||
"6c:1f:f7:8e:a9:86,${config.networkPrefix}.97.11,nas"
|
||||
"1a:c4:04:6e:29:02,${config.networkPrefix}.101.25,deconz"
|
||||
|
||||
"c4:a7:2b:c7:ea:30,${config.networkPrefix}.99.10,metz"
|
||||
"f0:2f:9e:d4:3b:21,${config.networkPrefix}.99.11,firetv-living"
|
||||
"e4:2a:ac:32:3f:79,${config.networkPrefix}.99.13,xbox"
|
||||
"f0:2f:9e:c1:74:72,${config.networkPrefix}.99.21,firetv-bedroom"
|
||||
|
||||
"fc:ee:28:03:63:e9,${config.networkPrefix}.100.148,k1c"
|
||||
"cc:50:e3:bc:27:64,${config.networkPrefix}.100.112,Nuki_Bridge_1A753F72"
|
||||
"34:6f:24:f3:af:ad,${config.networkPrefix}.100.137,daikin86604"
|
||||
"34:6f:24:c1:f8:54,${config.networkPrefix}.100.139,daikin53800"
|
||||
];
|
||||
|
||||
address = [
|
||||
"/fw.cloonar.com/${config.networkPrefix}.97.1"
|
||||
"/omada.cloonar.com/${config.networkPrefix}.97.2"
|
||||
"/web-02.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/pla.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/piped.cloonar.com/${config.networkPrefix}.97.5" # Replaced by Invidious
|
||||
"/pipedapi.cloonar.com/${config.networkPrefix}.97.5" # Replaced by Invidious
|
||||
"/invidious.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/fivefilters.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/n8n.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/home-assistant.cloonar.com/${config.networkPrefix}.97.20"
|
||||
"/mopidy.cloonar.com/${config.networkPrefix}.97.21"
|
||||
"/snapcast.cloonar.com/${config.networkPrefix}.97.21"
|
||||
"/lms.cloonar.com/${config.networkPrefix}.97.21"
|
||||
"/git.cloonar.com/${config.networkPrefix}.97.50"
|
||||
"/feeds.cloonar.com/188.34.191.144"
|
||||
"/nukibridge1a753f72.cloonar.smart/${config.networkPrefix}.100.112"
|
||||
"/allywatch.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/brn30055c566237.cloonar.multimedia/${config.networkPrefix}.99.100"
|
||||
|
||||
"/stage.wsw.at/10.254.235.22"
|
||||
"/prod.wsw.at/10.254.217.23"
|
||||
"/piwik.wohnservice-wien.at/10.254.240.109"
|
||||
"/wohnberatung-wien.at/10.254.240.109"
|
||||
"/wohnpartner-wien.at/10.254.240.109"
|
||||
"/wohnservice-wien.at/10.254.240.109"
|
||||
"/mieterhilfe.at/10.254.240.109"
|
||||
"/wienbautvor.at/10.254.240.109"
|
||||
"/wienwohntbesser.at/10.254.240.109"
|
||||
"/a.stage.wohnberatung-wien.at/10.254.240.110"
|
||||
"/a.stage.wohnpartner-wien.at/10.254.240.110"
|
||||
"/a.stage.wohnservice-wien.at/10.254.240.110"
|
||||
"/a.stage.mieterhilfe.at/10.254.240.110"
|
||||
"/a.stage.wienbautvor.at/10.254.240.110"
|
||||
"/a.stage.wienwohntbesser.at/10.254.240.110"
|
||||
"/b.stage.wohnberatung-wien.at/10.254.240.110"
|
||||
"/b.stage.wohnpartner-wien.at/10.254.240.110"
|
||||
"/b.stage.wohnservice-wien.at/10.254.240.110"
|
||||
"/b.stage.mieterhilfe.at/10.254.240.110"
|
||||
"/b.stage.wienbautvor.at/10.254.240.110"
|
||||
"/b.stage.wienwohntbesser.at/10.254.240.110"
|
||||
|
||||
"/web.hilgenberg-gmbh.de/91.107.197.169"
|
||||
# gaming
|
||||
"/foundry-vtt.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/sync.cloonar.com/${config.networkPrefix}.97.5"
|
||||
|
||||
# multimedia
|
||||
"/dl.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/jellyfin.cloonar.com/${config.networkPrefix}.97.5"
|
||||
|
||||
"/deconz.cloonar.multimedia/${config.networkPrefix}.97.22"
|
||||
|
||||
"/ddl-warez.to/172.67.184.30"
|
||||
"/cdnjs.cloudflare.com/104.17.24.14"
|
||||
|
||||
# esphome devices
|
||||
"/livingroom-bulb-1.cloonar.smart/${config.networkPrefix}.100.11"
|
||||
"/livingroom-bulb-2.cloonar.smart/${config.networkPrefix}.100.12"
|
||||
"/livingroom-bulb-3.cloonar.smart/${config.networkPrefix}.100.13"
|
||||
"/livingroom-bulb-4.cloonar.smart/${config.networkPrefix}.100.14"
|
||||
"/livingroom-bulb-5.cloonar.smart/${config.networkPrefix}.100.15"
|
||||
"/livingroom-bulb-6.cloonar.smart/${config.networkPrefix}.100.16"
|
||||
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.21"
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.22"
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.23"
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.24"
|
||||
|
||||
"/hallway-bulb-0.cloonar.smart/${config.networkPrefix}.100.31"
|
||||
"/hallway-bulb-0.cloonar.smart/${config.networkPrefix}.100.32"
|
||||
|
||||
"/bath-bulb-0.cloonar.smart/${config.networkPrefix}.100.41"
|
||||
"/bath-bulb-0.cloonar.smart/${config.networkPrefix}.100.42"
|
||||
|
||||
"/paraclub.at/188.34.191.144"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.dnsmasq = {
|
||||
requires = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedUDPPorts = [ 53 67 ];
|
||||
}
|
||||
59
hosts/fw/modules/firefox-sync.nix
Normal file
59
hosts/fw/modules/firefox-sync.nix
Normal file
@@ -0,0 +1,59 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
domain = "sync.cloonar.com";
|
||||
networkPrefix = config.networkPrefix;
|
||||
in {
|
||||
sops.secrets.firefox-sync = {
|
||||
mode = "0777";
|
||||
};
|
||||
|
||||
security.acme.certs."${domain}" = {
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
containers."firefox-sync" = {
|
||||
autoStart = true;
|
||||
ephemeral = false; # because of ssh key
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "${config.networkPrefix}.97.1";
|
||||
localAddress = "${config.networkPrefix}.97.6/24";
|
||||
bindMounts = {
|
||||
"/run/secrets/firefox-sync" = {
|
||||
hostPath = "/run/secrets/firefox-sync";
|
||||
isReadOnly = true;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networking = {
|
||||
hostName = "firefox-sync";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "${networkPrefix}.97.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
nameservers = [ "${networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
services.mysql.package = pkgs.mariadb;
|
||||
services.firefox-syncserver = {
|
||||
enable = true;
|
||||
settings.host = "0.0.0.0";
|
||||
singleNode = {
|
||||
enable = true;
|
||||
hostname = "0.0.0.0";
|
||||
url = "https://${domain}";
|
||||
};
|
||||
secrets = "/run/secrets/firefox-sync";
|
||||
logLevel = "debug";
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 5000 ];
|
||||
};
|
||||
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{ pkgs, ... }: {
|
||||
{ config, pkgs, ... }: {
|
||||
networking = {
|
||||
firewall.checkReversePath = false;
|
||||
nat.enable = false;
|
||||
@@ -8,6 +8,18 @@
|
||||
"cloonar-fw" = {
|
||||
family = "inet";
|
||||
content = ''
|
||||
chain snap-qos-raw {
|
||||
type filter hook prerouting priority raw; policy accept;
|
||||
tcp dport 1704 counter mark set 10 comment "Mark Snapcast traffic"
|
||||
tcp dport 3483 counter mark set 10 comment "Mark Squezelite traffic"
|
||||
udp dport 3483 counter mark set 10 comment "Mark Squezelite traffic"
|
||||
}
|
||||
|
||||
chain snap-qos-mangle {
|
||||
type filter hook postrouting priority mangle + 10; policy accept;
|
||||
mark 10 counter ip dscp set cs3 comment "Tag Snapcast with CS3"
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority 100; policy accept;
|
||||
}
|
||||
@@ -22,6 +34,7 @@
|
||||
type filter hook input priority filter; policy drop;
|
||||
iifname "lo" accept comment "trusted interfaces"
|
||||
iifname "lan" counter accept comment "Spice"
|
||||
iifname { "server", "vserver", "vm-*", "lan", "wg_cloonar" } counter accept comment "allow trusted to router"
|
||||
ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }
|
||||
tcp flags syn / fin,syn,rst,ack log prefix "refused connection: " level info
|
||||
}
|
||||
@@ -34,11 +47,14 @@
|
||||
iifname "lan" tcp dport 5931 counter accept comment "Spice"
|
||||
iifname { "server", "vserver", "vm-*", "lan", "wg_cloonar" } counter accept comment "allow trusted to router"
|
||||
iifname { "multimedia", "smart", "infrastructure", "podman0", "setup" } udp dport { 53, 5353 } counter accept comment "DNS"
|
||||
iifname { "multimedia", "smart", "infrastructure", "server", "lan", "guest" } udp dport { 67 } counter accept comment "DHCP"
|
||||
iifname { "wan", "multimedia" } icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
|
||||
# Accept mDNS for avahi reflection
|
||||
iifname "server" ip saddr 10.42.97.20/32 tcp dport { llmnr } counter accept
|
||||
iifname "server" ip saddr 10.42.97.20/32 udp dport { mdns, llmnr } counter accept
|
||||
iifname "server" ip saddr ${config.networkPrefix}.97.20/32 tcp dport { llmnr } counter accept
|
||||
iifname "server" ip saddr ${config.networkPrefix}.97.20/32 udp dport { mdns, llmnr } counter accept
|
||||
iifname "server" udp dport 5353 ip daddr 224.0.0.251 counter accept comment "Avahi mDNS"
|
||||
iifname "lan" udp dport 5353 ip daddr 224.0.0.251 counter accept comment "Avahi mDNS"
|
||||
|
||||
# Allow all returning traffic
|
||||
ct state { established, related } counter accept
|
||||
@@ -79,17 +95,31 @@
|
||||
# multimedia airplay
|
||||
iifname "multimedia" oifname { "lan" } counter accept
|
||||
iifname "multimedia" oifname "server" tcp dport { 1704, 1705 } counter accept
|
||||
iifname "multimedia" oifname "server" tcp dport { 3483, 9000 } counter accept
|
||||
iifname "multimedia" oifname "server" udp dport { 3483 } counter accept
|
||||
iifname "multimedia" oifname "server" icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
iifname "lan" oifname "server" udp dport { 5000, 5353, 6001 - 6011 } counter accept
|
||||
# avahi
|
||||
iifname "server" ip saddr 10.42.97.20/32 oifname { "lan" } counter accept
|
||||
iifname "server" ip saddr ${config.networkPrefix}.97.20/32 oifname { "lan" } counter accept
|
||||
|
||||
# Allow Chromecast
|
||||
iifname "lan" oifname "server" udp dport 5353 ip daddr 224.0.0.251 counter accept comment "mDNS query LAN→Server"
|
||||
iifname "server" oifname "lan" udp sport 5353 ip saddr 224.0.0.251 counter accept comment "mDNS response Server→LAN"
|
||||
iifname "lan" oifname "server" tcp dport 9881 counter accept comment "chromecast"
|
||||
|
||||
# SSDP / UPnP discovery if needed
|
||||
iifname { "lan", "server" } oifname { "server", "lan" } \
|
||||
udp dport 1900 ip daddr 239.255.255.250 counter accept comment "SSDP query"
|
||||
iifname { "lan", "server" } oifname { "server", "lan" } \
|
||||
udp sport 1900 ip saddr 239.255.255.250 counter accept comment "SSDP response"
|
||||
|
||||
# smart home coap
|
||||
iifname "smart" oifname "server" ip daddr 10.42.97.20/32 udp dport { 5683 } counter accept
|
||||
iifname "smart" oifname "server" ip daddr 10.42.97.20/32 tcp dport { 1883 } counter accept
|
||||
iifname "smart" oifname "server" ip daddr ${config.networkPrefix}.97.20/32 udp dport { 5683 } counter accept
|
||||
iifname "smart" oifname "server" ip daddr ${config.networkPrefix}.97.20/32 tcp dport { 1883 } counter accept
|
||||
|
||||
# Forward to git server
|
||||
oifname "server" ip daddr 10.42.97.50 tcp dport { 22 } counter accept
|
||||
oifname "server" ip daddr 10.42.97.5 tcp dport { 80, 443 } counter accept
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.50 tcp dport { 22 } counter accept
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.5 tcp dport { 80, 443 } counter accept
|
||||
|
||||
# lan and vpn to any
|
||||
iifname { "lan", "server", "vserver", "wg_cloonar" } oifname { "lan", "vb-*", "vm-*", "server", "vserver", "infrastructure", "multimedia", "smart", "wg_cloonar", "guest", "setup" } counter accept
|
||||
@@ -100,11 +130,11 @@
|
||||
# accept palword server
|
||||
iifname { "wan", "lan" } oifname "podman0" udp dport { 8211, 27015 } counter accept comment "palworld"
|
||||
# forward to ark server
|
||||
oifname "server" ip daddr 10.42.97.201 tcp dport { 27020 } counter accept comment "ark survival evolved"
|
||||
oifname "server" ip daddr 10.42.97.201 udp dport { 7777, 7778, 27015 } counter accept comment "ark survival evolved"
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.201 tcp dport { 27020 } counter accept comment "ark survival evolved"
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.201 udp dport { 7777, 7778, 27015 } counter accept comment "ark survival evolved"
|
||||
|
||||
# firefox-sync
|
||||
oifname "server" ip daddr 10.42.97.51 tcp dport { 5000 } counter accept comment "firefox-sync"
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.51 tcp dport { 5000 } counter accept comment "firefox-sync"
|
||||
|
||||
# allow all established, related
|
||||
ct state { established, related } accept comment "Allow established traffic"
|
||||
@@ -136,21 +166,22 @@
|
||||
content = ''
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority filter; policy accept;
|
||||
iifname "server" ip daddr 10.42.96.255 udp dport { 9 } dnat to 10.42.96.255
|
||||
iifname "wan" tcp dport { 22 } dnat to 10.42.97.50
|
||||
iifname "wan" tcp dport { 80, 443 } dnat to 10.42.97.5
|
||||
iifname "wan" tcp dport { 5000 } dnat to 10.42.97.51
|
||||
iifname { "wan", "lan" } udp dport { 7777, 7778, 27015 } dnat to 10.42.97.201
|
||||
iifname { "wan", "lan" } tcp dport { 27020 } dnat to 10.42.97.201
|
||||
iifname "server" ip daddr ${config.networkPrefix}.96.255 udp dport { 9 } dnat to ${config.networkPrefix}.96.255
|
||||
iifname "wan" tcp dport { 22 } dnat to ${config.networkPrefix}.97.50
|
||||
iifname "wan" tcp dport { 80, 443 } dnat to ${config.networkPrefix}.97.5
|
||||
iifname "wan" tcp dport { 5000 } dnat to ${config.networkPrefix}.97.51
|
||||
iifname { "wan", "lan" } udp dport { 7777, 7778, 27015 } dnat to ${config.networkPrefix}.97.201
|
||||
iifname { "wan", "lan" } tcp dport { 27020 } dnat to ${config.networkPrefix}.97.201
|
||||
}
|
||||
|
||||
# Setup NAT masquerading on external interfaces
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority filter; policy accept;
|
||||
oifname { "wan", "wg_cloonar", "wrwks", "wg_epicenter", "wg_ghetto_at" } masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr 10.42.97.50 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr 10.42.97.51 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr 10.42.97.201 masquerade
|
||||
iifname { "lan", "wg_cloonar" } ip daddr ${config.networkPrefix}.110.101 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr ${config.networkPrefix}.97.50 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr ${config.networkPrefix}.97.51 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr ${config.networkPrefix}.97.201 masquerade
|
||||
}
|
||||
'';
|
||||
};
|
||||
32
hosts/fw/modules/fivefilters.nix
Normal file
32
hosts/fw/modules/fivefilters.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{ config, pkgs, ... }: {
|
||||
users.users.fivefilters = {
|
||||
isSystemUser = true;
|
||||
group = "omada";
|
||||
home = "/var/lib/fivefilters";
|
||||
createHome = true;
|
||||
};
|
||||
users.groups.fivefilters = { };
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
# parent is created by createHome already, but harmless to repeat
|
||||
"d /var/lib/fivefilters 0755 fivefilters fivefilters - -"
|
||||
"d /var/lib/fivefilters/cache 0755 fivefilters fivefilters - -"
|
||||
];
|
||||
|
||||
# TODO: check if we can run docker service as other user than root
|
||||
virtualisation = {
|
||||
oci-containers.containers = {
|
||||
fivefilters = {
|
||||
autoStart = true;
|
||||
image = "heussd/fivefilters-full-text-rss:3.8.1";
|
||||
volumes = [
|
||||
"/var/lib/fivefilters/cache:/var/www/html/cache"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=server"
|
||||
"--ip=${config.networkPrefix}.97.10"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
162
hosts/fw/modules/foundry-vtt.nix
Normal file
162
hosts/fw/modules/foundry-vtt.nix
Normal file
@@ -0,0 +1,162 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
foundry-vtt = pkgs.callPackage ../pkgs/foundry-vtt {};
|
||||
cids = import ../modules/staticids.nix;
|
||||
hostConfig = config;
|
||||
url = "https://foundry-vtt.cloonar.com"; # URL to check
|
||||
targetService = "container@foundry-vtt.service"; # systemd unit to restart (e.g. "docker-container@myapp.service")
|
||||
threshold = 3; # consecutive failures before restart
|
||||
interval = "1min"; # how often to run
|
||||
timeoutSeconds = 10; # curl timeout
|
||||
|
||||
checkUrlScript = pkgs.writeShellScript "check-foundry-up" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
URL="$1"
|
||||
TARGET="$2"
|
||||
THRESHOLD="$3"
|
||||
TIMEOUT="$4"
|
||||
|
||||
STATE_DIR="/run/url-watchdog"
|
||||
mkdir -p "$STATE_DIR"
|
||||
SAFE_TARGET="$(systemd-escape --path "$TARGET")"
|
||||
STATE_FILE="$STATE_DIR/$SAFE_TARGET.count"
|
||||
|
||||
TMP="$(mktemp)"
|
||||
# Get HTTP status; "000" if curl fails.
|
||||
status="$(curl -sS -m "$TIMEOUT" -o "$TMP" -w "%{http_code}" "$URL" || echo "000")"
|
||||
|
||||
fail=0
|
||||
if [[ "$status" == "502" || "$status" == "504" || "$status" == "000" ]]; then
|
||||
fail=1
|
||||
fi
|
||||
|
||||
count=0
|
||||
if [[ -f "$STATE_FILE" ]]; then
|
||||
count="$(cat "$STATE_FILE" 2>/dev/null || echo 0)"
|
||||
fi
|
||||
|
||||
if [[ "$fail" -eq 1 ]]; then
|
||||
count=$((count+1))
|
||||
else
|
||||
count=0
|
||||
fi
|
||||
|
||||
if [[ "$count" -ge "$THRESHOLD" ]]; then
|
||||
printf '[%s] %s failing (%s) %sx -> restarting %s\n' "$(date -Is)" "$URL" "$status" "$count" "$TARGET"
|
||||
systemctl restart "$TARGET"
|
||||
count=0
|
||||
fi
|
||||
|
||||
echo "$count" > "$STATE_FILE"
|
||||
rm -f "$TMP"
|
||||
'';
|
||||
in {
|
||||
users.users.foundry-vtt = {
|
||||
isSystemUser = true;
|
||||
uid = cids.uids.foundry-vtt;
|
||||
home = "/var/lib/foundry-vtt";
|
||||
group = "foundry-vtt";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.foundry-vtt = {
|
||||
gid = cids.gids.foundry-vtt;
|
||||
};
|
||||
|
||||
|
||||
containers.foundry-vtt = {
|
||||
autoStart = true;
|
||||
ephemeral = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "${hostConfig.networkPrefix}.97.1";
|
||||
localAddress = "${hostConfig.networkPrefix}.97.21/24";
|
||||
bindMounts = {
|
||||
"/var/lib/foundry-vtt" = {
|
||||
hostPath = "/var/lib/foundry-vtt";
|
||||
isReadOnly = false;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networking = {
|
||||
hostName = "foundry-vtt";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "${hostConfig.networkPrefix}.96.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nameservers = [ "${hostConfig.networkPrefix}.97.1" ];
|
||||
};
|
||||
systemd.services.foundry-vtt = {
|
||||
description = "Foundry VTT Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
NODE_ENV = "production";
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.nodejs}/bin/node ${foundry-vtt}/share/foundry-vtt/main.js --dataPath=${config.users.users.foundry-vtt.home}";
|
||||
Restart = "always";
|
||||
User = "foundry-vtt";
|
||||
WorkingDirectory = "${config.users.users.foundry-vtt.home}";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.foundry-vtt = {
|
||||
isSystemUser = true;
|
||||
uid = cids.uids.foundry-vtt;
|
||||
home = "/var/lib/foundry-vtt";
|
||||
group = "foundry-vtt";
|
||||
};
|
||||
|
||||
users.groups.foundry-vtt = {
|
||||
gid = cids.gids.foundry-vtt;
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services."restart-foundry-vtt" = {
|
||||
description = "Restart foundry-vtt container";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.systemd}/bin/systemctl restart container@foundry-vtt.service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers."restart-foundry-vtt" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
# 03:00 local time (Europe/Vienna for you)
|
||||
OnCalendar = "03:00";
|
||||
# If the machine was off at 03:00, run once at next boot
|
||||
Persistent = true;
|
||||
Unit = "restart-foundry-vtt.service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.foundry-vtt-watchdog = {
|
||||
description = "Foundry VTT watchdog: restart ${targetService} on Nginx gateway errors";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${checkUrlScript} ${url} ${targetService} ${toString threshold} ${toString timeoutSeconds}";
|
||||
};
|
||||
# Ensure needed tools are on PATH inside the unit
|
||||
path = [ pkgs.curl pkgs.coreutils pkgs.systemd ];
|
||||
# Wait until networking is really up
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
};
|
||||
|
||||
systemd.timers.foundry-vtt-watchdog = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = interval;
|
||||
OnUnitActiveSec = interval;
|
||||
AccuracySec = "10s";
|
||||
};
|
||||
};
|
||||
}
|
||||
44
hosts/fw/modules/gitea-runner-image-README.md
Normal file
44
hosts/fw/modules/gitea-runner-image-README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Gitea Runner Docker Image
|
||||
|
||||
This directory contains the Dockerfile for the custom Gitea Actions runner image that includes additional dependencies needed for CI workflows.
|
||||
|
||||
## Included Tools
|
||||
|
||||
- **Base**: `shivammathur/node:latest` (includes Node.js and common development tools)
|
||||
- **Chrome dependencies**: Full Puppeteer/Chromium dependencies for headless browser testing
|
||||
- **webp**: WebP image format tools (`cwebp`, `dwebp`)
|
||||
- **libavif-bin**: AVIF image format tools (`avifenc`, `avifdec`)
|
||||
|
||||
## Building the Image
|
||||
|
||||
```bash
|
||||
cd hosts/fw/modules
|
||||
docker build -f gitea-runner.Dockerfile -t git.cloonar.com/infrastructure/gitea-runner:latest .
|
||||
```
|
||||
|
||||
## Pushing to Registry
|
||||
|
||||
First, authenticate with your Gitea container registry:
|
||||
|
||||
```bash
|
||||
docker login git.cloonar.com
|
||||
```
|
||||
|
||||
Then push the image:
|
||||
|
||||
```bash
|
||||
docker push git.cloonar.com/infrastructure/gitea-runner:latest
|
||||
```
|
||||
|
||||
## Using the Image
|
||||
|
||||
The image is already configured in `gitea-vm.nix` and will be used automatically by the Gitea Actions runners for jobs labeled with `ubuntu-latest`.
|
||||
|
||||
## Updating the Image
|
||||
|
||||
When you need to add new dependencies:
|
||||
|
||||
1. Edit `gitea-runner.Dockerfile`
|
||||
2. Rebuild the image with the commands above
|
||||
3. Push to the registry
|
||||
4. Restart the runner VMs: `systemctl restart microvm@git-runner-1.service microvm@git-runner-2.service`
|
||||
54
hosts/fw/modules/gitea-runner.Dockerfile
Normal file
54
hosts/fw/modules/gitea-runner.Dockerfile
Normal file
@@ -0,0 +1,54 @@
|
||||
FROM shivammathur/node:latest
|
||||
|
||||
# Install Chrome dependencies for Puppeteer
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
fonts-liberation \
|
||||
libappindicator3-1 \
|
||||
libasound2t64 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libc6 \
|
||||
libcairo2 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
libexpat1 \
|
||||
libfontconfig1 \
|
||||
libgbm1 \
|
||||
libgcc-s1 \
|
||||
libglib2.0-0 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libstdc++6 \
|
||||
libx11-6 \
|
||||
libx11-xcb1 \
|
||||
libxcb1 \
|
||||
libxcomposite1 \
|
||||
libxcursor1 \
|
||||
libxdamage1 \
|
||||
libxext6 \
|
||||
libxfixes3 \
|
||||
libxi6 \
|
||||
libxrandr2 \
|
||||
libxrender1 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
lsb-release \
|
||||
wget \
|
||||
xdg-utils \
|
||||
webp \
|
||||
libavif-bin \
|
||||
chromium \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
||||
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y google-chrome-stable && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Verify installations
|
||||
RUN cwebp -version && avifenc --version
|
||||
@@ -1,4 +1,4 @@
|
||||
{ lib, nixpkgs, pkgs, ... }: let
|
||||
{ config, lib, nixpkgs, pkgs, ... }: let
|
||||
# hostname = "git-02";
|
||||
# json = pkgs.formats.json { };
|
||||
runners = ["git-runner-1" "git-runner-2"];
|
||||
@@ -38,6 +38,13 @@ in {
|
||||
];
|
||||
};
|
||||
|
||||
systemd.network.networks."10-lan" = {
|
||||
matchConfig.PermanentMACAddress = "02:00:00:00:00:0${toString idx}";
|
||||
address = [ "${config.networkPrefix}.97.5${toString idx}/24" ];
|
||||
gateway = [ "${config.networkPrefix}.97.1" ];
|
||||
dns = [ "${config.networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
networking.hostName = runner;
|
||||
|
||||
virtualisation.podman.enable = true;
|
||||
@@ -48,12 +55,18 @@ in {
|
||||
name = runner;
|
||||
tokenFile = "/run/secrets/gitea-runner-token";
|
||||
labels = [
|
||||
"ubuntu-latest:docker://shivammathur/node:latest"
|
||||
# "ubuntu-latest:docker://shivammathur/node:latest"
|
||||
"ubuntu-latest:docker://git.cloonar.com/infrastructure/gitea-runner:1.0.0"
|
||||
];
|
||||
settings = {
|
||||
container = {
|
||||
network = "podman";
|
||||
};
|
||||
cache = {
|
||||
enabled = true;
|
||||
host = "${config.networkPrefix}.97.5${toString idx}"; # LAN IP of the machine running act_runner
|
||||
port = 8088; # any free TCP port
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -62,6 +75,11 @@ in {
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
];
|
||||
|
||||
networking.firewall = {
|
||||
enable = true; # default, but being explicit is fine
|
||||
allowedTCPPorts = [ 8088 ];
|
||||
};
|
||||
|
||||
system.stateVersion = "22.05";
|
||||
};
|
||||
}) (lib.listToAttrs (lib.lists.imap1 (i: v: { name=v; value=i; }) runners));
|
||||
@@ -2,6 +2,7 @@
|
||||
let
|
||||
cids = import ../modules/staticids.nix;
|
||||
domain = "git.cloonar.com";
|
||||
networkPrefix = config.networkPrefix;
|
||||
|
||||
user = {
|
||||
isSystemUser = true;
|
||||
@@ -27,8 +28,8 @@ in
|
||||
ephemeral = false; # because of ssh key
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "10.42.97.1";
|
||||
localAddress = "10.42.97.50/24";
|
||||
hostAddress = "${networkPrefix}.97.1";
|
||||
localAddress = "${networkPrefix}.97.50/24";
|
||||
bindMounts = {
|
||||
"/var/lib/gitea" = {
|
||||
hostPath = "/var/lib/gitea/";
|
||||
@@ -45,6 +46,7 @@ in
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
imports = [
|
||||
../fleet.nix
|
||||
../modules/cloonar-assistant-config-server.nix
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
@@ -55,11 +57,11 @@ in
|
||||
hostName = "git";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "10.42.96.1";
|
||||
address = "${networkPrefix}.96.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nameservers = [ "10.42.97.1" ];
|
||||
nameservers = [ "${networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
services.nginx.enable = true;
|
||||
@@ -68,6 +70,9 @@ in
|
||||
sslCertificateKey = "/var/lib/acme/gitea/key.pem";
|
||||
sslTrustedCertificate = "/var/lib/acme/gitea/chain.pem";
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
client_max_body_size 2048M;
|
||||
'';
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:3001/";
|
||||
};
|
||||
@@ -83,6 +88,9 @@ in
|
||||
HTTP_PORT = 3001;
|
||||
DOMAIN = domain;
|
||||
};
|
||||
repository = {
|
||||
DEFAULT_BRANCH = "main";
|
||||
};
|
||||
openid = {
|
||||
ENABLE_OPENID_SIGNIN = false;
|
||||
ENABLE_OPENID_SIGNUP = true;
|
||||
@@ -93,6 +101,7 @@ in
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
ENABLE_NOTIFY_MAIL = true;
|
||||
REQUIRE_SIGNIN_VIEW = false;
|
||||
};
|
||||
mailer = {
|
||||
ENABLED = true;
|
||||
@@ -103,6 +112,12 @@ in
|
||||
USER = "gitea@cloonar.com";
|
||||
};
|
||||
actions.ENABLED=true;
|
||||
attachment = {
|
||||
MAX_SIZE = 2048; # 2GB in MB for general attachments
|
||||
};
|
||||
packages = {
|
||||
ENABLED = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
193
hosts/fw/modules/grafana-monitor.nix
Normal file
193
hosts/fw/modules/grafana-monitor.nix
Normal file
@@ -0,0 +1,193 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
grafanaMonitorUser = "grafana-monitor";
|
||||
grafanaMonitorGroup = "grafana-monitor";
|
||||
stateDir = "/var/lib/${grafanaMonitorUser}";
|
||||
|
||||
# Monitoring script will be defined here later
|
||||
monitorScript = pkgs.writeShellScriptBin "grafana-online-check" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
GRAFANA_URL="https://grafana.cloonar.com/api/health"
|
||||
STATE_FILE="${stateDir}/status.env"
|
||||
PUSHOVER_API_TOKEN_FILE="/run/secrets/pushover-api-token"
|
||||
PUSHOVER_USER_KEY_FILE="/run/secrets/pushover-user-key"
|
||||
MAX_FAILURES=5
|
||||
|
||||
# Ensure state directory exists (NixOS creates $HOME for the user, which is stateDir)
|
||||
# The script runs as grafanaMonitorUser, so $HOME will be /var/lib/grafana-monitor
|
||||
mkdir -p "''${HOME}"
|
||||
|
||||
# Load current state or initialize
|
||||
CONSECUTIVE_FAILURES=0
|
||||
ALERT_SENT="false"
|
||||
LAST_KNOWN_STATUS="UP" # Assume UP initially if no state file
|
||||
|
||||
# Note: STATE_FILE uses $stateDir which is /var/lib/grafana-monitor.
|
||||
# The script will run with HOME=/var/lib/grafana-monitor.
|
||||
# So, using ''${HOME}/status.env or ''${STATE_FILE} should resolve to the same path.
|
||||
# Let's stick to ''${STATE_FILE} for consistency with its definition.
|
||||
if [[ -f "''${STATE_FILE}" ]]; then
|
||||
source "''${STATE_FILE}"
|
||||
fi
|
||||
|
||||
# Check secrets
|
||||
if [[ ! -f "''${PUSHOVER_API_TOKEN_FILE}" ]] || [[ ! -r "''${PUSHOVER_API_TOKEN_FILE}" ]]; then
|
||||
echo "Error: Pushover API token file (''${PUSHOVER_API_TOKEN_FILE}) not found or not readable." >&2
|
||||
exit 1
|
||||
fi
|
||||
PUSHOVER_API_TOKEN=$(cat "''${PUSHOVER_API_TOKEN_FILE}")
|
||||
|
||||
if [[ ! -f "''${PUSHOVER_USER_KEY_FILE}" ]] || [[ ! -r "''${PUSHOVER_USER_KEY_FILE}" ]]; then
|
||||
echo "Error: Pushover user key file (''${PUSHOVER_USER_KEY_FILE}) not found or not readable." >&2
|
||||
exit 1
|
||||
fi
|
||||
PUSHOVER_USER_KEY=$(cat "''${PUSHOVER_USER_KEY_FILE}")
|
||||
|
||||
# Internet connectivity check
|
||||
INTERNET_CHECK_URL="https://1.1.1.1" # Using a reliable IP to bypass potential DNS issues for the check itself
|
||||
echo "Performing internet connectivity check to ''${INTERNET_CHECK_URL}..."
|
||||
if ! ${pkgs.curl}/bin/curl --head --silent --fail --connect-timeout 3 --max-time 5 "''${INTERNET_CHECK_URL}" > /dev/null 2>&1; then
|
||||
echo "Internet connectivity check failed. Cannot reach ''${INTERNET_CHECK_URL}. Skipping Grafana check and exiting successfully."
|
||||
exit 0
|
||||
else
|
||||
echo "Internet connectivity check successful. Proceeding with Grafana check."
|
||||
fi
|
||||
echo "" # Add a blank line for readability before Grafana check logs
|
||||
echo "Checking Grafana at ''${GRAFANA_URL}..."
|
||||
ACTUAL_HTTP_CODE="000" # Default if curl doesn't provide one
|
||||
CURL_ERROR_MESSAGE=""
|
||||
CURL_STDERR_OUTPUT=$(mktemp)
|
||||
# Ensure temp file is cleaned up on exit, error, or interrupt
|
||||
trap 'rm -f "''${CURL_STDERR_OUTPUT}"' EXIT TERM INT HUP
|
||||
|
||||
# -L: follow redirects
|
||||
# -sS: silent mode, but show errors
|
||||
# --fail: curl exits with 22 on server errors (4xx, 5xx)
|
||||
# --connect-timeout 5: max time to connect
|
||||
# --max-time 10: max total time for operation
|
||||
# --stderr: redirect stderr to a file to capture detailed errors
|
||||
# -o /dev/null: discard response body
|
||||
# --write-out "%{http_code}": output the HTTP status code
|
||||
if ACTUAL_HTTP_CODE=$(${pkgs.curl}/bin/curl -L -sS --fail --connect-timeout 5 --max-time 10 \
|
||||
--stderr "''${CURL_STDERR_OUTPUT}" \
|
||||
-o /dev/null --write-out "%{http_code}" "''${GRAFANA_URL}"); then
|
||||
# Curl exited with 0. With --fail, this means HTTP status was 2xx.
|
||||
echo "Grafana is UP (HTTP ''${ACTUAL_HTTP_CODE})."
|
||||
CURRENT_STATUS="UP"
|
||||
if [[ "''${LAST_KNOWN_STATUS}" == "DOWN" && "''${ALERT_SENT}" == "true" ]]; then
|
||||
echo "Grafana recovered. Sending recovery notification."
|
||||
${pkgs.curl}/bin/curl -sS -X POST \
|
||||
-F "token=''${PUSHOVER_API_TOKEN}" \
|
||||
-F "user=''${PUSHOVER_USER_KEY}" \
|
||||
-F "message=Grafana at ''${GRAFANA_URL} is back online (HTTP ''${ACTUAL_HTTP_CODE})." \
|
||||
-F "title=Grafana Recovered (fw)" \
|
||||
-F "priority=0" \
|
||||
https://api.pushover.net/1/messages.json
|
||||
ALERT_SENT="false"
|
||||
fi
|
||||
CONSECUTIVE_FAILURES=0
|
||||
else
|
||||
# Curl exited with a non-zero status.
|
||||
CURL_EXIT_CODE=$?
|
||||
CURL_ERROR_MESSAGE=$(cat "''${CURL_STDERR_OUTPUT}" | tr -d '\n' | sed 's/"/\\"/g') # Read, remove newlines, escape quotes for JSON
|
||||
|
||||
echo "Grafana check failed. Curl Exit Code: ''${CURL_EXIT_CODE}. HTTP Code reported: ''${ACTUAL_HTTP_CODE}."
|
||||
echo "Curl Stderr: ''${CURL_ERROR_MESSAGE}"
|
||||
CURRENT_STATUS="DOWN"
|
||||
CONSECUTIVE_FAILURES=$(( ''${CONSECUTIVE_FAILURES} + 1 ))
|
||||
echo "Consecutive failures: ''${CONSECUTIVE_FAILURES}"
|
||||
|
||||
if [[ ''${CONSECUTIVE_FAILURES} -ge ''${MAX_FAILURES} && "''${ALERT_SENT}" == "false" ]]; then
|
||||
echo "Grafana has been offline for ''${CONSECUTIVE_FAILURES} checks (>= ''${MAX_FAILURES}). Sending alert."
|
||||
PUSHOVER_TITLE="Grafana OFFLINE (fw)"
|
||||
PUSHOVER_MSG="Grafana ''${GRAFANA_URL} offline for ''${MAX_FAILURES}+ min. HTTP:''${ACTUAL_HTTP_CODE}, CurlExit:''${CURL_EXIT_CODE}."
|
||||
if [[ -n "''${CURL_ERROR_MESSAGE}" ]]; then
|
||||
PUSHOVER_MSG+=" Err: ''${CURL_ERROR_MESSAGE}"
|
||||
fi
|
||||
# Truncate message if too long for Pushover (1024 chars)
|
||||
PUSHOVER_MSG=$(echo "''${PUSHOVER_MSG}" | cut -c 1-1024)
|
||||
|
||||
${pkgs.curl}/bin/curl -sS -X POST \
|
||||
-F "token=''${PUSHOVER_API_TOKEN}" \
|
||||
-F "user=''${PUSHOVER_USER_KEY}" \
|
||||
-F "message=''${PUSHOVER_MSG}" \
|
||||
-F "title=''${PUSHOVER_TITLE}" \
|
||||
-F "priority=1" \
|
||||
https://api.pushover.net/1/messages.json
|
||||
ALERT_SENT="true"
|
||||
fi
|
||||
fi
|
||||
# Temp file is removed by trap
|
||||
|
||||
# Save current state
|
||||
echo "Saving state: CONSECUTIVE_FAILURES=''${CONSECUTIVE_FAILURES}, ALERT_SENT=''${ALERT_SENT}, LAST_KNOWN_STATUS=''${CURRENT_STATUS}"
|
||||
(
|
||||
echo "CONSECUTIVE_FAILURES=''${CONSECUTIVE_FAILURES}"
|
||||
echo "ALERT_SENT=''${ALERT_SENT}"
|
||||
echo "LAST_KNOWN_STATUS=''${CURRENT_STATUS}"
|
||||
) > "''${STATE_FILE}" # Using STATE_FILE which is ${stateDir}/status.env
|
||||
chmod 600 "''${STATE_FILE}"
|
||||
|
||||
echo "Grafana check finished."
|
||||
'';
|
||||
in
|
||||
{
|
||||
# Module is now implicitly enabled when imported
|
||||
config = {
|
||||
users.users.${grafanaMonitorUser} = {
|
||||
isSystemUser = true;
|
||||
group = grafanaMonitorGroup;
|
||||
home = stateDir; # Home directory for state
|
||||
createHome = true; # NixOS will create this directory
|
||||
description = "User for Grafana online monitoring service";
|
||||
};
|
||||
users.groups.${grafanaMonitorGroup} = {};
|
||||
|
||||
# Sops secrets for Pushover
|
||||
sops.secrets."pushover-api-token" = {
|
||||
owner = grafanaMonitorUser;
|
||||
group = grafanaMonitorGroup;
|
||||
mode = "0400"; # Read-only for the user
|
||||
};
|
||||
sops.secrets."pushover-user-key" = {
|
||||
owner = grafanaMonitorUser;
|
||||
group = grafanaMonitorGroup;
|
||||
mode = "0400"; # Read-only for the user
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.curl
|
||||
pkgs.coreutils # for mkdir, cat, echo, rm used in script (though bash builtins are often used)
|
||||
];
|
||||
|
||||
systemd.services.grafana-online-check = {
|
||||
description = "Grafana Online Check Service";
|
||||
wantedBy = [ "multi-user.target" ]; # Or timers.target if only started by timer
|
||||
after = [ "network-online.target" ]; # Ensure network is up and secrets are available
|
||||
requires = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = grafanaMonitorUser;
|
||||
Group = grafanaMonitorGroup;
|
||||
ExecStart = "${monitorScript}/bin/grafana-online-check";
|
||||
# Permissions to write to its own home directory (stateDir) are implicit
|
||||
# If using StateDirectory= in systemd, it would be different.
|
||||
# For home directory usage, ensure the user has rights. `createHome = true` helps.
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.grafana-online-check = {
|
||||
description = "Timer to periodically check Grafana's online status";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "2min"; # Wait a bit after boot
|
||||
OnUnitActiveSec = "1min"; # Run every 1 minute after the last run
|
||||
Unit = "grafana-online-check.service";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
6
hosts/fw/modules/ha-customers/default.nix
Normal file
6
hosts/fw/modules/ha-customers/default.nix
Normal file
@@ -0,0 +1,6 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./ghetto.nix
|
||||
];
|
||||
}
|
||||
29
hosts/fw/modules/ha-customers/ghetto.nix
Normal file
29
hosts/fw/modules/ha-customers/ghetto.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
create_users = host: {
|
||||
users.users."${host.username}.ghetto.at" = {
|
||||
createHome = true;
|
||||
home = "/home/customers/ghetto/" + host.username;
|
||||
isNormalUser = false;
|
||||
isSystemUser = true;
|
||||
group = "sftp_users";
|
||||
openssh.authorizedKeys.keys = [
|
||||
host.key
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
];
|
||||
shell = null;
|
||||
};
|
||||
};
|
||||
|
||||
users = [
|
||||
{
|
||||
username = "fw";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGzJRWe8hsqAVnGSjPrcheloteWMzORoQ5Gj4IfhCROF";
|
||||
}
|
||||
];
|
||||
in {
|
||||
imports = builtins.map create_users users;
|
||||
}
|
||||
@@ -4,8 +4,9 @@ let
|
||||
pkgs-with-home-assistant = import (builtins.fetchGit {
|
||||
name = "new-home-assistant";
|
||||
url = "https://github.com/nixos/nixpkgs/";
|
||||
rev = "41dea55321e5a999b17033296ac05fe8a8b5a257";
|
||||
rev = "18dd725c29603f582cf1900e0d25f9f1063dbf11";
|
||||
}) {};
|
||||
networkPrefix = config.networkPrefix;
|
||||
in
|
||||
{
|
||||
users.users.hass = {
|
||||
@@ -35,8 +36,8 @@ in
|
||||
ephemeral = false;
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "10.42.97.1";
|
||||
localAddress = "10.42.97.20/24";
|
||||
hostAddress = "${networkPrefix}.97.1";
|
||||
localAddress = "${networkPrefix}.97.20/24";
|
||||
extraFlags = [
|
||||
"--capability=CAP_NET_ADMIN"
|
||||
"--capability=CAP_MKNOD"
|
||||
@@ -74,7 +75,9 @@ in
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networkPrefix = networkPrefix;
|
||||
imports = [
|
||||
../network-prefix.nix
|
||||
./3dprinter.nix
|
||||
./ac.nix
|
||||
# ./aeg.nix
|
||||
@@ -93,6 +96,7 @@ in
|
||||
./presense.nix
|
||||
./remote.nix
|
||||
./roborock.nix
|
||||
./scenes
|
||||
./scene-switch.nix
|
||||
./shelly.nix
|
||||
./sleep.nix
|
||||
@@ -103,11 +107,11 @@ in
|
||||
hostName = "home-assistant";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "10.42.96.1";
|
||||
address = "${networkPrefix}.96.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nameservers = [ "10.42.97.1" ];
|
||||
nameservers = [ "${networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
@@ -158,7 +162,7 @@ in
|
||||
};
|
||||
script = ''
|
||||
set -e
|
||||
HACS_VERSION="2.0.1" # Replace with the latest version
|
||||
HACS_VERSION="2.0.5" # Replace with the latest version
|
||||
HACS_DIR="/var/lib/hass/custom_components/hacs"
|
||||
|
||||
mkdir -p "$HACS_DIR"
|
||||
28
hosts/fw/modules/home-assistant/electricity.nix
Normal file
28
hosts/fw/modules/home-assistant/electricity.nix
Normal file
@@ -0,0 +1,28 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
in {
|
||||
services.home-assistant.customComponents = with pkgs.home-assistant-custom-components; [
|
||||
epex_spot
|
||||
];
|
||||
|
||||
services.home-assistant.config = {
|
||||
sensor = [
|
||||
{
|
||||
platform = "template";
|
||||
sensors = {
|
||||
electricity_price = {
|
||||
friendly_name = "Current Price of electricity";
|
||||
unit_of_measurement = "EUR/kWh";
|
||||
value_template = ''
|
||||
{{ ((states('sensor.epex_spot_data_price') | float ) + (0.0149 + 0.074 + 0.007 + 0.0074 + 0.0006)) | float }}
|
||||
'';
|
||||
entity_id = [
|
||||
"sensor.epex_spot_data_price"
|
||||
"sensor.time"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -7,6 +7,13 @@
|
||||
name = "enocean_switch_pc";
|
||||
}
|
||||
];
|
||||
"binary_sensor bed_1" = [
|
||||
{
|
||||
platform = "enocean";
|
||||
id = [ 254 207 162 105 ];
|
||||
name = "enocean_switch_bed_1";
|
||||
}
|
||||
];
|
||||
sensor = [
|
||||
{
|
||||
name = "Bathroom HT";
|
||||
@@ -71,6 +71,21 @@
|
||||
action = [
|
||||
{
|
||||
choose = [
|
||||
{
|
||||
conditions = [ "{{ is_state('automation.light_sunset', 'off') }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 100;
|
||||
color_temp = 250;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.toilet_lights' }}" ];
|
||||
sequence = [
|
||||
@@ -125,8 +140,8 @@
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 20;
|
||||
rgbw_color = [ 255 126 0 255 ];
|
||||
brightness_pct = 30;
|
||||
color_temp = 450;
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -259,46 +274,88 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
"automation bed_button_1" = {
|
||||
alias = "bed_button_1";
|
||||
trigger = {
|
||||
platform = "event";
|
||||
event_type = "shelly.click";
|
||||
event_data = {
|
||||
device = "shellybutton1-E8DB84AA196D";
|
||||
};
|
||||
};
|
||||
"automation bedroom light" = {
|
||||
alias = "bedroom light";
|
||||
trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "button_pressed";
|
||||
event_data = {
|
||||
id = [ 254 207 162 105 ];
|
||||
which = 1;
|
||||
onoff = 1;
|
||||
pushed = 1;
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "shelly.click";
|
||||
event_data = {
|
||||
device = "shellybutton1-E8DB84AA136D";
|
||||
click_type = "double";
|
||||
};
|
||||
}
|
||||
];
|
||||
action = [
|
||||
{
|
||||
choose = [
|
||||
{
|
||||
conditions = [ "{{ trigger.event.data.click_type == \"single\" }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
entity_id = "light.bed_reading_1";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ trigger.event.data.click_type == \"double\" }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
entity_id = "light.bedroom_lights";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ trigger.event.data.click_type == \"triple\" }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
entity_id = "light.bedroom_bed";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
service = "light.toggle";
|
||||
target = {
|
||||
entity_id = "light.bedroom_lights";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bed light" = {
|
||||
alias = "bed light";
|
||||
trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "button_pressed";
|
||||
event_data = {
|
||||
id = [ 254 207 162 105 ];
|
||||
which = 0;
|
||||
onoff = 1;
|
||||
pushed = 1;
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "shelly.click";
|
||||
event_data = {
|
||||
device = "shellybutton1-E8DB84AA136D";
|
||||
click_type = "triple";
|
||||
};
|
||||
}
|
||||
];
|
||||
action = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
target = {
|
||||
entity_id = "light.bedroom_bed";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation reading 1 light" = {
|
||||
alias = "reading 1 light";
|
||||
trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "button_pressed";
|
||||
event_data = {
|
||||
id = [ 254 207 162 105 ];
|
||||
which = 0;
|
||||
onoff = 0;
|
||||
pushed = 1;
|
||||
};
|
||||
}
|
||||
];
|
||||
action = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
target = {
|
||||
entity_id = "light.bed_reading_1";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
@@ -323,24 +380,6 @@
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ trigger.event.data.click_type == \"double\" }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
entity_id = "light.bedroom_lights";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ trigger.event.data.click_type == \"triple\" }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
entity_id = "light.bedroom_bed";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
@@ -357,12 +396,13 @@
|
||||
all = true;
|
||||
entities = [
|
||||
"light.livingroom_switch"
|
||||
"light.living_room_bulb_1"
|
||||
"light.living_room_bulb_2"
|
||||
"light.living_room_bulb_3"
|
||||
"light.living_room_bulb_4"
|
||||
"light.living_room_bulb_5"
|
||||
"light.living_room_bulb_6"
|
||||
"light.living_bulb_1"
|
||||
"light.living_bulb_2"
|
||||
"light.living_bulb_3"
|
||||
"light.living_bulb_4"
|
||||
"light.living_bulb_5"
|
||||
"light.living_bulb_6"
|
||||
# "light.living_room"
|
||||
];
|
||||
}
|
||||
{
|
||||
@@ -376,6 +416,7 @@
|
||||
all = true;
|
||||
entities = [
|
||||
"light.kitchen_switch"
|
||||
"light.kitchen_bulb_1"
|
||||
"light.kitchen"
|
||||
];
|
||||
}
|
||||
@@ -406,21 +447,62 @@
|
||||
"light.bathroom_bulb_2"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "switch";
|
||||
name = "Hallway Switch";
|
||||
entity_id = "switch.hallway";
|
||||
}
|
||||
{
|
||||
platform = "group";
|
||||
name = "Hallway Lights";
|
||||
all = true;
|
||||
entities = [
|
||||
"light.hallway_switch"
|
||||
"light.hallway_light_switch_mini_switch"
|
||||
"light.hallway_bulb_1"
|
||||
"light.hallway_bulb_2"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "template";
|
||||
lights = {
|
||||
hallway_group_proxy = {
|
||||
friendly_name = "Hallway Lights (Proxy)";
|
||||
# follow the real group’s on/off state
|
||||
value_template = "{{ is_state('light.hallway_lights','on') }}";
|
||||
turn_on = {
|
||||
service = "light.turn_on";
|
||||
data = { entity_id = "light.hallway_lights"; };
|
||||
};
|
||||
turn_off = {
|
||||
service = "light.turn_off";
|
||||
data = { entity_id = "light.hallway_lights"; };
|
||||
};
|
||||
# brightness support
|
||||
set_level = {
|
||||
service = "light.turn_on";
|
||||
data_template = {
|
||||
entity_id = "light.hallway_lights";
|
||||
brightness = "{{ brightness }}";
|
||||
};
|
||||
};
|
||||
# color temperature support (if you have CT-capable bulbs)
|
||||
set_temperature = {
|
||||
service = "light.turn_on";
|
||||
data_template = {
|
||||
entity_id = "light.hallway_lights";
|
||||
color_temp = "{{ color_temp }}";
|
||||
};
|
||||
};
|
||||
# RGB color support
|
||||
set_color = {
|
||||
service = "light.turn_on";
|
||||
data_template = {
|
||||
entity_id = "light.hallway_lights";
|
||||
rgb_color = [ "{{ red }}" "{{ green }}" "{{ blue }}" ];
|
||||
};
|
||||
};
|
||||
# always report as “available”
|
||||
availability_template = "true";
|
||||
# declare which color modes you need
|
||||
supported_color_modes = [ "brightness" "color_temp" "rgb" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "switch";
|
||||
name = "Toilet Switch";
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
let
|
||||
devices = [
|
||||
"device_tracker.dominiks_iphone"
|
||||
"device_tracker.dominiks_mp01"
|
||||
];
|
||||
in {
|
||||
services.home-assistant.extraComponents = [
|
||||
"nuki"
|
||||
];
|
||||
@@ -9,9 +14,7 @@
|
||||
mode = "restart";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = [
|
||||
"device_tracker.dominiks_iphone"
|
||||
];
|
||||
entity_id = devices;
|
||||
from = "not_home";
|
||||
to = "home";
|
||||
};
|
||||
@@ -76,7 +76,7 @@
|
||||
{
|
||||
service = "automation.turn_off";
|
||||
target = {
|
||||
entity_id = "automation.all_multimedia_off"; # Replace with your target automation ID
|
||||
entity_id = "automation.all_multimedia_off";
|
||||
};
|
||||
}
|
||||
{
|
||||
@@ -115,7 +115,7 @@
|
||||
{
|
||||
service = "automation.turn_on";
|
||||
target = {
|
||||
entity_id = "automation.all_multimedia_off"; # Replace with your target automation ID
|
||||
entity_id = "automation.all_multimedia_off";
|
||||
};
|
||||
}
|
||||
];
|
||||
@@ -246,6 +246,7 @@
|
||||
platform = "state";
|
||||
entity_id = "binary_sensor.multimedia_device_on";
|
||||
to = "off";
|
||||
for = "00:00:30";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
@@ -289,13 +290,6 @@
|
||||
command = "b64:JgDaAAABKZMUERMSExITEhMSExETEhMSExITEhMSExETNxQ2ExITEhMSEzcTNxM3ExITEhM3ExITNxMSEhITEhM3EzcTEhM3EwAFyAABKJQUERMSEhITEhMSExITEhMSEhITEhMSExITNxM3ExITEhMREzcTNxQ3EhITEhM3ExITNxMSExITEhM3EzcTEhM3EwAFyAABKJQUERMSExETEhMSExITEhMSExETEhMSExITNxM3ExITEhMREzcTOBI4ExETEhM3ExITNxMSExITEhM3EzcTEhM3E5IGAA0FAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
}
|
||||
# turn off tv switch
|
||||
{
|
||||
service = "switch.turn_off";
|
||||
target = {
|
||||
entity_id = "switch.tv_switch";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation all_multimedia_on" = {
|
||||
@@ -490,6 +484,12 @@
|
||||
entity_id = "script.turn_on_tv";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "media_player.turn_off";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user