feat(applications): add CUPS installation support (#4013)

* feat(applications): add CUPS installation support

* fix: use translation for print service preview_action

* fix: incorrect action for print service menu item

* chore: refactor naming, printer -> print service

* fix: commit untracked file

* chore: fix formatting to make linter happy
This commit is contained in:
Mariya 2025-12-20 22:30:11 -05:00 committed by GitHub
parent cf31148e46
commit d7b559c67e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 113 additions and 8 deletions

View File

@ -0,0 +1,23 @@
from typing import TYPE_CHECKING
from archinstall.lib.output import debug
if TYPE_CHECKING:
from archinstall.lib.installer import Installer
class PrintServiceApp:
@property
def packages(self) -> list[str]:
return ['cups', 'system-config-printer', 'cups-pk-helper']
@property
def services(self) -> list[str]:
return [
'cups.service',
]
def install(self, install_session: 'Installer') -> None:
debug('Installing print service')
install_session.add_additional_packages(self.packages)
install_session.enable_service(self.services)

View File

@ -2,6 +2,7 @@ from typing import TYPE_CHECKING
from archinstall.applications.audio import AudioApp from archinstall.applications.audio import AudioApp
from archinstall.applications.bluetooth import BluetoothApp from archinstall.applications.bluetooth import BluetoothApp
from archinstall.applications.print_service import PrintServiceApp
from archinstall.lib.models import Audio from archinstall.lib.models import Audio
from archinstall.lib.models.application import ApplicationConfiguration from archinstall.lib.models.application import ApplicationConfiguration
from archinstall.lib.models.users import User from archinstall.lib.models.users import User
@ -25,5 +26,8 @@ class ApplicationHandler:
users, users,
) )
if app_config.print_service_config and app_config.print_service_config.enabled:
PrintServiceApp().install(install_session)
application_handler = ApplicationHandler() application_handler = ApplicationHandler()

View File

@ -1,7 +1,7 @@
from typing import override from typing import override
from archinstall.lib.menu.abstract_menu import AbstractSubMenu from archinstall.lib.menu.abstract_menu import AbstractSubMenu
from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration
from archinstall.lib.translationhandler import tr from archinstall.lib.translationhandler import tr
from archinstall.tui.curses_menu import SelectMenu from archinstall.tui.curses_menu import SelectMenu
from archinstall.tui.menu_item import MenuItem, MenuItemGroup from archinstall.tui.menu_item import MenuItem, MenuItemGroup
@ -48,6 +48,12 @@ class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
preview_action=self._prev_audio, preview_action=self._prev_audio,
key='audio_config', key='audio_config',
), ),
MenuItem(
text=tr('Print service'),
action=select_print_service,
preview_action=self._prev_print_service,
key='print_service_config',
),
] ]
def _prev_bluetooth(self, item: MenuItem) -> str | None: def _prev_bluetooth(self, item: MenuItem) -> str | None:
@ -65,6 +71,15 @@ class ApplicationMenu(AbstractSubMenu[ApplicationConfiguration]):
return f'{tr("Audio")}: {config.audio.value}' return f'{tr("Audio")}: {config.audio.value}'
return None return None
def _prev_print_service(self, item: MenuItem) -> str | None:
if item.value is not None:
print_service_config: PrintServiceConfiguration = item.value
output = f'{tr("Print service")}: '
output += tr('Enabled') if print_service_config.enabled else tr('Disabled')
return output
return None
def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None: def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfiguration | None:
group = MenuItemGroup.yes_no() group = MenuItemGroup.yes_no()
@ -94,6 +109,34 @@ def select_bluetooth(preset: BluetoothConfiguration | None) -> BluetoothConfigur
raise ValueError('Unhandled result type') raise ValueError('Unhandled result type')
def select_print_service(preset: PrintServiceConfiguration | None) -> PrintServiceConfiguration | None:
group = MenuItemGroup.yes_no()
group.focus_item = MenuItem.no()
if preset is not None:
group.set_selected_by_value(preset.enabled)
header = tr('Would you like to configure the print service?') + '\n'
result = SelectMenu[bool](
group,
header=header,
alignment=Alignment.CENTER,
columns=2,
orientation=Orientation.HORIZONTAL,
allow_skip=True,
).run()
match result.type_:
case ResultType.Selection:
enabled = result.item() == MenuItem.yes()
return PrintServiceConfiguration(enabled)
case ResultType.Skip:
return preset
case _:
raise ValueError('Unhandled result type')
def select_audio(preset: AudioConfiguration | None = None) -> AudioConfiguration | None: def select_audio(preset: AudioConfiguration | None = None) -> AudioConfiguration | None:
items = [MenuItem(a.value, value=a) for a in Audio] items = [MenuItem(a.value, value=a) for a in Audio]
group = MenuItemGroup(items) group = MenuItemGroup(items)

View File

@ -324,6 +324,11 @@ class GlobalMenu(AbstractMenu[None]):
output += f'{tr("Audio")}: {audio_config.audio.value}' output += f'{tr("Audio")}: {audio_config.audio.value}'
output += '\n' output += '\n'
if app_config.print_service_config:
output += f'{tr("Print service")}: '
output += tr('Enabled') if app_config.print_service_config.enabled else tr('Disabled')
output += '\n'
return output return output
return None return None

View File

@ -1,9 +1,4 @@
from .application import ( from .application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration
ApplicationConfiguration,
Audio,
AudioConfiguration,
BluetoothConfiguration,
)
from .bootloader import Bootloader from .bootloader import Bootloader
from .device import ( from .device import (
BDevice, BDevice,
@ -76,6 +71,7 @@ __all__ = [
'PartitionTable', 'PartitionTable',
'PartitionType', 'PartitionType',
'PasswordStrength', 'PasswordStrength',
'PrintServiceConfiguration',
'ProfileConfiguration', 'ProfileConfiguration',
'Repository', 'Repository',
'SectorSize', 'SectorSize',

View File

@ -17,9 +17,14 @@ class AudioConfigSerialization(TypedDict):
audio: str audio: str
class PrintServiceConfigSerialization(TypedDict):
enabled: bool
class ApplicationSerialization(TypedDict): class ApplicationSerialization(TypedDict):
bluetooth_config: NotRequired[BluetoothConfigSerialization] bluetooth_config: NotRequired[BluetoothConfigSerialization]
audio_config: NotRequired[AudioConfigSerialization] audio_config: NotRequired[AudioConfigSerialization]
print_service_config: NotRequired[PrintServiceConfigSerialization]
@dataclass @dataclass
@ -50,10 +55,23 @@ class BluetoothConfiguration:
return BluetoothConfiguration(arg['enabled']) return BluetoothConfiguration(arg['enabled'])
@dataclass
class PrintServiceConfiguration:
enabled: bool
def json(self) -> PrintServiceConfigSerialization:
return {'enabled': self.enabled}
@staticmethod
def parse_arg(arg: dict[str, Any]) -> 'PrintServiceConfiguration':
return PrintServiceConfiguration(arg['enabled'])
@dataclass @dataclass
class ApplicationConfiguration: class ApplicationConfiguration:
bluetooth_config: BluetoothConfiguration | None = None bluetooth_config: BluetoothConfiguration | None = None
audio_config: AudioConfiguration | None = None audio_config: AudioConfiguration | None = None
print_service_config: PrintServiceConfiguration | None = None
@staticmethod @staticmethod
def parse_arg( def parse_arg(
@ -72,6 +90,9 @@ class ApplicationConfiguration:
if args and (audio_config := args.get('audio_config')) is not None: if args and (audio_config := args.get('audio_config')) is not None:
app_config.audio_config = AudioConfiguration.parse_arg(audio_config) app_config.audio_config = AudioConfiguration.parse_arg(audio_config)
if args and (print_service_config := args.get('print_service_config')) is not None:
app_config.print_service_config = PrintServiceConfiguration.parse_arg(print_service_config)
return app_config return app_config
def json(self) -> ApplicationSerialization: def json(self) -> ApplicationSerialization:
@ -83,4 +104,7 @@ class ApplicationConfiguration:
if self.audio_config: if self.audio_config:
config['audio_config'] = self.audio_config.json() config['audio_config'] = self.audio_config.json()
if self.print_service_config:
config['print_service_config'] = self.print_service_config.json()
return config return config

View File

@ -1779,6 +1779,12 @@ msgstr ""
msgid "Would you like to configure Bluetooth?" msgid "Would you like to configure Bluetooth?"
msgstr "" msgstr ""
msgid "Print service"
msgstr ""
msgid "Would you like to configure the print service?"
msgstr ""
msgid "Authentication" msgid "Authentication"
msgstr "" msgstr ""

View File

@ -7,6 +7,9 @@
}, },
"audio_config": { "audio_config": {
"audio": "pipewire" "audio": "pipewire"
},
"print_service_config": {
"enabled": true
} }
}, },
"auth_config": { "auth_config": {

View File

@ -7,7 +7,7 @@ from pytest import MonkeyPatch
from archinstall.default_profiles.profile import GreeterType from archinstall.default_profiles.profile import GreeterType
from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments from archinstall.lib.args import ArchConfig, ArchConfigHandler, Arguments
from archinstall.lib.hardware import GfxDriver from archinstall.lib.hardware import GfxDriver
from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration from archinstall.lib.models.application import ApplicationConfiguration, Audio, AudioConfiguration, BluetoothConfiguration, PrintServiceConfiguration
from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod from archinstall.lib.models.authentication import AuthenticationConfiguration, U2FLoginConfiguration, U2FLoginMethod
from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration from archinstall.lib.models.bootloader import Bootloader, BootloaderConfiguration
from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType from archinstall.lib.models.device import DiskLayoutConfiguration, DiskLayoutType
@ -132,6 +132,7 @@ def test_config_file_parsing(
app_config=ApplicationConfiguration( app_config=ApplicationConfiguration(
bluetooth_config=BluetoothConfiguration(enabled=True), bluetooth_config=BluetoothConfiguration(enabled=True),
audio_config=AudioConfiguration(audio=Audio.PIPEWIRE), audio_config=AudioConfiguration(audio=Audio.PIPEWIRE),
print_service_config=PrintServiceConfiguration(enabled=True),
), ),
auth_config=AuthenticationConfiguration( auth_config=AuthenticationConfiguration(
root_enc_password=Password(enc_password='password_hash'), root_enc_password=Password(enc_password='password_hash'),