1 c0de5a61 2020-05-17 mischa #!/usr/bin/env perl
3 fd54bb03 2023-05-05 mischa # Copyright (c) 2019-2023 Mischa Peters <mischa @ openbsd.amsterdam>
5 c0de5a61 2020-05-17 mischa # Permission to use, copy, modify, and distribute this software for any
6 c0de5a61 2020-05-17 mischa # purpose with or without fee is hereby granted, provided that the above
7 c0de5a61 2020-05-17 mischa # copyright notice and this permission notice appear in all copies.
9 c0de5a61 2020-05-17 mischa # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 c0de5a61 2020-05-17 mischa # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 c0de5a61 2020-05-17 mischa # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 c0de5a61 2020-05-17 mischa # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 c0de5a61 2020-05-17 mischa # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 c0de5a61 2020-05-17 mischa # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 c0de5a61 2020-05-17 mischa # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 c0de5a61 2020-05-17 mischa # vmm(4)/vmd(8) VM notify script for OpenBSD Amsterdam
18 c0de5a61 2020-05-17 mischa # 2020/05/17 initial release
19 b7df6b75 2021-05-09 mischa # 2021/05/09 complete restructure and KISS
20 fafc102e 2022-11-13 mischa # 2022/11/13 added price structure for notifications
21 fd54bb03 2023-05-05 mischa # 2023/05/05 variable clean up, rework IP logic
22 6be5987f 2024-01-02 mischa # 2023/12/03 price increase, +3
23 158def30 2025-01-01 mischa # 2024/09/02 long term users thanx (longterm)
24 eb044180 2025-01-01 mischa # 2024/12/23 price increase, +2 euro
26 c0de5a61 2020-05-17 mischa use 5.024;
27 c0de5a61 2020-05-17 mischa use strict;
28 c0de5a61 2020-05-17 mischa use warnings;
29 c0de5a61 2020-05-17 mischa use autodie;
30 b46b875a 2021-08-04 mischa use File::Basename;
31 fd54bb03 2023-05-05 mischa use HTTP::Tiny;
32 fd54bb03 2023-05-05 mischa use POSIX qw(strftime);
34 b7df6b75 2021-05-09 mischa # get function and function_variable (vmid) from arguments
35 c0de5a61 2020-05-17 mischa my $function = $ARGV[0] || "empty";
36 c0de5a61 2020-05-17 mischa my $function_variable = $ARGV[1] || "empty";
37 c0494dc8 2023-05-06 mischa my $message = $ARGV[2] || "empty";
39 1444d5cc 2024-05-09 mischa if ($function_variable =~ m/.txt/) {
40 1444d5cc 2024-05-09 mischa $function_variable = substr $function_variable, 0, -4;
41 1444d5cc 2024-05-09 mischa $function_variable = substr $function_variable, 4;
42 1444d5cc 2024-05-09 mischa say $function_variable;
45 fafc102e 2022-11-13 mischa # define the prices for the different components of the VM
46 158def30 2025-01-01 mischa my $base_price = '69';
47 fafc102e 2022-11-13 mischa my %memory_prices = ('2G' => '10', '4G' => '30', '8G' => '70');
48 fafc102e 2022-11-13 mischa my %hdd_prices = ('50G' => '50', '100G' => '100', '150G' => '150', '200G' => '200');
49 fd54bb03 2023-05-05 mischa my $ideal_url;
50 fd54bb03 2023-05-05 mischa my $paypal_url;
51 fd54bb03 2023-05-05 mischa my %stripe_urls = (
52 fd54bb03 2023-05-05 mischa 'sponsor' => 'SPONSORED',
53 fd54bb03 2023-05-05 mischa 'sponsored' => 'SPONSORED',
54 158def30 2025-01-01 mischa '49' => 'https://buy.stripe.com/28odR0aWK0jq0aAeV0',
55 158def30 2025-01-01 mischa '69' => 'https://buy.stripe.com/8wMaEO0i67LS1eE288',
56 158def30 2025-01-01 mischa '79' => 'https://buy.stripe.com/aEU5kuc0Ofek4qQ001',
57 158def30 2025-01-01 mischa '99' => 'https://buy.stripe.com/3cs28i6GuaY4f5ueUZ',
58 158def30 2025-01-01 mischa '119' => 'https://buy.stripe.com/14k00ae8W2ryg9y28a',
59 158def30 2025-01-01 mischa '129' => 'https://buy.stripe.com/cN2fZ81mac28g9ydQT',
60 158def30 2025-01-01 mischa '149' => 'https://buy.stripe.com/cN214ec0O1nuf5u6ow',
61 158def30 2025-01-01 mischa '279' => 'https://buy.stripe.com/28o5kuaWK6HO3mMeV8',
62 158def30 2025-01-01 mischa '339' => 'https://buy.stripe.com/9AQfZ80i6gio8H64gq',
65 c0de5a61 2020-05-17 mischa # fuction to parse _deploy.conf and vm*.txt files
66 c0de5a61 2020-05-17 mischa # all variables are stripped and added to either %vms or %conf
67 c0de5a61 2020-05-17 mischa sub get_variables {
68 c0de5a61 2020-05-17 mischa my ($hash_name, @files) = @_;
70 c0de5a61 2020-05-17 mischa my $filename;
71 c0de5a61 2020-05-17 mischa my $vm_name;
72 c0de5a61 2020-05-17 mischa my $vm_number;
74 c0de5a61 2020-05-17 mischa for my $file (@files) {
75 c0de5a61 2020-05-17 mischa # When hash is 'vms' use the vm_name as key
76 c0de5a61 2020-05-17 mischa # Otherwise use 'conf' as key
77 c0de5a61 2020-05-17 mischa if ($hash_name eq "vms") {
78 c0de5a61 2020-05-17 mischa ($filename = $file) =~ s/.*\///;
79 c0de5a61 2020-05-17 mischa ($vm_name = $filename) =~ s/\.txt//;
80 c0de5a61 2020-05-17 mischa ($vm_number = $vm_name) =~ s/^vm//;
81 c0de5a61 2020-05-17 mischa $hash{$vm_name}{'vm_number'} = $vm_number;
84 c0de5a61 2020-05-17 mischa open my $fh, "<", "$file";
85 c0de5a61 2020-05-17 mischa while (my $row = <$fh>) {
86 c0de5a61 2020-05-17 mischa next if ($row =~ /^\s*($|#)/);
87 c0de5a61 2020-05-17 mischa chomp ($row);
88 c0de5a61 2020-05-17 mischa (my $key, my $val) = split(/=/, $row, 2);
89 c0de5a61 2020-05-17 mischa if ($hash_name eq "vms") {
90 c0de5a61 2020-05-17 mischa ($hash{$vm_name}{$key} .= $val) =~ s/^"+|"+$//g;
92 c0de5a61 2020-05-17 mischa ($hash{$hash_name}{$key} .= $val) =~ s/^"+|"+$//g;
95 c0de5a61 2020-05-17 mischa close $fh;
97 c0de5a61 2020-05-17 mischa return %hash;
100 b7df6b75 2021-05-09 mischa sub mailout {
101 c0de5a61 2020-05-17 mischa my %conf = %{$_[0]};
102 c0de5a61 2020-05-17 mischa my %vms = %{$_[1]};
104 fd54bb03 2023-05-05 mischa my $template = "$conf{'conf'}{'TEMPLATES'}/email-$function.txt";
105 fd54bb03 2023-05-05 mischa my $server_number = $1 if $conf{'conf'}{'SERVER'} =~ /([0-9]+)/;
106 fd54bb03 2023-05-05 mischa my $evenodd = $server_number % 2 if $server_number;
107 c0de5a61 2020-05-17 mischa my $year = strftime("%Y", localtime);
108 c0de5a61 2020-05-17 mischa my $month = strftime("%m", localtime);
110 e71569aa 2020-06-15 mischa my $response = HTTP::Tiny->new->get('https://openbsd.amsterdam/index.html');
111 dfe4be5f 2022-12-05 mischa my $total_donated = $1 if $response->{'content'} =~ /([0-9,\.]+) donated to the OpenBSD/;
112 edb93e1c 2021-01-11 mischa my $total_vms = $1 if $response->{'content'} =~ /([0-9]+) VMs deployed/;
113 e71569aa 2020-06-15 mischa $response = HTTP::Tiny->new->get('https://openbsd.amsterdam/servers.html');
114 e71569aa 2020-06-15 mischa my $total_hosts = () = $response->{'content'} =~ /(\>Server )/g;
116 c0de5a61 2020-05-17 mischa for my $vm_name (sort keys %vms) {
117 c0de5a61 2020-05-17 mischa my $_date = $vms{$vm_name}{'date'};
118 fd54bb03 2023-05-05 mischa my $_payment = $vms{$vm_name}{'payment'} || 0;
119 64e24d55 2021-02-17 mischa my $_subscription = $vms{$vm_name}{'subscription'} || "no";
120 c0de5a61 2020-05-17 mischa my $_donated = $vms{$vm_name}{'donated'};
121 c0de5a61 2020-05-17 mischa my $_name = $vms{$vm_name}{'name'};
122 fd54bb03 2023-05-05 mischa my ($_firstname, $_lastname) = split(/ /, $_name, 2);
123 c0de5a61 2020-05-17 mischa my $_email = $vms{$vm_name}{'email'};
124 c0de5a61 2020-05-17 mischa my $_hostname = $vms{$vm_name}{'hostname'};
125 b7df6b75 2021-05-09 mischa my $_username = $vms{$vm_name}{'username'};
126 fafc102e 2022-11-13 mischa my $_memory = $vms{$vm_name}{'memory'} || '';
127 fafc102e 2022-11-13 mischa my $_disk2 = $vms{$vm_name}{'disk2'} || '';
128 fd54bb03 2023-05-05 mischa my $_instance = $vms{$vm_name}{'instance'} || $vm_name;
130 fd54bb03 2023-05-05 mischa my ($_ipv4_address, $_ipv4_subnet) = $vms{$vm_name}{'ipv4'} =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(\/\d{2})/ if $vms{$vm_name}{'ipv4'};
131 fd54bb03 2023-05-05 mischa my $_ipv4 = $_ipv4_address || $conf{'conf'}{'IP_PREFIX'} . "." . ($conf{'conf'}{'IP_START'} + $vms{$vm_name}{'vm_number'});
132 fd54bb03 2023-05-05 mischa my $_ipv4_netmask = $_ipv4_subnet || $conf{'conf'}{'NETMASK'};
133 fd54bb03 2023-05-05 mischa my $_ipv4_gateway = $vms{$vm_name}{'ipv4_gw'} || $conf{'conf'}{'ROUTER'};
135 fd54bb03 2023-05-05 mischa my $_ipv6 = $vms{$vm_name}{'ipv6'} || $conf{'conf'}{'IPV6_PREFIX'} . ":" . ($conf{'conf'}{'IPV6_START'} + $vms{$vm_name}{'vm_number'}) . "::" . ($conf{'conf'}{'IP_START'} + $vms{$vm_name}{'vm_number'});
136 fd54bb03 2023-05-05 mischa my $_ipv6_gateway = $vms{$vm_name}{'ipv6_gw'} || $conf{'conf'}{'IPV6_PREFIX'} . ":" . ($conf{'conf'}{'IPV6_START'} + $vms{$vm_name}{'vm_number'}) . "::1";
138 fd54bb03 2023-05-05 mischa if (! $_payment) {
139 a4481590 2023-04-23 mischa my $memory_price = $memory_prices{$_memory} || '0';
140 a4481590 2023-04-23 mischa my $hdd_price = $hdd_prices{$_disk2} || '0';
141 a4481590 2023-04-23 mischa $_payment = $base_price + $memory_price + $hdd_price;
142 fd54bb03 2023-05-05 mischa } elsif ($_payment =~ m/sponsor/) {
143 fd54bb03 2023-05-05 mischa $ideal_url = "SPONSORED";
144 fd54bb03 2023-05-05 mischa $paypal_url = "SPONSORED";
147 4e9862ef 2024-05-14 mischa #if ($_donated !~ m/renewal/) {
148 4e9862ef 2024-05-14 mischa #print "renewal not set\n";
152 fd54bb03 2023-05-05 mischa my $stripe = $stripe_urls{$_payment} || '';
153 fd54bb03 2023-05-05 mischa my $ideal = $ideal_url || "https://bunq.me/openbsdams/${_payment}/${_instance}%20$conf{'conf'}{'SERVER'}";
154 fd54bb03 2023-05-05 mischa my $paypal = $paypal_url || "https://paypal.me/runbsd/${_payment}eur";
156 b7df6b75 2021-05-09 mischa open(my $fh, '<', $template);
157 b7df6b75 2021-05-09 mischa open my $fh_email, "|-", "/usr/sbin/sendmail -t";
158 b7df6b75 2021-05-09 mischa printf $fh_email "To: %s\n", $_email;
159 c0494dc8 2023-05-06 mischa TEMPLATE: while (my $row = <$fh>) {
160 b7df6b75 2021-05-09 mischa chomp $row;
161 c0494dc8 2023-05-06 mischa if ($row =~ m/MESSAGE/) {
162 c0494dc8 2023-05-06 mischa if ($message ne "empty") {
163 c0494dc8 2023-05-06 mischa $row =~ s/MESSAGE/$message\n/g;
165 c0494dc8 2023-05-06 mischa next TEMPLATE;
169 8765243a 2022-08-27 mischa $row =~ s/FIRSTNAME/$_firstname/g;
170 fd54bb03 2023-05-05 mischa $row =~ s/VMID/$_instance/g;
171 fd54bb03 2023-05-05 mischa $row =~ s/SERVER/$conf{'conf'}{'SERVER'}/g;
172 fd54bb03 2023-05-05 mischa $row =~ s/DOMAIN/$conf{'conf'}{'DOMAIN'}/g;
173 b7df6b75 2021-05-09 mischa $row =~ s/HOSTNAME/$_hostname/g;
174 b7df6b75 2021-05-09 mischa $row =~ s/USERNAME/$_username/g;
176 fd54bb03 2023-05-05 mischa $row =~ s/IPV4$/$_ipv4/g;
177 fd54bb03 2023-05-05 mischa $row =~ s/IPV4NETMASK$/$_ipv4_netmask/g;
178 fd54bb03 2023-05-05 mischa $row =~ s/IPV4GW$/$_ipv4_gateway/g;
179 fd54bb03 2023-05-05 mischa $row =~ s/IPV6$/$_ipv6/g;
180 fd54bb03 2023-05-05 mischa $row =~ s/IPV6GW$/$_ipv6_gateway/g;
182 b7df6b75 2021-05-09 mischa $row =~ s/YEAR/$year/g;
183 b7df6b75 2021-05-09 mischa $row =~ s/TOTAL_DONATED/$total_donated/g;
184 b7df6b75 2021-05-09 mischa $row =~ s/TOTAL_VMS/$total_vms/g;
185 b7df6b75 2021-05-09 mischa $row =~ s/TOTAL_HOSTS/$total_hosts/g;
187 b7df6b75 2021-05-09 mischa $row =~ s/PAYMENT/$_payment/g;
188 a4481590 2023-04-23 mischa $row =~ s/STRIPE/$stripe/g;
189 fd54bb03 2023-05-05 mischa $row =~ s/IDEAL/$ideal/g;
190 fd54bb03 2023-05-05 mischa $row =~ s/PAYPAL/$paypal/g;
192 b7df6b75 2021-05-09 mischa if ($row =~ /TIME\((.*)\)/) {
193 b7df6b75 2021-05-09 mischa my @TIMES = split(/,/, $1);
194 a3ced255 2021-05-18 mischa $row =~ s/TIME\(.*\)/$TIMES[$evenodd]/g;
196 b7df6b75 2021-05-09 mischa print $fh_email "$row\n";
198 b7df6b75 2021-05-09 mischa close $fh_email;
199 fd54bb03 2023-05-05 mischa print "$function: $_date, $_payment, $_name, $_email, $_hostname, $conf{'conf'}{'SERVER'} ($_instance), $_ipv4\n";
203 c0de5a61 2020-05-17 mischa # check if _deploy.conf exists
204 c0de5a61 2020-05-17 mischa my $dev = $ENV{'HOME'} . "/openbsd.amsterdam/deploy.pl";
205 c0de5a61 2020-05-17 mischa my $prod = $ENV{'HOME'};
207 c0de5a61 2020-05-17 mischa my $debug;
208 c0de5a61 2020-05-17 mischa my %conf;
210 c0de5a61 2020-05-17 mischa if (-d "$dev") {
211 c0de5a61 2020-05-17 mischa $dir = $dev;
212 c0de5a61 2020-05-17 mischa $debug = 1;
213 c0de5a61 2020-05-17 mischa %conf = get_variables('conf', "$dir/_deploy.conf");
214 c0de5a61 2020-05-17 mischa } elsif (-d "$prod") {
215 c0de5a61 2020-05-17 mischa $dir = $prod;
216 c0de5a61 2020-05-17 mischa %conf = get_variables('conf', "$dir/_deploy.conf");
218 c0de5a61 2020-05-17 mischa printf "Unable to find config file\n";
222 c0de5a61 2020-05-17 mischa # parse all vm*.txt files in the VMS directory
223 c0de5a61 2020-05-17 mischa my @files = glob "$conf{'conf'}{'VMS'}/*.txt";
224 c0de5a61 2020-05-17 mischa %vms = get_variables('vms', @files);
226 b7df6b75 2021-05-09 mischa if ($function =~ /notify/) {
227 b7df6b75 2021-05-09 mischa mailout(\%conf, \%vms);
228 158def30 2025-01-01 mischa } elsif ($function =~ /(deployed|redeployed|cpu|msg|thanx|longterm)/ and $function_variable !~ /empty/) {
229 b7df6b75 2021-05-09 mischa my %slice = %vms{$function_variable};
230 b7df6b75 2021-05-09 mischa mailout(\%conf, \%slice);
231 047151d1 2021-05-09 mischa } elsif ($function =~ /(renewal|subscription|deprovision)/) {
232 b7df6b75 2021-05-09 mischa my $year = strftime("%Y", localtime);
233 b7df6b75 2021-05-09 mischa my $month = strftime("%m", localtime);
234 b7df6b75 2021-05-09 mischa for my $vm_name (sort keys %vms) {
235 fd54bb03 2023-05-05 mischa if ($vms{$vm_name}{'donated'} =~ /(done|expire|sponsor|sponsored|renewal)/) { delete $vms{$vm_name}; next; }
237 fd54bb03 2023-05-05 mischa my ($_vm_year, $_vm_month, $_vm_day) = split('/', $vms{$vm_name}{'date'}, 3);
238 49fb992d 2023-01-03 mischa if ($_vm_year >= $year) { delete $vms{$vm_name}; next; }
239 49fb992d 2023-01-03 mischa if ($_vm_month != $month) { delete $vms{$vm_name}; next; }
241 b7df6b75 2021-05-09 mischa if ($function =~ /(renewal|deprovision)/) {
242 49fb992d 2023-01-03 mischa if (defined $vms{$vm_name}{'subscription'} and $vms{$vm_name}{'subscription'} ne "") { delete $vms{$vm_name}; next; }
244 b7df6b75 2021-05-09 mischa if ($function =~ /subscription/) {
245 35bfa8a2 2021-07-05 mischa if (!defined $vms{$vm_name}{'subscription'} or $vms{$vm_name}{'subscription'} eq "" or $vms{$vm_name}{'subscription'} =~ /no/) { delete $vms{$vm_name}; next; }
248 b7df6b75 2021-05-09 mischa mailout(\%conf, \%vms);
249 047151d1 2021-05-09 mischa } elsif ($function =~ /stopped/) {
250 b7df6b75 2021-05-09 mischa my @stopped_vms = qx(vmctl show | grep stopped | awk '{print \$9}');
251 b7df6b75 2021-05-09 mischa for my $vm_name (sort keys %vms) {
252 b7df6b75 2021-05-09 mischa if (!grep(/$vm_name/, @stopped_vms)) { delete $vms{$vm_name}; next; }
253 d82c089e 2023-03-13 mischa if ($vms{$vm_name}{'donated'} =~ /expire/) { delete $vms{$vm_name}; next; }
255 b7df6b75 2021-05-09 mischa mailout(\%conf, \%vms);
257 158def30 2025-01-01 mischa print "usage: " . basename($0) . " [stopped | renewal | notify | deprovision] | [deployed | redeployed | cpu | msg | thanx | longterm ] <vmid>\n";