<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lilian S</title>
    <description>The latest articles on DEV Community by Lilian S (@liliy).</description>
    <link>https://dev.to/liliy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3806149%2Fb7a6d580-0088-495f-ae81-e844028a7991.jpg</url>
      <title>DEV Community: Lilian S</title>
      <link>https://dev.to/liliy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/liliy"/>
    <language>en</language>
    <item>
      <title>Scaling User Management on Linux: Moving Beyond the Manual Script</title>
      <dc:creator>Lilian S</dc:creator>
      <pubDate>Tue, 02 Jun 2026 15:42:09 +0000</pubDate>
      <link>https://dev.to/liliy/scaling-user-management-on-linux-moving-beyond-the-manual-script-3b27</link>
      <guid>https://dev.to/liliy/scaling-user-management-on-linux-moving-beyond-the-manual-script-3b27</guid>
      <description>&lt;h2&gt;
  
  
  The Scenario: The Help Desk Bottleneck
&lt;/h2&gt;

&lt;p&gt;From 2019 to 2021, while serving as Lead Backend Software Engineer at a fast-growing company, I occasionally support our Linux System Administration tasks. When the DevOps team encountered a critical bottleneck during an initiative to scale dozens of new server deployments, I stepped in to streamline the infrastructure processes.&lt;/p&gt;

&lt;p&gt;The DevOps team was being hampered by constant, fragmented requests from the help desk to manually create new Linux accounts for recruits testing the latest application. These interruptions were not only time-consuming but were directly preventing the team from focusing on the high-priority infrastructure deployments that define their core responsibilities.&lt;/p&gt;

&lt;p&gt;I realized that we weren't just struggling with a task; we were struggling with a scaling bottleneck. To regain the team's focus and ensure we hit our project deadlines, I decided to automate this workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Step: The Interactive Script
&lt;/h2&gt;

&lt;p&gt;My first objective was to develop a robust, automated shell script to efficiently create new Linux user accounts. I started with an interactive Bash script (create-user-interactive.sh) that prompted for input.&lt;/p&gt;

&lt;p&gt;This was a good educational exercise for learning the fundamentals of Bash—like useradd, passwd, and shell variables. However, I quickly learned that while interactive scripts are great for learning, they are rarely used in professional DevOps environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Manual Scripts Don’t Scale
&lt;/h2&gt;

&lt;p&gt;As I transitioned into a more infrastructure-focused role, I realized that manual scripts fail for three key reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lack of Automation:&lt;/strong&gt; DevOps is about "Infrastructure as Code" (IaC). Asking an engineer to sit at a terminal and type prompts is slow, error-prone, and destroys the ability to automate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lack of Centralization:&lt;/strong&gt; In a real team, we aren't creating users on individual local machines. We manage identity across hundreds of servers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security Risks:&lt;/strong&gt; Hardcoding passwords or piping them through echo is a major red flag.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Industry Standard: How We Actually Do It
&lt;/h2&gt;

&lt;p&gt;If you are working in a industry-standard DevOps team, you don't use manual scripts for user management. You use one of the following methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration Management (Ansible, Puppet):&lt;/strong&gt; We define the state of the user in a configuration file. Ansible is my favorite here because it is idempotent—if the user already exists, it does nothing; if they are missing, it creates them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Centralized Identity (LDAP, Active Directory):&lt;/strong&gt; In an enterprise, we connect servers to a central directory. When an employee leaves, we disable their account in one place, and they lose access everywhere instantly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud-Native IAM (AWS/GCP IAM):&lt;/strong&gt; For cloud infrastructure, we often skip OS-level accounts entirely, using services like AWS SSM Session Manager to connect to instances without managing local users or SSH keys.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Pragmatic Solution: create-user-automated.sh
&lt;/h2&gt;

&lt;p&gt;Sometimes, you still need a shell script. Perhaps you are working on a small project or need a bootstrap script for a server's first boot. If you must use a script, make it non-interactive so it can be automated by a CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;Here is the "DevOps" way to write that script (create-user-automated.sh):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Name: create-user-automated.sh&lt;/span&gt;
&lt;span class="c"&gt;# Description: Creates a new user on the local system non-interactively with a generated password.&lt;/span&gt;
&lt;span class="c"&gt;# Usage: ./create-user-automated.sh &amp;lt;username&amp;gt; &amp;lt;full_name&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# Example call: ./create-user-automated.sh dak "Dakota"&lt;/span&gt;


&lt;span class="c"&gt;# Ensure the script is run as root&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'Error: Please run with sudo or as root.'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check for correct number of arguments&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${#}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 2 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Usage: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;username&amp;gt; &amp;lt;full_name&amp;gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;COMMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;2&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Check if user already exists to maintain idempotency&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"User &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already exists. Skipping creation."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Generate a random password (12 characters, alphanumeric + special characters)&lt;/span&gt;
&lt;span class="nv"&gt;PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s%N&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RANDOM&lt;/span&gt;&lt;span class="k"&gt;}${&lt;/span&gt;&lt;span class="nv"&gt;RANDOM&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; | &lt;span class="nb"&gt;sha256sum&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 12&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Create the account&lt;/span&gt;
useradd &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMMENT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Could not create account for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Set the generated password&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | passwd &lt;span class="nt"&gt;--stdin&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Error: Could not set password for &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="c"&gt;# Force password change on first login&lt;/span&gt;
passwd &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null


&lt;span class="c"&gt;# Display output block for the Help Desk&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
------------------------------------------------------------
ACCOUNT PROVISIONING SUCCESSFUL
------------------------------------------------------------
System:        &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOSTNAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
User:          &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
Temp Password: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;

IMPORTANT: 
The user will be required to change this password upon 
their first successful login. Please forward these 
credentials to the user securely.
------------------------------------------------------------
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Engineer’s Mindset
&lt;/h2&gt;

&lt;p&gt;Moving from a manual, interactive script to an automated, idempotent one isn't just about writing cleaner code—it’s about a change in mindset. It’s about building systems that don't require our constant presence to function.&lt;/p&gt;

&lt;p&gt;By building this tool, I empowered the help desk to handle requests independently, ensured our provisioning was consistent and error-free, and most importantly, I regained the time I needed to focus on our high-priority server deployments.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>automation</category>
      <category>bash</category>
    </item>
  </channel>
</rss>
