action) {
'add' => $this->setName('backup:key:slot:add')->setDescription('Add a passphrase slot')
->addOption('label', 'l', InputOption::VALUE_REQUIRED, 'Who or where, e.g. "CTO" or "Safe"', 'Slot'),
'replace' => $this->setName('backup:key:slot:replace')->setDescription('Set a new passphrase for a slot (e.g. its holder left)')
->addArgument('slot', InputArgument::REQUIRED, 'Slot number (see backup:key:slots)')
->addOption('label', 'l', InputOption::VALUE_REQUIRED, 'New label'),
'remove' => $this->setName('backup:key:slot:remove')->setDescription('Remove a passphrase slot (not the last one)')
->addArgument('slot', InputArgument::REQUIRED, 'Slot number (see backup:key:slots)'),
default => $this->setName('backup:key:slots')->setDescription('List the passphrase slots of the backup key'),
};
if ($this->action === 'add' || $this->action === 'replace') {
$this->addOption('passphrase-file', null, InputOption::VALUE_REQUIRED, 'Read the passphrase from this file');
}
$this->setHelp($help);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
if (!$this->keys->isInitialized()) {
$output->writeln('No backup key yet: run occ backup:key:init first.');
return 1;
}
try {
switch ($this->action) {
case 'add':
$n = $this->slots->add($this->passphrase($input, $output), (string)$input->getOption('label'));
$output->writeln("Slot $n added.");
break;
case 'replace':
$this->slots->replace((int)$input->getArgument('slot'), $this->passphrase($input, $output), $input->getOption('label'));
$output->writeln('Slot ' . (int)$input->getArgument('slot') . ' has a new passphrase.');
break;
case 'remove':
$this->slots->remove((int)$input->getArgument('slot'));
$output->writeln('Slot ' . (int)$input->getArgument('slot') . ' removed.');
break;
default:
(new Table($output))->setHeaders(['slot', 'label', 'set on'])
->setRows(array_map(fn ($s) => [$s['slot'], $s['label'], $s['created'] ? date('Y-m-d H:i', $s['created']) : ''], $this->keys->slots()))->render();
return 0;
}
} catch (\Throwable $e) {
$output->writeln('' . $e->getMessage() . '');
return 1;
}
$output->writeln('Now download and confirm the new recovery kit: occ backup:key:kit -o FILE && occ backup:key:confirm');
return 0;
}
private function passphrase(InputInterface $input, OutputInterface $output): string {
if ($file = $input->getOption('passphrase-file')) {
return rtrim((string)@file_get_contents($file), "\r\n");
}
$h = $this->getHelper('question');
$p = (string)$h->ask($input, $output, (new Question('Passphrase (min. ' . \OCA\NgBackup\Service\KeyService::MIN_PASSPHRASE . ' characters): '))->setHidden(true)->setHiddenFallback(false));
if ($p !== (string)$h->ask($input, $output, (new Question('Repeat: '))->setHidden(true)->setHiddenFallback(false))) {
throw new \InvalidArgumentException('The passphrases do not match');
}
return $p;
}
}