2023-08-03 13:22:22 +00:00
|
|
|
import argparse
|
2023-10-18 16:29:51 +00:00
|
|
|
import logging
|
2023-08-03 13:22:22 +00:00
|
|
|
import sys
|
2023-11-15 13:28:40 +00:00
|
|
|
from pathlib import Path
|
2023-08-23 10:32:06 +00:00
|
|
|
from types import ModuleType
|
2023-08-03 13:22:22 +00:00
|
|
|
|
2024-06-08 13:01:45 +00:00
|
|
|
# These imports are unused, but necessary for @API.register to run once.
|
2024-06-16 14:29:09 +00:00
|
|
|
from clan_cli.api import directory, mdns_discovery
|
2024-06-11 10:10:55 +00:00
|
|
|
from clan_cli.arg_actions import AppendOptionAction
|
|
|
|
from clan_cli.clan import show
|
2024-06-08 13:01:45 +00:00
|
|
|
|
2024-06-16 14:29:09 +00:00
|
|
|
__all__ = ["directory", "mdns_discovery"]
|
2024-06-08 13:01:45 +00:00
|
|
|
|
2024-04-02 10:16:30 +00:00
|
|
|
from . import (
|
|
|
|
backups,
|
2024-06-11 10:10:55 +00:00
|
|
|
clan,
|
2024-04-02 10:16:30 +00:00
|
|
|
config,
|
|
|
|
facts,
|
|
|
|
flash,
|
|
|
|
flatpak,
|
|
|
|
history,
|
|
|
|
machines,
|
|
|
|
secrets,
|
|
|
|
vms,
|
|
|
|
)
|
2023-10-27 21:36:45 +00:00
|
|
|
from .custom_logger import setup_logging
|
2024-04-30 10:00:47 +00:00
|
|
|
from .dirs import get_clan_flake_toplevel_or_env
|
2024-01-16 15:16:12 +00:00
|
|
|
from .errors import ClanCmdError, ClanError
|
2024-03-06 10:12:20 +00:00
|
|
|
from .profiler import profile
|
2023-10-18 16:29:51 +00:00
|
|
|
from .ssh import cli as ssh_cli
|
2023-10-17 21:49:16 +00:00
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2023-11-29 11:40:48 +00:00
|
|
|
argcomplete: ModuleType | None = None
|
2023-08-03 13:22:22 +00:00
|
|
|
try:
|
2023-08-23 10:32:06 +00:00
|
|
|
import argcomplete # type: ignore[no-redef]
|
2023-08-03 13:22:22 +00:00
|
|
|
except ImportError:
|
2023-08-22 13:17:24 +00:00
|
|
|
pass
|
|
|
|
|
2023-08-03 13:22:22 +00:00
|
|
|
|
2024-05-29 07:00:51 +00:00
|
|
|
def flake_path(arg: str) -> str | Path:
|
|
|
|
flake_dir = Path(arg).resolve()
|
|
|
|
if flake_dir.exists() and flake_dir.is_dir():
|
|
|
|
return flake_dir
|
|
|
|
return arg
|
2023-09-23 23:00:59 +00:00
|
|
|
|
2024-05-29 07:00:51 +00:00
|
|
|
|
|
|
|
def add_common_flags(parser: argparse.ArgumentParser) -> None:
|
2023-09-23 23:00:59 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--debug",
|
|
|
|
help="Enable debug logging",
|
|
|
|
action="store_true",
|
2024-04-23 16:55:00 +00:00
|
|
|
default=False,
|
2023-09-23 23:00:59 +00:00
|
|
|
)
|
|
|
|
|
2023-11-03 11:27:06 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--option",
|
|
|
|
help="Nix option to set",
|
|
|
|
nargs=2,
|
|
|
|
metavar=("name", "value"),
|
2023-11-03 12:30:01 +00:00
|
|
|
action=AppendOptionAction,
|
2023-11-03 11:27:06 +00:00
|
|
|
default=[],
|
|
|
|
)
|
|
|
|
|
2023-11-03 11:51:17 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"--flake",
|
2024-04-30 10:00:47 +00:00
|
|
|
help="path to the flake where the clan resides in, can be a remote flake or local, can be set through the [CLAN_DIR] environment variable",
|
|
|
|
default=get_clan_flake_toplevel_or_env(),
|
2024-04-30 16:53:00 +00:00
|
|
|
metavar="PATH",
|
2023-12-01 14:45:05 +00:00
|
|
|
type=flake_path,
|
2023-11-03 11:51:17 +00:00
|
|
|
)
|
|
|
|
|
2024-05-29 07:00:51 +00:00
|
|
|
|
|
|
|
def register_common_flags(parser: argparse.ArgumentParser) -> None:
|
|
|
|
has_subparsers = False
|
|
|
|
for action in parser._actions:
|
|
|
|
if isinstance(action, argparse._SubParsersAction):
|
|
|
|
for choice, child_parser in action.choices.items():
|
|
|
|
has_subparsers = True
|
|
|
|
register_common_flags(child_parser)
|
|
|
|
if not has_subparsers:
|
|
|
|
add_common_flags(parser)
|
|
|
|
|
|
|
|
|
|
|
|
def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
prog=prog,
|
|
|
|
description="The clan cli tool.",
|
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
Online reference for the clan cli tool: https://docs.clan.lol/reference/cli/
|
|
|
|
For more detailed information, visit: https://docs.clan.lol
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
|
|
)
|
2024-06-08 15:53:11 +00:00
|
|
|
|
2023-08-03 13:22:22 +00:00
|
|
|
subparsers = parser.add_subparsers()
|
|
|
|
|
2024-06-08 15:53:11 +00:00
|
|
|
# Commands directly under the root i.e. "clan show"
|
|
|
|
show_parser = subparsers.add_parser(
|
|
|
|
"show",
|
2024-06-08 16:24:49 +00:00
|
|
|
help="Show meta about the clan if present.",
|
|
|
|
description="Show meta about the clan if present.",
|
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
This command prints the metadata of a clan.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
$ clan show --flake [PATH]
|
|
|
|
Name: My Empty Clan
|
|
|
|
Description: some nice description
|
|
|
|
Icon: A path to the png
|
|
|
|
|
|
|
|
Note: The meta results from clan/meta.json and manual flake arguments. It may not be present for clans not created via the clan-app.
|
|
|
|
|
|
|
|
"""
|
|
|
|
),
|
2024-06-08 15:53:11 +00:00
|
|
|
)
|
|
|
|
show_parser.set_defaults(func=show.show_command)
|
|
|
|
|
2023-11-28 12:23:48 +00:00
|
|
|
parser_backups = subparsers.add_parser(
|
2024-04-30 16:53:00 +00:00
|
|
|
"backups",
|
|
|
|
help="manage backups of clan machines",
|
|
|
|
description="manage backups of clan machines",
|
2024-05-27 09:48:08 +00:00
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
This subcommand provides an interface to backups that clan machines expose.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
$ clan backups list [MACHINE]
|
|
|
|
List backups for the machine [MACHINE]
|
|
|
|
|
|
|
|
$ clan backups create [MACHINE]
|
|
|
|
Create a backup for the machine [MACHINE].
|
|
|
|
|
|
|
|
$ clan backups restore [MACHINE] [PROVIDER] [NAME]
|
|
|
|
The backup to restore for the machine [MACHINE] with the configured [PROVIDER]
|
|
|
|
with the name [NAME].
|
|
|
|
|
|
|
|
For more detailed information, visit: https://docs.clan.lol/getting-started/backups/
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
2023-11-28 12:23:48 +00:00
|
|
|
)
|
|
|
|
backups.register_parser(parser_backups)
|
|
|
|
|
2023-10-09 12:01:34 +00:00
|
|
|
parser_flake = subparsers.add_parser(
|
2024-05-27 09:48:08 +00:00
|
|
|
"flakes",
|
|
|
|
help="create a clan flake inside the current directory",
|
2024-05-27 18:29:34 +00:00
|
|
|
description="create a clan flake inside the current directory",
|
2024-05-27 09:48:08 +00:00
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
Examples:
|
|
|
|
$ clan flakes create [DIR]
|
|
|
|
Will create a new clan flake in the specified directory and create it if it
|
|
|
|
doesn't exist yet. The flake will be created from a default template.
|
|
|
|
|
|
|
|
For more detailed information, visit: https://docs.clan.lol/getting-started
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
2023-09-24 12:04:18 +00:00
|
|
|
)
|
2024-05-30 17:51:53 +00:00
|
|
|
|
2024-06-11 10:10:55 +00:00
|
|
|
clan.register_parser(parser_flake)
|
2023-08-03 13:22:22 +00:00
|
|
|
|
2024-05-27 09:48:08 +00:00
|
|
|
parser_config = subparsers.add_parser(
|
|
|
|
"config",
|
2024-05-07 11:16:33 +00:00
|
|
|
help="read a nixos configuration option",
|
|
|
|
description="read a nixos configuration option",
|
2024-05-27 09:48:08 +00:00
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
|
|
)
|
2023-09-19 09:29:59 +00:00
|
|
|
config.register_parser(parser_config)
|
2023-08-03 13:22:22 +00:00
|
|
|
|
2024-05-27 18:14:37 +00:00
|
|
|
parser_ssh = subparsers.add_parser(
|
|
|
|
"ssh",
|
|
|
|
help="ssh to a remote machine",
|
2024-05-28 09:37:32 +00:00
|
|
|
description="ssh to a remote machine",
|
2024-05-27 18:14:37 +00:00
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
This subcommand allows seamless ssh access to the nixos-image builders.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
$ clan ssh [ssh_args ...] --json [JSON]
|
|
|
|
Will ssh in to the machine based on the deployment information contained in
|
|
|
|
the json string. [JSON] can either be a json formatted string itself, or point
|
|
|
|
towards a file containing the deployment information
|
|
|
|
|
|
|
|
For more detailed information, visit: https://docs.clan.lol/getting-started/deploy
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
|
|
)
|
2023-08-09 13:40:31 +00:00
|
|
|
ssh_cli.register_parser(parser_ssh)
|
2023-08-03 13:22:22 +00:00
|
|
|
|
2024-05-27 09:48:08 +00:00
|
|
|
parser_secrets = subparsers.add_parser(
|
|
|
|
"secrets",
|
|
|
|
help="manage secrets",
|
2024-05-27 18:29:34 +00:00
|
|
|
description="manage secrets",
|
2024-05-27 09:48:08 +00:00
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
This subcommand provides an interface to secret facts.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
$ clan secrets list [regex]
|
|
|
|
Will list secrets for all managed machines.
|
|
|
|
It accepts an optional regex, allowing easy filtering of returned secrets.
|
|
|
|
|
|
|
|
$ clan secrets get [SECRET]
|
|
|
|
Will display the content of the specified secret.
|
|
|
|
|
|
|
|
For more detailed information, visit: https://docs.clan.lol/getting-started/secrets/
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
|
|
)
|
2023-08-03 13:22:22 +00:00
|
|
|
secrets.register_parser(parser_secrets)
|
|
|
|
|
2024-05-27 09:48:08 +00:00
|
|
|
parser_facts = subparsers.add_parser(
|
|
|
|
"facts",
|
|
|
|
help="manage facts",
|
2024-05-27 18:29:34 +00:00
|
|
|
description="manage facts",
|
2024-05-27 09:48:08 +00:00
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
|
|
|
|
This subcommand provides an interface to facts of clan machines.
|
|
|
|
Facts are artifacts that a service can generate.
|
2024-05-29 07:00:51 +00:00
|
|
|
There are public and secret facts.
|
2024-05-27 09:48:08 +00:00
|
|
|
Public facts can be referenced by other machines directly.
|
|
|
|
Public facts can include: ip addresses, public keys.
|
|
|
|
Secret facts can include: passwords, private keys.
|
|
|
|
|
|
|
|
A service is an included clan-module that implements facts generation functionality.
|
|
|
|
For example the zerotier module will generate private and public facts.
|
|
|
|
In this case the public fact will be the resulting zerotier-ip of the machine.
|
|
|
|
The secret fact will be the zerotier-identity-secret, which is used by zerotier
|
|
|
|
to prove the machine has control of the zerotier-ip.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
$ clan facts generate
|
|
|
|
Will generate facts for all machines.
|
2024-05-29 07:00:51 +00:00
|
|
|
|
2024-05-27 09:48:08 +00:00
|
|
|
$ clan facts generate --service [SERVICE] --regenerate
|
|
|
|
Will regenerate facts, if they are already generated for a specific service.
|
|
|
|
This is especially useful for resetting certain passwords while leaving the rest
|
|
|
|
of the facts for a machine in place.
|
|
|
|
|
|
|
|
For more detailed information, visit: https://docs.clan.lol/getting-started/secrets/
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
|
|
)
|
2024-02-12 12:31:12 +00:00
|
|
|
facts.register_parser(parser_facts)
|
|
|
|
|
2023-08-24 14:25:06 +00:00
|
|
|
parser_machine = subparsers.add_parser(
|
2024-05-27 09:48:08 +00:00
|
|
|
"machines",
|
|
|
|
help="manage machines and their configuration",
|
2024-05-27 18:29:34 +00:00
|
|
|
description="manage machines and their configuration",
|
2024-05-27 09:48:08 +00:00
|
|
|
epilog=(
|
|
|
|
"""
|
|
|
|
This subcommand provides an interface to machines managed by clan.
|
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
$ clan machines list
|
|
|
|
List all the machines managed by clan.
|
|
|
|
|
|
|
|
$ clan machines update [MACHINES]
|
2024-05-29 07:00:51 +00:00
|
|
|
Will update the specified machine [MACHINE], if [MACHINE] is omitted, the command
|
2024-05-27 09:48:08 +00:00
|
|
|
will attempt to update every configured machine.
|
|
|
|
|
|
|
|
$ clan machines install [MACHINES] [TARGET_HOST]
|
|
|
|
Will install the specified machine [MACHINE], to the specified [TARGET_HOST].
|
|
|
|
|
|
|
|
For more detailed information, visit: https://docs.clan.lol/getting-started/deploy
|
|
|
|
"""
|
|
|
|
),
|
|
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
2023-08-11 14:27:01 +00:00
|
|
|
)
|
2023-08-24 14:25:06 +00:00
|
|
|
machines.register_parser(parser_machine)
|
2023-08-11 14:27:01 +00:00
|
|
|
|
2024-05-28 09:37:32 +00:00
|
|
|
parser_vms = subparsers.add_parser(
|
|
|
|
"vms", help="manage virtual machines", description="manage virtual machines"
|
|
|
|
)
|
2023-09-28 16:27:06 +00:00
|
|
|
vms.register_parser(parser_vms)
|
|
|
|
|
2024-05-28 09:37:32 +00:00
|
|
|
parser_history = subparsers.add_parser(
|
|
|
|
"history",
|
|
|
|
help="manage history",
|
|
|
|
description="manage history",
|
|
|
|
)
|
2023-12-11 18:09:34 +00:00
|
|
|
history.register_parser(parser_history)
|
|
|
|
|
2024-02-07 04:24:43 +00:00
|
|
|
parser_flash = subparsers.add_parser(
|
2024-05-28 09:37:32 +00:00
|
|
|
"flash",
|
|
|
|
help="flash machines to usb sticks or into isos",
|
|
|
|
description="flash machines to usb sticks or into isos",
|
2024-02-07 04:24:43 +00:00
|
|
|
)
|
|
|
|
flash.register_parser(parser_flash)
|
|
|
|
|
2023-08-22 13:17:24 +00:00
|
|
|
if argcomplete:
|
2023-08-03 13:22:22 +00:00
|
|
|
argcomplete.autocomplete(parser)
|
|
|
|
|
2024-05-29 07:00:51 +00:00
|
|
|
register_common_flags(parser)
|
|
|
|
|
2023-08-24 15:29:31 +00:00
|
|
|
return parser
|
2023-08-24 15:09:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
# this will be the entrypoint under /bin/clan (see pyproject.toml config)
|
2024-03-06 10:12:20 +00:00
|
|
|
@profile
|
2023-08-24 15:09:04 +00:00
|
|
|
def main() -> None:
|
2023-08-24 15:29:31 +00:00
|
|
|
parser = create_parser()
|
|
|
|
args = parser.parse_args()
|
2023-10-03 11:12:44 +00:00
|
|
|
|
2023-11-15 13:28:40 +00:00
|
|
|
if len(sys.argv) == 1:
|
|
|
|
parser.print_help()
|
|
|
|
|
2024-06-04 11:22:32 +00:00
|
|
|
if getattr(args, "debug", False):
|
2024-02-02 05:04:30 +00:00
|
|
|
setup_logging(logging.DEBUG, root_log_name=__name__.split(".")[0])
|
2023-10-29 18:35:29 +00:00
|
|
|
log.debug("Debug log activated")
|
2024-04-02 10:16:30 +00:00
|
|
|
if flatpak.is_flatpak():
|
|
|
|
log.debug("Running inside a flatpak sandbox")
|
2024-01-11 21:14:55 +00:00
|
|
|
else:
|
2024-02-02 05:04:30 +00:00
|
|
|
setup_logging(logging.INFO, root_log_name=__name__.split(".")[0])
|
2023-10-29 18:35:29 +00:00
|
|
|
|
2023-08-24 15:29:31 +00:00
|
|
|
if not hasattr(args, "func"):
|
|
|
|
return
|
2023-10-05 15:33:38 +00:00
|
|
|
|
2024-01-16 15:16:12 +00:00
|
|
|
try:
|
|
|
|
args.func(args)
|
|
|
|
except ClanError as e:
|
|
|
|
if args.debug:
|
2024-01-17 12:11:49 +00:00
|
|
|
log.exception(e)
|
2024-01-16 15:16:12 +00:00
|
|
|
sys.exit(1)
|
|
|
|
if isinstance(e, ClanCmdError):
|
|
|
|
if e.cmd.msg:
|
2024-01-19 13:10:22 +00:00
|
|
|
log.error(e.cmd.msg)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
log.error(e)
|
2024-01-16 15:16:12 +00:00
|
|
|
sys.exit(1)
|
2024-06-21 11:11:33 +00:00
|
|
|
except KeyboardInterrupt:
|
|
|
|
log.warning("Interrupted by user")
|
|
|
|
sys.exit(1)
|
2023-07-24 14:48:50 +00:00
|
|
|
|
2023-07-21 11:03:51 +00:00
|
|
|
|
2023-07-28 14:13:25 +00:00
|
|
|
if __name__ == "__main__":
|
2023-07-21 09:38:05 +00:00
|
|
|
main()
|