2026.07.30 (Thu)

✨ GPT-5.6 Sol’s Summary

A record of configuring storage, internal SSH, and a stable address on a dedicated Ubuntu Server; separating permissions for people, AI Cells, and business automation runtimes; and documenting the entire network as an operating foundation that Codex can carry forward.

At last, I set up a dedicated Linux server for the company’s services.

What I built, however, was not merely a computer that could boot Ubuntu. It was closer to an operating foundation designed to keep people, AI, and business automation services from trespassing on one another’s permissions and state even while they all run on the same server.

When I first got the SSD, my thinking was simple: install Linux on a Windows PC, connect over SSH, and it becomes a server. I immediately started wondering about a GUI. Was I going to regret insisting on CLI just to make it look like a “proper server” if I later needed a browser login or Computer Use?

Once I examined the problem piece by piece, it became clear that the server needing a GUI and me needing to use a GUI were completely different questions. If OAuth or a Codex login is required, I can open the URL printed in the SSH session in a MacBook browser. If Computer Use is needed, I can run it on my Mac or a work device while keeping Linux as the runtime where the result is deployed.

A server is not a machine for displaying a screen. It is an environment that keeps running.

UEFI, GPT, and LVM Configuration That Preserved the Windows Disk

Even the installation USB was confusing. I thought that if the Ventoy USB used MBR, Ubuntu would also be installed as MBR. When UEFI: USB, Partition 2 appeared in the boot menu, I was not even sure whether it was the correct path.

The partitioning scheme of the USB and that of the SSD receiving Ubuntu are separate matters. I only had to start the installer through the UEFI entry and select the correct target SSD. I left the existing Windows NVMe untouched and created an EFI System Partition, /boot, LVM, and the root filesystem only on the newly acquired 250 GB SSD.

The frightening part was not that Linux was difficult. It was the possibility of erasing the wrong disk. Seeing the Windows System Reserved partition made it look disposable, but I compared the disk models and capacities again in the installer and selected only the SSD dedicated to Ubuntu. The existing Windows disk remained intact.

I chose the standard Ubuntu Server 24.04.4 LTS installation—not minimized, and not a Desktop image. Without a GUI, it left a clean base centered on SSH and systemd, ready for a Docker runtime later.

DHCP Reservation and Internal SSH Before a Fixed Address

During installation, the wired LAN immediately received an address through DHCP. At first I thought pressing Create bond might make SSH work better or fix the address in place. A bond, however, combines multiple NICs for redundancy or aggregate bandwidth. It has nothing to do with fixing the address of a single wired NIC.

Instead of hard-coding an arbitrary static IP on the server, I tied the NIC’s MAC address to an internal IP through a DHCP reservation on the main router. The server remains a DHCP client, but it receives the same address after every reboot. The router also makes it possible to check for conflicts with other reserved devices.

I did not expose SSH to the Internet either. I created a dedicated Ed25519 key for the MacBook, checked the server host key against the console, and pinned it to the internal network address. I did not create external port forwarding for port 22 on the router.

Right after installation, ssh.service appeared as disabled, so for a moment I thought SSH had been installed incorrectly. In reality, ssh.socket was active and listening on port 22, and public-key login continued to succeed after reboot. Socket activation can start the corresponding service when a request arrives, so judging failure from that single service status line would have been a mistake.1

The distinction among DHCP leases, reservations, and IP–MAC binding that I learned a few days earlier while fixing the company router’s Internet outage carried directly into this server foundation. Receiving an address and being able to manage a machine safely through that address are not the same thing.

People, AI Cells, and Services Kept Out of a Single Account

Once SSH worked, I was about to install Oh My Zsh, Powerlevel10k, and my personal aliases as usual. Then I stopped again.

The first account created during installation belongs to the company and exists for bootstrap and recovery. Putting my everyday shell, AI runtime, and Operations Automation into that one account would certainly be convenient. It would also make it impossible to distinguish who ran what with which authority, and everything would become entangled when another administrator joined or an account had to be revoked.

So I first divided the accounts according to these rules.

bootstrap administrator
└─ used only for installation and recovery

usr-<person>
└─ account used by a person for SSH, sudo, and development work

aio-cell-<person>
└─ account under which a personal AI Cell runs continuously

svc-<domain>-<purpose>
└─ application runtime, data importer, or restricted operation broker

Leaving the company name and job title out of usernames was deliberate. A person remains the same person even when their affiliation and role change. It is better for a Linux login to represent a stable identity while permissions such as administrator, developer, and project participant change separately through groups and RBAC.

I decided to keep my OMZ, P10K, and aliases only in the human account. AI Cells and service accounts will not read interactive shell configuration. They will run through fixed systemd units, EnvironmentFiles, and absolute paths. A polished prompt improves my own productivity, but it has nothing to do with service reliability.

I have not rushed to create all of these accounts yet. Instead, I first settled their names, file ownership, login policy, and boundaries for sudo and Docker access. It is far simpler to draw those boundaries while the server is empty than to separate everything after it has already been mixed into one account.

An AI Cell Boundary Extending Through UID, HOME, and OAuth

The biggest shift in my understanding this time was about multi-user systems.

When you only see desktop GUIs, it feels as though the user visible on the current login screen occupies the entire computer. On Linux, the visible login screen and the users with running processes are separate concepts. Several users can connect over SSH at the same time, and processes belonging to service accounts that are not logged in can continue running under systemd.

This matters especially when extending the AI Cell model I had redefined around macOS users to Linux. An AI Cell is not merely a process name.

1 AI Cell
= 1 Linux UID and HOME set
= 1 OAuth and CODEX_HOME set
= 1 Gateway and Port set
= 1 Session, Memory, and Workspace set
= 1 systemd unit and cgroup budget set

Giving each person’s Cell a separate UID and HOME naturally separates authentication, memory, and workspace. Each Cell runs on a different port or Unix socket, and systemd keeps it alive after boot even when the user is not logged in over SSH. Several Cells can coexist on one server without one Cell’s OAuth or Memory being used as another Cell’s fallback.

Conversely, I decided not to give AI Cells sudo, a mount of the entire NAS, production database administrator credentials, or the Docker socket. Docker’s official documentation also warns that the docker group grants root-level privileges.2 Adding human, AI, and service accounts to that group for convenience would effectively erase the boundaries I had just drawn.

Docker operations will instead be performed explicitly by a human administrator through sudo, or by a narrowly restricted, root-owned helper and systemd unit that accept only predetermined operations. AI may coordinate a deployment, but it will not directly possess an arbitrary shell and unrestricted Docker privileges.

NAS as Ingress, Linux as the Application Runtime

The Linux server is not dedicated only to AI Orchestration. The Web, API, and DB for Operations Automation are also intended to run there in a Docker runtime. That does not mean putting everything in one account, one Compose project, or one network.

The existing NAS will continue to handle external HTTPS and reverse proxying. Linux will run the actual applications.

external user
→ HTTPS 443
→ company router
→ NAS TLS and hostname reverse proxy
→ Linux frontend
→ Docker-internal API
→ Docker-internal DB

There is no need to expose the NAS management port, Linux SSH, or the API and DB ports directly to the Internet. The NAS remains the ingress that handles hostnames and certificates as well as a storage and backup point, while Linux serves as the application runtime. I am not discarding the external HTTPS path built while moving leave automation to the NAS; only the final backend will move to the dedicated server.

I also kept code deployment separate from importing HR data from the NAS.

code
→ verify release and artifact hash
→ restricted deploy helper
→ health check and rollback

HR data
→ approved read-only folder on the NAS
→ staging, validation, and preview
→ approval
→ DB transaction and audit

AI Orchestration can analyze a change and request approval. Actual deployment and data import, however, will be carried out separately by a broker and importer that accept only defined operations. The MacBook can be used for development, dry runs, preview checks, and initiating approved work, but it will not be the canonical mutation point for the production database.

The Linux server does not yet have Docker or an application database. Therefore, I did not call it a production backend merely because DHCP reservation and SSH worked. The NAS reverse proxy target can change only after the exact frontend port, health check, backup and restore, and rollback path are ready.

A Codex Skill That Retains the Entire Network Topology

Continuity with the next conversation mattered just as much as the server configuration itself.

I updated a Codex Skill dedicated to the company network and its detailed inventory with the relationships among the router, two NAS devices, the existing Mac runtime, the new Linux server, external HTTPS, and internal SSH. This was not just an IP list. It distinguishes each device’s role and the origin, intermediate hops, and final runtime of each traffic flow.

The Skill also keeps completed facts separate from the intended future structure.

  • Ubuntu installation, reboot, and internal SSH are complete.
  • Multi-user identities and permission boundaries have been decided, but the accounts have not yet been created.
  • Docker, AI Cells, and Operations Automation have not yet been deployed on Linux.
  • The NAS reverse proxy will switch to Linux only after health checks and rollback are in place.

Now, even in a new Codex session, I no longer have to begin by explaining, “What was the server IP?”, “Is the NAS running the application?”, or “Why was the SSH service disabled?” The Skill carries the actual topology, security boundaries, verified state, and unfinished state together.

So the server is not yet a finished production server, but it is not merely a blank SSD with Ubuntu installed either. The next task is to put human accounts, AI Cells, and the Operations Automation runtime into operation within boundaries that are already defined.

From now on, whenever I install something, I expect to ask first: “Which account will run it?”, “Which HOME and credentials will it own?”, and “Which ports and data flows will be allowed?” More than building a single server, it feels as though I first established the rules that will let the server expand without continually breaking.

References

  1. A systemd socket unit can monitor a socket and activate the corresponding service in response to incoming traffic. Official systemd.socket manual 

  2. The docker group, which can access the Docker daemon’s Unix socket, grants root-level privileges. Docker Engine post-installation steps for Linux 

Leave a comment