24#include <QCoreApplication>
42 qDBusRegisterMetaType<SsuRepo>();
44 connect(
this, SIGNAL(done()),
45 QCoreApplication::instance(), SLOT(quit()), Qt::DirectConnection);
46 connect(&ssu, SIGNAL(done()),
47 this, SLOT(handleResponse()));
51 connect(ssuProxy, SIGNAL(done()),
52 this, SLOT(handleDBusResponse()));
62void SsuCli::handleResponse()
64 QTextStream qout(stdout);
67 qout <<
"Last operation failed: \n" << ssu.lastError() << endl;
68 QCoreApplication::exit(1);
70 qout <<
"Operation successful (direct)" << endl;
71 QCoreApplication::exit(0);
75void SsuCli::handleDBusResponse()
77 QTextStream qout(stdout);
79 if (ssuProxy->error()) {
80 qout <<
"Last operation failed: \n" << ssuProxy->lastError() << endl;
81 QCoreApplication::exit(1);
83 qout <<
"Operation successful" << endl;
84 QCoreApplication::exit(0);
88void SsuCli::optBrand(QStringList opt)
90 QTextStream qout(stdout);
92 if (opt.count() == 3 && opt.at(2) ==
"-s") {
95 }
else if (opt.count() == 2) {
96 qout <<
"Device brand is: " << ssu.brand() << endl;
101void SsuCli::optDomain(QStringList opt)
103 QTextStream qout(stdout);
104 QTextStream qerr(stderr);
106 if (opt.count() == 3 && opt.at(2) ==
"-s") {
107 qout << ssu.domain();
109 }
else if (opt.count() > 2 && opt.at(2) ==
"-c") {
110 if (opt.count() == 3) {
111 QVariantMap config = ssu.getDomainConfig(ssu.domain());
112 for (QVariantMap::iterator i = config.begin(); i != config.end(); i++) {
113 qout << i.key() <<
": " << i.value().toString() << endl;
116 }
else if (opt.count() == 4) {
117 QVariantMap config = ssu.getDomainConfig(ssu.domain());
118 qout << config.value(opt.at(3)).toString() << endl;
120 }
else if (opt.count() == 5) {
121 QVariantMap config = ssu.getDomainConfig(ssu.domain());
122 config.insert(opt.at(3), opt.at(4));
123 QDBusPendingReply<> reply = ssuProxy->setDomainConfig(ssu.domain(), config);
124 reply.waitForFinished();
125 if (reply.isError()) {
126 qerr << fallingBackToDirectUse(reply.error()) << endl;
127 ssu.setDomainConfig(ssu.domain(), config);
130 }
else if (opt.count() == 3) {
131 qout <<
"Changing domain from " << ssu.domain() <<
" to " << opt.at(2) << endl;
132 ssu.setDomain(opt.at(2));
135 }
else if (opt.count() == 2) {
136 qout <<
"Device domain is currently: " << ssu.domain() << endl;
141void SsuCli::optFlavour(QStringList opt)
143 QTextStream qout(stdout);
144 QTextStream qerr(stderr);
146 if (opt.count() == 3 && opt.at(2) ==
"-s") {
147 qout << ssu.flavour();
149 }
else if (opt.count() == 3) {
150 qout <<
"Changing flavour from " << ssu.flavour()
151 <<
" to " << opt.at(2) << endl;
153 QDBusPendingReply<> reply = ssuProxy->setFlavour(opt.at(2));
154 reply.waitForFinished();
155 if (reply.isError()) {
156 qerr << fallingBackToDirectUse(reply.error()) << endl;
157 ssu.setFlavour(opt.at(2));
159 SsuRepoManager repoManager;
165 }
else if (opt.count() == 2) {
166 qout <<
"Device flavour is currently: " << ssu.flavour() << endl;
171QString SsuCli::getModeString(
int mode) {
172 QStringList modeList;
175 modeList.append(
"DisableRepoManager");
177 modeList.append(
"RndMode");
179 modeList.append(
"ReleaseMode");
181 modeList.append(
"LenientMode");
183 modeList.append(
"UpdateMode");
185 modeList.append(
"AppInstallMode");
187 return modeList.join(
" | ");
190void SsuCli::optMode(QStringList opt)
192 QTextStream qout(stdout);
193 QTextStream qerr(stderr);
195 int deviceMode = ssu.deviceMode();
197 if (opt.count() == 2) {
198 qout <<
"Device mode is: " << deviceMode
199 <<
" (" << getModeString(deviceMode) <<
")" << endl;
203 qout <<
"Both Release and RnD mode set, device is in RnD mode" << endl;
206 }
else if (opt.count() == 3 && opt.at(2) ==
"-s") {
210 }
else if (opt.count() >= 3) {
212 int newMode = opt.at(2).toInt(&isInt);
215 for (
int i=2; i<opt.size(); i++) {
216 if (opt.at(i) ==
"DisableRepoManager")
218 else if (opt.at(i) ==
"RndMode")
220 else if (opt.at(i) ==
"ReleaseMode")
222 else if (opt.at(i) ==
"LenientMode")
224 else if (opt.at(i) ==
"UpdateMode")
226 else if (opt.at(i) ==
"AppInstallMode")
229 qout <<
"Unknown mode: " << opt.at(i) << endl;
236 qout <<
"Setting device mode from " << deviceMode
237 <<
" (" << getModeString(deviceMode)
238 <<
") to " << newMode
239 <<
" (" << getModeString(newMode) <<
")" << endl;
241 QDBusPendingReply<> reply = ssuProxy->setDeviceMode(newMode);
242 reply.waitForFinished();
243 if (reply.isError()) {
244 qerr << fallingBackToDirectUse(reply.error()) << endl;
245 ssu.setDeviceMode(Ssu::DeviceModeFlags(newMode));
247 SsuRepoManager repoManager;
256void SsuCli::optModel(QStringList opt)
258 QTextStream qout(stdout);
259 SsuDeviceInfo deviceInfo;
261 if (opt.count() == 3 && opt.at(2) ==
"-s") {
264 }
else if (opt.count() == 2) {
265 qout <<
"Device model is: " << deviceInfo.
deviceModel() << endl;
270void SsuCli::optModifyRepo(
enum Actions action, QStringList opt)
272 SsuRepoManager repoManager;
274 QTextStream qerr(stderr);
276 if (opt.count() == 3) {
278 if (action == Add && !systemRepos.contains(opt.at(2))) {
279 qerr <<
"Repository not defined: " << opt.at(2) << endl;
284 qerr <<
"Repository already added: " << opt.at(2) << endl;
290 qerr <<
"Cannot remove default repository: " << opt.at(2) << endl;
294 QDBusPendingReply<> reply = ssuProxy->modifyRepo(action, opt.at(2));
295 reply.waitForFinished();
296 if (reply.isError()) {
297 qerr << fallingBackToDirectUse(reply.error()) << endl;
301 repoManager.
add(opt.at(2));
306 repoManager.
remove(opt.at(2));
311 repoManager.
disable(opt.at(2));
316 repoManager.
enable(opt.at(2));
322 }
else if (opt.count() == 4 && action == Add) {
325 if (opt.at(2).indexOf(QRegExp(
"[a-z]*://", Qt::CaseInsensitive)) == 0) {
328 }
else if (opt.at(3).indexOf(QRegExp(
"[a-z]*://", Qt::CaseInsensitive)) == 0) {
332 qerr <<
"Invalid parameters for 'ssu ar': URL required." << endl;
336 if (systemRepos.contains(repo)) {
337 qerr <<
"Cannot override system repository: " << repo << endl;
343 qerr <<
"Repository already added: " << repo << endl;
347 QDBusPendingReply<> reply = ssuProxy->addRepo(repo, url);
348 reply.waitForFinished();
349 if (reply.isError()) {
350 qerr << fallingBackToDirectUse(reply.error()) << endl;
351 repoManager.
add(repo, url);
358void SsuCli::optRegister(QStringList opt)
364 QString username, password;
365 QTextStream qin(stdin);
366 QTextStream qout(stdout);
367 QTextStream qerr(stderr);
368 SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
370 struct termios termNew, termOld;
372 qout <<
"Username: " << flush;
373 username = qin.readLine();
375 tcgetattr(STDIN_FILENO, &termNew);
377 termNew.c_lflag &= ~ECHO;
378 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termNew) == -1)
379 qout <<
"WARNING: Unable to disable echo on your terminal, password will echo!" << endl;
381 qout <<
"Password: " << flush;
382 password = qin.readLine();
385 tcsetattr(STDIN_FILENO, TCSANOW, &termOld);
387 if (opt.count() == 3 && opt.at(2) ==
"-h")
388 ssuSettings->setValue(
"repository-url-variables/user", username);
390 QDBusPendingReply<> reply = ssuProxy->registerDevice(username, password);
391 reply.waitForFinished();
392 if (reply.isError()) {
393 qerr << fallingBackToDirectUse(reply.error()) << endl;
394 ssu.sendRegistration(username, password);
400void SsuCli::optRelease(QStringList opt)
402 QTextStream qout(stdout);
403 QTextStream qerr(stderr);
405 if (opt.count() == 3) {
406 if (opt.at(2) ==
"-r") {
407 qout <<
"Device release (RnD) is currently: " << ssu.release(
true) << endl;
410 qout <<
"Changing release from " << ssu.release()
411 <<
" to " << opt.at(2) << endl;
412 qout <<
"Your device is now in release mode!" << endl;
414 QDBusPendingReply<> reply = ssuProxy->setRelease(opt.at(2),
false);
415 reply.waitForFinished();
416 if (reply.isError()) {
417 qerr << fallingBackToDirectUse(reply.error()) << endl;
418 ssu.setRelease(opt.at(2));
420 SsuRepoManager repoManager;
427 }
else if (opt.count() == 2) {
428 qout <<
"Device release is currently: " << ssu.release() << endl;
430 }
else if (opt.count() == 4 && opt.at(2) ==
"-r") {
431 qout <<
"Changing release (RnD) from " << ssu.release(
true)
432 <<
" to " << opt.at(3) << endl;
433 qout <<
"Your device is now in RnD mode!" << endl;
435 QDBusPendingReply<> reply = ssuProxy->setRelease(opt.at(3),
true);
436 reply.waitForFinished();
437 if (reply.isError()) {
438 qerr << fallingBackToDirectUse(reply.error()) << endl;
439 ssu.setRelease(opt.at(3),
true);
441 SsuRepoManager repoManager;
450void SsuCli::optRepos(QStringList opt)
452 QTextStream qout(stdout);
453 QTextStream qerr(stderr);
454 SsuRepoManager repoManager;
455 SsuDeviceInfo deviceInfo;
456 QHash<QString, QString> repoParameters, repoOverride;
458 bool rndRepo =
false;
459 int micMode = 0, flagStart = 0;
464 if (opt.count() >= 3 && opt.at(2) ==
"-m") {
474 if (opt.count() >= 3 + micMode) {
476 if (opt.at(2 + micMode).contains(
"=")) {
477 flagStart = 2 + micMode;
478 }
else if (!opt.at(2 + micMode).contains(
"=") &&
479 opt.count() == 3 + micMode) {
481 device = opt.at(2 + micMode);
482 }
else if (!opt.at(2 + micMode).contains(
"=") &&
483 opt.count() > 3 + micMode &&
484 opt.at(3 + micMode).contains(
"=")) {
486 device = opt.at(2 + micMode);
487 flagStart = 3 + micMode;
494 if (flagStart != 0) {
495 for (
int i = flagStart; i < opt.count(); i++) {
496 if (opt.at(i).count(
"=") != 1) {
497 qout <<
"Invalid flag: " << opt.at(i) << endl;
501 QStringList split = opt.at(i).split(
"=");
502 repoOverride.insert(split.at(0), split.at(1));
506 if (repoOverride.contains(
"rnd")) {
507 if (repoOverride.value(
"rnd") ==
"true")
509 else if (repoOverride.value(
"rnd") ==
"false")
513 if (!device.isEmpty()) {
515 repoOverride.insert(
"model", device);
524 foreach (
const QString &repo, repos) {
525 QString repoName = repo;
526 if (repo.endsWith(
"-debuginfo")) {
527 repoName = repo.left(repo.size() - 10);
528 repoParameters.insert(
"debugSplit",
"debug");
529 }
else if (repoParameters.value(
"debugSplit") ==
"debug") {
530 repoParameters.remove(
"debugSplit");
533 QString repoUrl = ssu.repoUrl(repoName, rndRepo, repoParameters, repoOverride);
534 qout <<
"repo --name=" << repo <<
"-"
535 << repoOverride.value(
"release")
536 <<
" --baseurl=" << repoUrl << endl;
542 bool hasRepos =
false;
544 if (device.isEmpty()) {
547 qout <<
"Printing repository configuration for '" << device <<
"'" << endl << endl;
551 SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
553 if (!repos.isEmpty()) {
554 qout <<
"Enabled repositories (global): " << endl;
556 for (
int i = 0; i <= 3; i++) {
558 int longestField = 0;
559 foreach (
const QString &repo, repos)
560 if (repo.length() > longestField)
561 longestField = repo.length();
563 qout.setFieldAlignment(QTextStream::AlignLeft);
565 foreach (
const QString &repo, repos) {
567 QString repoName = repo;
568 if (repo.endsWith(
"-debuginfo")) {
569 repoName = repo.left(repo.size() - 10);
570 repoParameters.insert(
"debugSplit",
"debug");
571 }
else if (repoParameters.value(
"debugSplit") ==
"debug")
572 repoParameters.remove(
"debugSplit");
574 QString repoUrl = ssu.repoUrl(repoName, rndRepo, repoParameters, repoOverride);
575 qout <<
" - " << qSetFieldWidth(longestField) << repo << qSetFieldWidth(0) <<
" ... " << repoUrl << endl;
579 if (!device.isEmpty()) {
584 if (!repos.isEmpty()) {
585 qout << endl <<
"Enabled repositories (user): " << endl;
589 if (!repos.isEmpty()) {
590 if (device.isEmpty())
591 qout << endl <<
"Disabled repositories (global, might be overridden by user config): " << endl;
593 qout << endl <<
"Disabled repositories (global): " << endl;
597 if (!device.isEmpty())
599 if (ssuSettings->contains(
"disabled-repos"))
600 repos.append(ssuSettings->value(
"disabled-repos").toStringList());
601 if (!repos.isEmpty()) {
602 qout << endl <<
"Disabled repositories (user): " << endl;
608 qerr <<
"No repositories defined" << endl;
613void SsuCli::optSet(QStringList opt)
615 QTextStream qout(stdout);
617 SsuCoreConfig *settings = SsuCoreConfig::instance();
618 QHash<QString, QString> storageHash;
621 if (opt.count() == 5 && opt.at(2) ==
"-r") {
622 settings->setValue(
"repository-url-variables/" + opt.at(3), opt.at(4));
624 }
else if (opt.count() == 4 && opt.at(2) ==
"-r") {
625 settings->remove(
"repository-url-variables/" + opt.at(3));
627 }
else if (opt.count() == 3 && opt.at(2) ==
"-r") {
628 qout <<
"Repository specific variables:" << endl << endl;
629 var.
variableSection(settings,
"repository-url-variables", &storageHash);
631 }
else if (opt.count() == 4) {
632 settings->setValue(
"global-variables/" + opt.at(2), opt.at(3));
634 }
else if (opt.count() == 3) {
635 settings->remove(
"global-variables/" + opt.at(2));
637 }
else if (opt.count() == 2) {
638 qout <<
"Global variables:" << endl << endl;
644 if (!storageHash.isEmpty()) {
645 QHash<QString, QString>::const_iterator i = storageHash.constBegin();
646 while (i != storageHash.constEnd()) {
647 qout << i.key() <<
"=" << i.value() << endl;
655void SsuCli::optStatus(QStringList opt)
659 QTextStream qout(stdout);
660 QTextStream qerr(stderr);
661 SsuDeviceInfo deviceInfo;
669 QDBusPendingReply<QString> reply = ssuProxy->
deviceUid();
670 reply.waitForFinished();
671 if (reply.isError()) {
672 qerr <<
"DBus unavailable, UUID not necessarily connected to reality." << endl;
675 deviceUid = reply.value();
677 qout <<
"Device registration status: "
678 << (ssu.isRegistered() ?
"registered" :
"not registered") << endl;
683 qout <<
"Device variant: " << deviceInfo.
deviceVariant() << endl;
684 qout <<
"Device UID: " << deviceUid << endl;
686 qout <<
"Release (rnd): " << ssu.release(
true) <<
" (" << ssu.flavour() <<
")" << endl;
688 qout <<
"Release: " << ssu.release() << endl;
689 qout <<
"Domain: " << ssu.domain() << endl;
690 qout <<
"Brand: " << (ssu.brand().isEmpty() ?
"N/A" : ssu.brand()) << endl;
692 SsuCoreConfig *settings = SsuCoreConfig::instance();
693 QHash<QString, QString> storageHash;
696 if (!storageHash.isEmpty()) {
697 qout <<
"Global variables: " << endl;
698 QHash<QString, QString>::const_iterator i = storageHash.constBegin();
699 while (i != storageHash.constEnd()) {
700 qout << i.key() <<
"=" << i.value() << endl;
706void SsuCli::optUpdateCredentials(QStringList opt)
708 QTextStream qout(stdout);
714 if (opt.count() == 3 && opt.at(2) ==
"-f")
717 if (!ssu.isRegistered()) {
718 qout <<
"Device is not registered, can't update credentials" << endl;
720 QCoreApplication::exit(1);
722 ssu.updateCredentials(force);
727void SsuCli::optUpdateRepos(QStringList opt)
731 QTextStream qerr(stdout);
733 QDBusPendingReply<> reply = ssuProxy->updateRepos();
734 reply.waitForFinished();
735 if (reply.isError()) {
736 qerr << fallingBackToDirectUse(reply.error()) << endl;
737 SsuRepoManager repoManager;
745 QTextStream qerr(stderr);
747 QStringList arguments = QCoreApplication::arguments();
749 SsuCoreConfig *ssuSettings = SsuCoreConfig::instance();
750 if (!ssuSettings->isWritable())
751 qerr <<
"WARNING: ssu.ini does not seem to be writable. Setting values might not work." << endl;
754 if (arguments.count() < 2) {
761 const char *shortopt;
764 void (SsuCli::*handler)(QStringList opt);
767 "status",
"s", 0, 0, &SsuCli::optStatus,
768 "updaterepos",
"ur", 0, 0, &SsuCli::optUpdateRepos,
771 "addrepo",
"ar", 1, 2, &SsuCli::optAddRepo,
772 "removerepo",
"rr", 1, 1, &SsuCli::optRemoveRepo,
773 "enablerepo",
"er", 1, 1, &SsuCli::optEnableRepo,
774 "disablerepo",
"dr", 1, 1, &SsuCli::optDisableRepo,
778 "register",
"r", 0, -1, &SsuCli::optRegister,
779 "repos",
"lr", 0, -1, &SsuCli::optRepos,
780 "brand",
"b", 0, -1, &SsuCli::optBrand,
781 "flavour",
"fl", 0, -1, &SsuCli::optFlavour,
782 "mode",
"m", 0, -1, &SsuCli::optMode,
783 "model",
"mo", 0, -1, &SsuCli::optModel,
784 "release",
"re", 0, -1, &SsuCli::optRelease,
785 "update",
"up", 0, -1, &SsuCli::optUpdateCredentials,
786 "domain",
"do", 0, -1, &SsuCli::optDomain,
787 "set",
"set", 0, -1, &SsuCli::optSet,
791 int argc = arguments.count() - 2;
793 for (
unsigned int i = 0; i <
sizeof(handlers) /
sizeof(handlers[0]); i++) {
794 if ((arguments.at(1) != handlers[i].longopt) &&
795 (arguments.at(1) != handlers[i].shortopt)) {
799 if (argc < handlers[i].minargs) {
800 usage(QString(
"%1: Too few arguments").arg(handlers[i].longopt));
804 if (handlers[i].maxargs != -1 && argc > handlers[i].maxargs) {
805 usage(QString(
"%1: Too many arguments").arg(handlers[i].longopt));
810 (this->*(handlers[i].handler))(arguments);
822 QCoreApplication::exit(0);
823 else if (state == UserError)
827void SsuCli::uidWarning()
829 if (geteuid() != 0) {
830 QTextStream qout(stderr);
831 qout <<
"You're not root. Run 'ssu ur' as root to recreate repository files" << endl;
835void SsuCli::usage(
const QString &message)
837 QTextStream qout(stderr);
838 qout <<
"\nUsage: ssu <command> [-command-options] [arguments]" << endl
840 <<
"Repository management:" << endl
841 <<
"\tmode, m \tShow repository mode information." << endl
842 <<
"\t <int> \tSet repository mode as an integer (see docs)." << endl
843 <<
"\t <modes> \tCombo of modes to set from: DisableRepoManager RndMode " << endl
844 <<
"\t \tReleaseMode LenientMode UpdateMode AppInstallMode" << endl
845 <<
"\tupdaterepos, ur \tupdate repository files" << endl
846 <<
"\trepos, lr \tlist configured repositories" << endl
847 <<
"\t [-m] \tformat output suitable for kickstart" << endl
848 <<
"\t [device] \tuse repos for 'device'" << endl
849 <<
"\t [flags] \tadditional flags" << endl
850 <<
"\t rnd=<bool> \tset rnd or release mode (default: take from host)" << endl
851 <<
"\taddrepo, ar <repo> \tadd this repository" << endl
852 <<
"\t [url] \tspecify URL, if not configured" << endl
853 <<
"\tremoverepo, rr <repo> \tremove this repository from configuration" << endl
854 <<
"\tenablerepo, er <repo> \tenable this repository" << endl
855 <<
"\tdisablerepo, dr <repo> \tdisable this repository" << endl
857 <<
"Configuration management:" << endl
858 <<
"\tflavour, fl \tdisplay flavour used (RnD only)" << endl
859 <<
"\t [newflavour] \tset new flavour" << endl
860 <<
"\trelease, re \tdisplay release used" << endl
861 <<
"\t [-r] \tuse RnD release" << endl
862 <<
"\t [newrelease] \tset new (RnD)release" << endl
863 <<
"\tset \tdisplay global variables" << endl
864 <<
"\t [-r] \toperate on repository only variables" << endl
865 <<
"\t <variable> \tdisplay value of <variable>" << endl
866 <<
"\t <variable> <value> \tset value of <variable> to <value>" << endl
867 <<
"\tdomain do \tdisplay current device domain" << endl
868 <<
"\t [newdomain] \tset new domain" << endl
869 <<
"\t [-c] \tshow domain configuration" << endl
870 <<
"\t -c <variable> \tshow single domain variable" << endl
871 <<
"\t -c <variable> <val>\tset single domain variable" << endl
873 <<
"Device management:" << endl
874 <<
"\tstatus, s \tprint registration status and device information" << endl
875 <<
"\tregister, r \tregister this device" << endl
876 <<
"\t [-h] \tconfigure user for OBS home" << endl
877 <<
"\tupdate, up \tupdate repository credentials" << endl
878 <<
"\t [-f] \tforce update" << endl
879 <<
"\tmodel, mo \tprint name of device model (like N9)" << endl
880 <<
"\tbrand, b \tprint brand of device model" << endl
882 if (!message.isEmpty())
883 qout << message << endl;
885 QCoreApplication::exit(1);
888QString SsuCli::fallingBackToDirectUse(
const QDBusError &reason)
const
890 if (reason.type() == QDBusError::Disconnected)
891 return QStringLiteral(
"DBus unavailable, falling back to libssu");
893 return QStringLiteral(
"WARNING: DBus call failed, falling back to libssu: ") + reason.message();
Q_INVOKABLE QString deviceUid()
QStringList disabledRepos()
Q_INVOKABLE void setDeviceModel(const QString &model=QString())
Q_INVOKABLE QString deviceVariant(bool fallback=false)
Q_INVOKABLE QString displayName(int type)
Q_INVOKABLE QString deviceModel()
int remove(const QString &repo)
int add(const QString &repo, const QString &repoUrl=QString())
QStringList repos(int filter=Ssu::NoFilter|Ssu::UserBlacklist)
int enable(const QString &repo)
int disable(const QString &repo)
void variableSection(const QString §ion, QHash< QString, QString > *storageHash)
@ DisableRepoManager
Disable automagic repository management.
@ ReleaseMode
Enable Release mode.
@ RndMode
Enable RnD mode for device.
@ UpdateMode
Do repo isolation and similar bits important for updating devices.
@ AppInstallMode
Do repo isolation, but keep store repository enabled.
@ LenientMode
Disable strict mode (i.e., keep unmanaged repositories).
@ DeviceModel
Marketed device name, like Pogoblaster 3000. Board mappings key "prettyModel".
@ DeviceDesignation
Type designation, like NCC-1701. Beard mappings key "deviceDesignation".
@ UserFilter
Only user configured repositories.
@ NoFilter
All repositories (global | user).
@ UserBlacklist
User blacklist applied.
@ BoardFilter
Only global repositories.
@ Available
Include all defined repos, including disabled.