From d6600630bc41f51969ae07865a18498630d59e51 Mon Sep 17 00:00:00 2001 From: Menno van Leeuwen Date: Tue, 22 Jul 2025 23:26:31 +0200 Subject: [PATCH] Remove cloud server configuration files and references and add dynmamic dns Shit --- config/ansible/docs/dynamic-dns-setup.md | 124 +++ config/ansible/inventory.ini | 1 - config/ansible/playbook.yml | 2 +- config/ansible/tasks/global/symlinks.yml | 67 +- .../tasks/global/utils/dynamic-dns-cf.go | 903 ++++++++++++++++++ config/ansible/tasks/servers/dynamic-dns.yml | 99 ++ config/ansible/tasks/servers/juicefs.yml | 2 +- config/ansible/tasks/servers/server.yml | 9 +- .../tasks/servers/services/caddy/Caddyfile.j2 | 10 +- .../services/redis/docker-compose.yml.j2 | 2 +- .../uptime-kuma/docker-compose.yml.j2 | 22 - .../services/uptime-kuma/uptime-kuma.yml | 31 - .../ansible/tasks/workstations/flatpaks.yml | 3 - config/ansible/templates/dynamic-dns.env.j2 | 12 + config/ansible/templates/juicefs.service.j2 | 2 +- config/home-manager/flake.nix | 14 - .../common/hosts/mennos-cloud-server.nix | 4 - .../home-manager/packages/common/packages.nix | 2 - 18 files changed, 1184 insertions(+), 125 deletions(-) create mode 100644 config/ansible/docs/dynamic-dns-setup.md create mode 100644 config/ansible/tasks/global/utils/dynamic-dns-cf.go create mode 100644 config/ansible/tasks/servers/dynamic-dns.yml delete mode 100644 config/ansible/tasks/servers/services/uptime-kuma/docker-compose.yml.j2 delete mode 100644 config/ansible/tasks/servers/services/uptime-kuma/uptime-kuma.yml create mode 100644 config/ansible/templates/dynamic-dns.env.j2 delete mode 100644 config/home-manager/packages/common/hosts/mennos-cloud-server.nix diff --git a/config/ansible/docs/dynamic-dns-setup.md b/config/ansible/docs/dynamic-dns-setup.md new file mode 100644 index 0000000..1d84cd5 --- /dev/null +++ b/config/ansible/docs/dynamic-dns-setup.md @@ -0,0 +1,124 @@ +# Dynamic DNS OnePassword Setup + +This document explains how to set up the required OnePassword entries for the Dynamic DNS automation. + +## Overview + +The Dynamic DNS task automatically retrieves credentials from OnePassword using the Ansible OnePassword lookup plugin. This eliminates the need for vault files and provides better security. + +## Required OnePassword Entries + +### 1. CloudFlare API Token + +**Location:** `CloudFlare API Token` in `Dotfiles` vault, field `password` + +**Setup Steps:** + +1. Go to [CloudFlare API Tokens](https://dash.cloudflare.com/profile/api-tokens) +2. Click "Create Token" +3. Use the "Edit zone DNS" template +4. Configure permissions: + - Zone: DNS: Edit + - Zone Resources: Include all zones (or specific zones for your domains) +5. Add IP address filtering if desired (optional but recommended) +6. Click "Continue to summary" and "Create Token" +7. Copy the token and save it in OnePassword: + - Title: `CloudFlare API Token` + - Vault: `Dotfiles` + - Field: `password` (this should be the main password field) + +### 2. Telegram Bot Credentials + +**Location:** `Telegram DynDNS Bot` in `Dotfiles` vault, fields `password` and `chat_id` + +**Setup Steps:** + +#### Create Telegram Bot: + +1. Message [@BotFather](https://t.me/BotFather) on Telegram +2. Send `/start` then `/newbot` +3. Follow the prompts to create your bot +4. Save the bot token (format: `123456789:ABCdefGHijklMNopQRstUVwxyz`) + +#### Get Chat ID: + +1. Send any message to your new bot +2. Visit: `https://api.telegram.org/bot/getUpdates` +3. Look for `"chat":{"id":YOUR_CHAT_ID}` in the response +4. Save the chat ID (format: `987654321` or `-987654321` for groups) + +#### Save in OnePassword: + +- Title: `Telegram DynDNS Bot` +- Vault: `Dotfiles` +- Fields: + - `password`: Your bot token (123456789:ABCdefGHijklMNopQRstUVwxyz) + - `chat_id`: Your chat ID (987654321) + +## Verification + +You can test that the OnePassword lookups work by running: + +```bash +# Test CloudFlare token lookup +ansible localhost -m debug -a "msg={{ lookup('community.general.onepassword', 'CloudFlare API Token', vault='Dotfiles', field='password') }}" + +# Test Telegram bot token +ansible localhost -m debug -a "msg={{ lookup('community.general.onepassword', 'Telegram DynDNS Bot', vault='Dotfiles', field='password') }}" + +# Test Telegram chat ID +ansible localhost -m debug -a "msg={{ lookup('community.general.onepassword', 'Telegram DynDNS Bot', vault='Dotfiles', field='chat_id') }}" +``` + +## Security Notes + +- Credentials are never stored in version control +- Environment file (`~/.local/bin/dynamic-dns.env`) has 600 permissions +- OnePassword CLI must be authenticated before running Ansible +- Make sure to run `op signin` before executing the playbook + +## Troubleshooting + +### OnePassword CLI Not Authenticated + +```bash +op signin +``` + +### Missing Fields in OnePassword + +Ensure the exact field names match: + +- CloudFlare: field must be named `password` +- Telegram: fields must be named `password` and `chat_id` + +### Invalid CloudFlare Token + +- Check token has `Zone:DNS:Edit` permissions +- Verify token is active in CloudFlare dashboard +- Test with: `curl -H "Authorization: Bearer YOUR_TOKEN" https://api.cloudflare.com/client/v4/user/tokens/verify` + +### Telegram Not Working + +- Ensure you've sent at least one message to your bot +- Verify chat ID format (numbers only, may start with -) +- Test with: `go run dynamic-dns-cf.go --test-telegram` + +## Usage + +Once set up, the dynamic DNS will automatically: + +- Update DNS records every 15 minutes +- Send Telegram notifications when IP changes +- Log all activity to system journal (`journalctl -t dynamic-dns`) + +## Domains Configured + +The automation updates these domains: + +- `vleeuwen.me` +- `mvl.sh` +- `mennovanleeuwen.nl` + +To modify the domain list, edit the wrapper script at: +`~/.local/bin/dynamic-dns-update.sh` diff --git a/config/ansible/inventory.ini b/config/ansible/inventory.ini index 854b072..27fcf41 100644 --- a/config/ansible/inventory.ini +++ b/config/ansible/inventory.ini @@ -6,6 +6,5 @@ mennos-cachyos-desktop ansible_connection=local [servers] mennos-server ansible_connection=local -mennos-cloud-server ansible_connection=local mennos-vm ansible_connection=local mennos-cachyos-desktop ansible_connection=local diff --git a/config/ansible/playbook.yml b/config/ansible/playbook.yml index cbaee65..ebe910e 100644 --- a/config/ansible/playbook.yml +++ b/config/ansible/playbook.yml @@ -16,4 +16,4 @@ - name: Include server tasks ansible.builtin.import_tasks: tasks/servers/server.yml - when: inventory_hostname in ['mennos-server', 'mennos-cloud-server', 'mennos-hobbypc', 'mennos-vm', 'mennos-cachyos-desktop'] + when: inventory_hostname in ['mennos-server', 'mennos-hobbypc', 'mennos-vm', 'mennos-cachyos-desktop'] diff --git a/config/ansible/tasks/global/symlinks.yml b/config/ansible/tasks/global/symlinks.yml index bc66d3f..a061322 100644 --- a/config/ansible/tasks/global/symlinks.yml +++ b/config/ansible/tasks/global/symlinks.yml @@ -1,38 +1,43 @@ --- - name: Server setup block: - - name: Set user home directory - ansible.builtin.set_fact: - user_home: "{{ ansible_env.HOME if ansible_user_id == 'root' else lookup('env', 'HOME') }}" + - name: Set user home directory + ansible.builtin.set_fact: + user_home: "{{ ansible_env.HOME if ansible_user_id == 'root' else lookup('env', 'HOME') }}" - - name: Create basic symlinks - ansible.builtin.file: - src: "{{ item.src | replace('~', user_home) | replace('$DOTFILES_PATH', lookup('env', 'DOTFILES_PATH')) }}" - dest: "{{ item.dest | replace('~', user_home) }}" - state: link - force: true - follow: false - loop: - - { src: "$DOTFILES_PATH/config/home-manager", dest: "~/.config/home-manager" } - - { src: "$DOTFILES_PATH/config/ssh/config", dest: "~/.ssh/config" } - - { src: "$DOTFILES_PATH/config/starship.toml", dest: "~/.config/starship.toml" } - - { src: "$DOTFILES_PATH/.bashrc", dest: "~/.bashrc.extra" } + - name: Create basic symlinks + ansible.builtin.file: + src: "{{ item.src | replace('~', user_home) | replace('$DOTFILES_PATH', lookup('env', 'DOTFILES_PATH')) }}" + dest: "{{ item.dest | replace('~', user_home) }}" + state: link + force: true + follow: false + loop: + - { + src: "$DOTFILES_PATH/config/home-manager", + dest: "~/.config/home-manager", + } + - { src: "$DOTFILES_PATH/config/ssh/config", dest: "~/.ssh/config" } + - { + src: "$DOTFILES_PATH/config/starship.toml", + dest: "~/.config/starship.toml", + } + - { src: "$DOTFILES_PATH/.bashrc", dest: "~/.bashrc.extra" } - - name: Create gitconfig symlink - ansible.builtin.file: - src: "{{ gitconfig_mapping[inventory_hostname] | replace('~', user_home) | replace('$DOTFILES_PATH', lookup('env', 'DOTFILES_PATH')) }}" - dest: "{{ user_home }}/.gitconfig" - state: link - force: true - follow: false - vars: - gitconfig_mapping: - mennos-desktop: "$DOTFILES_PATH/config/git/gitconfig.wsl" - mennos-cachyos-desktop: "$DOTFILES_PATH/config/git/gitconfig.linux" - mennos-cachyos-laptop: "$DOTFILES_PATH/config/git/gitconfig.linux" - mennos-laptop-w: "$DOTFILES_PATH/config/git/gitconfig.wsl" - mennos-server: "$DOTFILES_PATH/config/git/gitconfig.mennos-server" - mennos-cloud-server: "$DOTFILES_PATH/config/git/gitconfig.mennos-server" - mennos-vm: "$DOTFILES_PATH/config/git/gitconfig.mennos-server" + - name: Create gitconfig symlink + ansible.builtin.file: + src: "{{ gitconfig_mapping[inventory_hostname] | replace('~', user_home) | replace('$DOTFILES_PATH', lookup('env', 'DOTFILES_PATH')) }}" + dest: "{{ user_home }}/.gitconfig" + state: link + force: true + follow: false + vars: + gitconfig_mapping: + mennos-desktop: "$DOTFILES_PATH/config/git/gitconfig.wsl" + mennos-cachyos-desktop: "$DOTFILES_PATH/config/git/gitconfig.linux" + mennos-cachyos-laptop: "$DOTFILES_PATH/config/git/gitconfig.linux" + mennos-laptop-w: "$DOTFILES_PATH/config/git/gitconfig.wsl" + mennos-server: "$DOTFILES_PATH/config/git/gitconfig.mennos-server" + mennos-vm: "$DOTFILES_PATH/config/git/gitconfig.mennos-server" tags: - symlinks diff --git a/config/ansible/tasks/global/utils/dynamic-dns-cf.go b/config/ansible/tasks/global/utils/dynamic-dns-cf.go new file mode 100644 index 0000000..fa69af6 --- /dev/null +++ b/config/ansible/tasks/global/utils/dynamic-dns-cf.go @@ -0,0 +1,903 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" +) + +// CloudFlare API structures +type CloudFlareResponse struct { + Success bool `json:"success"` + Errors []CloudFlareError `json:"errors"` + Result json.RawMessage `json:"result"` + Messages []CloudFlareMessage `json:"messages"` +} + +type CloudFlareError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type CloudFlareMessage struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type DNSRecord struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Content string `json:"content"` + TTL int `json:"ttl"` + ZoneID string `json:"zone_id"` +} + +type Zone struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type TokenVerification struct { + ID string `json:"id"` + Status string `json:"status"` +} + +type NotificationInfo struct { + RecordName string + OldIP string + NewIP string + IsNew bool +} + +// Configuration +type Config struct { + APIToken string + RecordNames []string + IPSources []string + DryRun bool + Verbose bool + Force bool + TTL int + TelegramBotToken string + TelegramChatID string + Client *http.Client +} + +// Default IP sources +var defaultIPSources = []string{ + "https://ifconfig.co/ip", + "https://ip.seeip.org", + "https://ipv4.icanhazip.com", + "https://api.ipify.org", +} + +func main() { + config := &Config{ + Client: &http.Client{Timeout: 10 * time.Second}, + } + + // Command line flags + var ipSourcesFlag string + var recordsFlag string + var listZones bool + var testTelegram bool + flag.StringVar(&recordsFlag, "record", "", "DNS A record name(s) to update - comma-separated for multiple (required)") + flag.StringVar(&ipSourcesFlag, "ip-sources", "", "Comma-separated list of IP detection services (optional)") + flag.BoolVar(&config.DryRun, "dry-run", false, "Show what would be done without making changes") + flag.BoolVar(&config.Verbose, "verbose", false, "Enable verbose logging") + flag.BoolVar(&listZones, "list-zones", false, "List all accessible zones and exit") + flag.BoolVar(&config.Force, "force", false, "Force update even if IP hasn't changed") + flag.BoolVar(&testTelegram, "test-telegram", false, "Send a test Telegram notification and exit") + flag.IntVar(&config.TTL, "ttl", 300, "TTL for DNS record in seconds") + + // Custom usage function + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "CloudFlare Dynamic DNS Tool\n\n") + fmt.Fprintf(os.Stderr, "Updates CloudFlare DNS A records with your current public IP address.\n") + fmt.Fprintf(os.Stderr, "Supports multiple records, dry-run mode, and Telegram notifications.\n\n") + + fmt.Fprintf(os.Stderr, "USAGE:\n") + fmt.Fprintf(os.Stderr, " %s [OPTIONS]\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, "REQUIRED ENVIRONMENT VARIABLES:\n") + fmt.Fprintf(os.Stderr, " CLOUDFLARE_API_TOKEN CloudFlare API token with Zone:DNS:Edit permissions\n") + fmt.Fprintf(os.Stderr, " Get from: https://dash.cloudflare.com/profile/api-tokens\n\n") + + fmt.Fprintf(os.Stderr, "OPTIONAL ENVIRONMENT VARIABLES:\n") + fmt.Fprintf(os.Stderr, " TELEGRAM_BOT_TOKEN Telegram bot token for notifications\n") + fmt.Fprintf(os.Stderr, " TELEGRAM_CHAT_ID Telegram chat ID to send notifications to\n\n") + + fmt.Fprintf(os.Stderr, "OPTIONS:\n") + flag.PrintDefaults() + + fmt.Fprintf(os.Stderr, "\nEXAMPLES:\n") + fmt.Fprintf(os.Stderr, " # Update single record\n") + fmt.Fprintf(os.Stderr, " %s -record home.example.com\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, " # Update multiple records\n") + fmt.Fprintf(os.Stderr, " %s -record \"home.example.com,api.example.com,vpn.mydomain.net\"\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, " # Dry run with verbose output\n") + fmt.Fprintf(os.Stderr, " %s -dry-run -verbose -record home.example.com\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, " # Force update even if IP hasn't changed\n") + fmt.Fprintf(os.Stderr, " %s -force -record home.example.com\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, " # Custom TTL and IP sources\n") + fmt.Fprintf(os.Stderr, " %s -record home.example.com -ttl 600 -ip-sources \"https://ifconfig.co/ip,https://api.ipify.org\"\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, " # List accessible CloudFlare zones\n") + fmt.Fprintf(os.Stderr, " %s -list-zones\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, " # Test Telegram notifications\n") + fmt.Fprintf(os.Stderr, " %s -test-telegram\n\n", os.Args[0]) + + fmt.Fprintf(os.Stderr, "SETUP:\n") + fmt.Fprintf(os.Stderr, " 1. Create CloudFlare API token:\n") + fmt.Fprintf(os.Stderr, " - Go to https://dash.cloudflare.com/profile/api-tokens\n") + fmt.Fprintf(os.Stderr, " - Use 'Edit zone DNS' template\n") + fmt.Fprintf(os.Stderr, " - Select your zones\n") + fmt.Fprintf(os.Stderr, " - Copy token and set CLOUDFLARE_API_TOKEN environment variable\n\n") + + fmt.Fprintf(os.Stderr, " 2. Optional: Setup Telegram notifications:\n") + fmt.Fprintf(os.Stderr, " - Message @BotFather on Telegram to create a bot\n") + fmt.Fprintf(os.Stderr, " - Get your chat ID by messaging your bot, then visit:\n") + fmt.Fprintf(os.Stderr, " https://api.telegram.org/bot/getUpdates\n") + fmt.Fprintf(os.Stderr, " - Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID environment variables\n\n") + + fmt.Fprintf(os.Stderr, "NOTES:\n") + fmt.Fprintf(os.Stderr, " - Records can be in different CloudFlare zones\n") + fmt.Fprintf(os.Stderr, " - Only updates when IP actually changes (unless -force is used)\n") + fmt.Fprintf(os.Stderr, " - Supports both root domains and subdomains\n") + fmt.Fprintf(os.Stderr, " - Telegram notifications sent only when IP changes\n") + fmt.Fprintf(os.Stderr, " - Use -dry-run to test without making changes\n\n") + } + + flag.Parse() + + // Validate required arguments (unless listing zones or testing telegram) + if recordsFlag == "" && !listZones && !testTelegram { + fmt.Fprintf(os.Stderr, "Error: -record flag is required\n") + flag.Usage() + os.Exit(1) + } + + // Parse record names + if recordsFlag != "" { + config.RecordNames = strings.Split(recordsFlag, ",") + // Trim whitespace from each record name + for i, record := range config.RecordNames { + config.RecordNames[i] = strings.TrimSpace(record) + } + } + + // Get API token from environment + config.APIToken = os.Getenv("CLOUDFLARE_API_TOKEN") + if config.APIToken == "" { + fmt.Fprintf(os.Stderr, "Error: CLOUDFLARE_API_TOKEN environment variable is required\n") + fmt.Fprintf(os.Stderr, "Get your API token from: https://dash.cloudflare.com/profile/api-tokens\n") + fmt.Fprintf(os.Stderr, "Create a token with 'Zone:DNS:Edit' permissions for your zone\n") + os.Exit(1) + } + + // Get optional Telegram credentials + config.TelegramBotToken = os.Getenv("TELEGRAM_BOT_TOKEN") + config.TelegramChatID = os.Getenv("TELEGRAM_CHAT_ID") + + if config.Verbose && config.TelegramBotToken != "" && config.TelegramChatID != "" { + fmt.Println("Telegram notifications enabled") + } + + // Parse IP sources + if ipSourcesFlag != "" { + config.IPSources = strings.Split(ipSourcesFlag, ",") + } else { + config.IPSources = defaultIPSources + } + + if config.Verbose { + fmt.Printf("Config: Records=%v, TTL=%d, DryRun=%v, Force=%v, IPSources=%v\n", + config.RecordNames, config.TTL, config.DryRun, config.Force, config.IPSources) + } + + // If testing telegram, do that and exit (skip API token validation) + if testTelegram { + if err := testTelegramNotification(config); err != nil { + fmt.Fprintf(os.Stderr, "Error testing Telegram: %v\n", err) + os.Exit(1) + } + return + } + + // Validate API token + if err := validateToken(config); err != nil { + fmt.Fprintf(os.Stderr, "Error validating API token: %v\n", err) + os.Exit(1) + } + + if config.Verbose { + fmt.Println("API token validated successfully") + } + + // If listing zones, do that and exit + if listZones { + if err := listAllZones(config); err != nil { + fmt.Fprintf(os.Stderr, "Error listing zones: %v\n", err) + os.Exit(1) + } + return + } + + // Get current public IP + currentIP, err := getCurrentIP(config) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting current IP: %v\n", err) + os.Exit(1) + } + + if config.Verbose { + fmt.Printf("Current public IP: %s\n", currentIP) + fmt.Printf("Processing %d record(s)\n", len(config.RecordNames)) + } + + // Process each record + var totalUpdates int + var allNotifications []NotificationInfo + + for _, recordName := range config.RecordNames { + if config.Verbose { + fmt.Printf("\n--- Processing record: %s ---\n", recordName) + } + + // Find the zone for the record + zoneName, zoneID, err := findZoneForRecord(config, recordName) + if err != nil { + fmt.Fprintf(os.Stderr, "Error finding zone for %s: %v\n", recordName, err) + continue + } + + if config.Verbose { + fmt.Printf("Found zone: %s (ID: %s)\n", zoneName, zoneID) + } + + // Find existing DNS record + record, err := findDNSRecordByName(config, zoneID, recordName) + if err != nil { + fmt.Fprintf(os.Stderr, "Error finding DNS record %s: %v\n", recordName, err) + continue + } + + // Compare IPs + if record != nil { + if record.Content == currentIP && !config.Force { + fmt.Printf("DNS record %s already points to %s - no update needed\n", recordName, currentIP) + continue + } + + if config.Verbose { + if record.Content == currentIP { + fmt.Printf("DNS record %s already points to %s, but forcing update\n", + recordName, currentIP) + } else { + fmt.Printf("DNS record %s currently points to %s, needs update to %s\n", + recordName, record.Content, currentIP) + } + } + } else { + if config.Verbose { + fmt.Printf("DNS record %s does not exist, will create it\n", recordName) + } + } + + // Update or create record + if config.DryRun { + if record != nil { + if record.Content == currentIP && config.Force { + fmt.Printf("DRY RUN: Would force update DNS record %s (already %s)\n", + recordName, currentIP) + } else { + fmt.Printf("DRY RUN: Would update DNS record %s from %s to %s\n", + recordName, record.Content, currentIP) + } + } else { + fmt.Printf("DRY RUN: Would create DNS record %s with IP %s\n", + recordName, currentIP) + } + + // Collect notification info for dry-run + if record == nil || record.Content != currentIP || config.Force { + var oldIPForNotification string + if record != nil { + oldIPForNotification = record.Content + } + allNotifications = append(allNotifications, NotificationInfo{ + RecordName: recordName, + OldIP: oldIPForNotification, + NewIP: currentIP, + IsNew: record == nil, + }) + } + continue + } + + var wasUpdated bool + var oldIP string + + if record != nil { + oldIP = record.Content + err = updateDNSRecordByName(config, zoneID, record.ID, recordName, currentIP) + if err != nil { + fmt.Fprintf(os.Stderr, "Error updating DNS record %s: %v\n", recordName, err) + continue + } + fmt.Printf("Successfully updated DNS record %s to %s\n", recordName, currentIP) + wasUpdated = true + } else { + err = createDNSRecordByName(config, zoneID, recordName, currentIP) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating DNS record %s: %v\n", recordName, err) + continue + } + fmt.Printf("Successfully created DNS record %s with IP %s\n", recordName, currentIP) + wasUpdated = true + } + + // Collect notification info for actual updates + if wasUpdated && (record == nil || oldIP != currentIP || config.Force) { + allNotifications = append(allNotifications, NotificationInfo{ + RecordName: recordName, + OldIP: oldIP, + NewIP: currentIP, + IsNew: record == nil, + }) + totalUpdates++ + } + } + + // Send batch notification if there were any changes + if len(allNotifications) > 0 { + sendBatchTelegramNotification(config, allNotifications, config.DryRun) + } + + if !config.DryRun && config.Verbose { + fmt.Printf("\nProcessed %d record(s), %d update(s) made\n", len(config.RecordNames), totalUpdates) + } +} + +func validateToken(config *Config) error { + req, err := http.NewRequest("GET", "https://api.cloudflare.com/client/v4/user/tokens/verify", nil) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+config.APIToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var cfResp CloudFlareResponse + if err := json.NewDecoder(resp.Body).Decode(&cfResp); err != nil { + return err + } + + if !cfResp.Success { + return fmt.Errorf("token validation failed: %v", cfResp.Errors) + } + + var tokenInfo TokenVerification + if err := json.Unmarshal(cfResp.Result, &tokenInfo); err != nil { + return err + } + + if tokenInfo.Status != "active" { + return fmt.Errorf("token is not active, status: %s", tokenInfo.Status) + } + + return nil +} + +func getCurrentIP(config *Config) (string, error) { + var lastError error + + for _, source := range config.IPSources { + if config.Verbose { + fmt.Printf("Trying IP source: %s\n", source) + } + + resp, err := config.Client.Get(source) + if err != nil { + lastError = err + if config.Verbose { + fmt.Printf("Failed to get IP from %s: %v\n", source, err) + } + continue + } + + body, err := io.ReadAll(resp.Body) + resp.Body.Close() + + if err != nil { + lastError = err + continue + } + + if resp.StatusCode != 200 { + lastError = fmt.Errorf("HTTP %d from %s", resp.StatusCode, source) + continue + } + + ip := strings.TrimSpace(string(body)) + if ip != "" { + return ip, nil + } + + lastError = fmt.Errorf("empty response from %s", source) + } + + return "", fmt.Errorf("failed to get IP from any source, last error: %v", lastError) +} + +func findZoneForRecord(config *Config, recordName string) (string, string, error) { + // Extract domain from record name (e.g., "sub.example.com" -> try "example.com", "com") + parts := strings.Split(recordName, ".") + + if config.Verbose { + fmt.Printf("Finding zone for record: %s\n", recordName) + } + + for i := 0; i < len(parts); i++ { + zoneName := strings.Join(parts[i:], ".") + + + + req, err := http.NewRequest("GET", + fmt.Sprintf("https://api.cloudflare.com/client/v4/zones?name=%s", zoneName), nil) + if err != nil { + continue + } + + req.Header.Set("Authorization", "Bearer "+config.APIToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + continue + } + + var cfResp CloudFlareResponse + err = json.NewDecoder(resp.Body).Decode(&cfResp) + resp.Body.Close() + + if err != nil || !cfResp.Success { + continue + } + + var zones []Zone + if err := json.Unmarshal(cfResp.Result, &zones); err != nil { + continue + } + + if len(zones) > 0 { + return zones[0].Name, zones[0].ID, nil + } + } + + return "", "", fmt.Errorf("no zone found for record %s", recordName) +} + +func findDNSRecordByName(config *Config, zoneID string, recordName string) (*DNSRecord, error) { + url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records?type=A&name=%s", + zoneID, recordName) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", "Bearer "+config.APIToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var cfResp CloudFlareResponse + if err := json.NewDecoder(resp.Body).Decode(&cfResp); err != nil { + return nil, err + } + + if !cfResp.Success { + return nil, fmt.Errorf("API error: %v", cfResp.Errors) + } + + var records []DNSRecord + if err := json.Unmarshal(cfResp.Result, &records); err != nil { + return nil, err + } + + if len(records) == 0 { + return nil, nil // Record doesn't exist + } + + return &records[0], nil +} + +func updateDNSRecordByName(config *Config, zoneID, recordID, recordName, ip string) error { + data := map[string]interface{}{ + "type": "A", + "name": recordName, + "content": ip, + "ttl": config.TTL, + } + + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + + url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s", zoneID, recordID) + req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonData)) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+config.APIToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var cfResp CloudFlareResponse + if err := json.NewDecoder(resp.Body).Decode(&cfResp); err != nil { + return err + } + + if !cfResp.Success { + return fmt.Errorf("API error: %v", cfResp.Errors) + } + + return nil +} + +func createDNSRecordByName(config *Config, zoneID, recordName, ip string) error { + data := map[string]interface{}{ + "type": "A", + "name": recordName, + "content": ip, + "ttl": config.TTL, + } + + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + + url := fmt.Sprintf("https://api.cloudflare.com/client/v4/zones/%s/dns_records", zoneID) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+config.APIToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var cfResp CloudFlareResponse + if err := json.NewDecoder(resp.Body).Decode(&cfResp); err != nil { + return err + } + + if !cfResp.Success { + return fmt.Errorf("API error: %v", cfResp.Errors) + } + + return nil +} + +func listAllZones(config *Config) error { + req, err := http.NewRequest("GET", "https://api.cloudflare.com/client/v4/zones", nil) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+config.APIToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + var cfResp CloudFlareResponse + if err := json.NewDecoder(resp.Body).Decode(&cfResp); err != nil { + return err + } + + if !cfResp.Success { + return fmt.Errorf("API error: %v", cfResp.Errors) + } + + var zones []Zone + if err := json.Unmarshal(cfResp.Result, &zones); err != nil { + return err + } + + fmt.Printf("Found %d accessible zones:\n", len(zones)) + for _, zone := range zones { + fmt.Printf(" - %s (ID: %s)\n", zone.Name, zone.ID) + } + + if len(zones) == 0 { + fmt.Println("No zones found. Make sure your API token has Zone:Read permissions.") + } + + return nil +} + +func sendTelegramNotification(config *Config, record *DNSRecord, oldIP, newIP string, isDryRun bool) { + // Skip if Telegram is not configured + if config.TelegramBotToken == "" || config.TelegramChatID == "" { + return + } + + var message string + dryRunPrefix := "" + if isDryRun { + dryRunPrefix = "๐Ÿงช DRY RUN - " + } + + if record == nil { + message = fmt.Sprintf("%s๐Ÿ†• DNS Record Created\n\n"+ + "Record: %s\n"+ + "New IP: %s\n"+ + "TTL: %d seconds", + dryRunPrefix, "test-record", newIP, config.TTL) + } else { + message = fmt.Sprintf("%s๐Ÿ”„ IP Address Changed\n\n"+ + "Record: %s\n"+ + "Old IP: %s\n"+ + "New IP: %s\n"+ + "TTL: %d seconds", + dryRunPrefix, "test-record", oldIP, newIP, config.TTL) + } + + // Prepare Telegram API request + telegramURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", config.TelegramBotToken) + + payload := map[string]interface{}{ + "chat_id": config.TelegramChatID, + "text": message, + "parse_mode": "HTML", + } + + jsonData, err := json.Marshal(payload) + if err != nil { + if config.Verbose { + fmt.Printf("Failed to marshal Telegram payload: %v\n", err) + } + return + } + + // Send notification + req, err := http.NewRequest("POST", telegramURL, bytes.NewBuffer(jsonData)) + if err != nil { + if config.Verbose { + fmt.Printf("Failed to create Telegram request: %v\n", err) + } + return + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + if config.Verbose { + fmt.Printf("Failed to send Telegram notification: %v\n", err) + } + return + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + if config.Verbose { + fmt.Println("Telegram notification sent successfully") + } + } else { + if config.Verbose { + body, _ := io.ReadAll(resp.Body) + fmt.Printf("Telegram notification failed (HTTP %d): %s\n", resp.StatusCode, string(body)) + } + } +} + +func testTelegramNotification(config *Config) error { + if config.TelegramBotToken == "" || config.TelegramChatID == "" { + return fmt.Errorf("Telegram not configured. Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID environment variables") + } + + fmt.Println("Testing Telegram notification...") + + // Send a test message + message := "๐Ÿงช Dynamic DNS Test\n\n" + + "This is a test notification from your CloudFlare Dynamic DNS tool.\n\n" + + "โœ… Telegram integration is working correctly!" + + telegramURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", config.TelegramBotToken) + + payload := map[string]interface{}{ + "chat_id": config.TelegramChatID, + "text": message, + "parse_mode": "HTML", + } + + jsonData, err := json.Marshal(payload) + if err != nil { + return fmt.Errorf("failed to marshal payload: %v", err) + } + + req, err := http.NewRequest("POST", telegramURL, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("failed to create request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + + if resp.StatusCode == 200 { + fmt.Println("โœ… Test notification sent successfully!") + if config.Verbose { + fmt.Printf("Response: %s\n", string(body)) + } + return nil + } else { + return fmt.Errorf("failed to send notification (HTTP %d): %s", resp.StatusCode, string(body)) + } +} + +func sendBatchTelegramNotification(config *Config, notifications []NotificationInfo, isDryRun bool) { + // Skip if Telegram is not configured + if config.TelegramBotToken == "" || config.TelegramChatID == "" { + return + } + + if len(notifications) == 0 { + return + } + + var message string + dryRunPrefix := "" + if isDryRun { + dryRunPrefix = "๐Ÿงช DRY RUN - " + } + + if len(notifications) == 1 { + // Single record notification + notif := notifications[0] + if notif.IsNew { + message = fmt.Sprintf("%s๐Ÿ†• DNS Record Created\n\n"+ + "Record: %s\n"+ + "New IP: %s\n"+ + "TTL: %d seconds", + dryRunPrefix, notif.RecordName, notif.NewIP, config.TTL) + } else if notif.OldIP == notif.NewIP { + message = fmt.Sprintf("%s๐Ÿ”„ DNS Record Force Updated\n\n"+ + "Record: %s\n"+ + "IP: %s (unchanged)\n"+ + "TTL: %d seconds\n"+ + "Note: Forced update requested", + dryRunPrefix, notif.RecordName, notif.NewIP, config.TTL) + } else { + message = fmt.Sprintf("%s๐Ÿ”„ IP Address Changed\n\n"+ + "Record: %s\n"+ + "Old IP: %s\n"+ + "New IP: %s\n"+ + "TTL: %d seconds", + dryRunPrefix, notif.RecordName, notif.OldIP, notif.NewIP, config.TTL) + } + } else { + // Multiple records notification + var newCount, updatedCount int + for _, notif := range notifications { + if notif.IsNew { + newCount++ + } else { + updatedCount++ + } + } + + message = fmt.Sprintf("%s๐Ÿ“‹ Multiple DNS Records Updated\n\n", dryRunPrefix) + if newCount > 0 { + message += fmt.Sprintf("๐Ÿ†• Created: %d record(s)\n", newCount) + } + if updatedCount > 0 { + message += fmt.Sprintf("๐Ÿ”„ Updated: %d record(s)\n", updatedCount) + } + message += fmt.Sprintf("\nNew IP: %s\nTTL: %d seconds\n\nRecords:", notifications[0].NewIP, config.TTL) + + for _, notif := range notifications { + if notif.IsNew { + message += fmt.Sprintf("\nโ€ข %s (new)", notif.RecordName) + } else if notif.OldIP == notif.NewIP { + message += fmt.Sprintf("\nโ€ข %s (forced)", notif.RecordName) + } else { + message += fmt.Sprintf("\nโ€ข %s (%s โ†’ %s)", notif.RecordName, notif.OldIP, notif.NewIP) + } + } + } + + // Send the notification using the same logic as single notifications + telegramURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", config.TelegramBotToken) + + payload := map[string]interface{}{ + "chat_id": config.TelegramChatID, + "text": message, + "parse_mode": "HTML", + } + + jsonData, err := json.Marshal(payload) + if err != nil { + if config.Verbose { + fmt.Printf("Failed to marshal Telegram payload: %v\n", err) + } + return + } + + req, err := http.NewRequest("POST", telegramURL, bytes.NewBuffer(jsonData)) + if err != nil { + if config.Verbose { + fmt.Printf("Failed to create Telegram request: %v\n", err) + } + return + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := config.Client.Do(req) + if err != nil { + if config.Verbose { + fmt.Printf("Failed to send Telegram notification: %v\n", err) + } + return + } + defer resp.Body.Close() + + if resp.StatusCode == 200 { + if config.Verbose { + fmt.Println("Telegram notification sent successfully") + } + } else { + if config.Verbose { + body, _ := io.ReadAll(resp.Body) + fmt.Printf("Telegram notification failed (HTTP %d): %s\n", resp.StatusCode, string(body)) + } + } +} diff --git a/config/ansible/tasks/servers/dynamic-dns.yml b/config/ansible/tasks/servers/dynamic-dns.yml new file mode 100644 index 0000000..e8d55fc --- /dev/null +++ b/config/ansible/tasks/servers/dynamic-dns.yml @@ -0,0 +1,99 @@ +--- +- name: Dynamic DNS setup + block: + - name: Create environment file for dynamic DNS + ansible.builtin.template: + src: "{{ playbook_dir }}/templates/dynamic-dns.env.j2" + dest: "{{ ansible_user_dir }}/.local/bin/dynamic-dns.env" + mode: "0600" + + - name: Create dynamic DNS wrapper script + ansible.builtin.copy: + dest: "{{ ansible_user_dir }}/.local/bin/dynamic-dns-update.sh" + mode: "0755" + content: | + #!/bin/bash + + # Load environment variables + source {{ ansible_user_dir }}/.local/bin/dynamic-dns.env + + # Change to the directory containing the binary + cd {{ ansible_user_dir }}/.local/bin + + # Run dynamic DNS update (binary compiled by utils.yml) + dynamic-dns-cf -record "vleeuwen.me,mvl.sh,mennovanleeuwen.nl" 2>&1 | logger -t dynamic-dns + + - name: Setup cron job for dynamic DNS updates (fallback) + ansible.builtin.cron: + name: "Dynamic DNS Update" + minute: "*/15" + job: "{{ ansible_user_dir }}/.local/bin/dynamic-dns-update.sh" + user: "{{ ansible_user }}" + state: present + ignore_errors: true + tags: [cron] + + - name: Create systemd user directory + ansible.builtin.file: + path: "{{ ansible_user_dir }}/.config/systemd/user" + state: directory + mode: "0755" + + - name: Create dynamic DNS systemd timer + ansible.builtin.copy: + dest: "{{ ansible_user_dir }}/.config/systemd/user/dynamic-dns.timer" + mode: "0644" + content: | + [Unit] + Description=Dynamic DNS Update Timer + Requires=dynamic-dns.service + + [Timer] + OnCalendar=*:0/15 + Persistent=true + + [Install] + WantedBy=timers.target + + - name: Create dynamic DNS systemd service + ansible.builtin.copy: + dest: "{{ ansible_user_dir }}/.config/systemd/user/dynamic-dns.service" + mode: "0644" + content: | + [Unit] + Description=Dynamic DNS Update + After=network-online.target + Wants=network-online.target + + [Service] + Type=oneshot + ExecStart={{ ansible_user_dir }}/.local/bin/dynamic-dns-update.sh + EnvironmentFile={{ ansible_user_dir }}/.local/bin/dynamic-dns.env + + [Install] + WantedBy=default.target + + - name: Reload systemd user daemon + ansible.builtin.systemd: + daemon_reload: true + scope: user + + - name: Enable and start dynamic DNS timer + ansible.builtin.systemd: + name: dynamic-dns.timer + enabled: true + state: started + scope: user + + - name: Display setup completion message + ansible.builtin.debug: + msg: | + Dynamic DNS setup complete! + - Systemd timer: systemctl --user status dynamic-dns.timer + - Check logs: journalctl --user -u dynamic-dns.service -f + - Manual run: ~/.local/bin/dynamic-dns-update.sh + - Domains: vleeuwen.me, mvl.sh, mennovanleeuwen.nl + + when: inventory_hostname == 'mennos-cachyos-desktop' + tags: + - dynamic-dns diff --git a/config/ansible/tasks/servers/juicefs.yml b/config/ansible/tasks/servers/juicefs.yml index ebae982..0bf5171 100644 --- a/config/ansible/tasks/servers/juicefs.yml +++ b/config/ansible/tasks/servers/juicefs.yml @@ -70,7 +70,7 @@ - name: Include JuiceFS Redis tasks ansible.builtin.include_tasks: services/redis/redis.yml - when: inventory_hostname == 'mennos-cloud-server' + when: inventory_hostname == 'mennos-cachyos-desktop' - name: Enable and start JuiceFS service ansible.builtin.systemd: diff --git a/config/ansible/tasks/servers/server.yml b/config/ansible/tasks/servers/server.yml index 873effb..2bf7458 100644 --- a/config/ansible/tasks/servers/server.yml +++ b/config/ansible/tasks/servers/server.yml @@ -18,6 +18,11 @@ tags: - juicefs + - name: Include Dynamic DNS tasks + ansible.builtin.include_tasks: dynamic-dns.yml + tags: + - dynamic-dns + - name: System performance optimizations ansible.posix.sysctl: name: "{{ item.name }}" @@ -46,10 +51,6 @@ vars: services: - - name: uptime-kuma - enabled: true - hosts: - - mennos-cloud-server - name: gitea enabled: true hosts: diff --git a/config/ansible/tasks/servers/services/caddy/Caddyfile.j2 b/config/ansible/tasks/servers/services/caddy/Caddyfile.j2 index 10f6e42..c4bcf11 100644 --- a/config/ansible/tasks/servers/services/caddy/Caddyfile.j2 +++ b/config/ansible/tasks/servers/services/caddy/Caddyfile.j2 @@ -28,14 +28,7 @@ } {% endif %} -{% if inventory_hostname == 'mennos-cloud-server' %} -status.vleeuwen.me status.mvl.sh { - import country_block - reverse_proxy uptime-kuma:3001 - tls {{ caddy_email }} -} - -{% elif inventory_hostname == 'mennos-cachyos-desktop' %} +{% if inventory_hostname == 'mennos-cachyos-desktop' %} git.mvl.sh { import country_block reverse_proxy gitea:3000 @@ -213,5 +206,4 @@ drive.mvl.sh drive.vleeuwen.me { tls {{ caddy_email }} } - {% endif %} diff --git a/config/ansible/tasks/servers/services/redis/docker-compose.yml.j2 b/config/ansible/tasks/servers/services/redis/docker-compose.yml.j2 index 9896517..f021055 100644 --- a/config/ansible/tasks/servers/services/redis/docker-compose.yml.j2 +++ b/config/ansible/tasks/servers/services/redis/docker-compose.yml.j2 @@ -5,7 +5,7 @@ services: ports: - "6379:6379" volumes: - - /mnt/services/redis-data:/data + - /mnt/services/redis:/data command: ["redis-server", "--appendonly", "yes", "--requirepass", "{{ REDIS_PASSWORD }}"] environment: - TZ=Europe/Amsterdam diff --git a/config/ansible/tasks/servers/services/uptime-kuma/docker-compose.yml.j2 b/config/ansible/tasks/servers/services/uptime-kuma/docker-compose.yml.j2 deleted file mode 100644 index 444030a..0000000 --- a/config/ansible/tasks/servers/services/uptime-kuma/docker-compose.yml.j2 +++ /dev/null @@ -1,22 +0,0 @@ -services: - uptime-kuma: - image: louislam/uptime-kuma:latest - restart: unless-stopped - volumes: - - {{ uptime_kuma_data_dir }}:/app/data - - /var/run/docker.sock:/var/run/docker.sock:ro - environment: - - PUID=1000 - - PGID=100 - - TZ=Europe/Amsterdam - ports: - - "3001:3001" - extra_hosts: - - "host.docker.internal:host-gateway" - networks: - - caddy_network - -networks: - caddy_network: - external: true - name: caddy_default diff --git a/config/ansible/tasks/servers/services/uptime-kuma/uptime-kuma.yml b/config/ansible/tasks/servers/services/uptime-kuma/uptime-kuma.yml deleted file mode 100644 index 8616068..0000000 --- a/config/ansible/tasks/servers/services/uptime-kuma/uptime-kuma.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -- name: Deploy Uptime Kuma service - block: - - name: Set Uptime Kuma directories - ansible.builtin.set_fact: - uptime_kuma_service_dir: "{{ ansible_env.HOME }}/services/uptime-kuma" - uptime_kuma_data_dir: "/mnt/object_storage/services/uptime-kuma" - - - name: Create Uptime Kuma directory - ansible.builtin.file: - path: "{{ uptime_kuma_service_dir }}" - state: directory - mode: "0755" - - - name: Deploy Uptime Kuma docker-compose.yml - ansible.builtin.template: - src: docker-compose.yml.j2 - dest: "{{ uptime_kuma_service_dir }}/docker-compose.yml" - mode: "0644" - register: uptime_kuma_compose - - - name: Stop Uptime Kuma service if config changed - ansible.builtin.command: docker compose -f "{{ uptime_kuma_service_dir }}/docker-compose.yml" down --remove-orphans - when: uptime_kuma_compose.changed - - - name: Start Uptime Kuma service - ansible.builtin.command: docker compose -f "{{ uptime_kuma_service_dir }}/docker-compose.yml" up -d - when: uptime_kuma_compose.changed or uptime_kuma_start | default(false) | bool - tags: - - services - - uptime_kuma diff --git a/config/ansible/tasks/workstations/flatpaks.yml b/config/ansible/tasks/workstations/flatpaks.yml index d4f3e6f..1e4950f 100644 --- a/config/ansible/tasks/workstations/flatpaks.yml +++ b/config/ansible/tasks/workstations/flatpaks.yml @@ -48,10 +48,7 @@ - tv.plex.PlexDesktop # Messaging - - org.telegram.desktop - - org.signal.Signal - com.rtosta.zapzap - - io.github.equicord.equibop # Utilities - com.ranfdev.DistroShelf diff --git a/config/ansible/templates/dynamic-dns.env.j2 b/config/ansible/templates/dynamic-dns.env.j2 new file mode 100644 index 0000000..0ba01a6 --- /dev/null +++ b/config/ansible/templates/dynamic-dns.env.j2 @@ -0,0 +1,12 @@ +# Dynamic DNS Environment Configuration +# This file contains sensitive credentials and should be kept secure +# Credentials are automatically retrieved from OnePassword + +# CloudFlare API Token (required) +# Retrieved from OnePassword: CloudFlare API Token +export CLOUDFLARE_API_TOKEN="{{ lookup('community.general.onepassword', 'CloudFlare API Token', vault='Dotfiles', field='password') }}" + +# Telegram Bot Credentials (for notifications when IP changes) +# Retrieved from OnePassword: Telegram DynDNS Bot +export TELEGRAM_BOT_TOKEN="{{ lookup('community.general.onepassword', 'Telegram DynDNS Bot', vault='Dotfiles', field='password') }}" +export TELEGRAM_CHAT_ID="{{ lookup('community.general.onepassword', 'Telegram DynDNS Bot', vault='Dotfiles', field='chat_id') }}" diff --git a/config/ansible/templates/juicefs.service.j2 b/config/ansible/templates/juicefs.service.j2 index 8e7a083..d59a0a3 100644 --- a/config/ansible/templates/juicefs.service.j2 +++ b/config/ansible/templates/juicefs.service.j2 @@ -5,7 +5,7 @@ Before=docker.service [Service] Type=simple -ExecStart=/usr/local/bin/juicefs mount redis://:{{ redis_password }}@100.82.178.14:6379/0 /mnt/object_storage \ +ExecStart=/usr/local/bin/juicefs mount redis://:{{ redis_password }}@mennos-cachyos-desktop:6379/0 /mnt/object_storage \ --cache-dir=/var/jfsCache \ --buffer-size=4096 \ --prefetch=16 \ diff --git a/config/home-manager/flake.nix b/config/home-manager/flake.nix index 2e3df1c..65c46e1 100644 --- a/config/home-manager/flake.nix +++ b/config/home-manager/flake.nix @@ -89,20 +89,6 @@ }; }; - "mennos-cloud-server" = home-manager.lib.homeManagerConfiguration { - inherit pkgs; - modules = [ ./home.nix ]; - extraSpecialArgs = { - inherit - pkgs - pkgs-unstable - opnix - ; - isServer = true; - hostname = "mennos-cloud-server"; - }; - }; - "mennos-vm" = home-manager.lib.homeManagerConfiguration { inherit pkgs; modules = [ ./home.nix ]; diff --git a/config/home-manager/packages/common/hosts/mennos-cloud-server.nix b/config/home-manager/packages/common/hosts/mennos-cloud-server.nix deleted file mode 100644 index 46f1ad7..0000000 --- a/config/home-manager/packages/common/hosts/mennos-cloud-server.nix +++ /dev/null @@ -1,4 +0,0 @@ -{ pkgs-unstable, ... }: -{ - home.packages = with pkgs-unstable; [ ]; -} diff --git a/config/home-manager/packages/common/packages.nix b/config/home-manager/packages/common/packages.nix index 138dbc6..fc4e1bf 100644 --- a/config/home-manager/packages/common/packages.nix +++ b/config/home-manager/packages/common/packages.nix @@ -17,8 +17,6 @@ [ ./hosts/mennos-cachyos-laptop.nix ] else if hostname == "mennos-server" then [ ./hosts/mennos-server.nix ] - else if hostname == "mennos-cloud-server" then - [ ./hosts/mennos-cloud-server.nix ] else if hostname == "mennos-vm" then [ ./hosts/mennos-vm.nix ] else