Skip to content

Commit 45f1bbe

Browse files
committed
feat: cosmic-initial-setup OEM mode support
1 parent 6813da2 commit 45f1bbe

File tree

12 files changed

+246
-120
lines changed

12 files changed

+246
-120
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ i18n-embed-fl = "0.7"
6565
rust-embed = "8"
6666
futures-util = "0.3.30"
6767
timedate-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings" }
68+
futures-channel = "0.3.31"
6869

6970
[dependencies.greetd_ipc]
7071
version = "0.10.3"

cosmic-greeter.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ service = "cosmic-greeter"
77
[default_session]
88
command = "cosmic-comp systemd-cat -t cosmic-greeter cosmic-greeter"
99
user = "cosmic-greeter"
10+
11+
[initial_session]
12+
command = "cosmic-greeter"
13+
user = "cosmic-initial-setup"

daemon/src/main.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use cosmic_greeter_daemon::UserData;
2-
use std::{env, error::Error, future::pending, io, path::Path};
2+
use std::{env, error::Error, future::pending, io, path::Path, process::Command};
33
use zbus::{connection::Builder, DBusError};
44

5+
const INITIAL_SETUP_HOME: &str = "/run/cosmic-initial-setup/";
6+
57
//IMPORTANT: this function is critical to the security of this proxy. It must ensure that the
68
// callback is executed with the permissions of the specified user id. A good test is to see if
79
// the /etc/shadow file can be read with a non-root user, it should fail with EPERM.
@@ -46,6 +48,44 @@ struct GreeterProxy;
4648

4749
#[zbus::interface(name = "com.system76.CosmicGreeter")]
4850
impl GreeterProxy {
51+
async fn initial_setup_start(&mut self) -> Result<(), GreeterError> {
52+
let home_dir = Path::new(INITIAL_SETUP_HOME);
53+
54+
if let Err(why) = std::fs::create_dir_all(&home_dir.join(".local/share")) {
55+
eprintln!("failed to create local share dir: {why:?}");
56+
}
57+
58+
if let Err(why) = std::fs::create_dir_all(&home_dir.join(".local/state")) {
59+
eprintln!("failed to create local share dir: {why:?}");
60+
}
61+
62+
_ = Command::new("chown")
63+
.args(&["-R", "cosmic-initial-setup:nogroup", INITIAL_SETUP_HOME])
64+
.status();
65+
66+
_ = Command::new("chsh")
67+
.args(&["-s", "/bin/bash", "cosmic-initial-setup"])
68+
.status();
69+
70+
Ok(())
71+
}
72+
73+
async fn initial_setup_end(&mut self, new_user: String) -> Result<(), GreeterError> {
74+
let home_dir = ["/home/", &new_user].concat();
75+
_ = Command::new("rm").args(&["-rf", &home_dir]).status();
76+
_ = Command::new("chown")
77+
.args(&[
78+
"-R",
79+
&[&new_user, ":", &new_user].concat(),
80+
INITIAL_SETUP_HOME,
81+
])
82+
.status();
83+
_ = Command::new("mv")
84+
.args(&[INITIAL_SETUP_HOME, &home_dir])
85+
.status();
86+
Ok(())
87+
}
88+
4989
fn get_user_data(&mut self) -> Result<String, GreeterError> {
5090
// The pwd::Passwd method is unsafe (but not labelled as such) due to using global state (libc pwent functions).
5191
// To prevent issues, this should only be called once in the entire process space at a time

dbus/com.system76.CosmicGreeter.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@
1111
<allow send_destination="com.system76.CosmicGreeter"/>
1212
<allow receive_sender="com.system76.CosmicGreeter"/>
1313
</policy>
14+
<policy user="cosmic-initial-setup">
15+
<allow send_destination="com.system76.CosmicGreeter"/>
16+
<allow receive_sender="com.system76.CosmicGreeter"/>
17+
</policy>
1418
</busconfig>

debian/control

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Depends:
2424
bash,
2525
cosmic-comp,
2626
cosmic-greeter-daemon,
27+
cosmic-initial-setup,
28+
cosmic-session,
2729
dbus,
2830
${misc:Depends},
2931
${shlibs:Depends}

debian/rules

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ export VENDOR ?= 1
77
dh $@
88

99
override_dh_auto_clean:
10-
if ! ischroot && test "${VENDOR}" = "1"; then \
11-
just vendor; \
12-
fi
10+
(! ischroot && test "${VENDOR}" = "1") && just vendor || true
1311

1412
override_dh_auto_build:
15-
just build-vendored
13+
test "${VENDOR}" = "1" && just build-vendored || just
1614

1715
override_dh_auto_install:
1816
just rootdir=$(DESTDIR) install-debian
@@ -24,4 +22,3 @@ execute_after_dh_install:
2422
override_dh_installsystemd:
2523
dh_installsystemd -pcosmic-greeter --no-start -r cosmic-greeter.service
2624
dh_installsystemd -pcosmic-greeter-daemon cosmic-greeter-daemon.service
27-

src/greeter.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ static USERNAME_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new(
5757
default_service = "com.system76.CosmicGreeter",
5858
default_path = "/com/system76/CosmicGreeter"
5959
)]
60-
trait Greeter {
60+
pub trait Greeter {
61+
async fn initial_setup_start(&mut self) -> Result<(), zbus::Error>;
62+
6163
async fn get_user_data(&self) -> Result<String, zbus::Error>;
6264
}
6365

64-
async fn user_data_dbus() -> Result<Vec<UserData>, Box<dyn Error>> {
66+
pub async fn user_data_dbus() -> Result<Vec<UserData>, Box<dyn Error>> {
6567
let connection = Connection::system().await?;
6668

6769
// `dbus_proxy` macro creates `MyGreaterProxy` based on `Notifications` trait.
@@ -72,7 +74,7 @@ async fn user_data_dbus() -> Result<Vec<UserData>, Box<dyn Error>> {
7274
Ok(user_datas)
7375
}
7476

75-
fn user_data_fallback() -> Vec<UserData> {
77+
pub fn user_data_fallback() -> Vec<UserData> {
7678
// The pwd::Passwd method is unsafe (but not labelled as such) due to using global state (libc pwent functions).
7779
/* unsafe */
7880
{
@@ -932,6 +934,7 @@ impl cosmic::Application for App {
932934
heartbeat_handle: None,
933935
entering_name: false,
934936
};
937+
935938
(app, common_task)
936939
}
937940

src/greeter/ipc.rs

Lines changed: 107 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use super::{Message, SocketState};
55
use cosmic::iced::Subscription;
6+
use futures_channel::mpsc::Sender;
67
use futures_util::SinkExt;
78
use greetd_ipc::codec::TokioCodec;
89
use std::sync::Arc;
@@ -17,131 +18,127 @@ pub fn subscription() -> Subscription<Message> {
1718
Subscription::run_with_id(
1819
std::any::TypeId::of::<GreetdSubscription>(),
1920
cosmic::iced_futures::stream::channel(1, |mut sender| async move {
20-
let (tx, mut rx) = mpsc::channel::<greetd_ipc::Request>(1);
21+
let (tx, rx) = mpsc::channel::<greetd_ipc::Request>(1);
2122
_ = sender.send(Message::GreetdChannel(tx)).await;
23+
service(sender, rx).await
24+
}),
25+
)
26+
}
2227

23-
let socket_path =
24-
std::env::var_os("GREETD_SOCK").expect("GREETD_SOCK environment not set");
28+
pub async fn service(mut sender: Sender<Message>, mut rx: mpsc::Receiver<greetd_ipc::Request>) {
29+
let socket_path = std::env::var_os("GREETD_SOCK").expect("GREETD_SOCK environment not set");
2530

26-
let mut interval = tokio::time::interval(Duration::from_secs(1));
31+
let mut interval = tokio::time::interval(Duration::from_secs(1));
2732

28-
loop {
29-
_ = sender.send(Message::Reconnect).await;
33+
loop {
34+
_ = sender.send(Message::Reconnect).await;
3035

31-
let mut stream = match UnixStream::connect(&socket_path).await {
32-
Ok(stream) => stream,
33-
Err(why) => {
34-
log::error!("greetd IPC socket connection failed: {why:?}");
35-
_ = sender.send(Message::Socket(SocketState::Error(Arc::new(why))));
36+
let mut stream = match UnixStream::connect(&socket_path).await {
37+
Ok(stream) => stream,
38+
Err(why) => {
39+
log::error!("greetd IPC socket connection failed: {why:?}");
40+
_ = sender.send(Message::Socket(SocketState::Error(Arc::new(why))));
3641

37-
break;
38-
}
39-
};
42+
break;
43+
}
44+
};
4045

41-
_ = sender.send(Message::Socket(SocketState::Open)).await;
46+
_ = sender.send(Message::Socket(SocketState::Open)).await;
4247

43-
while let Some(request) = rx.recv().await {
44-
if let Err(why) = request.write_to(&mut stream).await {
45-
log::error!("error writing to GREETD_SOCK stream: {why:?}");
46-
break;
47-
}
48+
while let Some(request) = rx.recv().await {
49+
if let Err(why) = request.write_to(&mut stream).await {
50+
log::error!("error writing to GREETD_SOCK stream: {why:?}");
51+
break;
52+
}
4853

49-
match greetd_ipc::Response::read_from(&mut stream).await {
50-
Ok(response) => {
51-
match response {
52-
greetd_ipc::Response::AuthMessage {
53-
auth_message_type,
54-
auth_message,
55-
} => match auth_message_type {
56-
greetd_ipc::AuthMessageType::Secret => {
57-
_ = sender
58-
.send(
59-
common::Message::Prompt(
60-
auth_message,
61-
true,
62-
Some(String::new()),
63-
)
64-
.into(),
65-
)
66-
.await;
67-
}
68-
greetd_ipc::AuthMessageType::Visible => {
69-
_ = sender
70-
.send(
71-
common::Message::Prompt(
72-
auth_message,
73-
false,
74-
Some(String::new()),
75-
)
76-
.into(),
77-
)
78-
.await;
79-
}
80-
//TODO: treat error type differently?
81-
greetd_ipc::AuthMessageType::Info
82-
| greetd_ipc::AuthMessageType::Error => {
83-
_ = sender
84-
.send(
85-
common::Message::Prompt(auth_message, false, None)
86-
.into(),
87-
)
88-
.await;
89-
}
90-
},
91-
greetd_ipc::Response::Error {
92-
error_type: _,
93-
description,
94-
} => {
95-
//TODO: use error_type?
96-
match request {
97-
greetd_ipc::Request::CancelSession => {
98-
// Do not send errors for cancel session to gui
99-
log::warn!(
100-
"error while cancelling session: {}",
101-
description
102-
);
54+
match greetd_ipc::Response::read_from(&mut stream).await {
55+
Ok(response) => {
56+
match response {
57+
greetd_ipc::Response::AuthMessage {
58+
auth_message_type,
59+
auth_message,
60+
} => match auth_message_type {
61+
greetd_ipc::AuthMessageType::Secret => {
62+
_ = sender
63+
.send(
64+
common::Message::Prompt(
65+
auth_message,
66+
true,
67+
Some(String::new()),
68+
)
69+
.into(),
70+
)
71+
.await;
72+
}
73+
greetd_ipc::AuthMessageType::Visible => {
74+
_ = sender
75+
.send(
76+
common::Message::Prompt(
77+
auth_message,
78+
false,
79+
Some(String::new()),
80+
)
81+
.into(),
82+
)
83+
.await;
84+
}
85+
//TODO: treat error type differently?
86+
greetd_ipc::AuthMessageType::Info
87+
| greetd_ipc::AuthMessageType::Error => {
88+
_ = sender
89+
.send(common::Message::Prompt(auth_message, false, None).into())
90+
.await;
91+
}
92+
},
93+
greetd_ipc::Response::Error {
94+
error_type: _,
95+
description,
96+
} => {
97+
//TODO: use error_type?
98+
match request {
99+
greetd_ipc::Request::CancelSession => {
100+
// Do not send errors for cancel session to gui
101+
log::warn!("error while cancelling session: {}", description);
103102

104-
// Reconnect to socket
105-
break;
106-
}
107-
_ => {
108-
_ = sender.send(Message::Error(description)).await;
109-
}
110-
}
103+
// Reconnect to socket
104+
break;
105+
}
106+
_ => {
107+
_ = sender.send(Message::Error(description)).await;
111108
}
112-
greetd_ipc::Response::Success => match request {
113-
greetd_ipc::Request::CreateSession { .. } => {
114-
// User has no auth required, proceed to login
115-
_ = sender.send(Message::Login).await;
116-
}
117-
greetd_ipc::Request::PostAuthMessageResponse { .. } => {
118-
// All auth is completed, proceed to login
119-
_ = sender.send(Message::Login).await;
120-
}
121-
greetd_ipc::Request::StartSession { .. } => {
122-
// Session has been started, exit greeter
123-
_ = sender.send(Message::Exit).await;
124-
}
125-
greetd_ipc::Request::CancelSession => {
126-
log::info!("greetd IPC session canceled");
127-
// Reconnect to socket
128-
break;
129-
}
130-
},
131109
}
132110
}
133-
Err(err) => {
134-
log::error!("failed to read socket: {:?}", err);
135-
break;
136-
}
111+
greetd_ipc::Response::Success => match request {
112+
greetd_ipc::Request::CreateSession { .. } => {
113+
// User has no auth required, proceed to login
114+
_ = sender.send(Message::Login).await;
115+
}
116+
greetd_ipc::Request::PostAuthMessageResponse { .. } => {
117+
// All auth is completed, proceed to login
118+
_ = sender.send(Message::Login).await;
119+
}
120+
greetd_ipc::Request::StartSession { .. } => {
121+
// Session has been started, exit greeter
122+
_ = sender.send(Message::Exit).await;
123+
}
124+
greetd_ipc::Request::CancelSession => {
125+
log::info!("greetd IPC session canceled");
126+
// Reconnect to socket
127+
break;
128+
}
129+
},
137130
}
138131
}
139-
140-
log::info!("reconnecting to greetd IPC socket");
141-
interval.tick().await;
132+
Err(err) => {
133+
log::error!("failed to read socket: {:?}", err);
134+
break;
135+
}
142136
}
137+
}
143138

144-
futures_util::future::pending().await
145-
}),
146-
)
139+
log::info!("reconnecting to greetd IPC socket");
140+
interval.tick().await;
141+
}
142+
143+
futures_util::future::pending().await
147144
}

0 commit comments

Comments
 (0)