| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::cpanel; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 71326 | use Exporter 'import'; | 
|  | 1 |  |  |  |  | 10 |  | 
|  | 1 |  |  |  |  | 82 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | our $VERSION = '0.004'; | 
| 6 |  |  |  |  |  |  | our @EXPORT_OK = qw(dispatch_cmd_print dispatch_cmd_raw_p dir_walk_p); | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | =head1 NAME | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | App::cpanel - CLI for cPanel UAPI and API 2 | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | =begin markdown | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | # PROJECT STATUS | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | [](https://metacpan.org/pod/App::cpanel) | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | =end markdown | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | $ cpanel uapi Notifications get_notifications_count | 
| 23 |  |  |  |  |  |  | $ cpanel uapi ResourceUsage get_usages | 
| 24 |  |  |  |  |  |  | $ cpanel uapi Fileman list_files dir=public_html | 
| 25 |  |  |  |  |  |  | $ cpanel uapi Fileman get_file_content dir=public_html file=index.html | 
| 26 |  |  |  |  |  |  | $ cpanel download public_html/index.html | 
| 27 |  |  |  |  |  |  | $ cpanel api2 Fileman fileop op=chmod metadata=0755 sourcefiles=public_html/cgi-bin/hello-world | 
| 28 |  |  |  |  |  |  | $ cpanel api2 Fileman fileop op=unlink sourcefiles=public_html/cgi-bin/hello-world | 
| 29 |  |  |  |  |  |  | $ cpanel api2 Fileman mkdir path= name=new-dir-at-top | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | # this one is one at a time but can overwrite files | 
| 32 |  |  |  |  |  |  | $ cpanel api2 Fileman savefile dir=public_html/cgi-bin filename=hello-world content="$(cat public_html/cgi-bin/hello-world)" | 
| 33 |  |  |  |  |  |  | # this is multiple files but refuses to overwrite | 
| 34 |  |  |  |  |  |  | $ cpanel upload public_html/cgi-bin hello-world | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | # download | 
| 37 |  |  |  |  |  |  | $ cpanel mirror public_html public_html cpanel localfs | 
| 38 |  |  |  |  |  |  | # upload | 
| 39 |  |  |  |  |  |  | $ cpanel mirror public_html public_html localfs cpanel | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | CLI for cPanel UAPI and also API 2, due to missing functionality in UAPI. | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | Stores session token in F<~/.cpanel-token>, a two-line file. First line | 
| 46 |  |  |  |  |  |  | is the URL component that goes after C. Second is the C | 
| 47 |  |  |  |  |  |  | cookie, which you can get from your browser's DevTools. | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | Stores relevant domain name in F<~/.cpanel-domain>. | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | Exportable: | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | =head2 dispatch_cmd_print | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | Will print the return value, using L except for | 
| 58 |  |  |  |  |  |  | C. | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | =head2 dispatch_cmd_raw_p | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | Returns a promise of the decoded JSON value or Ced content. | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =head2 dir_walk_p | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | Takes C<$from_dir>, C<$to_dir>, C<$from_map>, C<$to_map>. Copies the | 
| 67 |  |  |  |  |  |  | information in the first directory to the second, using the respective | 
| 68 |  |  |  |  |  |  | maps. Assumes UNIX-like semantics in filenames, i.e. C<$dir/$file>. | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | Returns a promise of completion. | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | The maps are hash-refs whose values are functions, and the keys are: | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | =head3 ls | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | Takes C<$dir>. Returns a promise of two hash-refs, of directories and of | 
| 77 |  |  |  |  |  |  | files. Each has keys of relative filename, values are an array-ref | 
| 78 |  |  |  |  |  |  | containing a string octal number representing UNIX permissions, and a | 
| 79 |  |  |  |  |  |  | number giving the C. Must reject if does not exist. | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | =head3 mkdir | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | Takes C<$dir>. Returns a promise of having created the directory. | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | =head3 read | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | Takes C<$dir>, C<$file>. Returns a promise of the file contents. | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | =head3 write | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | Takes C<$dir>, C<$file>. Returns a promise of having written the file | 
| 92 |  |  |  |  |  |  | contents. | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | =head3 chmod | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | Takes C<$path>, C<$perms>. Returns a promise of having changed the | 
| 97 |  |  |  |  |  |  | permissions. | 
| 98 |  |  |  |  |  |  |  | 
| 99 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | L | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | L | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | =head1 AUTHOR | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | Ed J | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | This is free software; you can redistribute it and/or modify it under | 
| 112 |  |  |  |  |  |  | the same terms as the Perl 5 programming language system itself. | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | =cut | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 1 |  |  | 1 |  | 7 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 20 |  | 
| 117 | 1 |  |  | 1 |  | 4 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 34 |  | 
| 118 | 1 |  |  | 1 |  | 538 | use Mojo::URL; | 
|  | 1 |  |  |  |  | 207308 |  | 
|  | 1 |  |  |  |  | 8 |  | 
| 119 | 1 |  |  | 1 |  | 574 | use Mojo::UserAgent; | 
|  | 1 |  |  |  |  | 273019 |  | 
|  | 1 |  |  |  |  | 11 |  | 
| 120 | 1 |  |  | 1 |  | 57 | use Mojo::File qw(path); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 47 |  | 
| 121 | 1 |  |  | 1 |  | 7 | use Mojo::Util qw(dumper encode); | 
|  | 1 |  |  |  |  | 8 |  | 
|  | 1 |  |  |  |  | 3429 |  | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | my %cmd2func = ( | 
| 124 |  |  |  |  |  |  | uapi => [ \&uapi_p, 1 ], | 
| 125 |  |  |  |  |  |  | download => [ \&download_p, 0 ], | 
| 126 |  |  |  |  |  |  | upload => [ \&upload_p, 1 ], | 
| 127 |  |  |  |  |  |  | api2 => [ \&api2_p, 1 ], | 
| 128 |  |  |  |  |  |  | mirror => [ \&mirror_p, 1 ], | 
| 129 |  |  |  |  |  |  | ); | 
| 130 |  |  |  |  |  |  | my $token_file = "$ENV{HOME}/.cpanel-token"; | 
| 131 |  |  |  |  |  |  | my $domain_file = "$ENV{HOME}/.cpanel-domain"; | 
| 132 |  |  |  |  |  |  | my %localfs_map = ( | 
| 133 |  |  |  |  |  |  | ls => \&localfs_ls, | 
| 134 |  |  |  |  |  |  | mkdir => \&localfs_mkdir, | 
| 135 |  |  |  |  |  |  | read => \&localfs_read, | 
| 136 |  |  |  |  |  |  | write => \&localfs_write, | 
| 137 |  |  |  |  |  |  | chmod => \&localfs_chmod, | 
| 138 |  |  |  |  |  |  | ); | 
| 139 |  |  |  |  |  |  | my %cpanel_map = ( | 
| 140 |  |  |  |  |  |  | ls => \&cpanel_ls, | 
| 141 |  |  |  |  |  |  | mkdir => \&cpanel_mkdir, | 
| 142 |  |  |  |  |  |  | read => \&cpanel_read, | 
| 143 |  |  |  |  |  |  | write => \&cpanel_write, | 
| 144 |  |  |  |  |  |  | chmod => \&cpanel_chmod, | 
| 145 |  |  |  |  |  |  | ); | 
| 146 |  |  |  |  |  |  | our %MAP2HASH = ( | 
| 147 |  |  |  |  |  |  | localfs => \%localfs_map, | 
| 148 |  |  |  |  |  |  | cpanel => \%cpanel_map, | 
| 149 |  |  |  |  |  |  | ); | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | sub dispatch_cmd_print { | 
| 152 | 0 |  |  | 0 | 1 | 0 | my $cmd = shift; | 
| 153 | 0 | 0 |  |  |  | 0 | die "No command\n" unless $cmd; | 
| 154 | 0 | 0 |  |  |  | 0 | die "Unknown command '$cmd'\n" unless my $info = $cmd2func{$cmd}; | 
| 155 | 0 |  |  |  |  | 0 | my $p = dispatch_cmd_raw_p($cmd, @_); | 
| 156 | 0 | 0 |  |  |  | 0 | $p = $p->then(\&dumper) if $info->[1]; | 
| 157 | 0 |  |  | 0 |  | 0 | $p->then(sub { print @_ }, sub { warn encode 'UTF-8', join '', @_ })->wait; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | sub dispatch_cmd_raw_p { | 
| 161 | 0 |  |  | 0 | 1 | 0 | my $cmd = shift; | 
| 162 | 0 | 0 |  |  |  | 0 | die "No command\n" unless $cmd; | 
| 163 | 0 | 0 |  |  |  | 0 | die "Unknown command '$cmd'\n" unless my $info = $cmd2func{$cmd}; | 
| 164 | 0 |  |  |  |  | 0 | goto &{$info->[0]}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | sub api_request { | 
| 168 | 0 |  |  | 0 | 0 | 0 | my ($method, $domain, $token, $parts, $args, @extra_args) = @_; | 
| 169 | 0 |  |  |  |  | 0 | my ($url_token, $cookie_token) = split /\s+/, $token; | 
| 170 | 0 |  |  |  |  | 0 | my $url = Mojo::URL->new("https://$domain:2083"); | 
| 171 | 0 |  |  |  |  | 0 | $url->path(join '/', '', "cpsess$url_token", @$parts); | 
| 172 | 0 | 0 |  |  |  | 0 | $url->query(%$args) if $args; | 
| 173 | 0 |  |  |  |  | 0 | CORE::state $ua = Mojo::UserAgent->new; # state as needs to live long enough to complete request | 
| 174 | 0 |  |  |  |  | 0 | $ua->$method( | 
| 175 |  |  |  |  |  |  | $url->to_abs . "", | 
| 176 |  |  |  |  |  |  | { Cookie => "cpsession=$cookie_token" }, | 
| 177 |  |  |  |  |  |  | @extra_args, | 
| 178 |  |  |  |  |  |  | ); | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | sub read_file { | 
| 182 | 0 |  |  | 0 | 0 | 0 | my ($file) = @_; | 
| 183 | 0 | 0 |  |  |  | 0 | die "$file: $!\n" unless -f $file; | 
| 184 | 0 | 0 |  |  |  | 0 | die "$file: too readable\n" if (stat $file)[2] & 0044; | 
| 185 | 0 |  |  |  |  | 0 | local $/; | 
| 186 | 0 | 0 |  |  |  | 0 | open my $fh, $file or die "$file: $!\n"; | 
| 187 | 0 |  |  |  |  | 0 | my $content = <$fh>; | 
| 188 | 0 | 0 |  |  |  | 0 | die "No content in '$file'\n" unless $content; | 
| 189 | 0 |  |  |  |  | 0 | $content =~ s/^\s*(.*?)\s*$/$1/g; | 
| 190 | 0 |  |  |  |  | 0 | $content; | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 0 |  |  | 0 | 0 | 0 | sub read_token { read_file($token_file) } | 
| 194 | 0 |  |  | 0 | 0 | 0 | sub read_domain { read_file($domain_file) } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub _error_or_json { | 
| 197 | 0 |  |  | 0 |  | 0 | my $res = $_[0]->res; | 
| 198 | 0 | 0 |  |  |  | 0 | die $res->code . ": " . $res->message . "\n" if $res->code != 200; | 
| 199 | 0 |  |  |  |  | 0 | $res->json; | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | sub _uapi_error_or_json { | 
| 203 | 0 |  |  | 0 |  | 0 | my $json = $_[0]; | 
| 204 | 0 | 0 |  |  |  | 0 | if (!$json->{status}) { | 
| 205 |  |  |  |  |  |  | die join '', "Failed:\n", | 
| 206 | 0 | 0 |  |  |  | 0 | map "$_\n", map @{ $json->{$_} || [] }, qw(errors warnings); | 
|  | 0 |  |  |  |  | 0 |  | 
| 207 |  |  |  |  |  |  | } | 
| 208 | 0 |  |  |  |  | 0 | $json; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | sub uapi_p { | 
| 212 | 0 |  |  | 0 | 0 | 0 | my ($module, $function, @args) = @_; | 
| 213 | 0 | 0 |  |  |  | 0 | die "No module\n" unless $module; | 
| 214 | 0 | 0 |  |  |  | 0 | die "No function\n" unless $function; | 
| 215 | 0 |  |  |  |  | 0 | my ($token, $domain) = (read_token(), read_domain()); | 
| 216 | 0 | 0 |  |  |  | 0 | my $args_hash = ref($args[0]) eq 'HASH' | 
|  |  | 0 |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | ? $args[0] | 
| 218 |  |  |  |  |  |  | : { map split('=', $_, 2), @args } | 
| 219 |  |  |  |  |  |  | if @args; | 
| 220 | 0 |  |  |  |  | 0 | my $tx_p = api_request 'get_p', $domain, $token, | 
| 221 |  |  |  |  |  |  | [ 'execute', $module, $function ], | 
| 222 |  |  |  |  |  |  | $args_hash; | 
| 223 | 0 |  |  |  |  | 0 | $tx_p->then(\&_error_or_json)->then(\&_uapi_error_or_json); | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  |  | 
| 226 |  |  |  |  |  |  | sub download_p { | 
| 227 | 0 |  |  | 0 | 0 | 0 | my ($file) = @_; | 
| 228 | 0 | 0 |  |  |  | 0 | die "No file\n" unless $file; | 
| 229 | 0 |  |  |  |  | 0 | my ($token, $domain) = (read_token(), read_domain()); | 
| 230 | 0 |  |  |  |  | 0 | my $tx_p = api_request 'get_p', $domain, $token, | 
| 231 |  |  |  |  |  |  | [ 'download' ], | 
| 232 |  |  |  |  |  |  | { skipencode => 1, file => $file }; | 
| 233 |  |  |  |  |  |  | $tx_p->then(sub { | 
| 234 | 0 |  |  | 0 |  | 0 | my $res = $_[0]->res; | 
| 235 | 0 | 0 |  |  |  | 0 | die $res->code . ": " . $res->message . "\n" if $res->code != 200; | 
| 236 | 0 |  |  |  |  | 0 | $res->body; | 
| 237 | 0 |  |  |  |  | 0 | }); | 
| 238 |  |  |  |  |  |  | } | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | sub make_upload_form { | 
| 241 | 0 |  |  | 0 | 0 | 0 | my $dir = shift; | 
| 242 | 0 |  |  |  |  | 0 | my $counter = 0; | 
| 243 |  |  |  |  |  |  | +{ | 
| 244 |  |  |  |  |  |  | dir => $dir, | 
| 245 |  |  |  |  |  |  | map { | 
| 246 | 0 |  |  |  |  | 0 | my $p = path $_; | 
|  | 0 |  |  |  |  | 0 |  | 
| 247 | 0 |  |  |  |  | 0 | ('file-' . ++$counter => { | 
| 248 |  |  |  |  |  |  | filename => $p->basename, | 
| 249 |  |  |  |  |  |  | content => $p->slurp, | 
| 250 |  |  |  |  |  |  | }); | 
| 251 |  |  |  |  |  |  | } @_, | 
| 252 |  |  |  |  |  |  | }; | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | sub upload_p { | 
| 256 | 0 |  |  | 0 | 0 | 0 | my ($dir, @files) = @_; | 
| 257 | 0 | 0 |  |  |  | 0 | die "No dir\n" unless $dir; | 
| 258 | 0 | 0 |  |  |  | 0 | die "No files\n" unless @files; | 
| 259 | 0 |  |  |  |  | 0 | my ($token, $domain) = (read_token(), read_domain()); | 
| 260 | 0 |  |  |  |  | 0 | my $tx_p = api_request 'post_p', $domain, $token, | 
| 261 |  |  |  |  |  |  | [ 'execute', 'Fileman', 'upload_files' ], | 
| 262 |  |  |  |  |  |  | undef, | 
| 263 |  |  |  |  |  |  | form => make_upload_form($dir, @files), | 
| 264 |  |  |  |  |  |  | ; | 
| 265 | 0 |  |  |  |  | 0 | $tx_p->then(\&_error_or_json)->then(\&_uapi_error_or_json); | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | sub _api2_error_or_json { | 
| 269 | 0 |  |  | 0 |  | 0 | my $json = $_[0]; | 
| 270 | 0 |  |  |  |  | 0 | my $result = $json->{cpanelresult}; | 
| 271 | 0 | 0 | 0 |  |  | 0 | if (!$result or !$result->{event}{result} or $result->{error}) { | 
|  |  |  | 0 |  |  |  |  | 
| 272 |  |  |  |  |  |  | die join '', "Failed:\n", | 
| 273 |  |  |  |  |  |  | map "$_\n", | 
| 274 |  |  |  |  |  |  | ($result->{error} ? $result->{error} : ()), | 
| 275 | 0 | 0 |  |  |  | 0 | (map "$_->{src}: $_->{err}", grep !$_->{result}, @{$result->{data} || []}), | 
|  | 0 | 0 |  |  |  | 0 |  | 
| 276 |  |  |  |  |  |  | ; | 
| 277 |  |  |  |  |  |  | } | 
| 278 | 0 |  |  |  |  | 0 | $json; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | sub api2_p { | 
| 282 | 0 |  |  | 0 | 0 | 0 | my ($module, $function, @args) = @_; | 
| 283 | 0 | 0 |  |  |  | 0 | die "No module\n" unless $module; | 
| 284 | 0 | 0 |  |  |  | 0 | die "No function\n" unless $function; | 
| 285 | 0 |  |  |  |  | 0 | my ($token, $domain) = (read_token(), read_domain()); | 
| 286 | 0 | 0 |  |  |  | 0 | my $args_hash = ref($args[0]) eq 'HASH' | 
|  |  | 0 |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | ? $args[0] | 
| 288 |  |  |  |  |  |  | : { map split('=', $_, 2), @args } | 
| 289 |  |  |  |  |  |  | if @args; | 
| 290 |  |  |  |  |  |  | my $tx_p = api_request 'post_p', $domain, $token, | 
| 291 |  |  |  |  |  |  | [ qw(json-api cpanel) ], | 
| 292 |  |  |  |  |  |  | { | 
| 293 |  |  |  |  |  |  | cpanel_jsonapi_module => $module, | 
| 294 |  |  |  |  |  |  | cpanel_jsonapi_func => $function, | 
| 295 |  |  |  |  |  |  | cpanel_jsonapi_apiversion => 2, | 
| 296 | 0 | 0 |  |  |  | 0 | %{ $args_hash || {} }, | 
|  | 0 |  |  |  |  | 0 |  | 
| 297 |  |  |  |  |  |  | }; | 
| 298 | 0 |  |  |  |  | 0 | $tx_p->then(\&_error_or_json)->then(\&_api2_error_or_json); | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | sub dir_walk_p { | 
| 302 | 2 |  |  | 2 | 1 | 141 | my ($from_dir, $to_dir, $from_map, $to_map, $to_dir_created) = @_; | 
| 303 | 2 |  |  |  |  | 5 | my $to_dir_create_p; | 
| 304 | 2 | 100 |  |  |  | 6 | if ($to_dir_created) { | 
| 305 | 1 |  |  |  |  | 15 | $to_dir_create_p = Mojo::Promise->resolve(1); | 
| 306 |  |  |  |  |  |  | } else { | 
| 307 | 1 |  |  |  |  | 2 | my $from_dir_perms; | 
| 308 |  |  |  |  |  |  | $to_dir_create_p = $to_map->{ls}->($to_dir)->catch(sub { | 
| 309 |  |  |  |  |  |  | # only create if ls fails | 
| 310 | 1 |  |  | 1 |  | 411 | $to_map->{mkdir}->($to_dir) | 
| 311 |  |  |  |  |  |  | })->then(sub { | 
| 312 | 1 |  |  | 1 |  | 474 | $from_map->{ls}->(path($from_dir)->dirname) | 
| 313 |  |  |  |  |  |  | })->then(sub { | 
| 314 | 1 |  |  | 1 |  | 489 | my ($dirs, $files) = @_; | 
| 315 | 1 |  | 50 |  |  | 3 | $from_dir_perms = $dirs->{path($from_dir)->basename}[0] || '0755'; | 
| 316 |  |  |  |  |  |  | })->then(sub { | 
| 317 | 1 |  |  | 1 |  | 137 | $to_map->{chmod}->($to_dir, $from_dir_perms) | 
| 318 | 1 |  |  |  |  | 8 | }); | 
| 319 |  |  |  |  |  |  | } | 
| 320 |  |  |  |  |  |  | $to_dir_create_p->then(sub { | 
| 321 | 2 |  |  | 2 |  | 580 | $from_map->{ls}->($from_dir) | 
| 322 |  |  |  |  |  |  | })->then(sub { | 
| 323 | 2 |  |  | 2 |  | 1020 | my ($dirs, $files) = @_; | 
| 324 |  |  |  |  |  |  | my @dir_create_p = map { | 
| 325 | 2 |  |  |  |  | 10 | my $this_dir = $_; | 
|  | 1 |  |  |  |  | 3 |  | 
| 326 |  |  |  |  |  |  | $to_map->{mkdir}->("$to_dir/$this_dir") | 
| 327 | 1 |  |  |  |  | 280 | ->then(sub { $to_map->{chmod}->("$to_dir/$this_dir", $dirs->{$this_dir}[0]) }) | 
| 328 | 1 |  |  |  |  | 565 | ->then(sub { dir_walk_p("$from_dir/$this_dir", "$to_dir/$this_dir", $from_map, $to_map, 1) }) | 
| 329 | 1 |  |  |  |  | 5 | } sort keys %$dirs; | 
| 330 |  |  |  |  |  |  | my @file_create_p = map { | 
| 331 | 2 |  |  |  |  | 217 | my $this_file = $_; | 
|  | 3 |  |  |  |  | 201 |  | 
| 332 |  |  |  |  |  |  | $from_map->{read}->($from_dir, $this_file) | 
| 333 | 3 |  |  |  |  | 586 | ->then(sub { $to_map->{write}->($to_dir, $this_file, $_[0]) }) | 
| 334 | 3 |  |  |  |  | 715 | ->then(sub { $to_map->{chmod}->("$to_dir/$this_file", $files->{$this_file}[0]) }) | 
| 335 | 3 |  |  |  |  | 11 | } sort keys %$files; | 
| 336 | 2 |  |  |  |  | 417 | Mojo::Promise->all(@dir_create_p, @file_create_p); | 
| 337 | 2 |  |  |  |  | 626 | }); | 
| 338 |  |  |  |  |  |  | } | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | sub mirror_p { | 
| 341 | 0 |  |  | 0 | 0 |  | my ($from_dir, $to_dir, $from_map, $to_map) = @_; | 
| 342 | 0 | 0 |  |  |  |  | die "No from_dir\n" unless $from_dir; | 
| 343 | 0 | 0 |  |  |  |  | die "No to_dir\n" unless $to_dir; | 
| 344 | 0 | 0 |  |  |  |  | die "No from_map\n" unless $from_map; | 
| 345 | 0 | 0 |  |  |  |  | die "No to_map\n" unless $to_map; | 
| 346 | 0 | 0 |  |  |  |  | die "Invalid from_map\n" unless $from_map = $MAP2HASH{$from_map}; | 
| 347 | 0 | 0 |  |  |  |  | die "Invalid to_map\n" unless $to_map = $MAP2HASH{$to_map}; | 
| 348 | 0 |  |  |  |  |  | dir_walk_p $from_dir, $to_dir, $from_map, $to_map; | 
| 349 |  |  |  |  |  |  | } | 
| 350 |  |  |  |  |  |  |  | 
| 351 |  |  |  |  |  |  | sub localfs_ls { | 
| 352 | 0 |  |  | 0 | 0 |  | my ($dir) = @_; | 
| 353 | 0 |  |  |  |  |  | my $dir_path = path($dir); | 
| 354 |  |  |  |  |  |  | my %files = map { | 
| 355 | 0 |  |  |  |  |  | ($_->basename => [ sprintf("%04o", $_->lstat->mode & 07777), $_->lstat->mtime ]) | 
|  | 0 |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | } $dir_path->list->each; | 
| 357 |  |  |  |  |  |  | my %dirs = map { | 
| 358 | 0 |  |  |  |  |  | ($_->basename => [ sprintf("%04o", $_->lstat->mode & 07777), $_->lstat->mtime ]) | 
| 359 | 0 |  |  | 0 |  |  | } $dir_path->list({dir => 1})->grep(sub { !$files{$_->basename} })->each; | 
|  | 0 |  |  |  |  |  |  | 
| 360 | 0 |  |  |  |  |  | Mojo::Promise->resolve(\%dirs, \%files); | 
| 361 |  |  |  |  |  |  | } | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | sub localfs_mkdir { | 
| 364 | 0 |  |  | 0 | 0 |  | my ($dir) = @_; | 
| 365 | 0 |  |  |  |  |  | my $dir_path = path($dir); | 
| 366 | 0 |  |  |  |  |  | $dir_path->make_path; | 
| 367 | 0 |  |  |  |  |  | Mojo::Promise->resolve(1); | 
| 368 |  |  |  |  |  |  | } | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | sub localfs_read { | 
| 371 | 0 |  |  | 0 | 0 |  | my ($dir, $file) = @_; | 
| 372 | 0 |  |  |  |  |  | my $path = path($dir)->child($file); | 
| 373 | 0 |  |  |  |  |  | Mojo::Promise->resolve($path->slurp); | 
| 374 |  |  |  |  |  |  | } | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | sub localfs_write { | 
| 377 | 0 |  |  | 0 | 0 |  | my ($dir, $file, $content) = @_; | 
| 378 | 0 |  |  |  |  |  | my $path = path($dir)->child($file); | 
| 379 | 0 |  |  |  |  |  | $path->spurt($content); | 
| 380 | 0 |  |  |  |  |  | Mojo::Promise->resolve(1); | 
| 381 |  |  |  |  |  |  | } | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | sub localfs_chmod { | 
| 384 | 0 |  |  | 0 | 0 |  | my ($path, $perms) = @_; | 
| 385 | 0 |  |  |  |  |  | $path = path($path); | 
| 386 | 0 |  |  |  |  |  | $path->chmod(oct $perms); | 
| 387 | 0 |  |  |  |  |  | Mojo::Promise->resolve(1); | 
| 388 |  |  |  |  |  |  | } | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | sub cpanel_ls { | 
| 391 | 0 |  |  | 0 | 0 |  | my ($dir) = @_; | 
| 392 |  |  |  |  |  |  | uapi_p(qw(Fileman list_files), { dir => $dir })->then(sub { | 
| 393 | 0 |  |  | 0 |  |  | my (%dirs, %files); | 
| 394 |  |  |  |  |  |  | ($_->{type} eq 'dir' ? \%dirs : \%files)->{$_->{file}} = | 
| 395 |  |  |  |  |  |  | [ $_->{nicemode}, $_->{mtime} ] | 
| 396 | 0 | 0 |  |  |  |  | for @{ $_[0]->{data} }; | 
|  | 0 |  |  |  |  |  |  | 
| 397 | 0 |  |  |  |  |  | (\%dirs, \%files); | 
| 398 | 0 |  |  |  |  |  | }); | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  |  | 
| 401 |  |  |  |  |  |  | sub cpanel_read { | 
| 402 | 0 |  |  | 0 | 0 |  | my ($dir, $file) = @_; | 
| 403 | 0 |  |  |  |  |  | download_p "$dir/$file"; | 
| 404 |  |  |  |  |  |  | } | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | sub cpanel_mkdir { | 
| 407 | 0 |  |  | 0 | 0 |  | my ($dir) = @_; | 
| 408 | 0 |  |  |  |  |  | $dir = path $dir; | 
| 409 | 0 |  |  |  |  |  | api2_p qw(Fileman mkdir), { path => $dir->dirname, name => $dir->basename }; | 
| 410 |  |  |  |  |  |  | } | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | sub cpanel_write { | 
| 413 | 0 |  |  | 0 | 0 |  | my ($dir, $file, $content) = @_; | 
| 414 | 0 |  |  |  |  |  | api2_p qw(Fileman savefile), { | 
| 415 |  |  |  |  |  |  | dir => $dir, filename => $file, content => $content, | 
| 416 |  |  |  |  |  |  | }; | 
| 417 |  |  |  |  |  |  | } | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | sub cpanel_chmod { | 
| 420 | 0 |  |  | 0 | 0 |  | my ($path, $perms) = @_; | 
| 421 | 0 |  |  |  |  |  | api2_p qw(Fileman fileop), { | 
| 422 |  |  |  |  |  |  | op => 'chmod', metadata => $perms, sourcefiles => $path, | 
| 423 |  |  |  |  |  |  | }; | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | 1; |