feat: add host id allocation

Signed-off-by: sisungo <[email protected]>
This commit is contained in:
2026-07-19 13:05:26 +08:00
parent bfcc98b50e
commit c72134a89e
8 changed files with 124 additions and 9 deletions
+38
View File
@@ -0,0 +1,38 @@
use crate::client;
use clap::Parser;
use semios_account::{
protocol::{CreateGroupArgs, HostIdRule},
record::HostGid,
};
#[derive(Debug, Clone, Parser)]
pub struct Cli {
#[arg(short = 'D', long, default_value_t)]
description: String,
#[arg(short = 'N', long)]
fullname: Option<String>,
#[arg(short = 'G', long)]
host_gid: Option<HostGid>,
groupname: String,
}
pub fn main(cli: Cli) -> anyhow::Result<()> {
let mut client = client()?;
let host_gid = match cli.host_gid {
Some(x) => HostIdRule::Manual(x),
None => HostIdRule::Auto(1000, HostGid::MAX),
};
client.create_group(CreateGroupArgs {
groupname: cli.groupname,
fullname: cli.fullname,
host_gid,
description: cli.description,
})?;
Ok(())
}
+7 -2
View File
@@ -4,7 +4,7 @@ use crate::client;
use anyhow::anyhow;
use clap::Parser;
use semios_account::{
protocol::CreateUserArgs,
protocol::{CreateUserArgs, HostIdRule},
record::{HostUid, SecureTag},
wellknown,
};
@@ -67,10 +67,15 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
let mut defaults = HashMap::new();
defaults.insert(wellknown::CLI_LOGIN_SHELL.into(), cli.cli_shell);
let host_uid = match cli.host_uid {
Some(x) => HostIdRule::Manual(x),
None => HostIdRule::Auto(1000, HostUid::MAX),
};
client.create_user(CreateUserArgs {
username: cli.username,
fullname: cli.fullname,
host_uid: cli.host_uid,
host_uid,
description: cli.description,
secure_tags,
extra_records: Default::default(),
+5
View File
@@ -1,4 +1,5 @@
mod authenticate;
mod create_group;
mod create_user;
mod info;
mod update_auth;
@@ -15,6 +16,9 @@ pub enum Cli {
/// Create new user
CreateUser(create_user::Cli),
/// Create new group
CreateGroup(create_group::Cli),
/// Authenticate user
Authenticate(authenticate::Cli),
@@ -28,6 +32,7 @@ fn main() {
let result = match cli {
Cli::Info(cli) => info::main(cli),
Cli::CreateUser(cli) => create_user::main(cli),
Cli::CreateGroup(cli) => create_group::main(cli),
Cli::Authenticate(cli) => authenticate::main(cli),
Cli::UpdateAuth(cli) => update_auth::main(cli),
};
+23 -3
View File
@@ -15,7 +15,7 @@ use semios_account::{
record::{GroupInfo, SecureTag, UserInfo},
secret::Secret,
};
use std::{fmt::Debug, path::PathBuf, pin::Pin, sync::Arc};
use std::{fmt::Debug, path::PathBuf, pin::Pin, range::RangeInclusive, sync::Arc};
use uuid::Uuid;
pub async fn launch_server(app_state: Arc<AppState>) -> anyhow::Result<()> {
@@ -215,11 +215,21 @@ async fn create_user(
args: CreateUserArgs,
) -> Result<(), Error> {
require_secure_tags(&state, caller, &[SecureTag::CreateUser]).await?;
let host_uid = match args.host_uid {
HostIdRule::Unallocated => None,
HostIdRule::Manual(x) => Some(x),
HostIdRule::Auto(a, b) => Some(
state
.local_db
.allocate_host_uid(RangeInclusive::from(a..=b))
.ok_or(Error::AllocHostId)?,
),
};
let user_info = UserInfo {
uuid: Uuid::new_v7(uuid::Timestamp::now(uuid::ContextV7::new())),
fullname: args.fullname.unwrap_or_else(|| args.username.clone()),
username: args.username,
host_uid: args.host_uid,
host_uid,
description: args.description,
secure_tags: args.secure_tags,
extra_records: args.extra_records,
@@ -241,11 +251,21 @@ async fn create_group(
args: CreateGroupArgs,
) -> Result<(), Error> {
require_secure_tags(&state, caller, &[SecureTag::CreateUser]).await?;
let host_gid = match args.host_gid {
HostIdRule::Unallocated => None,
HostIdRule::Manual(x) => Some(x),
HostIdRule::Auto(a, b) => Some(
state
.local_db
.allocate_host_gid(RangeInclusive::from(a..=b))
.ok_or(Error::AllocHostId)?,
),
};
let group_info = GroupInfo {
uuid: Uuid::new_v7(uuid::Timestamp::now(uuid::ContextV7::new())),
fullname: args.fullname.unwrap_or_else(|| args.groupname.clone()),
groupname: args.groupname,
host_gid: args.host_gid,
host_gid,
description: args.description,
creation_time: timestamp_s(),
};
+33 -2
View File
@@ -1,4 +1,4 @@
use rusqlite::params;
use rusqlite::{OptionalExtension, ToSql, params, types::FromSql};
use semios_account::{
auth::{UserAuthMethodFlags, UserAuthMethodRecord},
error::Error,
@@ -6,7 +6,7 @@ use semios_account::{
secret::{Secret, SecurityFlags},
};
use serde::de::DeserializeOwned;
use std::{path::Path, sync::Mutex};
use std::{path::Path, range::RangeInclusive, sync::Mutex};
use uuid::Uuid;
/// Schema of the database.
@@ -285,6 +285,18 @@ impl LocalDb {
self._find_uuid_by("group", "group_host_gid", &host_gid.to_string())
}
pub fn allocate_host_uid(&self, range: RangeInclusive<HostUid>) -> Option<HostUid> {
self._allocate_host_id_max("user", "user_host_uid", range)
.ok()
.map(|x| x.map(|y| y + 1).unwrap_or(range.start))
}
pub fn allocate_host_gid(&self, range: RangeInclusive<HostGid>) -> Option<HostGid> {
self._allocate_host_id_max("group", "group_host_gid", range)
.ok()
.map(|x| x.map(|y| y + 1).unwrap_or(range.start))
}
pub fn is_first_run(&self) -> bool {
!self._kv_get("initialized").is_some()
}
@@ -349,6 +361,25 @@ impl LocalDb {
.ok()
}
fn _allocate_host_id_max<T: FromSql + ToSql + PartialEq>(
&self,
table: &'static str,
column: &'static str,
range: RangeInclusive<T>,
) -> rusqlite::Result<Option<T>> {
let lock = self.0.lock().unwrap();
let mut stmt = lock.prepare_cached(&format!(
"SELECT MAX(CAST(\"{column}\" AS INTEGER))
FROM \"{table}\"
WHERE CAST(\"{column}\" AS INTEGER) BETWEEN ?1 AND ?2"
))?;
let max: Option<T> = stmt.query_one(params![range.start, range.last], |row| row.get(0))?;
if max == Some(range.last) {
return Ok(None);
}
Ok(max)
}
fn _kv_get(&self, key: &str) -> Option<String> {
let lock = self.0.lock().unwrap();
let mut stmt = lock
+4
View File
@@ -92,4 +92,8 @@ impl Client {
pub fn create_user(&mut self, args: CreateUserArgs) -> Result<(), Error> {
self.invoke(CREATE_USER.into(), args)
}
pub fn create_group(&mut self, args: CreateGroupArgs) -> Result<(), Error> {
self.invoke(CREATE_GROUP.into(), args)
}
}
+4
View File
@@ -44,6 +44,10 @@ pub enum Error {
#[error("the specified auth method is not activated for the user")]
NotActivatedAuthMethod,
/// Cannot allocate host ID
#[error("cannot allocate host id")]
AllocHostId,
/// TODO
#[error("already exists")]
AlreadyExists,
+10 -2
View File
@@ -184,7 +184,7 @@ pub struct CreateUserArgs {
pub fullname: Option<String>,
/// Specify a host UID.
pub host_uid: Option<HostUid>,
pub host_uid: HostIdRule<HostUid>,
/// User description.
#[serde(default)]
@@ -225,7 +225,7 @@ pub struct CreateGroupArgs {
pub fullname: Option<String>,
/// Group ID on current host.
pub host_gid: Option<HostGid>,
pub host_gid: HostIdRule<HostGid>,
/// Group description.
#[serde(default)]
@@ -238,6 +238,14 @@ pub struct RemoveGroupArgs {
pub uuid: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HostIdRule<T> {
Unallocated,
Auto(T, T),
Manual(T),
}
// ==- Methods: Authentication -==
pub const GET_USER_AUTH_METHODS: &str = "GetUserAuthMethods";