Friday, October 23, 2026

VMware: PowerShell script for finding unused virtual machines

This script is designed to find virtual machines on VMware vCenter Server that have not had connections via the VMRC or HTML5 console by users for a long time. The algorithm uses the server's "Events", so it is important that the "Event retention" policy is configured for a long period (preferably 365 days).

This setting is performed in vCenter->Settings->General->Edit->Database. There you need to increase the "Event retention (days)" value and restart the vCenter server to apply the parameters.

Below is the script itself, which works as follows: for each virtual machine from the specified folder and subfolders, it finds the latest event of two types (VmAcquiredTicketEvent, VmAcquiredMksTicketEvent) and writes one row to the report.

<#
.SYNOPSIS
    Last console connection for each VM from a vCenter folder (VMRC + HTML5).
.DESCRIPTION
    Recursively collects VMs from the specified folder, finds the latest event
    VmAcquiredTicketEvent or VmAcquiredMksTicketEvent for each VM
    and saves the report to .xls (SpreadsheetML) without external modules.
#>

# ======================== SETTINGS ========================
$vCenterServer = "vcenter.mydomain.local"
$FolderName    = "VM_Folder"
$Days          = 360
$OutputPath    = Join-Path -Path $PSScriptRoot -ChildPath "vm_console_last_event.xls"
# ==========================================================

$eventTypes = @("VmAcquiredTicketEvent", "VmAcquiredMksTicketEvent")
$startTime  = (Get-Date).AddDays(-$Days)
$finishTime = Get-Date

function Get-VMsRecursive {
    param($Folder)
    $vms = @(Get-VM -Location $Folder -ErrorAction SilentlyContinue)
    foreach ($sub in @(Get-Folder -Location $Folder -ErrorAction SilentlyContinue)) {
        $vms += Get-VMsRecursive -Folder $sub
    }
    return $vms
}

function ConvertTo-XmlSafe {
    param([string]$s)
    if ($null -eq $s) { return "" }
    return $s.Replace("&","&amp;").Replace("<","&lt;").Replace(">","&gt;").Replace('"',"&quot;").Replace("'","&apos;")
}

function Export-ToSpreadsheetML {
    param([object[]]$Rows, [string]$Path, [string]$Title)

    $columns = @(
        @{ Name = 'VM';                          Width = 200 },
        @{ Name = 'User';                Width = 160 },
        @{ Name = 'Last connection time'; Width = 170 },
        @{ Name = 'Message';                   Width = 500 }
    )

    $sb = New-Object System.Text.StringBuilder
    [void]$sb.AppendLine('<?xml version="1.0" encoding="UTF-8"?>')
    [void]$sb.AppendLine('<?mso-application progid="Excel.Sheet"?>')
    [void]$sb.AppendLine('<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"')
    [void]$sb.AppendLine(' xmlns:o="urn:schemas-microsoft-com:office:office"')
    [void]$sb.AppendLine(' xmlns:x="urn:schemas-microsoft-com:office:excel"')
    [void]$sb.AppendLine(' xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">')
    [void]$sb.AppendLine(@'
 <Styles>
  <Style ss:ID="Default" ss:Name="Normal">
   <Alignment ss:Vertical="Top"/>
   <Font ss:FontName="Calibri" ss:Size="11"/>
  </Style>
  <Style ss:ID="Header">
   <Alignment ss:Vertical="Center" ss:Horizontal="Center" ss:WrapText="1"/>
   <Font ss:FontName="Calibri" ss:Size="11" ss:Bold="1" ss:Color="#FFFFFF"/>
   <Interior ss:Color="#4472C4" ss:Pattern="Solid"/>
  </Style>
  <Style ss:ID="CellWrap">
   <Alignment ss:Vertical="Top" ss:WrapText="1"/>
   <Font ss:FontName="Calibri" ss:Size="11"/>
  </Style>
  <Style ss:ID="CellNoEvent">
   <Font ss:FontName="Calibri" ss:Size="11" ss:Color="#C00000" ss:Italic="1"/>
  </Style>
  <Style ss:ID="Title">
   <Font ss:FontName="Calibri" ss:Size="12" ss:Bold="1" ss:Color="#1F3864"/>
  </Style>
 </Styles>
'@)

    [void]$sb.AppendLine(' <Worksheet ss:Name="Console_Connections">')
    [void]$sb.AppendLine('  <Table>')
    foreach ($c in $columns) { [void]$sb.AppendLine("   <Column ss:Width=`"$($c.Width)`"/>") }

    $rowNum = 1
    if ($Title) {
        [void]$sb.AppendLine("   <Row ss:Height=`"22`"><Cell ss:StyleID=`"Title`" ss:MergeAcross=`"$($columns.Count - 1)`"><Data ss:Type=`"String`">$(ConvertTo-XmlSafe $Title)</Data></Cell></Row>")
        [void]$sb.AppendLine('   <Row/>')
        $rowNum += 2
    }

    [void]$sb.AppendLine('   <Row ss:Height="28">')
    foreach ($c in $columns) { [void]$sb.AppendLine("    <Cell ss:StyleID=`"Header`"><Data ss:Type=`"String`">$(ConvertTo-XmlSafe $c.Name)</Data></Cell>") }
    [void]$sb.AppendLine('   </Row>')
    $headerRow = $rowNum++

    foreach ($r in $Rows) {
        $style = if ($r.'Last connection time' -eq '-') { "CellNoEvent" } else { "CellWrap" }
        [void]$sb.AppendLine('   <Row>')
        foreach ($c in $columns) { [void]$sb.AppendLine("    <Cell ss:StyleID=`"$style`"><Data ss:Type=`"String`">$(ConvertTo-XmlSafe ([string]$r.($c.Name)))</Data></Cell>") }
        [void]$sb.AppendLine('   </Row>')
        $rowNum++
    }

    [void]$sb.AppendLine('  </Table>')
    [void]$sb.AppendLine("  <AutoFilter x:Range=`"R${headerRow}C1:R$($rowNum - 1)C$($columns.Count)`" xmlns=`"urn:schemas-microsoft-com:office:excel`"/>")
    [void]$sb.AppendLine('  <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel">')
    [void]$sb.AppendLine('   <FreezePanes/><FrozenNoSplit/>')
    [void]$sb.AppendLine("   <SplitHorizontal>$headerRow</SplitHorizontal><TopRowBottomPane>$headerRow</TopRowBottomPane><ActivePane>2</ActivePane>")
    [void]$sb.AppendLine('  </WorksheetOptions>')
    [void]$sb.AppendLine(' </Worksheet>')
    [void]$sb.AppendLine('</Workbook>')

    $sb.ToString() | Out-File -FilePath $Path -Encoding UTF8
}

# --- Connection ---
Write-Host "Connecting to $vCenterServer..." -ForegroundColor Cyan
try { Connect-VIServer -Server $vCenterServer -ErrorAction Stop | Out-Null }
catch { Write-Error "Failed to connect: $($_.Exception.Message)"; return }

try {
    $folder = @(Get-Folder -Name $FolderName -Type VM -ErrorAction SilentlyContinue)[0]
    if (-not $folder) {
        Write-Error "Folder '$FolderName' not found."
        Get-Folder -Type VM | Select-Object Name, Id | Format-Table -AutoSize | Out-Host
        return
    }

    Write-Host "Folder: $($folder.Name) | Period: $($startTime.ToString('yyyy-MM-dd')) — $($finishTime.ToString('yyyy-MM-dd'))" -ForegroundColor Green

    $vms = Get-VMsRecursive -Folder $folder | Group-Object Id | ForEach-Object { $_.Group[0] }
    if ($vms.Count -eq 0) { Write-Host "No VMs found." -ForegroundColor Yellow; return }
    Write-Host "VMs found: $($vms.Count)`n" -ForegroundColor Green

    $rows = New-Object System.Collections.Generic.List[object]
    $i = 0

    foreach ($vm in $vms) {
        $i++
        Write-Progress -Activity "Processing VMs" -Status "$($vm.Name) ($i of $($vms.Count))" -PercentComplete (($i / $vms.Count) * 100)

        try {
            $last = Get-VIEvent -Entity $vm -Start $startTime -Finish $finishTime -ErrorAction Stop |
                    Where-Object { $eventTypes -contains $_.GetType().Name } |
                    Sort-Object CreatedTime -Descending |
                    Select-Object -First 1
        }
        catch {
            $rows.Add([PSCustomObject]@{
                'VM' = $vm.Name; 'User' = '-'
                'Last connection time' = 'Error'
                'Message' = $_.Exception.Message
            })
            continue
        }

        if ($last) {
            $rows.Add([PSCustomObject]@{
                'VM'                          = $vm.Name
                'User'                = if ($last.UserName) { $last.UserName } else { '-' }
                'Last connection time' = $last.CreatedTime.ToString('yyyy-MM-dd HH:mm:ss')
                'Message'                   = [string]$last.FullFormattedMessage
            })
        }
        else {
            $rows.Add([PSCustomObject]@{
                'VM'                          = $vm.Name
                'User'                = '-'
                'Last connection time' = '-'
                'Message'                   = 'No connection events'
            })
        }
    }

    Write-Progress -Activity "Processing VMs" -Completed

    $outDir = Split-Path -Parent $OutputPath
    if ($outDir -and -not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null }

    $title = "Last console connection — vCenter: $vCenterServer, folder: '$FolderName', period: $($startTime.ToString('yyyy-MM-dd')) — $($finishTime.ToString('yyyy-MM-dd'))"
    Export-ToSpreadsheetML -Rows $rows -Path $OutputPath -Title $title

    $withEvents = @($rows | Where-Object { $_.'Last connection time' -ne '-' }).Count
    Write-Host "`nDone. VMs: $($vms.Count), with connections: $withEvents, without: $($vms.Count - $withEvents)" -ForegroundColor Green
    Write-Host "File: $OutputPath" -ForegroundColor Green
}
finally {
    Disconnect-VIServer -Server $vCenterServer -Confirm:$false -ErrorAction SilentlyContinue | Out-Null
}

Saturday, April 25, 2026

Brocade: User Management on optical switches

In the Fabric OS (FOS) operating system of Brocade switches, user account management is performed using the "userConfig" command, which supports the following roles for the chassis and logical fabrics (LF):
admin - full access to all commands;
user - read-only, minimal privileges;
switchadmin - switch management, but not zoning;
zoneadmin - zoning management;
fabricadmin - fabric service management;
basicswitchadmin - limited switch management;
operator - extended rights (port management, but not configuration);
securityadmin - security policy management.

For example, to create an account with read-only privileges, the role "user" is used.
For a more detailed view of a role, run:
roleconfig --show <role>


To view user accounts, use the following commands:

Show all users:
userconfig --show -a
Show a specific user:
userconfig --show <username>
Show users with a specific role:
userconfig --show -r <role>


To create a new user:

userconfig --add <username> -r <LF_role> -l <LF_ID_list> [-h <home_LF>] [-c <chassis_role>] [-p <password>] [-d <description>]

Example of creating a read-only user "monitor":
userconfig --add monitor -l 1-128 -h 128 -r user -c user -p 'mypassword' -d "Monitoring account"

where
-r user — role inside logical fabrics (read-only);
-l 1-128 — access to all LFs from 1 to 128;
-h 128 — home LF (defaults to FID 128);
-c user — chassis-level role (also read-only);
-p — password;
-d — account description.

Important: for read-only access, be sure to specify "-c user" and "-r user". Without "-c user" the user may not have access to the global configuration.


To modify parameters of an existing user, use the "userconfig --change" command, which allows changing the role, LF list, home LF, lock status, description, and password:

userconfig --change <username> [-l <LF_list> -r <LF_role>] [-h <home_LF>] [-c <chassis_role>] [-e yes|no] [-x] [-u] [-d <description>] [-at <HH:MM-HH:MM>]

Example of changing the role of user "monitor" to admin:
userconfig --change monitor -l 1-128 -h 128 -r admin -c admin

Example of changing a user's description:
userconfig --change monitor -d "New description"

Example of setting the home LF to 10:
userconfig --change monitor -h 10

Example of unlocking an account (after several failed attempts):
userconfig --change monitor -u

Force a user to change their password at next login:
userconfig --change monitor -x

Example of disabling an account (without deletion):
userconfig --change monitor -e no

Example of enabling an account:
userconfig --change monitor -e yes

Change password (interactively):
passwd monitor

Example of setting a time‑based access restriction (from 9:00 to 18:00):
userconfig --change monitor -at 09:00-18:00

Example of removing a time‑based access restriction:
userconfig --change monitor -at 00:00-00:00


To manage access to logical fabrics:

To add access to additional LFs, use the command:
userconfig --addlf <username> -l <LF_list> -r <LF_role> [-h <home_LF>] [-c <chassis_role>]

Example of adding access to LF 10 with the user role for user "monitor":
userconfig --addlf monitor -l 10 -r user

To remove access to specific LFs:
userconfig --deletelf <username> -l <LF_list>

Example of removing access to LF 10:
userconfig --deletelf monitor -l 10


To delete a user, use the "userconfig --delete" command:

userconfig --delete <username>

Example of deleting a user:
userconfig --delete monitor


To diagnose login problems, check the audit logs:

auditdump --show | grep -i <username>

Note: executing the commands --add, --change, --delete, --addlf, --deletelf requires administrator privileges (having the admin or securityadmin role).

Wednesday, April 15, 2026

Debian: Setting Up an XRDP Remote Desktop Server for KDE-Plasma or Gnome

To configure a remote desktop server in Debian OS, perform the following steps:

Installing "xrdp":
sudo apt update && apt upgrade
sudo apt install xrdp dbus-x11 -y



If the Russian keyboard layout is missing in the remote session, add the following lines to the end of the file "/etc/xrdp/xrdp_keyboard.ini" and restart the service "sudo systemctl restart xrdp":

[rdp_keyboard_ru]
keyboard_type=4
keyboard_type=7
keyboard_subtype=1
model=pc105
options=grp:alt_shift_toggle
rdp_layouts=default_rdp_layouts
layouts_map=layouts_map_ru

[layouts_map_ru]
rdp_layout_us=us,ru
rdp_layout_ru=us,ru



If you see a black screen after logging into the remote session, replace the content of the file "/etc/xrdp/startwm.sh" with:

#!/bin/sh
# Destroy variables that may interfere with the D-Bus session
unset DBUS_SESSION_BUS_ADDRESS
unset XDG_RUNTIME_DIR
# Launch D-Bus for the user session
export $(dbus-launch)
# Set keyboard layouts for Russian and English, switch with Alt+Shift
setxkbmap -layout "us,ru" -variant ",winkeys" -option "grp:alt_shift_toggle"
# Launch KDE Plasma in X11 mode
exec startplasma-x11

and restart the service "sudo systemctl restart xrdp".


To add a policy allowing power management actions from a remote session:

Create the file "/etc/polkit-1/rules.d/10-allow-reboot.rules" with the following content:

polkit.addRule(function(action, subject) {
var actions = [
"org.freedesktop.login1.reboot",
"org.freedesktop.login1.reboot-multiple-sessions",
"org.freedesktop.login1.power-off",
"org.freedesktop.login1.power-off-multiple-sessions",
"org.freedesktop.login1.suspend",
"org.freedesktop.login1.suspend-multiple-sessions",
"org.freedesktop.login1.hibernate",
"org.freedesktop.login1.hibernate-multiple-sessions"
];
if (actions.indexOf(action.id) !== -1) {
return polkit.Result.YES;
}
});



If you encounter problems, you can check the log:
sudo tail -50 /var/log/xrdp-sesman.log

Tuesday, January 13, 2026

Debian: Console Russification

Russification takes place in three key stages: configuring locales, if necessary — checking and installing console fonts and encoding, and then configuring the Russian keyboard layout.

Installing and Configuring the Russian Locale.

The locale defines the system message language, date, time, and number formats. The main step is to generate the Russian locale:
1. Install or update the locales package:
sudo apt update
sudo apt install locales


2. Configure the locale using the dpkg-reconfigure utility:
sudo dpkg-reconfigure locales

In the opened text menu, use the spacebar to select the item ru_RU.UTF-8 and press "Enter". On the next screen, choose ru_RU.UTF-8 as the default system locale and press "Enter" again.

Configuring Console Encoding and Font.

The next step is to configure the local console to use UTF-8 and the correct font.
1. Install the console setup package:
sudo apt install console-setup

2. Configure the console font and encoding:
sudo dpkg-reconfigure console-setup

In the menu that appears, select sequentially:
Encoding: UTF-8;
Font: For Cyrillic support, Terminus or Fixed are good choices, select one of them;
Font size: You can leave it as 8x16 (a readable option);
Character set for the console: Be sure to choose an option that includes Cyrillic (for example, Combined - Latin; Slavic and non-Slavic Cyrillic).

The settings should apply automatically. If not, reboot the machine or switch between virtual consoles and back.

Configuring the Russian Keyboard Layout.

After configuring the locale, messages may become Russian, but to input Cyrillic text in the console, you will also need to configure the keyboard.
1. Install the configuration package:
sudo apt install keyboard-configuration

2. Configure the layout:
sudo dpkg-reconfigure keyboard-configuration

In the menu that appears, sequentially select the appropriate keyboard model (can be left as default), layout (Russian), layout variant, and the key for switching languages (for example, Alt+Shift).

Additionally: To find out the current keyboard model, execute:
grep XKBMODEL /etc/default/keyboard

Additional Steps and Diagnostics:

After all configurations, reboot the system or at least log out of the console and log back in.
To check the results, run the command "locale". If everything is configured correctly, the LANG line and most others will show ru_RU.UTF-8.

Tuesday, December 23, 2025

Debian: Configuring VMware VM Customization Specifications for Debian GNU/Linux OS

Unfortunately, when using "VM Customization Specifications" to deploy Debian OS, the "open-vm-tools" scripts cannot correctly configure certain parameters for this system. To solve this issue, we'll use a custom script that will handle the functionality that doesn't work properly in the tools themselves.

Execute the following steps to configure "VM Customization Specifications":

1. Prepare a Debian OS template with the necessary software that should already be included with new hosts.
2. Make sure to install "open-vm-tools" and verify that the "vmtoolsd" service is set to autostart:
sudo apt-get install open-vm-tools
sudo systemctl enable vmtoolsd && sudo systemctl start vmtoolsd


3. Enable support for customization scripts:
sudo vmware-toolbox-cmd config set deployPkg enable-custom-scripts true

To check the current status of the parameter, execute:
sudo vmware-toolbox-cmd config get deployPkg enable-custom-scripts

4. Next, configure "VM Customization Specifications" on vCenter Server. During configuration, specify: OS type - Linux, hostname generation rules, time zone, TCP/IP settings, and add the following script:

#!/bin/bash

# Function to convert subnet mask to CIDR prefix
mask2cidr() {
    local mask=$1
    local n=0
    IFS=.

    for byte in $mask; do
        case $byte in
            255) n=$((n+8));;
            254) n=$((n+7));;
            252) n=$((n+6));;
            248) n=$((n+5));;
            240) n=$((n+4));;
            224) n=$((n+3));;
            192) n=$((n+2));;
            128) n=$((n+1));;
            0);;
            *) echo 24; return 1;;
        esac
    done

    echo $n
}

# Function to determine network management method with priority: NM -> SystemD -> Ifupdown
get_network_manager() {
    local interface=$1

    # 1. Check NetworkManager first
    if command -v nmcli >/dev/null 2>&1; then
        # Check if the interface is managed by NetworkManager
        if nmcli -t -f DEVICE device 2>/dev/null | grep -q "^${interface}$"; then
            echo "NetworkManager"
            return
        fi

        # Check if NetworkManager is active as a service
        if systemctl is-active NetworkManager >/dev/null 2>&1; then
            echo "NetworkManager"
            return
        fi

        # Check if there is a NetworkManager connection for this interface
        if nmcli -t -f NAME,DEVICE connection show 2>/dev/null | grep -q ":${interface}$"; then
            echo "NetworkManager"
            return
        fi
    fi

    # 2. Check systemd-networkd
    if systemctl is-active systemd-networkd >/dev/null 2>&1 && \
       systemctl is-active systemd-resolved >/dev/null 2>&1; then
        echo "SystemD"
        return
    fi

    # 3. Check ifupdown (traditional Debian method)
    if [ -f "/etc/network/interfaces" ]; then
        # Check if the interface is mentioned in the configuration
        if grep -q -E "^(auto|allow-hotplug|iface|mapping).*${interface}" /etc/network/interfaces; then
            echo "Ifupdown"
            return
        fi

        # Check for configs in interfaces.d
        if [ -d "/etc/network/interfaces.d" ]; then
            for config in /etc/network/interfaces.d/*; do
                if [ -f "$config" ] && grep -q "${interface}" "$config"; then
                    echo "Ifupdown"
                    return
                fi
            done
        fi
    fi

    # Check if the networking service (ifupdown) is active
    if systemctl is-active networking >/dev/null 2>&1; then
        echo "Ifupdown"
        return
    fi

    # 4. If nothing is found, check by service presence
    if systemctl is-enabled NetworkManager >/dev/null 2>&1; then
        echo "NetworkManager"
    elif systemctl is-enabled systemd-networkd >/dev/null 2>&1; then
        echo "SystemD"
    elif systemctl is-enabled networking >/dev/null 2>&1; then
        echo "Ifupdown"
    else
        # If nothing is determined, use ifupdown as a fallback
        echo "Ifupdown"
    fi
}

# Function to configure NetworkManager
configure_networkmanager() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7

    # Delete existing connection for this interface
    nmcli connection delete "$interface" 2>/dev/null || true
    nmcli connection delete "Wired connection 1" 2>/dev/null || true
    nmcli connection delete "System ${interface}" 2>/dev/null || true

    # Create a new connection
    if [ "$bootproto" = "static" ]; then
        local prefix_length=$(mask2cidr "$netmask")
        nmcli con add type ethernet con-name "$interface" ifname "$interface" ipv4.method manual \
            ipv4.addresses "${ipaddr}/${prefix_length}" \
            ipv4.gateway "$gateway" \
            ipv4.dns "$dns_servers" \
            ipv4.dns-search "$domain" \
            autoconnect yes
    else
        nmcli con add type ethernet con-name "$interface" ifname "$interface" \
            ipv4.method auto autoconnect yes
    fi

    # Activate the connection
    nmcli con up "$interface"
}

# Function to configure SystemD networkd
configure_systemd() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7
    local dns_from_dhcp=$8

    # Make sure the service is active
    systemctl enable systemd-networkd 2>/dev/null || true
    systemctl start systemd-networkd 2>/dev/null || true

    # Clear the /etc/systemd/network directory
    rm -rf /etc/systemd/network/*

    # Create basic configuration for loopback interface
    mkdir -p /etc/systemd/network
    cat > /etc/systemd/network/00-loopback.network <<EOF
[Match]
Name=lo

[Network]
Address=127.0.0.1/8
Address=::1/128
EOF

    # Create configuration for the main interface
    CONFIG_FILE="/etc/systemd/network/10-${interface}.network"

    if [ "$bootproto" = "static" ]; then
        if [ -z "$ipaddr" ] || [ -z "$netmask" ]; then
            echo "Error: IPADDR and NETMASK are required for static configuration"
            exit 1
        fi

        prefix_length=$(mask2cidr "$netmask")
        if [ -z "$prefix_length" ]; then
            prefix_length=24
        fi

        # Create config for static IP
        cat > "$CONFIG_FILE" <<EOF
[Match]
Name=$interface

[Network]
Address=$ipaddr/$prefix_length
EOF

        if [ -n "$gateway" ]; then
            echo "Gateway=$gateway" >> "$CONFIG_FILE"
        fi

        # Add DNS servers
        if [ -n "$dns_servers" ]; then
            for dns in $dns_servers; do
                echo "DNS=$dns" >> "$CONFIG_FILE"
            done
        fi
    else
        # Create config for DHCP
        cat > "$CONFIG_FILE" <<EOF
[Match]
Name=$interface

[Network]
DHCP=ipv4

[DHCPv4]
UseDomains=true
EOF

        # Configure DNS usage
        if [ "$dns_from_dhcp" = "yes" ]; then
            echo "UseDNS=yes" >> "$CONFIG_FILE"
        else
            echo "UseDNS=no" >> "$CONFIG_FILE"
            if [ -n "$dns_servers" ]; then
                for dns in $dns_servers; do
                    echo "DNS=$dns" >> "$CONFIG_FILE"
                done
            fi
        fi
    fi

    # Configure systemd-resolved if needed
    if [ -n "$dns_servers" ] || [ -n "$domain" ]; then
        mkdir -p /etc/systemd/resolved.conf.d
        cat > /etc/systemd/resolved.conf.d/10-${interface}.conf <<EOF
[Resolve]
EOF

        if [ -n "$dns_servers" ]; then
            echo "DNS=$dns_servers" >> /etc/systemd/resolved.conf.d/10-${interface}.conf
        fi

        if [ -n "$domain" ]; then
            echo "Domains=$domain" >> /etc/systemd/resolved.conf.d/10-${interface}.conf
        fi

        systemctl enable systemd-resolved 2>/dev/null || true
        systemctl start systemd-resolved 2>/dev/null || true

        # Create symlink for resolv.conf
        ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf 2>/dev/null || true
    fi

    systemctl restart systemd-networkd

    if systemctl is-active systemd-resolved >/dev/null 2>&1; then
        systemctl restart systemd-resolved
    fi
}

# Function to configure Ifupdown (/etc/network/interfaces)
configure_ifupdown() {
    local interface=$1
    local bootproto=$2
    local ipaddr=$3
    local netmask=$4
    local gateway=$5
    local dns_servers=$6
    local domain=$7

    echo "Configuring interface $interface using ifupdown" >&2

    # Create base interfaces file if it doesn't exist
    if [ ! -f /etc/network/interfaces ]; then
        mkdir -p /etc/network
        cat > /etc/network/interfaces <<EOF
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback
EOF
    fi

    # Remove ALL configuration for this interface
    # Create temporary file without current interface configuration
    tmpfile=$(mktemp)
    in_block=0

    while IFS= read -r line; do
        # Check if a new interface block starts
        if [[ "$line" =~ ^(auto|allow-hotplug|iface)[[:space:]]+$interface([[:space:]]|$) ]]; then
            in_block=1
            continue
        fi

        # If we are inside a block and encounter another block start, exit the block
        if [[ $in_block -eq 1 && "$line" =~ ^(auto|allow-hotplug|iface)[[:space:]]+ ]]; then
            in_block=0
        fi

        # If we are not inside the block of the interface being removed, save the line
        if [[ $in_block -eq 0 ]]; then
            echo "$line" >> "$tmpfile"
        fi
    done < /etc/network/interfaces

    # Copy back and remove temporary file
    mv "$tmpfile" /etc/network/interfaces
    chmod 644 /etc/network/interfaces

    # Remove possible duplicate empty lines
    sed -i '/^$/N; /^\n$/D' /etc/network/interfaces
    sed -i -e :a -e '/^\n*$/{$d;N;ba' -e '}' /etc/network/interfaces

    # Add new configuration
    {
        echo ""
        echo "auto $interface"
        if [ "$bootproto" = "static" ]; then
            echo "iface $interface inet static"
            echo "    address $ipaddr"
            echo "    netmask $netmask"
            [ -n "$gateway" ] && echo "    gateway $gateway"
            [ -n "$dns_servers" ] && echo "    dns-nameservers $dns_servers"
            [ -n "$domain" ] && echo "    dns-search $domain"
        else
            echo "iface $interface inet dhcp"
        fi
    } >> /etc/network/interfaces

    # Configure DNS via resolvconf if installed
    if command -v resolvconf >/dev/null 2>&1; then
        # Create temporary file for resolvconf
        TMP_RESOLV="/tmp/resolv.conf.${interface}"
        echo "# Generated by network configuration script" > "$TMP_RESOLV"

        if [ -n "$domain" ]; then
            echo "domain $domain" >> "$TMP_RESOLV"
            echo "search $domain" >> "$TMP_RESOLV"
        fi

        if [ -n "$dns_servers" ]; then
            for dns in $dns_servers; do
                echo "nameserver $dns" >> "$TMP_RESOLV"
            done
        fi

        # Update resolvconf
        resolvconf -a "${interface}" < "$TMP_RESOLV" 2>/dev/null || true
        rm -f "$TMP_RESOLV"
    else
        # Direct configuration of /etc/resolv.conf
        if [ -n "$dns_servers" ] || [ -n "$domain" ]; then
            echo "# Generated by network configuration script" > /etc/resolv.conf

            if [ -n "$domain" ]; then
                echo "domain $domain" >> /etc/resolv.conf
                echo "search $domain" >> /etc/resolv.conf
            fi

            if [ -n "$dns_servers" ]; then
                for dns in $dns_servers; do
                    echo "nameserver $dns" >> /etc/resolv.conf
                done
            fi
        fi
    fi

    # Restart network service
    if systemctl is-active networking >/dev/null 2>&1; then
        systemctl restart networking
    else
        # Old method of network restart
        ifdown "$interface" 2>/dev/null || true
        ifup "$interface" 2>/dev/null || true
    fi
}

# --- Function to clean logs and temporary files ---
clean_system() {
    # Clean logs (preserving directory structure)
    find /var/log -type f -name "*.log" -exec truncate -s 0 {} \; 2>/dev/null || true
    find /var/log -type f -name "*.gz" -delete 2>/dev/null || true
    find /var/log -type f -name "*.old" -delete 2>/dev/null || true
    find /var/log -type f -name "lastlog" -exec rm -f {} \; 2>/dev/null || true

    # Clean temporary files
    rm -rf /tmp/* 2>/dev/null || true
    rm -rf /var/tmp/* 2>/dev/null || true

    # Clean command history and user data
    for user_home in /home/*; do
        if [[ -d "$user_home" ]]; then
            user=$(basename "$user_home")
            # Clean bash_history and other histories
            truncate -s 0 "$user_home/.bash_history" 2>/dev/null || true
            # Clean application caches
            rm -rf "$user_home/.cache/*" 2>/dev/null || true
        fi
    done

    # Clean root
    truncate -s 0 /root/.bash_history 2>/dev/null || true
    rm -rf /root/.cache/* 2>/dev/null || true

    # Delete random seed files
    rm -f /var/lib/systemd/random-seed 2>/dev/null || true
    
    # Reset machine-id
    truncate -s 0 /etc/machine-id
    rm /var/lib/dbus/machine-id
    ln -s /etc/machine-id /var/lib/dbus/machine-id
}

# --- Function to delete SSH host keys ---
reset_ssh_keys() {
    rm -f /etc/ssh/ssh_host_* 2>/dev/null || true

    # Regenerate SSH server keys
    if command -v dpkg-reconfigure >/dev/null 2>&1; then
        dpkg-reconfigure -f noninteractive openssh-server 2>/dev/null || true
    fi

    # Alternative method for Debian
    if [ -x /usr/sbin/sshd ]; then
        /usr/sbin/sshd-keygen -A 2>/dev/null || true
    fi
}

# ============================================================
# Main script logic
# ============================================================
if [ "$1" = "precustomization" ]; then
    # Perform system cleanup
    clean_system

    # Check and create /etc/network/interfaces
    if [ ! -f /etc/network/interfaces ]; then
        echo "Creating /etc/network/interfaces file"
        mkdir -p /etc/network
        cat > /etc/network/interfaces <<EOF
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback
EOF
    fi

    # Check and create /etc/sysconfig/network-scripts directory if needed
    if [ ! -d "/etc/sysconfig/network-scripts" ]; then
        mkdir -p /etc/sysconfig/network-scripts 2>/dev/null || true
    fi

    # Pre-configuration stage
    VMCUST_DIR=$(ls -d /var/run/.vmware-imgcust* 2>/dev/null | head -n 1)
    if [ -n "$VMCUST_DIR" ]; then
        CUST_CFG_PATH="$VMCUST_DIR/cust.cfg"
        if [ -f "$CUST_CFG_PATH" ]; then
            cp "$CUST_CFG_PATH" "/root/cust.cfg"
        fi
    fi

elif [ "$1" = "postcustomization" ]; then
    # Post-configuration stage
    reset_ssh_keys

    if [ ! -f "/root/cust.cfg" ]; then
        echo "Error: configuration file /root/cust.cfg not found"
        exit 1
    fi

    CFG_FILE="/root/cust.cfg"

    # Parse configuration parameters
    BOOTPROTO=$(awk -F' = ' '/^BOOTPROTO/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    IPADDR=$(awk -F' = ' '/^IPADDR/ {line=$2} END{print line}' "$CFG_FILE")
    NETMASK=$(awk -F' = ' '/^NETMASK/ {line=$2} END{print line}' "$CFG_FILE")
    GATEWAY=$(awk -F' = ' '/^GATEWAY/ {line=$2} END{print line}' "$CFG_FILE")
    HOSTNAME=$(awk -F' = ' '/^HOSTNAME/ {line=$2} END{print line}' "$CFG_FILE")
    DOMAIN=$(awk -F' = ' '/^DOMAINNAME/ {line=$2} END{print line}' "$CFG_FILE")
    MACADDR=$(awk -F' = ' '/^MACADDR/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    DNS_SERVERS=$(awk -F' = ' '/^NAMESERVER\|[0-9]/ {print $2}' "$CFG_FILE" | tr '\n' ' ' | sed 's/ $//')
    DNS_FROM_DHCP=$(awk -F' = ' '/^DNSFROMDHCP/ {line=$2} END{print tolower(line)}' "$CFG_FILE")
    TIMEZONE=$(awk -F' = ' '/^TIMEZONE/ {line=$2} END{print line}' "$CFG_FILE")
    UTC=$(awk -F' = ' '/^UTC/ {line=$2} END{print tolower(line)}' "$CFG_FILE")

    # Determine network interface
    INTERFACE=$(ip -o link | awk -v mac="$MACADDR" 'tolower($0) ~ mac {gsub(":", "", $2); print $2}')

    if [ -z "$INTERFACE" ]; then
        INTERFACE=$(ip route | awk '/default/ {print $5; exit}')
    fi

    if [ -z "$INTERFACE" ]; then
        # Last attempt: take the first Ethernet interface
        INTERFACE=$(ip -o link | awk -F': ' '$2 !~ /^lo|virbr|docker|veth/ {print $2; exit}' | sed 's/@.*//')
    fi

    if [ -z "$INTERFACE" ]; then
        echo "Error: could not determine network interface"
        exit 1
    fi

    # Determine network management method
    NET_MGR=$(get_network_manager "$INTERFACE")
    echo "Determined network management method: $NET_MGR for interface $INTERFACE"

    case "$NET_MGR" in
        "NetworkManager")
            configure_networkmanager "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
        "SystemD")
            configure_systemd "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN" "$DNS_FROM_DHCP"
            ;;
        "Ifupdown")
            configure_ifupdown "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
        *)
            echo "Unknown network management method, using ifupdown as fallback"
            configure_ifupdown "$INTERFACE" "$BOOTPROTO" "$IPADDR" "$NETMASK" \
                "$GATEWAY" "$DNS_SERVERS" "$DOMAIN"
            ;;
    esac

    # Common settings
    hostnamectl set-hostname "$HOSTNAME" 2>/dev/null || echo "$HOSTNAME" > /etc/hostname
    sed -i "/127\.0\.1\.1/d" /etc/hosts
    echo "127.0.1.1 $HOSTNAME.$DOMAIN $HOSTNAME" >> /etc/hosts

    # Configure time zone
    if [ -n "$TIMEZONE" ]; then
        timedatectl set-timezone "$TIMEZONE" 2>/dev/null || true

        # Alternative method for Debian
        if [ -f "/usr/share/zoneinfo/$TIMEZONE" ]; then
            cp "/usr/share/zoneinfo/$TIMEZONE" /etc/localtime 2>/dev/null || true
            echo "$TIMEZONE" > /etc/timezone 2>/dev/null || true
        fi
    fi

    # Configure BIOS time format (UTC or local)
    if [ -n "$UTC" ]; then
        if [ "$UTC" = "yes" ]; then
            # Set UTC time in BIOS
            timedatectl set-local-rtc 0 2>/dev/null || true
        else
            # Set local time in BIOS
            timedatectl set-local-rtc 1 2>/dev/null || true
        fi
    fi

    # Save configuration
    CONFIG_LOG="/root/vm-customization.log"
    {
        echo "=========================================="
        echo "Configuration completed: $(date)"
        echo "=========================================="
        echo "CONFIG_SOURCE=$CFG_FILE"
        echo "BOOTPROTO=$BOOTPROTO"
        echo "INTERFACE=$INTERFACE"
        echo "NETWORK_MANAGER=$NET_MGR"
        echo "HOSTNAME=$HOSTNAME"
        echo "DOMAIN=$DOMAIN"
        echo "TIMEZONE=$TIMEZONE"
        echo "UTC=$UTC"

        [ -n "$DNS_FROM_DHCP" ] && echo "DNS_FROM_DHCP=$DNS_FROM_DHCP"
        [ -n "$DNS_SERVERS" ] && echo "DNS_SERVERS=\"$DNS_SERVERS\""

        if [ "$BOOTPROTO" = "static" ]; then
            echo "IP_ADDRESS=$IPADDR"
            echo "NETMASK=$NETMASK"
            [ -n "$GATEWAY" ] && echo "GATEWAY=$GATEWAY"
        fi
    } > "$CONFIG_LOG"

    rm -f "/root/cust.cfg"

    # Sync changes to disk
    sync

    echo "Configuration completed successfully. Network management method: $NET_MGR"

    ##############################################################################################
    # PLACE FOR ADDITIONAL SYSTEM CONFIGURATION
    # Here you can add commands for final machine setup:
    # - Installing additional necessary software
    # - Configuration via Ansible or other configuration management systems
    # - Execution of user scripts
    # - Setting up monitoring, logging, and other services

    # Example: install basic packages
    # apt-get update && apt-get install -y curl wget net-tools

    ##############################################################################################
fi

This script covers all possible network configuration options: IfUpDown, NetworkManager, Systemd-Networkd, both with static IP and DHCP.

Friday, December 19, 2025

Debian: Setting up a swap file in Debian instead of a separate partition

Swap (swap file) is a special area on the disk that the operating system uses when physical RAM becomes insufficient. Although modern systems often have large amounts of RAM, swap can still be useful. Even with sufficient RAM, it is advisable to have a small swap (e.g., 1-2 GB) as a backup. Historically, the swap partition was placed at the end of the disk due to the peculiarities of early file systems and disk controllers: this allowed faster allocation and use of contiguous space, as placement at the disk end (closer to the "unserviced" area) reduced fragmentation and sped up access, especially on HDDs, where write speed decreases towards the center of the disk, but often there was more free space at the end without affecting the root and system partitions at the beginning. In modern realities and with virtualization use, it has become inconvenient to expand the disk and file system when necessary if a swap partition exists at the end of the disk. Therefore, this partition can be removed, and swap can be used as a file on the main partition.

To reconfigure, follow these steps:

1. Before creating a swap file, check how your swap is currently configured:
sudo swapon --show
or
free -h

2. Ensure you have enough free disk space:
df -h

It is recommended to leave at least 5% of free space on the partition after creating the swap file.

3. Create the swap file using the "dd" utility:
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress

where
bs=1M — block size of 1 MB;
count=2048 — number of blocks (resulting size: 2048 MB ≈ 2 GB);
status=progress — shows progress (not available in all versions of dd).

IMPORTANT!!!
The recommended swap size is often from 50% to 100% of the RAM amount, but no more than twice. However, for modern systems with a large amount of RAM (16 GB or more), 2-4 GB of swap is often sufficient unless you plan to use hibernation (then the swap size should be no less than the amount of RAM).

4. Restrict access to the file to only root:
sudo chmod 600 /swapfile

5. Format the file for swap:
sudo mkswap /swapfile

6. Activate swap:
sudo swapon /swapfile

7. Verify that swap is active:
sudo swapon --show
or
free -h

8. Disable the swap partition (/dev/sda3):
sudo swapoff /dev/sda3

9. Reconfigure "/etc/fstab" for correct automatic mounting of the swap file at system boot. To do this, open "fstab" for editing, find a line like:
UUID=9f3c3eb7-dbc0-45ca-9299-5bb6f9ae8958 none swap sw 0 0

Change "UUID=9f3c3eb7-dbc0-45ca-9299-5bb6f9ae8958" in the line to "/swapfile" and save the configuration file.

10. Now delete the "/dev/sda3" partition and expand the main "/dev/sda2" partition using the "fdisk" utility:
sudo fdisk /dev/sda

Enter "p" to view the partition list. Next, enter "d" and select the partition number where the swap was previously located. Check with "p" whether the correct partition was deleted. To save changes, enter "w"; to exit and cancel changes in case of an error, enter "q".

11. Expand your main partition using the freed disk space. Inside the "fdisk" utility, enter the command "e", specify your partition number, indicate how much to increase it. Exit the utility with the "w" command.

12. Finally, expand the file system of the main partition:
sudo resize2fs /dev/sda2

13. Check the changes with the commands:
lsblk -l
df -h

14. Disable hibernation mode, as this mechanism natively works only with block devices (partitions), and not with files:
echo "RESUME=none" | sudo tee /etc/initramfs-tools/conf.d/resume
sudo update-initramfs -u -k all

Wednesday, December 17, 2025

Ubuntu: Resetting the root password while accessing the server console in EFI boot mode

If you need to reset a forgotten password on Ubuntu OS installed in EFI mode, follow these steps:
  1. During boot, hold the "ESC" key to enter the GRUB menu. A "grub>" command prompt should appear.

  2. Execute the "ls" command. The console will display available partitions, for example:
    (hd0,gpt3) (hd0,gpt2) (hd0,gpt1) (lvm/ubuntu--vg-ubuntu--lv)

  3. Now, check these partitions with the commands:
    ls (hd0,gpt1)/
    ls (hd0,gpt2)/
    ls (hd0,gpt3)/
    ls (lvm/ubuntu--vg-ubuntu--lv)/


    You need to find:
    The System Partition - contains directories like familiar folders at its root: boot, home, usr, etc.
    The Boot Partition - contains:
    - The kernel file (vmlinuz): a file whose name starts with "vmlinuz-", e.g., "vmlinuz-6.8.0-31-generic".
    - The initrd file: a file whose name starts with "initrd.img-" and contains the same version as the kernel, e.g., "initrd.img-6.8.0-31-generic".
    Write down or remember the full names of these two files.

  4. Manually set the boot commands.
    Let's assume your boot partition is "(hd0,gpt2)" and the system partition is "(lvm/ubuntu--vg-ubuntu--lv)".

  5. Tell GRUB where to find the boot files (the boot partition):
    set root=(hd0,gpt2)

  6. Load the kernel:
    linux /vmlinuz-6.8.0-31-generic root=/dev/mapper/ubuntu--vg-ubuntu--lv rw init=/bin/bash
    where
    root=/dev/mapper/ubuntu--vg-ubuntu--lv - the path to the LVM volume as the root partition;
    rw - mount for writing (ro - read-only);
    init=/bin/bash - boot directly into the root shell.

  7. Load the initrd image (necessary for LVM to work):
    initrd /initrd.img-6.8.0-31-generic

  8. Start the boot process:
    boot
    After the OS image loads, an input prompt in the format "root@(none):/#" will appear.

  9. Change the password for "root" or another user:
    passwd root

  10. Perform a reboot:
    reboot -f

If boot errors occur and the "(initramfs)" input prompt appears instead of "root@(none):/#", try mounting the filesystem manually:

  1. Create a mount point:
    mkdir /rootfs

  2. Mount your LVM volume:
    mount /dev/mapper/ubuntu--vg-ubuntu--lv /rootfs

  3. Check the mount:
    ls /rootfs
    You should see the contents of the root filesystem (/): folders like bin, home, usr, etc, etc.

  4. Use the chroot command to make the mounted partition the root for subsequent commands:
    chroot /rootfs

  5. Change the password for "root" or another user:
    passwd root
    Enter the new password twice.

  6. Reboot the system to normal mode:

    Exit chroot:
    exit

    Unmount the partition:
    umount /rootfs

    Perform a reboot:
    reboot -f

Note: Make sure to use the exact kernel version and LVM path found on your system when entering the commands.