File Coverage

blib/lib/Setup/Unix/User.pm
Criterion Covered Total %
statement 136 160 85.0
branch 59 100 59.0
condition 34 74 45.9
subroutine 14 15 93.3
pod 4 4 100.0
total 247 353 69.9


line stmt bran cond sub pod time code
1             package Setup::Unix::User;
2              
3             our $DATE = '2017-07-10'; # DATE
4             our $VERSION = '0.14'; # VERSION
5              
6 4     4   62162 use 5.010001;
  4         15  
7 4     4   20 use strict;
  4         9  
  4         73  
8 4     4   17 use warnings;
  4         7  
  4         118  
9 4     4   859 use experimental 'smartmatch';
  4         5885  
  4         30  
10 4     4   6832 use Log::ger;
  4         198  
  4         26  
11              
12 4     4   4409 use List::Util qw(first);
  4         8  
  4         310  
13 4     4   819 use PerlX::Maybe;
  4         2980  
  4         186  
14 4     4   2379 use Setup::File;
  4         66287  
  4         287  
15 4     4   1510 use Unix::Passwd::File;
  4         36301  
  4         7364  
16              
17             require Exporter;
18             our @ISA = qw(Exporter);
19             our @EXPORT_OK = qw(setup_unix_user);
20              
21             our %SPEC;
22              
23             $SPEC{':package'} = {
24             v => 1.1,
25             summary => 'Setup Unix user (existence, home dir, group memberships)',
26             };
27              
28             sub _rand_pass {
29 0     0   0 require Text::Password::Pronounceable;
30 0         0 Text::Password::Pronounceable->generate(10, 16);
31             }
32              
33             my %common_args = (
34             etc_dir => {
35             summary => 'Location of passwd files',
36             schema => ['str*' => {default=>'/etc'}],
37             },
38             user => {
39             schema => 'str*',
40             summary => 'User name',
41             },
42             );
43              
44             $SPEC{deluser} = {
45             v => 1.1,
46             summary => 'Delete user',
47             args => {
48             %common_args,
49             },
50             features => {
51             tx => {v=>2},
52             idempotent => 1,
53             },
54             };
55             sub deluser {
56 217     217 1 4565939 my %args = @_;
57              
58 217   50     2078 my $tx_action = $args{-tx_action} // '';
59 217         1025 my $dry_run = $args{-dry_run};
60 217 50       1672 my $user = $args{user} or return [400, "Please specify user"];
61 217 50       2864 $user =~ $Unix::Passwd::File::re_user
62             or return [400, "Invalid user"];
63 217         1510 my %ca = (etc_dir => $args{etc_dir}, user=>$user);
64 217         735 my $res;
65              
66 217 100       1303 if ($tx_action eq 'check_state') {
    50          
67 115         997 $res = Unix::Passwd::File::get_user(%ca);
68 115 50 66     97686 return $res unless $res->[0] == 200 || $res->[0] == 404;
69              
70 115 100       1131 return [304, "User $user already doesn't exist"] if $res->[0] == 404;
71 103 50       500 log_info("(DRY) Deleting Unix user $user ...") if $dry_run;
72             return [200, "User $user needs to be deleted", undef, {undo_actions=>[
73 103         5125 [adduser => {%ca, uid => $res->[2]{uid}}],
74             ]}];
75             } elsif ($tx_action eq 'fix_state') {
76 102         1093 log_info("Deleting Unix user $user ...");
77 102         1051 return Unix::Passwd::File::delete_user(%ca);
78             }
79 0         0 [400, "Invalid -tx_action"];
80             }
81              
82             my %adduser_args = (
83             uid => {
84             summary => 'Add with specified UID',
85             description => <<'_',
86              
87             If not specified, will search an unused UID from `min_uid` to `max_uid`.
88              
89             _
90             schema => 'int',
91             },
92             min_uid => {
93             schema => [int => {default=>1000}],
94             },
95             max_uid => {
96             schema => [int => {default=>65534}],
97             },
98             gid => {
99             summary => 'When creating group, use specific GID',
100             description => <<'_',
101              
102             If not specified, will search an unused GID from `min_gid` to `max_gid`.
103              
104             _
105             schema => 'int',
106             },
107             min_gid => {
108             schema => [int => {default=>1000}],
109             },
110             max_gid => {
111             schema => [int => {default=>65534}],
112             },
113             gecos => {
114             schema => 'str',
115             },
116             pass => {
117             schema => 'str',
118             },
119             home => {
120             schema => 'str',
121             },
122             shell => {
123             schema => 'str',
124             },
125             );
126             $SPEC{adduser} = {
127             v => 1.1,
128             summary => 'Add user',
129             args => {
130             %common_args,
131             %adduser_args,
132             group => {
133             schema => 'str*',
134             summary => 'Group name',
135             },
136             },
137             features => {
138             tx => {v=>2},
139             idempotent => 1,
140             },
141             };
142             sub adduser {
143 273     273 1 8590672 my %args = @_;
144              
145 273   50     2569 my $tx_action = $args{-tx_action} // '';
146 273         1155 my $dry_run = $args{-dry_run};
147 273 50       1940 my $user = $args{user} or return [400, "Please specify user"];
148 273 50       4255 $user =~ $Unix::Passwd::File::re_user
149             or return [400, "Invalid user"];
150 273         1245 my $uid = $args{uid};
151 273         1571 my %ca0 = (etc_dir => $args{etc_dir});
152 273         1658 my %ca = (%ca0, user=>$user);
153 273         879 my $res;
154              
155 273 100       1774 if ($tx_action eq 'check_state') {
    50          
156 149         1270 $res = Unix::Passwd::File::get_user(%ca);
157 149 50 66     124125 return $res unless $res->[0] == 200 || $res->[0] == 404;
158              
159 149 100       1327 if ($res->[0] == 200) {
160 17 100 100     171 if (!defined($uid) || $uid == $res->[2]{uid}) {
161 15         623 return [304, "User $user already exists"];
162             } else {
163 2         70 return [412, "User $user already exists but with different ".
164             "UID ($res->[2]{uid}, wanted $uid)"];
165             }
166             } else {
167 132 50       700 log_info("(DRY) Adding Unix user $user ...") if $dry_run;
168 132         6228 return [200, "User $user needs to be added", undef,
169             {undo_actions=>[
170             [deluser => {%ca}],
171             ]}];
172             }
173             } elsif ($tx_action eq 'fix_state') {
174             # we don't want to have to get_user() when fixing state, to reduce
175             # number of read passes to the passwd files
176 124         1387 log_info("Adding Unix user $user ...");
177             $res = Unix::Passwd::File::add_user(
178             %ca,
179             maybe uid => $uid,
180             min_uid => $args{min_uid} // 1000,
181             max_uid => $args{max_uid} // 65534,
182             maybe group => $args{group},
183             maybe gid => $args{gid},
184             min_gid => $args{min_gid} // 1000,
185             max_gid => $args{max_gid} // 65534,
186             maybe pass => $args{pass},
187             maybe gecos => $args{gecos},
188             maybe home => $args{home},
189             maybe shell => $args{shell},
190 124   50     6068 );
      50        
      50        
      50        
191 124 100       590053 if ($res->[0] == 200) {
192 122         1149 $args{-stash}{result}{uid} = $uid;
193 122         723 $args{-stash}{result}{gid} = $res;
194 122         6120 return [200, "Created"];
195             } else {
196 2         81 return $res;
197             }
198             }
199 0         0 [400, "Invalid -tx_action"];
200             }
201              
202             $SPEC{add_delete_user_groups} = {
203             v => 1.1,
204             summary => "Add/delete user from group memberships",
205             args => {
206             %common_args,
207             add_to => {
208             schema => ['array*' => {of=>'str*'}],
209             req => 1,
210             },
211             delete_from => {
212             schema => ['array*' => {of=>'str*'}],
213             req => 1,
214             },
215             },
216             features => {
217             tx => {v=>2},
218             idempotent => 1,
219             },
220             };
221             sub add_delete_user_groups {
222 39     39 1 696078 my %args = @_;
223              
224 39   50     268 my $tx_action = $args{-tx_action} // '';
225 39         140 my $dry_run = $args{-dry_run};
226 39 50       196 my $user = $args{user} or return [400, "Please specify user"];
227 39 50       424 $user =~ $Unix::Passwd::File::re_user
228             or return [400, "Invalid user"];
229             my $add_to = $args{add_to}
230 39 50       438 or return [400, "Please specify add_to"];
231             my $del_from = $args{delete_from}
232 39 50       232 or return [400, "Please specify delete_from"];
233 39         189 my %ca = (etc_dir=>$args{etc_dir}, user=>$user);
234 39         111 my $res;
235              
236 39 100       229 if ($tx_action eq 'check_state') {
    50          
237 20         146 $res = Unix::Passwd::File::get_user_groups(%ca);
238 20 50       10355 return $res unless $res->[0] == 200;
239 20         64 my $cur_groups = $res->[2];
240              
241 20         61 my (@needs_add, @needs_del);
242 20         76 for (@$add_to) {
243 20 100       159 push @needs_add, $_ unless $_ ~~ $cur_groups;
244             }
245 20         73 for (@$del_from) {
246 20 100       122 push @needs_del, $_ if $_ ~~ $cur_groups;
247             }
248              
249 20 100 66     120 if (@needs_add || @needs_del) {
250 19 50       84 log_info(
251             "(DRY) Fixing user %s's membership, groups to add to: %s, ".
252             "groups to delete from: %s ...",
253             $user, \@needs_add, \@needs_del) if $dry_run;
254 19         986 return [200, "User $user needs to fix membership, groups to add ".
255             "to: (".join(", ",@needs_add)."), groups to delete from ".
256             "(".join(", ",@needs_del).")",
257             undef, {undo_actions=>[
258             [add_delete_user_groups=>{
259             %ca,
260             add_to=>\@needs_del, delete_from=>\@needs_add}],
261             ]}
262             ];
263             } else {
264 1         31 return [304, "User $user already belongs to the wanted groups"];
265             }
266             } elsif ($tx_action eq 'fix_state') {
267             # we don't want to have to get_user_groups() when fixing state, to
268             # reduce number of read passes to the passwd files
269 19         188 log_info("Fixing user %s's membership, groups to add to: %s, ".
270             "groups to delete from: %s ...",
271             $user, $add_to, $del_from);
272 19         209 return Unix::Passwd::File::add_delete_user_groups(
273             %ca, add_to=>$add_to, delete_from=>$del_from);
274             }
275 0         0 [400, "Invalid -tx_action"];
276             }
277              
278             $SPEC{setup_unix_user} = {
279             v => 1.1,
280             summary => "Setup Unix user (existence, group memberships)",
281             description => <<'_',
282              
283             On do, will create Unix user if not already exists. And also make sure user
284             belong to specified groups (and not belong to unwanted groups). Return the
285             created UID/GID in the result.
286              
287             On undo, will delete Unix user (along with its initially created home dir and
288             files) if it was created by this function. Also will restore old group
289             memberships.
290              
291             _
292             args => {
293             %common_args,
294             should_exist => {
295             schema => ['bool' => {default => 1}],
296             summary => 'Whether user should exist',
297             },
298             should_already_exist => {
299             schema => 'bool',
300             summary => 'Whether user should already exist',
301             },
302             member_of => {
303             schema => ['array' => {of=>'str*'}],
304             summary => 'List of Unix group names that the user must be '.
305             'member of',
306             description => <<'_',
307              
308             If not specified, member_of will be set to just the primary group. The primary
309             group will always be added even if not specified.
310              
311             _
312             },
313             not_member_of => {
314             schema => ['array' => {of=>'str*'}],
315             summary => 'List of Unix group names that the user must NOT be '.
316             'member of',
317             },
318             (map {("new_$_" => $adduser_args{$_})} keys %adduser_args),
319             create_home => {
320             schema => [bool => {default=>1}],
321             summary => 'Whether to create home directory when creating user',
322             },
323             ## new_home_mode? new_home_group? fix existing home dir?
324             #home_mode => {
325             # schema => [str => {default=>0700}],
326             # summary => 'Permission mode when creating home directory',
327             #},
328             use_skel => {
329             schema => [bool => {default=>1}],
330             summary => 'Whether to copy files from skeleton dir '.
331             'when creating user',
332             },
333             skel_dir => {
334             schema => [str => {default => '/etc/skel'}],
335             summary => 'Directory to get skeleton files when creating user',
336             },
337             group => {
338             schema => 'str*',
339             summary => 'Group name',
340             },
341             min_uid => {},
342             max_uid => {},
343             min_new_uid => {},
344             max_new_uid => {},
345             min_gid => {},
346             max_gid => {},
347             min_new_gid => {},
348             max_new_gid => {},
349             },
350             features => {
351             tx => {v=>2},
352             idempotent => 1,
353             },
354             };
355             sub setup_unix_user {
356 9     9 1 709854 require File::Copy::Undoable;
357 9         6288 require File::Trash::Undoable;
358              
359 9         98 my %args = @_;
360              
361             # TMP, SCHEMA
362 9         39 my $dry_run = $args{-dry_run};
363             my $taid = $args{-tx_action_id}
364 9 100       58 or return [400, "Please specify -tx_action_id"];
365 8 50       49 my $user = $args{user} or return [400, "Please specify user"];
366 8 50       97 $user =~ $Unix::Passwd::File::re_user
367             or return [400, "Invalid user"];
368 8   50     56 my $should_exist = $args{should_exist} // 1;
369 8         30 my $should_aexist = $args{should_already_exist};
370 8   50     47 my $create_home = $args{create_home} // 1;
371             #my $home_mode = $args{home_mode} // 0700;
372 8   50     42 my $use_skel = $args{use_skel} // 1;
373 8   50     38 my $skel_dir = $args{skel_dir} // "/etc/skel";
374 8   33     73 my $group = $args{group} // $args{user};
375 8   50     45 my $member_of = $args{member_of} // [];
376 8 100       49 push @$member_of, $group unless $group ~~ @$member_of;
377 8   50     36 my $not_member_of = $args{not_member_of} // [];
378 8         39 for (@$member_of) {
379 16 50       75 return [400, "Group $_ is in member_of and not_member_of"]
380             if $_ ~~ @$not_member_of;
381             }
382 8         47 my %ca0 = (etc_dir=>$args{etc_dir});
383 8         38 my %ca = (%ca0, user=>$user);
384              
385             # we use this function so we reduce the number of read passes through the
386             # passwd files.
387 8         66 my $res = Unix::Passwd::File::list_users_and_groups(%ca0, detail=>1);
388 8 50       9436 return $res unless $res->[0] == 200;
389 8         30 my $users = $res->[2][0];
390 8         24 my $groups = $res->[2][1];
391 8     32   80 my $uentry = first {$_->{user} eq $user} @$users;
  32         148  
392 8         44 my $exists = !!$uentry;
393 8   33     44 my $home = $uentry->{home} // $args{new_home} // "/home/$user";
      0        
394              
395 8         29 my (@do, @undo);
396              
397             my %addargs = (
398             %ca,
399             maybe group => $args{group},
400             maybe uid => $args{new_uid},
401             maybe min_uid => $args{min_new_uid},
402             maybe max_uid => $args{max_new_uid},
403             maybe gid => $args{new_gid},
404             maybe min_gid => $args{min_new_gid},
405             maybe max_gid => $args{max_new_gid},
406             maybe pass => $args{new_pass},
407             maybe gecos => $args{new_gecos},
408             maybe home => $args{new_home},
409             maybe shell => $args{new_shell},
410 8         224 );
411              
412             #$log->tracef("user=%s, exists=%s, uentry=%s, should_exist=%s, ", $user, $exists, $uentry, $should_exist);
413             {
414             # create user
415 8 50       42 if ($exists) {
  8         37  
416 8 50       35 if (!$should_exist) {
417 0 0       0 log_info("(DRY) Deleting user $user ...") if $dry_run;
418 0         0 push @do , [deluser=>{%ca}];
419 0         0 unshift @undo, [adduser=>\%addargs];
420 0         0 last;
421             }
422             } else {
423 0 0       0 if ($should_aexist) {
    0          
424 0         0 return [412, "User $user should already exist"];
425             } elsif ($should_exist) {
426 0 0       0 log_info("(DRY) Adding user $user ...") if $dry_run;
427 0         0 push @do , [adduser=>\%addargs];
428 0         0 unshift @undo, [deluser=>{%ca}];
429             }
430             }
431              
432             # fix group membership
433 8 50       29 if ($exists) {
434 8         22 my (@needs_add, @needs_del);
435 8         32 for my $l (@$groups) {
436 48 100       155 next if $l->{group} eq $group; # leave primary group alone
437 40         129 my @mm = split /,/, $l->{members};
438             push @needs_add, $l->{group}
439 40 100 100     236 if $l->{group} ~~ @$member_of && !($user ~~ @mm);
440             push @needs_del, $l->{group}
441 40 100 100     234 if $l->{group} ~~ @$not_member_of && ($user ~~ @mm);
442             }
443 8 100 66     92 push @do,
444             [add_delete_user_groups=>{
445             %ca,
446             add_to=>\@needs_add, delete_from=>\@needs_del}]
447             if @needs_add || @needs_del;
448             } else {
449 0 0 0     0 unless (!@$not_member_of && @$member_of==1 &&
      0        
450             $member_of->[0] eq $group) { # the default, no need fix
451 0         0 push @do,
452             [add_delete_user_groups=>{
453             %ca,
454             add_to=>$member_of, delete_from=>$not_member_of}];
455             }
456             }
457              
458             # create homedir
459 8   33     47 my $home = $uentry->{home} // $args{new_home} // "/home/$user";
      0        
460 8 50 0     40 if ($create_home && (!$exists || !(-d $home))) {
      33        
461 0 0       0 log_info("(DRY) Creating home directory for $user in $home ...")
462             if $dry_run;
463 0 0       0 if ($use_skel) {
464 0 0       0 return [412, "Skeleton directory $skel_dir doesn't exist"]
465             unless (-d $skel_dir);
466 0         0 push @do, (
467             ["File::Copy::Undoable::cp" => {
468             source=>$skel_dir,
469             target=>$home, target_owner=>$user,
470             }],
471             );
472 0         0 unshift @undo, (
473             ["File::Trash::Undoable::trash" => {
474             path=>$home,
475             suffix=>substr($taid,0,8),
476             }],
477             );
478             } else {
479 0         0 push @do, (
480             ["Setup::File::mkdir" => {path=>$home}],
481             );
482 0         0 unshift @undo, (
483             ["Setup::File::rmdir" => {path=>$home}],
484             );
485             }
486 0         0 push @do, (
487             ["Setup::File::chmod" => {path=>$home, mode=>0700}], #$home_mode
488             );
489             }
490             } #block
491              
492 8 100       32 if (@do) {
493 7         403 return [200, "", undef, {do_actions=>\@do, undo_actions=>\@undo}];
494             } else {
495 1         49 return [304, "Already fixed"];
496             }
497             }
498              
499             1;
500             # ABSTRACT: Setup Unix user (existence, home dir, group memberships)
501              
502             __END__
503              
504             =pod
505              
506             =encoding UTF-8
507              
508             =head1 NAME
509              
510             Setup::Unix::User - Setup Unix user (existence, home dir, group memberships)
511              
512             =head1 VERSION
513              
514             This document describes version 0.14 of Setup::Unix::User (from Perl distribution Setup-Unix-User), released on 2017-07-10.
515              
516             =head1 FUNCTIONS
517              
518              
519             =head2 add_delete_user_groups
520              
521             Usage:
522              
523             add_delete_user_groups(%args) -> [status, msg, result, meta]
524              
525             Add/delete user from group memberships.
526              
527             This function is not exported.
528              
529             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
530              
531              
532             Arguments ('*' denotes required arguments):
533              
534             =over 4
535              
536             =item * B<add_to>* => I<array[str]>
537              
538             =item * B<delete_from>* => I<array[str]>
539              
540             =item * B<etc_dir> => I<str> (default: "/etc")
541              
542             Location of passwd files.
543              
544             =item * B<user> => I<str>
545              
546             User name.
547              
548             =back
549              
550             Special arguments:
551              
552             =over 4
553              
554             =item * B<-tx_action> => I<str>
555              
556             For more information on transaction, see L<Rinci::Transaction>.
557              
558             =item * B<-tx_action_id> => I<str>
559              
560             For more information on transaction, see L<Rinci::Transaction>.
561              
562             =item * B<-tx_recovery> => I<str>
563              
564             For more information on transaction, see L<Rinci::Transaction>.
565              
566             =item * B<-tx_rollback> => I<str>
567              
568             For more information on transaction, see L<Rinci::Transaction>.
569              
570             =item * B<-tx_v> => I<str>
571              
572             For more information on transaction, see L<Rinci::Transaction>.
573              
574             =back
575              
576             Returns an enveloped result (an array).
577              
578             First element (status) is an integer containing HTTP status code
579             (200 means OK, 4xx caller error, 5xx function error). Second element
580             (msg) is a string containing error message, or 'OK' if status is
581             200. Third element (result) is optional, the actual result. Fourth
582             element (meta) is called result metadata and is optional, a hash
583             that contains extra information.
584              
585             Return value: (any)
586              
587              
588             =head2 adduser
589              
590             Usage:
591              
592             adduser(%args) -> [status, msg, result, meta]
593              
594             Add user.
595              
596             This function is not exported.
597              
598             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
599              
600              
601             Arguments ('*' denotes required arguments):
602              
603             =over 4
604              
605             =item * B<etc_dir> => I<str> (default: "/etc")
606              
607             Location of passwd files.
608              
609             =item * B<gecos> => I<str>
610              
611             =item * B<gid> => I<int>
612              
613             When creating group, use specific GID.
614              
615             If not specified, will search an unused GID from C<min_gid> to C<max_gid>.
616              
617             =item * B<group> => I<str>
618              
619             Group name.
620              
621             =item * B<home> => I<str>
622              
623             =item * B<max_gid> => I<int> (default: 65534)
624              
625             =item * B<max_uid> => I<int> (default: 65534)
626              
627             =item * B<min_gid> => I<int> (default: 1000)
628              
629             =item * B<min_uid> => I<int> (default: 1000)
630              
631             =item * B<pass> => I<str>
632              
633             =item * B<shell> => I<str>
634              
635             =item * B<uid> => I<int>
636              
637             Add with specified UID.
638              
639             If not specified, will search an unused UID from C<min_uid> to C<max_uid>.
640              
641             =item * B<user> => I<str>
642              
643             User name.
644              
645             =back
646              
647             Special arguments:
648              
649             =over 4
650              
651             =item * B<-tx_action> => I<str>
652              
653             For more information on transaction, see L<Rinci::Transaction>.
654              
655             =item * B<-tx_action_id> => I<str>
656              
657             For more information on transaction, see L<Rinci::Transaction>.
658              
659             =item * B<-tx_recovery> => I<str>
660              
661             For more information on transaction, see L<Rinci::Transaction>.
662              
663             =item * B<-tx_rollback> => I<str>
664              
665             For more information on transaction, see L<Rinci::Transaction>.
666              
667             =item * B<-tx_v> => I<str>
668              
669             For more information on transaction, see L<Rinci::Transaction>.
670              
671             =back
672              
673             Returns an enveloped result (an array).
674              
675             First element (status) is an integer containing HTTP status code
676             (200 means OK, 4xx caller error, 5xx function error). Second element
677             (msg) is a string containing error message, or 'OK' if status is
678             200. Third element (result) is optional, the actual result. Fourth
679             element (meta) is called result metadata and is optional, a hash
680             that contains extra information.
681              
682             Return value: (any)
683              
684              
685             =head2 deluser
686              
687             Usage:
688              
689             deluser(%args) -> [status, msg, result, meta]
690              
691             Delete user.
692              
693             This function is not exported.
694              
695             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
696              
697              
698             Arguments ('*' denotes required arguments):
699              
700             =over 4
701              
702             =item * B<etc_dir> => I<str> (default: "/etc")
703              
704             Location of passwd files.
705              
706             =item * B<user> => I<str>
707              
708             User name.
709              
710             =back
711              
712             Special arguments:
713              
714             =over 4
715              
716             =item * B<-tx_action> => I<str>
717              
718             For more information on transaction, see L<Rinci::Transaction>.
719              
720             =item * B<-tx_action_id> => I<str>
721              
722             For more information on transaction, see L<Rinci::Transaction>.
723              
724             =item * B<-tx_recovery> => I<str>
725              
726             For more information on transaction, see L<Rinci::Transaction>.
727              
728             =item * B<-tx_rollback> => I<str>
729              
730             For more information on transaction, see L<Rinci::Transaction>.
731              
732             =item * B<-tx_v> => I<str>
733              
734             For more information on transaction, see L<Rinci::Transaction>.
735              
736             =back
737              
738             Returns an enveloped result (an array).
739              
740             First element (status) is an integer containing HTTP status code
741             (200 means OK, 4xx caller error, 5xx function error). Second element
742             (msg) is a string containing error message, or 'OK' if status is
743             200. Third element (result) is optional, the actual result. Fourth
744             element (meta) is called result metadata and is optional, a hash
745             that contains extra information.
746              
747             Return value: (any)
748              
749              
750             =head2 setup_unix_user
751              
752             Usage:
753              
754             setup_unix_user(%args) -> [status, msg, result, meta]
755              
756             Setup Unix user (existence, group memberships).
757              
758             On do, will create Unix user if not already exists. And also make sure user
759             belong to specified groups (and not belong to unwanted groups). Return the
760             created UID/GID in the result.
761              
762             On undo, will delete Unix user (along with its initially created home dir and
763             files) if it was created by this function. Also will restore old group
764             memberships.
765              
766             This function is not exported by default, but exportable.
767              
768             This function is idempotent (repeated invocations with same arguments has the same effect as single invocation). This function supports transactions.
769              
770              
771             Arguments ('*' denotes required arguments):
772              
773             =over 4
774              
775             =item * B<create_home> => I<bool> (default: 1)
776              
777             Whether to create home directory when creating user.
778              
779             =item * B<etc_dir> => I<str> (default: "/etc")
780              
781             Location of passwd files.
782              
783             =item * B<group> => I<str>
784              
785             Group name.
786              
787             =item * B<max_gid> => I<any>
788              
789             =item * B<max_new_gid> => I<any>
790              
791             =item * B<max_new_uid> => I<any>
792              
793             =item * B<max_uid> => I<any>
794              
795             =item * B<member_of> => I<array[str]>
796              
797             List of Unix group names that the user must be member of.
798              
799             If not specified, member_of will be set to just the primary group. The primary
800             group will always be added even if not specified.
801              
802             =item * B<min_gid> => I<any>
803              
804             =item * B<min_new_gid> => I<any>
805              
806             =item * B<min_new_uid> => I<any>
807              
808             =item * B<min_uid> => I<any>
809              
810             =item * B<new_gecos> => I<str>
811              
812             =item * B<new_gid> => I<int>
813              
814             When creating group, use specific GID.
815              
816             If not specified, will search an unused GID from C<min_gid> to C<max_gid>.
817              
818             =item * B<new_home> => I<str>
819              
820             =item * B<new_max_gid> => I<int> (default: 65534)
821              
822             =item * B<new_max_uid> => I<int> (default: 65534)
823              
824             =item * B<new_min_gid> => I<int> (default: 1000)
825              
826             =item * B<new_min_uid> => I<int> (default: 1000)
827              
828             =item * B<new_pass> => I<str>
829              
830             =item * B<new_shell> => I<str>
831              
832             =item * B<new_uid> => I<int>
833              
834             Add with specified UID.
835              
836             If not specified, will search an unused UID from C<min_uid> to C<max_uid>.
837              
838             =item * B<not_member_of> => I<array[str]>
839              
840             List of Unix group names that the user must NOT be member of.
841              
842             =item * B<should_already_exist> => I<bool>
843              
844             Whether user should already exist.
845              
846             =item * B<should_exist> => I<bool> (default: 1)
847              
848             Whether user should exist.
849              
850             =item * B<skel_dir> => I<str> (default: "/etc/skel")
851              
852             Directory to get skeleton files when creating user.
853              
854             =item * B<use_skel> => I<bool> (default: 1)
855              
856             Whether to copy files from skeleton dir when creating user.
857              
858             =item * B<user> => I<str>
859              
860             User name.
861              
862             =back
863              
864             Special arguments:
865              
866             =over 4
867              
868             =item * B<-tx_action> => I<str>
869              
870             For more information on transaction, see L<Rinci::Transaction>.
871              
872             =item * B<-tx_action_id> => I<str>
873              
874             For more information on transaction, see L<Rinci::Transaction>.
875              
876             =item * B<-tx_recovery> => I<str>
877              
878             For more information on transaction, see L<Rinci::Transaction>.
879              
880             =item * B<-tx_rollback> => I<str>
881              
882             For more information on transaction, see L<Rinci::Transaction>.
883              
884             =item * B<-tx_v> => I<str>
885              
886             For more information on transaction, see L<Rinci::Transaction>.
887              
888             =back
889              
890             Returns an enveloped result (an array).
891              
892             First element (status) is an integer containing HTTP status code
893             (200 means OK, 4xx caller error, 5xx function error). Second element
894             (msg) is a string containing error message, or 'OK' if status is
895             200. Third element (result) is optional, the actual result. Fourth
896             element (meta) is called result metadata and is optional, a hash
897             that contains extra information.
898              
899             Return value: (any)
900              
901             =head1 FAQ
902              
903             =head2 How to create user without creating a group with the same name as that user?
904              
905             By default, C<group> is set to the same name as the user. This will create group
906             with the same name as the user (if the group didn't exist). You can set C<group>
907             to an existing group, e.g. C<users> and the setup function will not create a new
908             group with the same name as user. But note that the group must already exist (if
909             it does not, you can create it first using L<Setup::Unix::Group>).
910              
911             =head1 HOMEPAGE
912              
913             Please visit the project's homepage at L<https://metacpan.org/release/Setup-Unix-User>.
914              
915             =head1 SOURCE
916              
917             Source repository is at L<https://github.com/perlancar/perl-Setup-Unix-User>.
918              
919             =head1 BUGS
920              
921             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Setup-Unix-User>
922              
923             When submitting a bug or request, please include a test-file or a
924             patch to an existing test-file that illustrates the bug or desired
925             feature.
926              
927             =head1 SEE ALSO
928              
929             L<Setup>
930              
931             L<Setup::Unix::Group>
932              
933             =head1 AUTHOR
934              
935             perlancar <perlancar@cpan.org>
936              
937             =head1 COPYRIGHT AND LICENSE
938              
939             This software is copyright (c) 2017, 2015, 2014, 2012, 2011 by perlancar@cpan.org.
940              
941             This is free software; you can redistribute it and/or modify it under
942             the same terms as the Perl 5 programming language system itself.
943              
944             =cut