ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/bin/aemp
Revision: 1.35
Committed: Fri Sep 4 21:01:22 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.34: +31 -1 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.35 #!/opt/bIn/perl
2 root 1.1
3     =head1 NAME
4    
5     aemp - AnyEvent:MP utility
6    
7     =head1 SYNOPSIS
8    
9     aemp command args...
10    
11     # protocol commands
12 root 1.19 aemp snd <port> <arg...> # send a message
13     aemp mon <port> # wait till port is killed
14     aemp rpc <port> <arg...> # send message, append reply
15     aemp eval <node> <expr...> # evaluate expression
16 root 1.22 aemp trace <nodeid> # trace the network topology
17 root 1.8
18     # run a node
19 root 1.26 aemp run configure_args... # run a node
20 root 1.8
21 root 1.23 # node configuration: node ID
22 root 1.19 aemp setnodeid <nodeid> # configure the real node id
23     aemp delnodeid # reset node id to default (= inherit)
24 root 1.1
25     # node configuration: secret
26 root 1.19 aemp gensecret # generate a random shared secret
27     aemp setsecret <secret> # set the shared secret
28     aemp delsecret # remove the secret (= inherit)
29 root 1.1
30     # node configuration: TLS
31 root 1.19 aemp gencert # generate a random certificate
32     aemp setcert <file> # set a certificate (key.pem + certificate.pem)
33     aemp delcert # remove certificate (= inherit)
34 root 1.8
35 root 1.17 # node configuration: seed addresses for bootstrapping
36 root 1.19 aemp setseeds <host:port>,... # set seeds
37     aemp delseeds # clear all seeds (= inherit)
38     aemp addseed <host:port> # add a seed
39     aemp delseed <host:port> # remove seed
40 root 1.17
41     # node configuration: bind addresses
42 root 1.19 aemp setbinds <host:port>,... # set binds
43     aemp delbinds # clear all binds (= inherit)
44     aemp addbind <host:port> # add a bind address
45     aemp delbind <host:port> # remove a bind address
46 root 1.8
47     # node configuration: services
48 root 1.19 aemp setservices initfunc,... # set service functions
49     aemp delservices # clear all services (= inherit)
50     aemp addservice <initfunc> # add an instance of a service
51     aemp delservice <initfunc> # delete one instance of a service
52 root 1.6
53 root 1.10 # profile-specific configuration
54     aemp profile <name> <command>... # apply command to profile only
55 root 1.26 aemp setparent <name> # specify a parent profile
56 root 1.25 aemp delparent # clear parent again
57 root 1.19 aemp delprofile <name> # eradicate the named profile
58     aemp showprofile <name> # display given profile
59 root 1.25 aemp showconfig <name> ... # display effective config
60 root 1.10
61 root 1.1 =head1 DESCRIPTION
62    
63 root 1.8 With aemp you can configure various aspects of AnyEvent::MP and its
64 root 1.22 protocol, send various messages and even run a node.
65 root 1.1
66 root 1.23 The F<aemp> utility works like F<cvs>, F<svn> or other commands: the first
67     argument defines which operation (subcommand) is requested, after which
68     arguments for this operation are expected. When a subcommand does not eat
69     all remaining arguments, the remaining arguments will again be interpreted
70     as subcommand and so on.
71    
72     This means you can chain multiple commands, which is handy for profile
73     configuration, e.g.:
74    
75 root 1.27 aemp gensecret profile xyzzy binds 4040,4041 nodeid anon/
76    
77     Please note that all C<setxxx> subcommands have an alias without the
78     C<set> prefix.
79 root 1.23
80 root 1.34 All configuration data is stored in a human-readable (JSON) config file
81     stored in F<~/.perl-anyevent-mp> (or F<%appdata%/perl-anyevent-mp>
82     on loser systems). Feel free to look at it or edit it, the format is
83     relatively simple.
84    
85 root 1.35 =head2 SPECIFYING ARGUMENTS
86    
87     Arguments can be specified just as with any other shell command, with a
88     few special cases:
89    
90     If the I<first> argument starts with a literal C<[>-character, then it is
91     interpreted as a UTF-8 encoded JSON text. The resulting array replaces all
92     arguments.
93    
94     Otherwise, if I<any> argument starts with one of C<[>, C<{> or C<">, then
95     it is interpreted as UTF-8 encoded JSON text (or a single value in case of
96     C<">), and the resulting reference or scalar replaces the argument.
97    
98     This allows you, for example, to specify binds in F<aemp run> (using POSIX
99     shell syntax):
100    
101     aemp run binds '["*:4040"]'
102    
103 root 1.23 =head2 RUNNING A NODE
104    
105     This can be used to run a node - together with some services, this makes
106 elmex 1.32 it unnecessary to write any wrapper programs.
107 root 1.23
108     =over 4
109    
110 root 1.30 =item run <configure_args>...
111 root 1.23
112 root 1.26 Runs a node by calling C<AnyEvent::MP::Kernel::configure> with the given
113 root 1.23 arguments. The node runs under L<AnyEvent::Watchdog>, can be restarted
114 root 1.30 (and autorestarted, see the L<AnyEvent::Watchdog> manual). A very common
115 root 1.33 invocation is to just specify a profile using the profile name
116 root 1.30
117 root 1.33 aemp run database-backend
118 root 1.30
119     ... but you can use most arguments that C<configure> understands:
120    
121     aemp run nodeid mynode2 profile someprofile
122 root 1.23
123     Care has been taken to load (almost) no modules other than
124     L<AnyEvent::Watchdog> and the modules it loads, so everything (including
125     the L<AnyEvent::MP> modules themselves) will be freshly loaded on restart,
126     which makes upgrading everything except the perl binary easy.
127    
128     =back
129    
130     =head2 PROTOCOL COMMANDS
131    
132     These commands actually communicate with other nodes. They all use a node
133 elmex 1.32 profile name of C<aemp> (specifying a default node ID of C<anon/> and a
134 root 1.25 binds list containing C<*:*> only).
135 root 1.23
136     They all use a timeout of five seconds, after which they give up.
137    
138     =over 4
139    
140     =item snd <port> <arguments...>
141    
142     Simply send a message to the given port - where you get the port ID from
143     is your problem.
144    
145     Exits after ensuring that the message has been delivered to its node.
146    
147 elmex 1.32 Most useful to take advantage of some undocumented functionality inside
148 root 1.23 nodes, such as node ports being able to call any method:
149    
150     aemp snd doomed AnyEvent::Watchdog::restart 1
151    
152     =item rpc <port> <arg...>
153    
154     Like F<aemp snd>, but appends a local reply port to the message and waits
155     for a message to it.
156    
157     Any return values will be JSON-encoded and printed separated by commas
158     (kind of like a JSON array without []-brackets).
159    
160 elmex 1.32 Example: ask the (undocumented) time service of a node for it's current
161 root 1.23 time.
162    
163     aemp rpc mynode time
164    
165     =item mon <port>
166    
167     Monitors the port and exits when it's monitorign callback is called. Most
168     useful to monitor node ports.
169    
170     Example: monitor some node.
171    
172     aemp mon doomed
173    
174     =item eval <node> <expr...>
175    
176     Joins all remaining arguments into a string and evaluates it on the given
177     node. Return values are handled as with F<aemp rpc>.
178    
179     Example: find the unix process ID of the node called posicks.
180    
181     aemp eval posicks '$$'
182    
183     =item trace <node>
184    
185     Asks the given node for all currently connected nodes, then asks those
186     nodes for the same, thus tracing all node connections.
187    
188 root 1.31 =back
189 root 1.23
190     =head2 CONFIGURATION/NODE ID/SECRET/CERTIFICATE
191    
192     These commands deal with rather basic settings, the node ID, the shared
193     secret and the TLS certificate.
194    
195     =over 4
196    
197     =item setnodeid <nodeid>
198    
199     Set the node ID to the given string.
200    
201     =item delnodeid
202    
203     Removes the node ID again, which means it is inherited again from it's
204     parent profile, or stays unset.
205    
206     =item gensecret
207    
208     Generates a random shared secret and sets it. The shared secret is used to
209     authenticate nodes to each other when TLS is not required.
210    
211     =item setsecret <secret>
212    
213     Sets the shared secret tot he given string, which can be anything.
214    
215     =item delsecret
216    
217     Removes the shared secret again, which means it is inherited again from
218     it's parent profile, or stays unset.
219    
220     =item gencert
221    
222 elmex 1.32 Generates a self-signed certificate and key, and sets it. This works
223 root 1.23 similarly to a shared secret: when all nodes have it, TLS will be used to
224     authenticate and encrypt all traffic.
225    
226     =item setcert <file>
227    
228     Set a node certificate (and optionally any CA certificates) from the given
229     file. The file must contain the key, followed by the certificate, followed
230     by any CA certificates you want to trust, all in PEM format.
231    
232     See L<AnyEvent::TLS> for some more details - this sets the C<cert> and
233     C<ca_cert> options.
234    
235     =item delcert
236    
237     Removes the certificate(s) again, which means it is inherited again from
238     it's parent profile, or stays unset.
239    
240     =back
241    
242     =head2 CONFIGURATION/SEEDS
243    
244     To discover the network you have to specify some seed addresses, which are
245     basically C<host:port> pairs where you expect some long-running nodes. It
246     does no harm to have a node as its own seed (they will eventually be
247     ignored).
248    
249 root 1.31 =over 4
250    
251 root 1.23 =item setseeds <host:port>,...
252    
253     Sets or replaces the list of seeds, which must be specified as a
254     comma-separated list of C<host:port> pairs. The C<host> can be a hostname,
255     an IP address, or C<*> to signify all local host addresses. If the
256     C<:port> is omitted, then the default port of C<4040> is assumed.
257    
258     An empty list is allowed.
259    
260     Example: use C<doomed> with default port as only seednode.
261    
262     aemp setseeds doomed
263    
264     =item delseeds
265    
266     Removes the seed list again, which means it is inherited again from it's
267     parent profile, or stays unset.
268    
269     =item addseed <host:port>
270    
271     Adds a single seed address.
272    
273     =item delseed <host:port>
274    
275     Deletes the given seed address, if it exists.
276    
277     =back
278    
279     =head2 CONFIGURATION/BINDS
280    
281     To be able to be reached from other nodes, a node must I<bind> itself
282     to some listening socket(s). The list of these can either bs specified
283     manually, or AnyEvent::MP can guess them. Nodes without any binds are
284     possible to some extent.
285    
286     =over 4
287    
288     =item setbinds <host:port>,...
289    
290     Sets the list of bind addresses explicitly - see the F<aemp setseeds>
291     command for the exact syntax. In addition, a value of C<0> for the port
292     means to use a dynamically-assigned port.
293    
294 root 1.25 Note that the C<*>, C<*:*> or C<*:port> values are very useful here.
295 root 1.23
296     Example: bind on the default port (4040) on all local interfaces.
297    
298     aemp setbinds "*"
299    
300     Example: bind on a random port on all local interfaces.
301    
302 root 1.25 aemp setbinds "*:*"
303 root 1.23
304     Example: resolve "doomed.mydomain" and try to bind on port C<4040> of all
305     IP addressess returned.
306    
307     aep setbinds doomed.mydomain
308    
309     =item delbinds
310    
311     Removes the bind list again, which means it is inherited again from it's
312     parent profile, or stays unset.
313    
314     =item addbind <host:port>
315    
316     Adds a single bind address.
317    
318     =item delbind <host:port>
319    
320     Deletes the given bind address, if it exists.
321    
322     =back
323    
324     =head2 CONFIGURATION/SERVICES
325    
326     Services are modules (or functions) that are automatically loaded (or
327     executed) when a node starts. They are especially useful when used in
328     conjunction with F<aemp run>, to configure which services a node should
329     run.
330    
331     =over 4
332    
333     =item setservices <initfunc>...
334    
335     Sets or replaces the list of services, which must be specified as a
336     comma-separated list.
337    
338     Each entry in the list is interpreted as either a module name to
339     load (when it ends with C<::>) or a function to call (all other
340     cases). The algorithm to find the function is the same as used for C<<
341     L<AnyEvent::MP>::spawn >>.
342    
343     =item delservices
344    
345     Removes the service list again, which means it is inherited again from
346     it's parent profile, or stays unset.
347    
348     =item addservice <initfunc>
349    
350     Adds a single service.
351    
352     =item delservice <initfunc>
353    
354     Deletes the given service, if it exists.
355    
356     =back
357    
358     =head2 CONFIGURATION/PROFILE MANAGEMENT
359    
360     All the above configuration functions by default affect the I<global
361     default configuration>, which is basically used to augment every profile
362     and node configuration.
363    
364     =over 4
365    
366     =item profile <name> ...
367    
368     This subcommand makes the following subcommands act only on a specific
369     named profile, instead of on the global default. The profile is created if
370     necessary.
371    
372     Example: create a C<server> profile, give it a random node name, some seed
373     nodes and bind it on an unspecified port on all local interfaces. You
374     should add some services then and run the node...
375    
376 root 1.30 aemp profile server nodeid anon/ seeds doomed,10.0.0.2:5000 binds "*:*"
377 root 1.23
378     =item delprofile <name>
379    
380     Deletes the profile of the given name.
381    
382 root 1.26 =item setparent <name>
383 root 1.24
384     Sets the parent profile to use - values not specified in a profile will be
385     taken from the parent profile (even recursively, with the global default
386     config being the default parent). This is useful to configure profile
387     I<classes> and then to inherit from them for individual nodes.
388    
389     Note that you can specify circular parent chains and even a parent for the
390     global configuration. Neither will do you any good, however.
391    
392     Example: inherit all values not specified in the C<doomed> profile from
393     the C<server> profile.
394    
395 root 1.26 aemp profile doomed setparent server
396 root 1.24
397     =item delparent
398    
399     Removes the parent again from the profile, if any was set, so the profile
400     inherits directly from the global default config again.
401    
402 root 1.23 =item showprofile <name>
403    
404     Shows the values of the given profile, and only those, no inherited
405     values.
406    
407 root 1.25 =item showconfig <name> <key value...>
408 root 1.23
409 root 1.24 Shows the I<effective> config, i.e. the values as used by a node started
410 root 1.25 with the given profile name. Any additional key-value pairs specified
411 root 1.26 augment the configuration, just as with C<configure>.
412 root 1.24
413 root 1.25 If all arguments are omitted, show the global default config.
414 root 1.23
415     =back
416    
417 root 1.1 =cut
418    
419     use common::sense;
420    
421 root 1.19 # should come before anything else, so all modules
422     # will be loaded on each restart
423 root 1.22 BEGIN {
424 root 1.35 if (@ARGV == 1 && $ARGV[0] =~ /^\[/) {
425     require JSON::XS;
426     @ARGV = @{ JSON::XS->new->utf8->decode (shift) };
427     } else {
428     for (@ARGV) {
429     if (/^[\[\{\"]/) {
430     require JSON::XS;
431     $_ = JSON::XS->new->utf8->allow_nonref->decode ($_);
432     }
433     }
434     }
435    
436 root 1.8 if ($ARGV[0] eq "run") {
437     shift;
438    
439 root 1.13 # d'oh
440 root 1.22 require AnyEvent::Watchdog;
441 root 1.19 # now we can load extra modules
442    
443 root 1.29 require AnyEvent::Watchdog::Util;
444     AnyEvent::Watchdog::Util::autorestart (1);
445     AnyEvent::Watchdog::Util::heartbeat (300);
446 root 1.13
447 root 1.8 require AnyEvent;
448 root 1.25 require AnyEvent::MP::Kernel;
449 root 1.26 AnyEvent::MP::Kernel::configure (@ARGV);
450 root 1.8
451 root 1.16 AnyEvent::detect () eq "AnyEvent::Impl::EV"
452 root 1.8 ? EV::loop ()
453     : AE::cv ()->recv;
454     }
455 root 1.22 }
456 root 1.8
457 root 1.1 use Carp ();
458    
459 root 1.17 use JSON::XS;
460    
461 root 1.1 use AnyEvent;
462 root 1.4 use AnyEvent::Util;
463    
464 root 1.8 use AnyEvent::MP;
465 root 1.1 use AnyEvent::MP::Config;
466    
467     sub my_run_cmd {
468     my ($cmd) = @_;
469    
470     my $cv = &run_cmd;
471     my $status = $cv->recv;
472    
473     $status
474     and die "@$cmd: command failed with exit status $status.";
475     }
476    
477     sub gen_cert {
478 root 1.2 my_run_cmd [qw(openssl req
479     -new -nodes -x509 -days 3650
480     -newkey rsa:2048 -keyout /dev/fd/3
481     -batch -subj /CN=AnyEvent::MP
482     )],
483 root 1.5 "<", "/dev/null",
484 root 1.1 ">" , \my $cert,
485     "3>", \my $key,
486 root 1.4 "2>", "/dev/null";
487 root 1.1
488     "$cert$key"
489     }
490    
491 root 1.25 sub init {
492 root 1.26 configure profile => "aemp", nodeid => "anon/";
493 root 1.25 }
494    
495 root 1.10 our $cfg = AnyEvent::MP::Config::config;
496 root 1.8 our $profile = $cfg;
497 root 1.1
498 root 1.11 sub trace {
499     my ($node) = @_;
500     my $cv = AE::cv;
501     my %seen;
502    
503 root 1.28 my $to = AE::timer 15, 0, sub { exit 1 };
504 root 1.11
505 root 1.25 init;
506 root 1.11
507     my $reply = port {
508 root 1.12 my ($node, @neigh) = @_;
509 root 1.11
510 root 1.12 @neigh = grep $_ ne $NODE, @neigh;
511    
512     print +(join " ", $node, @neigh), "\n";
513    
514     for (@neigh) {
515 root 1.11 unless ($seen{$_}++) {
516     $cv->begin;
517     snd $_, up_nodes => $SELF => $_;
518     }
519     }
520    
521     $cv->end;
522     };
523    
524     $cv->begin;
525     snd $reply, seed => $node;
526    
527     $cv->recv;
528     }
529    
530 root 1.10 sub docmd;
531    
532 root 1.1 our %CMD = (
533     snd => sub {
534 root 1.17 my $port = shift @ARGV;
535 root 1.25 init;
536 root 1.1
537 root 1.7 snd $port, @ARGV; @ARGV = ();
538 root 1.1
539     my $cv = AE::cv;
540 root 1.28 my $to = AE::timer 5, 0, sub { exit 1 };
541 root 1.1 mon $port, $cv;
542 root 1.19 my $reply = port sub { &$cv };
543     snd node_of $port, snd => $reply, "message sent successfully";
544 root 1.1
545     print join " ", $cv->recv, "\n";
546     },
547    
548 root 1.7 rpc => sub {
549 root 1.17 my $port = shift @ARGV;
550 root 1.25 init;
551 root 1.7
552     my $cv = AE::cv;
553 root 1.28 my $to = AE::timer 5, 0, sub { exit 1 };
554 root 1.19 snd $port, @ARGV, port { &$cv }; @ARGV = ();
555 root 1.7 mon $port, $cv;
556    
557 root 1.23 print +(substr JSON::XS->new->encode ([$cv->recv]), 1, -1), "\n";
558 root 1.7 },
559    
560 root 1.1 mon => sub {
561 root 1.17 my $port = shift @ARGV;
562 root 1.25 init;
563 root 1.1
564     mon $port, my $cv = AE::cv;
565     print join " ", $cv->recv, "\n";
566     },
567    
568 root 1.19 eval => sub {
569     my $node = node_of shift @ARGV;
570     my $expr = join " ", @ARGV; @ARGV = ();
571 root 1.25 init;
572 root 1.19
573     my $cv = AE::cv;
574 root 1.28 my $to = AE::timer 5, 0, sub { exit 1 };
575 root 1.19 AnyEvent::MP::Kernel::eval_on $node, $expr, port { &$cv };
576     mon $node, $cv;
577    
578     my ($err, @res) = $cv->recv;
579    
580     die $err if length $err;
581    
582     print +(substr JSON::XS->new->encode (\@res), 1, -1), "\n";
583     },
584    
585 root 1.11 trace => sub {
586     @ARGV >= 1
587 root 1.17 or die "node id missing\n";
588 root 1.11
589 root 1.17 trace shift @ARGV;
590 root 1.11 },
591    
592 root 1.17 setnodeid => sub {
593 root 1.8 @ARGV >= 1
594     or die "shared secret missing\n";
595    
596 root 1.17 $profile->{nodeid} = shift @ARGV;
597 root 1.8 ++$cfg->{dirty};
598     },
599 root 1.17 delnodeid => sub {
600     delete $profile->{nodeid};
601 root 1.8 ++$cfg->{dirty};
602     },
603    
604 root 1.1 setsecret => sub {
605 root 1.8 @ARGV >= 1
606 root 1.1 or die "shared secret missing\n";
607    
608 root 1.8 $profile->{secret} = shift @ARGV;
609 root 1.1 ++$cfg->{dirty};
610     },
611     gensecret => sub {
612 root 1.18 $profile->{secret} = AnyEvent::MP::Kernel::alnumbits AnyEvent::MP::Kernel::nonce 64;
613 root 1.1 ++$cfg->{dirty};
614     },
615 root 1.17 delsecret => sub {
616 root 1.8 delete $profile->{secret};
617 root 1.1 ++$cfg->{dirty};
618     },
619    
620     setcert => sub {
621 root 1.8 @ARGV >= 1
622 root 1.1 or die "key+certificate pem filename missing\n";
623    
624     open my $fh, "<", $ARGV[0]
625     or die "$ARGV[0]: $!";
626    
627     local $/;
628 root 1.8 $profile->{cert} = <$fh>;
629 root 1.1 ++$cfg->{dirty};
630     },
631     gencert => sub {
632 root 1.8 $profile->{cert} = gen_cert;
633 root 1.1 ++$cfg->{dirty};
634     },
635 root 1.17 delcert => sub {
636 root 1.8 delete $profile->{cert};
637 root 1.1 ++$cfg->{dirty};
638     },
639 root 1.6
640 root 1.17 setbinds => sub {
641 root 1.19 @ARGV >= 1
642     or die "bind addresses missing\n";
643     $profile->{binds} = [split /,/, shift @ARGV];
644 root 1.17 ++$cfg->{dirty};
645     },
646     delbinds => sub {
647     delete $profile->{binds};
648     ++$cfg->{dirty};
649     },
650     addbind => sub {
651     @ARGV >= 1
652     or die "bind address missing\n";
653     my $bind = shift @ARGV;
654    
655     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
656     push @{ $profile->{binds} }, $bind;
657     ++$cfg->{dirty};
658     },
659     delbind => sub {
660     @ARGV >= 1
661     or die "bind address missing\n";
662     my $bind = shift @ARGV;
663    
664     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
665     ++$cfg->{dirty};
666     },
667    
668 root 1.6 setseeds => sub {
669 root 1.19 @ARGV >= 1
670     or die "seed addresses missing\n";
671     $profile->{seeds} = [split /,/, shift @ARGV];
672 root 1.6 ++$cfg->{dirty};
673     },
674 root 1.17 delseeds => sub {
675     delete $profile->{seeds};
676     ++$cfg->{dirty};
677     },
678 root 1.6 addseed => sub {
679 root 1.10 @ARGV >= 1
680 root 1.17 or die "seed address missing\n";
681 root 1.6 my $seed = shift @ARGV;
682 root 1.10
683 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
684     push @{ $profile->{seeds} }, $seed;
685     ++$cfg->{dirty};
686     },
687     delseed => sub {
688 root 1.10 @ARGV >= 1
689 root 1.17 or die "seed address missing\n";
690 root 1.8 my $seed = shift @ARGV;
691 root 1.10
692 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
693     ++$cfg->{dirty};
694     },
695    
696     setservices => sub {
697 root 1.19 @ARGV >= 1
698     or die "service specifications missing\n";
699     $profile->{services} = [split /,/, shift @ARGV];
700 root 1.6 ++$cfg->{dirty};
701     },
702 root 1.17 delservices => sub {
703     delete $profile->{services};
704     ++$cfg->{dirty};
705     },
706 root 1.9 addservice => sub {
707 root 1.10 @ARGV >= 1
708     or die "service specification missing\n";
709 root 1.8 my $service = shift @ARGV;
710     push @{ $profile->{services} }, $service;
711     ++$cfg->{dirty};
712     },
713 root 1.9 delservice => sub {
714 root 1.10 @ARGV >= 1
715     or die "service specification missing\n";
716 root 1.8 my $service = shift @ARGV;
717     for (0 .. $#{ $profile->{services} }) {
718     next unless $profile->{services}[$_] eq $service;
719     splice @{ $profile->{services} }, $_, 1;
720     last;
721     }
722 root 1.6 ++$cfg->{dirty};
723     },
724 root 1.10
725     profile => sub {
726 root 1.19 @ARGV >= 1
727     or die "profile name is missing\n";
728 root 1.10 my $name = shift @ARGV;
729    
730     $profile = $cfg->{profile}{$name} ||= {};
731 root 1.14 ++$cfg->{dirty};
732 root 1.10 },
733     delprofile => sub {
734     @ARGV >= 1
735     or die "profile name is missing\n";
736     my $name = shift @ARGV;
737    
738     delete $cfg->{profile}{$name};
739 root 1.14 ++$cfg->{dirty};
740 root 1.10 },
741 root 1.26 setparent => sub {
742 root 1.24 @ARGV >= 1
743     or die "profile name is missing\n";
744    
745     $profile->{parent} = shift @ARGV;
746     ++$cfg->{dirty};
747     },
748     delparent => sub {
749     delete $profile->{parent};
750     ++$cfg->{dirty};
751     },
752 root 1.17 showprofile => sub {
753     @ARGV >= 1
754     or die "profile name is missing\n";
755     my $name = shift @ARGV;
756    
757     print JSON::XS->new->pretty->encode ($cfg->{profile}{$name} || {});
758     },
759     showconfig => sub {
760     my $name = @ARGV ? shift @ARGV : AnyEvent::MP::Kernel::_nodename;
761    
762 root 1.25 my $profile = AnyEvent::MP::Config::find_profile $name, @ARGV;
763     @ARGV = ();
764 root 1.24
765     # make it look nicer:
766     delete $profile->{profile};
767     delete $profile->{parent};
768    
769     print JSON::XS->new->pretty->encode ($profile);
770 root 1.17 },
771 root 1.25
772     # undocumented
773     _resolve => sub {
774     print +(join ",", (AnyEvent::MP::Kernel::_resolve shift @ARGV)->recv), "\n";
775     },
776 root 1.1 );
777    
778 root 1.27 for (keys %CMD) {
779     $CMD{$1} = $CMD{$_} if /^set(.*)$/;
780     }
781    
782 root 1.1 sub docmd {
783     my $cmd = shift @ARGV;
784    
785     $CMD{$cmd}
786 root 1.23 or die "$cmd: no such aemp command (try perldoc aemp, or man aemp)";
787 root 1.1
788     $CMD{$cmd}();
789     }
790    
791     @ARGV
792 root 1.23 or die "Usage: aemp subcommand ... (try perldoc aemp, or man aemp)\n";
793 root 1.1
794 root 1.19 docmd while @ARGV;
795 root 1.1
796