Skip to content

Commit

Permalink
🧹 move user creation into gucc
Browse files Browse the repository at this point in the history
  • Loading branch information
vnepogodin committed Jun 28, 2024
1 parent 4d0abe4 commit 3239393
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 24 deletions.
1 change: 1 addition & 0 deletions gucc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_library(${PROJECT_NAME} SHARED
src/luks.cpp include/gucc/luks.hpp
src/zfs.cpp include/gucc/zfs.hpp
src/btrfs.cpp include/gucc/btrfs.hpp
src/user.cpp include/gucc/user.hpp
#src/chwd_profiles.cpp src/chwd_profiles.hpp
#src/disk.cpp src/disk.hpp
)
Expand Down
1 change: 1 addition & 0 deletions gucc/include/gucc/io_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ auto safe_getenv(const char* env_name) noexcept -> std::string_view;
void exec(const std::vector<std::string>& vec) noexcept;
auto exec(std::string_view command, bool interactive = false) noexcept -> std::string;
void arch_chroot(std::string_view command, std::string_view mountpoint, bool interactive = false) noexcept;
auto arch_chroot_checked(std::string_view command, std::string_view mountpoint) noexcept -> bool;

} // namespace gucc::utils

Expand Down
25 changes: 25 additions & 0 deletions gucc/include/gucc/user.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#ifndef USER_HPP
#define USER_HPP

#include <string> // for string
#include <string_view> // for string_view
#include <vector> // for vector

namespace gucc::user {

struct UserInfo final {
std::string_view username;
std::string_view password;
std::string_view shell;
std::string_view sudoers_group;
};

// Create group on the system
auto create_group(std::string_view group, std::string_view mountpoint) noexcept -> bool;

// Create user on the system
auto create_new_user(const user::UserInfo& user_info, const std::vector<std::string>& default_groups, std::string_view mountpoint) noexcept -> bool;

} // namespace gucc::user

#endif // USER_HPP
1 change: 1 addition & 0 deletions gucc/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ gucc_lib = library('gucc',
'src/luks.cpp',
'src/zfs.cpp',
'src/btrfs.cpp',
'src/user.cpp',
],
include_directories : [include_directories('include')],
dependencies: deps
Expand Down
14 changes: 13 additions & 1 deletion gucc/src/io_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,22 @@ void arch_chroot(std::string_view command, std::string_view mountpoint, bool int
const auto& cmd_formatted = fmt::format(FMT_COMPILE("arch-chroot {} {} 2>>/tmp/cachyos-install.log 2>&1"), mountpoint, command);

#ifdef NDEVENV
gucc::utils::exec(cmd_formatted, interactive);
utils::exec(cmd_formatted, interactive);
#else
spdlog::info("Running with arch-chroot(interactive='{}'): '{}'", interactive, cmd_formatted);
#endif
}

auto arch_chroot_checked(std::string_view command, std::string_view mountpoint) noexcept -> bool {

Check failure on line 103 in gucc/src/io_utils.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/io_utils.cpp:103:26 [bugprone-easily-swappable-parameters

2 adjacent parameters of 'arch_chroot_checked' of similar type ('std::string_view') are easily swapped by mistake
// TODO(vnepogodin): refactor to move output into variable and print into log
const auto& cmd_formatted = fmt::format(FMT_COMPILE("arch-chroot {} {} 2>>/tmp/cachyos-install.log 1>/dev/null"), mountpoint, command);

#ifdef NDEVENV
return utils::exec(cmd_formatted, true) == "0";
#else
spdlog::info("Running with checked arch-chroot: '{}'", cmd_formatted);
return true;
#endif
}

} // namespace gucc::utils
129 changes: 129 additions & 0 deletions gucc/src/user.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include "gucc/user.hpp"
#include "gucc/io_utils.hpp"
#include "gucc/string_utils.hpp"

#include <algorithm> // for find
#include <filesystem>
#include <fstream> // for ofstream

#include <fmt/compile.h>
#include <fmt/format.h>

#include <spdlog/spdlog.h>

#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuseless-cast"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif

#include <range/v3/algorithm/contains.hpp>

#if defined(__clang__)
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif

namespace fs = std::filesystem;

using namespace std::string_view_literals;

namespace gucc::user {

auto create_group(std::string_view group, std::string_view mountpoint) noexcept -> bool {

Check failure on line 37 in gucc/src/user.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

/gucc/src/user.cpp:37:19 [bugprone-easily-swappable-parameters

2 adjacent parameters of 'create_group' of similar type ('std::string_view') are easily swapped by mistake
// TODO(vnepogodin):
// 1. add parameter to check if the group was already created
// 2. add parameter if the group should be --system group
const auto& cmd = fmt::format(FMT_COMPILE("groupadd {}"), group);
return utils::arch_chroot_checked(cmd, mountpoint);
}

auto create_new_user(const user::UserInfo& user_info, const std::vector<std::string>& default_groups, std::string_view mountpoint) noexcept -> bool {
if (!user_info.sudoers_group.empty() && !ranges::contains(default_groups, user_info.sudoers_group)) {
spdlog::error("Failed to create user {}! User default groups doesn't contain sudoers group({})", user_info.username, user_info.sudoers_group);
return false;
}

// Create needed groups
for (const auto& default_group : default_groups) {
if (!user::create_group(default_group, mountpoint)) {
spdlog::error("Failed to create group {}", default_group);
return false;
}
}

// Create the user
spdlog::info("Creating user {}", user_info.username);
const auto& usercmd = [](auto&& username, auto&& user_shell) -> std::string {
using namespace std::string_view_literals;
static constexpr auto USER_BASE_CMD = "useradd -m -U"sv;
if (!user_shell.empty()) {
return fmt::format(FMT_COMPILE("{} -s {} {}"), USER_BASE_CMD, user_shell, username);
}
return fmt::format(FMT_COMPILE("{} {}"), USER_BASE_CMD, username);
}(user_info.username, user_info.shell);

if (!utils::arch_chroot_checked(usercmd, mountpoint)) {
spdlog::error("Failed to create user with {}", usercmd);
return false;
}

// Set user groups
spdlog::info("Setting groups for user {}", user_info.username);
const auto& groups_set_cmd = fmt::format(FMT_COMPILE("usermod -aG {} {}"), utils::join(default_groups, ","), user_info.username);
if (!utils::arch_chroot_checked(groups_set_cmd, mountpoint)) {
spdlog::error("Failed to set user groups with {}", groups_set_cmd);
return false;
}

// Setup user permissions
spdlog::info("Setting user permissions for {}", user_info.username);
const auto& user_group = fmt::format(FMT_COMPILE("{0}:{0}"), user_info.username);
const auto& user_homedir = fmt::format(FMT_COMPILE("/home/{}"), user_info.username);
const auto& setup_cmd = fmt::format(FMT_COMPILE("chown -R {} {}"), user_group, user_homedir);
if (!utils::arch_chroot_checked(setup_cmd, mountpoint)) {
spdlog::error("Failed to setup user permissions on {} as {}", user_homedir, user_group);
return false;
}

// TODO(vnepogodin): should encrypt user password properly here
const auto& encrypted_passwd = utils::exec(fmt::format(FMT_COMPILE("openssl passwd {}"), user_info.password));
const auto& password_set_cmd = fmt::format(FMT_COMPILE("usermod -p '{}' {}"), encrypted_passwd, user_info.username);
if (!utils::arch_chroot_checked(password_set_cmd, mountpoint)) {
spdlog::error("Failed to set password for user {}", user_info.username);
return false;
}

// Setup sudoers
if (user_info.sudoers_group.empty()) {
spdlog::info("skipping sudoers group is empty");
return true;
}

const auto& sudoers_filepath = fmt::format(FMT_COMPILE("{}/etc/sudoers.d/10-installer"), mountpoint);
{
const auto& sudoers_line = fmt::format(FMT_COMPILE("%{} ALL=(ALL) ALL\n"), user_info.sudoers_group);
std::ofstream sudoers_file{sudoers_filepath, std::ios::out | std::ios::trunc};
if (!sudoers_file.is_open()) {
spdlog::error("Failed to open sudoers for writing {}", sudoers_filepath);
return false;
}
sudoers_file << sudoers_line;
}

std::error_code err{};
fs::permissions(sudoers_filepath,
fs::perms::owner_read | fs::perms::group_read, // 0440
fs::perm_options::replace, err);
if (err) {
spdlog::error("Failed to set permissions for sudoers file: {}", err.message());
return false;
}
return true;
}

} // namespace gucc::user
37 changes: 14 additions & 23 deletions src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "gucc/luks.hpp"
#include "gucc/pacmanconf_repo.hpp"
#include "gucc/string_utils.hpp"
#include "gucc/user.hpp"

#include <algorithm> // for transform
#include <array> // for array
Expand Down Expand Up @@ -470,29 +471,18 @@ void create_new_user(const std::string_view& user, const std::string_view& passw
}
}

// Create the user, set password, then remove temporary password file
utils::arch_chroot("groupadd sudo", false);
utils::arch_chroot(fmt::format(FMT_COMPILE("groupadd {}"), user), false);
utils::arch_chroot(fmt::format(FMT_COMPILE("useradd {0} -m -g {0} -G sudo,storage,power,network,video,audio,lp,sys,input -s {1}"), user, shell), false);
spdlog::info("add user to groups");

// check if user has been created
const auto& user_check = gucc::utils::exec(fmt::format(FMT_COMPILE("arch-chroot {} getent passwd {}"), mountpoint, user));
if (user_check.empty()) {
spdlog::error("User has not been created!");
}
std::error_code err{};
gucc::utils::exec(fmt::format(FMT_COMPILE("echo -e \"{0}\\n{0}\" > /tmp/.passwd"), password));
const auto& ret_status = gucc::utils::exec(fmt::format(FMT_COMPILE("arch-chroot {} passwd {} < /tmp/.passwd &>/dev/null"), mountpoint, user), true);
spdlog::info("create user pwd: {}", ret_status);
fs::remove("/tmp/.passwd", err);

// Set up basic configuration files and permissions for user
// arch_chroot "cp /etc/skel/.bashrc /home/${USER}"
utils::arch_chroot(fmt::format(FMT_COMPILE("chown -R {0}:{0} /home/{0}"), user), false);
const auto& sudoers_file = fmt::format(FMT_COMPILE("{}/etc/sudoers"), mountpoint);
if (fs::exists(sudoers_file)) {
gucc::utils::exec(fmt::format(FMT_COMPILE("sed -i '/NOPASSWD/!s/# %sudo/%sudo/g' {}"), sudoers_file));
// Create user with sudoers and default groups
using namespace std::string_literals;
using namespace std::string_view_literals;
const gucc::user::UserInfo user_info{
.username = user,
.password = password,
.shell = shell,
.sudoers_group = "sudo"sv,
};
const std::vector default_user_groups{"sudo"s, "storage"s, "power"s, "network"s, "video"s, "audio"s, "lp"s, "sys"s, "input"s};
if (!gucc::user::create_new_user(user_info, default_user_groups, mountpoint)) {
spdlog::error("Failed to create user");
}
#else
spdlog::debug("user := {}, password := {}", user, password);
Expand Down Expand Up @@ -1768,6 +1758,7 @@ void enable_autologin([[maybe_unused]] const std::string_view& dm, [[maybe_unuse
gucc::utils::exec(fmt::format(FMT_COMPILE("sed -i 's/^#autologin-user=/autologin-user={}/' /mnt/etc/lightdm/lightdm.conf"), user));
gucc::utils::exec("sed -i 's/^#autologin-user-timeout=0/autologin-user-timeout=0/' /mnt/etc/lightdm/lightdm.conf");

// TODO(vnepogodin): refactor with gucc
utils::arch_chroot("groupadd -r autologin", false);
utils::arch_chroot(fmt::format(FMT_COMPILE("gpasswd -a {} autologin"), user), false);
} else if (dm == "sddm") {
Expand Down

0 comments on commit 3239393

Please sign in to comment.