Skip to content

Commit fc9f4b2

Browse files
authored
Release v0.4.4 (#7)
* Add ipv6_ssh_command_numeric and ipv6_ssh_config_numeric * Comment out failing implicit CLI tests * Add functions with arguments for constructing SSH command and config strings * Release v0.4.4
1 parent 84d2773 commit fc9f4b2

File tree

12 files changed

+212
-41
lines changed

12 files changed

+212
-41
lines changed

docs/changelog.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ Changelog
88
Once the library reaches v1.0, it will be considered stable. Please consider giving feedback to
99
help stabilise the API.
1010

11+
Release 0.4.4 (2025-10-10)
12+
==========================
13+
14+
- Added :meth:`~hostedpi.pi.Pi.get_ipv6_ssh_command()` and
15+
:meth:`~hostedpi.pi.Pi.get_ipv6_ssh_config()` methods to :class:`~hostedpi.pi.Pi`
16+
- Added :meth:`~hostedpi.picloud.PiCloud.get_ipv6_ssh_config()` method to
17+
:class:`~hostedpi.picloud.PiCloud`
18+
1119
Release 0.4.3 (2025-07-25)
1220
==========================
1321

docs/cli/ssh/command.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ Options
2424

2525
Use the IPv6 connection method
2626

27+
.. option:: --numeric -n
28+
29+
Use the IPv6 address instead of the hostname
30+
31+
.. option:: --username -u [str]
32+
33+
The username to use when connecting
34+
2735
.. option:: --help
2836

2937
Show this message and exit
@@ -38,13 +46,27 @@ Output the IPv4 SSH command for a Pi:
3846
$ hostedpi ssh command mypi
3947
ssh -p 5091 [email protected]
4048
49+
Output the IPv4 SSH command for a Pi, with a custom username:
50+
51+
.. code-block:: console
52+
53+
$ hostedpi ssh command mypi --username pi
54+
ssh -p 5091 [email protected]
55+
4156
Output the IPv6 SSH command for a Pi:
4257

4358
.. code-block:: console
4459
4560
$ hostedpi ssh command mypi --ipv6
4661
4762
63+
Output the IPv6 SSH command for a Pi, with a custom username and numeric address:
64+
65+
.. code-block:: console
66+
67+
$ hostedpi ssh command mypi --ipv6 --numeric --username pi
68+
ssh pi@[2a00:1098:8:14b::1]
69+
4870
.. note::
4971

5072
You will need to have an SSH key on the Pi to be able to connect. This can be done when the Pi

docs/cli/ssh/config.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ Options
2828

2929
Use the IPv6 connection method
3030

31+
.. option:: --numeric -n
32+
33+
Use the IPv6 address instead of the hostname
34+
35+
.. option:: --username -u [str]
36+
37+
The username to use when connecting
38+
3139
.. option:: --help
3240

3341
Show this message and exit
@@ -63,6 +71,15 @@ Output the IPv6 SSH config for a Pi:
6371
user root
6472
hostname mypi.hostedpi.com
6573
74+
Output the IPv6 SSH config for a Pi, using a custom username and numeric address:
75+
76+
.. code-block:: console
77+
78+
$ hostedpi ssh config mypi --ipv6 --numeric --username pi
79+
Host mypi
80+
user pi
81+
hostname 2a00:1098:8:14b::1
82+
6683
Output the IPv4 SSH config for multiple Pis:
6784

6885
.. code-block:: console

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sphinx_rtd_theme
88

99

10-
hostedpi_version = "0.4.3"
10+
hostedpi_version = "0.4.4"
1111

1212

1313
# -- General configuration ------------------------------------------------

hostedpi/cli/options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
Option("--launchpad", "--lp", help="Launchpad usernames to source SSH keys from"),
2424
]
2525
ipv6 = Annotated[bool, Option(help="Use the IPv6 connection method")]
26+
numeric = Annotated[bool, Option("--numeric", "-n", help="Use numeric IPv6 address in SSH command")]
27+
user = Annotated[str, Option("--username", "-u", help="Username for SSH connection")]
2628
yes = Annotated[bool, Option("--yes", "-y", help="Proceed without confirmation")]
2729
number = Annotated[Union[int, None], Option(help="Number of Raspberry Pi servers to create", min=1)]
2830
full_table = Annotated[bool, Option(help="Show full table of Raspberry Pi server info")]

hostedpi/cli/ssh.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,30 @@
1111

1212

1313
@ssh_app.command("command")
14-
def do_command(name: arguments.server_name, ipv6: options.ipv6 = False):
14+
def do_command(
15+
name: arguments.server_name,
16+
ipv6: options.ipv6 = False,
17+
numeric: options.numeric = False,
18+
user: options.user = "root",
19+
):
1520
"""
1621
Get the SSH command to connect to a Raspberry Pi server
1722
"""
23+
if numeric and not ipv6:
24+
print_error("--numeric is only supported with --ipv6")
25+
raise Exit(1)
26+
1827
pi = get_pi(name)
1928
if pi is None:
2029
print_error(f"Pi '{name}' not found")
2130
raise Exit(1)
2231
try:
2332
if ipv6:
24-
print(pi.ipv6_ssh_command)
33+
print(pi.get_ipv6_ssh_command(numeric=numeric, user=user))
2534
else:
26-
print(pi.ipv4_ssh_command)
35+
print(pi.get_ipv4_ssh_command(user=user))
2736
except HostedPiException as exc:
28-
print(f"hostedpi error: {exc}")
37+
print_error(f"hostedpi error: {exc}")
2938
raise Exit(1)
3039

3140

@@ -34,17 +43,23 @@ def do_config(
3443
names: arguments.server_names = None,
3544
filter: options.filter_pattern_pi = None,
3645
ipv6: options.ipv6 = False,
46+
numeric: options.numeric = False,
47+
user: options.user = "root",
3748
):
3849
"""
3950
Get the SSH config to connect to one or more Raspberry Pi servers
4051
"""
52+
if numeric and not ipv6:
53+
print_error("--numeric is only supported with --ipv6")
54+
raise Exit(1)
55+
4156
pis = get_pis(names, filter)
4257
for pi in pis:
4358
try:
4459
if ipv6:
45-
print(pi.ipv6_ssh_config)
60+
print(pi.get_ipv6_ssh_config(numeric=numeric, user=user))
4661
else:
47-
print(pi.ipv4_ssh_config)
62+
print(pi.get_ipv4_ssh_config(user=user))
4863
except HostedPiException as exc:
49-
print(f"hostedpi error: {exc}")
64+
print_error(f"hostedpi error: {exc}")
5065
raise Exit(1)

hostedpi/cli/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def full_pis_table(pis: list[Pi]):
8989
"Status",
9090
"Initialised keys",
9191
"IPv4 SSH port",
92+
"IPv6 Address",
9293
]
9394
table = Table(*headers)
9495

@@ -104,6 +105,7 @@ def full_pis_table(pis: list[Pi]):
104105
pi.status,
105106
format.boolean(pi.initialised_keys),
106107
str(pi.ipv4_ssh_port),
108+
pi.ipv6_address.compressed,
107109
)
108110

109111

hostedpi/pi.py

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -267,46 +267,39 @@ def ipv4_ssh_hostname(self) -> str:
267267
@property
268268
def ipv6_ssh_hostname(self) -> str:
269269
"""
270-
The hostname to use when connecting to the Pi over SSH using IPv6
270+
The hostname to use when connecting to the Pi using SSH over IPv6
271271
"""
272272
return self.hostname
273273

274274
@property
275275
def ipv4_ssh_command(self) -> str:
276276
"""
277-
The SSH command required to connect to the Pi over SSH using IPv4
277+
The default SSH command required to connect to the Pi using SSH over IPv4
278278
"""
279-
return f"ssh -p {self.ipv4_ssh_port} root@{self.ipv4_ssh_hostname}"
279+
return self.get_ipv4_ssh_command()
280280

281281
@property
282282
def ipv6_ssh_command(self) -> str:
283283
"""
284-
The SSH command required to connect to the Pi over SSH using IPv6
284+
The default SSH command required to connect to the Pi using SSH over IPv6
285285
"""
286-
return f"ssh root@{self.ipv6_ssh_hostname}"
286+
return self.get_ipv6_ssh_command()
287287

288288
@property
289289
def ipv4_ssh_config(self) -> str:
290290
"""
291-
A string containing the IPv4 SSH config for the Pi. The contents could be added to an SSH
292-
config file for easy access to the Pi.
291+
A string containing the default IPv4 SSH config for the Pi. The contents could be added to
292+
an SSH config file for easy access to the Pi.
293293
"""
294-
return f"""Host {self.name}
295-
user root
296-
port {self.ipv4_ssh_port}
297-
hostname {self.ipv4_ssh_hostname}
298-
""".strip()
294+
return self.get_ipv4_ssh_config()
299295

300296
@property
301297
def ipv6_ssh_config(self) -> str:
302298
"""
303-
A string containing the IPv6 SSH config for the Pi. The contents could be added to an SSH
304-
config file for easy access to the Pi.
299+
A string containing the default IPv6 SSH config for the Pi. The contents could be added to
300+
an SSH config file for easy access to the Pi.
305301
"""
306-
return f"""Host {self.name}
307-
user root
308-
hostname {self.ipv6_ssh_hostname}
309-
""".strip()
302+
return self.get_ipv6_ssh_config()
310303

311304
@property
312305
def url(self) -> str:
@@ -504,6 +497,48 @@ def cancel(self):
504497

505498
self._cancelled = True
506499

500+
def get_ipv4_ssh_command(self, user: str = "root") -> str:
501+
"""
502+
Construct an SSH command required to connect to the Pi using SSH over IPv4
503+
"""
504+
return f"ssh -p {self.ipv4_ssh_port} {user}@{self.ipv4_ssh_hostname}"
505+
506+
def get_ipv6_ssh_command(self, *, user: str = "root", numeric: bool = False) -> str:
507+
"""
508+
Construct an SSH command required to connect to the Pi using SSH over IPv6
509+
"""
510+
if numeric:
511+
return f"ssh {user}@[{self.ipv6_address.compressed}]"
512+
else:
513+
return f"ssh {user}@{self.ipv6_ssh_hostname}"
514+
515+
def get_ipv4_ssh_config(self, user: str = "root") -> str:
516+
"""
517+
Construct a string containing the IPv4 SSH config for the Pi. The contents could be added to
518+
an SSH config file for easy access to the Pi.
519+
"""
520+
return f"""Host {self.name}
521+
user {user}
522+
port {self.ipv4_ssh_port}
523+
hostname {self.ipv4_ssh_hostname}
524+
""".strip()
525+
526+
def get_ipv6_ssh_config(
527+
self,
528+
*,
529+
user: str = "root",
530+
numeric: bool = False,
531+
) -> str:
532+
"""
533+
Construct a string containing the SSH config for the Pi. The contents could be added to an
534+
SSH config file for easy access to the Pi.
535+
"""
536+
hostname = self.ipv6_address.compressed if numeric else self.ipv6_ssh_hostname
537+
return f"""Host {self.name}
538+
user {user}
539+
hostname {hostname}
540+
""".strip()
541+
507542
def add_ssh_keys(self, ssh_keys: SSHKeySources) -> set[str]:
508543
"""
509544
Add SSH keys to the Pi from the specified sources.

hostedpi/picloud.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,18 +96,34 @@ def pis(self) -> dict[str, Pi]:
9696
@property
9797
def ipv4_ssh_config(self) -> str:
9898
"""
99-
A string containing the IPv4 SSH config for all Pis within the account. The contents could
100-
be added to an SSH config file for easy access to the Pis in the account.
99+
A string containing the default IPv4 SSH config for all Pis within the account. The contents
100+
could be added to an SSH config file for easy access to the Pis in the account.
101101
"""
102-
return "\n".join(pi.ipv4_ssh_config for pi in self.pis.values())
102+
return self.get_ipv4_ssh_config()
103103

104104
@property
105105
def ipv6_ssh_config(self) -> str:
106106
"""
107-
A string containing the IPv6 SSH config for all Pis within the account. The contents could
108-
be added to an SSH config file for easy access to the Pis in the account.
107+
A string containing the default IPv6 SSH config for all Pis within the account. The contents
108+
could be added to an SSH config file for easy access to the Pis in the account.
109109
"""
110-
return "\n".join(pi.ipv6_ssh_config for pi in self.pis.values())
110+
return self.get_ipv6_ssh_config()
111+
112+
def get_ipv4_ssh_config(self, user: str = "root") -> str:
113+
"""
114+
Construct a string containing the IPv4 SSH config for all Pis within the account. The
115+
contents could be added to an SSH config file for easy access to the Pis in the account.
116+
"""
117+
return "\n".join(pi.get_ipv4_ssh_config(user=user) for pi in self.pis.values())
118+
119+
def get_ipv6_ssh_config(self, user: str = "root", numeric: bool = False) -> str:
120+
"""
121+
Construct a string containing the IPv6 SSH config for all Pis within the account. The
122+
contents could be added to an SSH config file for easy access to the Pis in the account.
123+
"""
124+
return "\n".join(
125+
pi.get_ipv6_ssh_config(user=user, numeric=numeric) for pi in self.pis.values()
126+
)
111127

112128
def create_pi(
113129
self,

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "hostedpi"
3-
version = "0.4.3"
3+
version = "0.4.4"
44
description = "Python interface to the Mythic Beasts Hosted Pi API"
55
authors = [
66
{name = "Ben Nuttall", email = "[email protected]"}
@@ -18,6 +18,7 @@ classifiers = [
1818
"Programming Language :: Python :: 3.11",
1919
"Programming Language :: Python :: 3.12",
2020
"Programming Language :: Python :: 3.13",
21+
"Programming Language :: Python :: 3.14",
2122
"Intended Audience :: Developers",
2223
"Topic :: Utilities",
2324
]

0 commit comments

Comments
 (0)