Skip to content

Commit 34eb0f2

Browse files
committed
Add support for systemd-homed
1 parent d40d77c commit 34eb0f2

File tree

4 files changed

+152
-20
lines changed

4 files changed

+152
-20
lines changed

archinstall/lib/disk/device_handler.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,12 +271,15 @@ def format(
271271
options = []
272272

273273
match fs_type:
274-
case FilesystemType.Btrfs | FilesystemType.F2fs | FilesystemType.Xfs:
274+
case FilesystemType.Btrfs | FilesystemType.Xfs:
275275
# Force overwrite
276276
options.append('-f')
277+
case FilesystemType.F2fs:
278+
# Force overwrite and enable encrypt
279+
options.extend(('-f', '-O', 'encrypt'))
277280
case FilesystemType.Ext2 | FilesystemType.Ext3 | FilesystemType.Ext4:
278-
# Force create
279-
options.append('-F')
281+
# Force create and enable encrypt
282+
options.extend(('-F', '-O', 'encrypt'))
280283
case FilesystemType.Fat12 | FilesystemType.Fat16 | FilesystemType.Fat32:
281284
mkfs_type = 'fat'
282285
# Set FAT size

archinstall/lib/installer.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
from .args import arch_config_handler
3838
from .exceptions import DiskError, HardwareIncompatibilityError, RequirementError, ServiceException, SysCallError
39-
from .general import SysCommand, run
39+
from .general import SysCommand, SysCommandWorker, run
4040
from .hardware import SysInfo
4141
from .locale.utils import verify_keyboard_layout, verify_x11_keyboard_layout
4242
from .luks import Luks2
@@ -1701,27 +1701,36 @@ def _create_user(self, user: User) -> None:
17011701
if not handled_by_plugin:
17021702
info(f'Creating user {user.username}')
17031703

1704-
cmd = f'arch-chroot {self.target} useradd -m'
1704+
if user.homed.use_homed:
1705+
self.enable_service('systemd-homed')
17051706

1706-
if user.sudo:
1707-
cmd += ' -G wheel'
1707+
identity = user.to_identity()
17081708

1709-
cmd += f' {user.username}'
1709+
cmd = f'arch-chroot {self.target} homectl create -P --identity=-'
1710+
run(cmd.split(" "), input_data=identity.encode())
1711+
else:
1712+
cmd = f'arch-chroot {self.target} useradd -m'
17101713

1711-
try:
1712-
SysCommand(cmd)
1713-
except SysCallError as err:
1714-
raise SystemError(f'Could not create user inside installation: {err}')
1714+
if user.sudo:
1715+
cmd += ' -G wheel'
1716+
1717+
cmd += f' {user.username}'
1718+
1719+
try:
1720+
SysCommand(cmd)
1721+
except SysCallError as err:
1722+
raise SystemError(f'Could not create user inside installation: {err}')
17151723

17161724
for plugin in plugins.values():
17171725
if hasattr(plugin, 'on_user_created'):
17181726
if result := plugin.on_user_created(self, user):
17191727
handled_by_plugin = result
17201728

1721-
self.set_user_password(user)
1729+
if not user.homed.use_homed:
1730+
self.set_user_password(user)
17221731

1723-
for group in user.groups:
1724-
SysCommand(f'arch-chroot {self.target} gpasswd -a {user.username} {group}')
1732+
for group in user.groups:
1733+
SysCommand(f'arch-chroot {self.target} gpasswd -a {user.username} {group}')
17251734

17261735
if user.sudo:
17271736
self.enable_sudo(user)

archinstall/lib/interactions/manage_users_conf.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
from archinstall.tui.curses_menu import EditMenu, SelectMenu
88
from archinstall.tui.menu_item import MenuItem, MenuItemGroup
99
from archinstall.tui.result import ResultType
10-
from archinstall.tui.types import Alignment, Orientation
10+
from archinstall.tui.types import Alignment, FrameProperties, Orientation
1111

1212
from ..menu.list_manager import ListManager
13-
from ..models.users import User
13+
from ..models.users import User, StorageMechanism, HomedConfiguration
1414
from ..utils.util import get_password
1515

1616

@@ -111,7 +111,55 @@ def _add_user(self) -> User | None:
111111
case _:
112112
raise ValueError('Unhandled result type')
113113

114-
return User(username, password, sudo)
114+
homed_configuration = self._configure_homed()
115+
116+
return User(username, password, sudo, homed_configuration)
117+
118+
119+
def _configure_homed(self) -> HomedConfiguration:
120+
header = str(tr('Should the user use systemd-homed?\n'))
121+
122+
group = MenuItemGroup.yes_no()
123+
group.focus_item = MenuItem.no()
124+
125+
result = SelectMenu[bool](
126+
group,
127+
header=header,
128+
alignment=Alignment.CENTER,
129+
columns=2,
130+
orientation=Orientation.HORIZONTAL,
131+
search_enabled=False,
132+
allow_skip=False,
133+
).run()
134+
135+
match result.type_:
136+
case ResultType.Selection:
137+
if result.item() != MenuItem.yes():
138+
return HomedConfiguration(use_homed=False)
139+
case _:
140+
raise ValueError('Unhandled result type')
141+
142+
items = [
143+
MenuItem('Directory', value=StorageMechanism.DIRECTORY),
144+
MenuItem('LUKS', value=StorageMechanism.LUKS),
145+
#MenuItem('fscrypt', value=StorageMechanism.FSCRYPT),
146+
]
147+
148+
group = MenuItemGroup(items, sort_items=False)
149+
result = SelectMenu[StorageMechanism](
150+
group,
151+
alignment=Alignment.CENTER,
152+
frame=FrameProperties.min('Filesystem'),
153+
allow_skip=False,
154+
).run()
155+
156+
match result.type_:
157+
case ResultType.Selection:
158+
return HomedConfiguration(use_homed=True, storage_mechanism=result.item().value)
159+
case _:
160+
raise ValueError('Unhandled result type')
161+
162+
return HomedConfiguration(use_homed=False)
115163

116164

117165
def ask_for_additional_users(prompt: str = '', defined_users: list[User] = []) -> list[User]:

archinstall/lib/models/users.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
from dataclasses import dataclass, field
24
from enum import Enum
35
from typing import NotRequired, TypedDict, override
@@ -100,12 +102,40 @@ def _check_password_strength(
100102
return PasswordStrength.VERY_WEAK
101103

102104

105+
106+
class StorageMechanism:
107+
DIRECTORY = 'directory'
108+
LUKS = 'luks'
109+
FSCRYPT = 'fscrypt'
110+
111+
112+
class HomedConfiguration:
113+
def __init__(
114+
self,
115+
use_homed: bool,
116+
storage_mechanism: StorageMechanism | None = None,
117+
):
118+
self.use_homed = use_homed
119+
self.storage_mechanism = storage_mechanism
120+
121+
@override
122+
def __eq__(self, other: object) -> bool:
123+
if not isinstance(other, HomedConfiguration):
124+
return NotImplemented
125+
126+
return (
127+
self.use_homed == other.use_homed
128+
and self.storage_mechanism == other.storage_mechanism
129+
)
130+
131+
103132
UserSerialization = TypedDict(
104133
'UserSerialization',
105134
{
106135
'username': str,
107136
'!password': NotRequired[str],
108137
'sudo': bool,
138+
'homed': HomedConfiguration,
109139
'groups': list[str],
110140
'enc_password': str | None,
111141
},
@@ -158,18 +188,20 @@ class User:
158188
username: str
159189
password: Password
160190
sudo: bool
191+
homed: HomedConfiguration
161192
groups: list[str] = field(default_factory=list)
162193

163194
@override
164195
def __str__(self) -> str:
165196
# safety overwrite to make sure password is not leaked
166-
return f'User({self.username=}, {self.sudo=}, {self.groups=})'
197+
return f'User({self.username=}, {self.sudo=}, {self.homed}, {self.groups=})'
167198

168-
def table_data(self) -> dict[str, str | bool | list[str]]:
199+
def table_data(self) -> dict[str, str | str | bool | bool | list[str]]:
169200
return {
170201
'username': self.username,
171202
'password': self.password.hidden(),
172203
'sudo': self.sudo,
204+
'homed': self.homed.use_homed,
173205
'groups': self.groups,
174206
}
175207

@@ -178,9 +210,48 @@ def json(self) -> UserSerialization:
178210
'username': self.username,
179211
'enc_password': self.password.enc_password,
180212
'sudo': self.sudo,
213+
'homed': self.homed,
181214
'groups': self.groups,
182215
}
183216

217+
def to_identity(self) -> str:
218+
if not self.homed.use_homed:
219+
raise ValueError('Homed is not enabled for this user')
220+
221+
identity = {
222+
'userName': self.username,
223+
'disposition': 'regular',
224+
'privileged': {
225+
'hashedPassword': [
226+
self.password.enc_password
227+
],
228+
},
229+
'enforcePasswordPolicy': False,
230+
'secret': {
231+
'password': [
232+
self.password.plaintext,
233+
],
234+
}
235+
}
236+
237+
identity['memberOf'] = []
238+
239+
if self.sudo:
240+
identity['memberOf'].append('wheel')
241+
242+
if self.groups:
243+
identity['memberOf'].extend(self.groups)
244+
245+
match self.homed.storage_mechanism:
246+
case StorageMechanism.DIRECTORY:
247+
identity['storage'] = 'directory'
248+
case StorageMechanism.LUKS:
249+
identity['storage'] = 'luks'
250+
case StorageMechanism.FSCRYPT:
251+
identity['storage'] = 'fscrypt'
252+
253+
return json.dumps(identity)
254+
184255
@classmethod
185256
def parse_arguments(
186257
cls,
@@ -208,6 +279,7 @@ def parse_arguments(
208279
username=username,
209280
password=password,
210281
sudo=entry.get('sudo', False) is True,
282+
homed=entry.get('homed', HomedConfiguration(use_homed=False)),
211283
groups=groups,
212284
)
213285

0 commit comments

Comments
 (0)