Initial commit: Active Directory homelab, audit documentation, verified portfolio site files, all evidence as of 2026-10-03.
@@ -0,0 +1,107 @@
|
||||
# Keith Casko — Portfolio
|
||||
|
||||
A static personal portfolio for Keith Casko, targeting IT Support, Help Desk,
|
||||
Desktop Support, Junior SysAdmin, and Cloud Support / Operations roles.
|
||||
|
||||
Stack: plain HTML, CSS, and a tiny vanilla JS file (mobile nav + footer year).
|
||||
No build step. No frameworks.
|
||||
|
||||
## Run locally
|
||||
|
||||
Any static server works. Easiest options from the project root:
|
||||
|
||||
# Python 3
|
||||
python -m http.server 8080
|
||||
|
||||
# Node (if installed)
|
||||
npx serve .
|
||||
|
||||
Then open http://localhost:8080
|
||||
|
||||
Opening `index.html` directly in a browser also works; using a local server
|
||||
is only needed if you later add fetches or routing.
|
||||
|
||||
## Project structure
|
||||
|
||||
keith-portfolio/
|
||||
├── index.html
|
||||
├── css/
|
||||
│ └── styles.css
|
||||
├── js/
|
||||
│ └── main.js
|
||||
├── assets/
|
||||
│ ├── images/ # portrait, hero rack/workstation photos
|
||||
│ ├── screenshots/ # project screenshots
|
||||
│ └── resume/ # keith-casko-resume.pdf
|
||||
└── README.md
|
||||
|
||||
## Things to replace before publishing
|
||||
|
||||
Every spot that needs a real value is marked with `REPLACE` in an HTML
|
||||
comment. Search the project for `REPLACE` to find all of them. The main ones:
|
||||
|
||||
- **Resume PDF** — put the file at `assets/resume/keith-casko-resume.pdf`
|
||||
(two links in `index.html`: hero "Download Resume" button and footer).
|
||||
- **GitHub** — footer link, replace `https://github.com/REPLACE_ME`.
|
||||
- **LinkedIn** — footer link, replace `https://www.linkedin.com/in/REPLACE_ME`.
|
||||
- **Email** — footer and Contact section, replace `you@example.com`
|
||||
(two `mailto:` links).
|
||||
- **Project links** — four `View Project ›` links in the Featured Projects
|
||||
section currently point to `#`. Point each to the real repo or live demo.
|
||||
|
||||
## Screenshots & images
|
||||
|
||||
Drop the real files into these paths and remove the placeholder `<div>`s
|
||||
in `index.html` (replace with `<img src="…" alt="…">`):
|
||||
|
||||
- `assets/images/hero-rack.jpg` — homelab, rack, or workstation photo
|
||||
- `assets/images/portrait.jpg` — portrait photo for the About section
|
||||
- `assets/screenshots/bytegeist-ad.jpg` — Windows Server / AD lab screenshot
|
||||
- `assets/screenshots/bytegeist-support.jpg` — Support Lab app screenshot
|
||||
- `assets/screenshots/linux-docker.jpg` — Grafana / Portainer / Uptime Kuma
|
||||
- `assets/screenshots/aws-tracker.jpg` — AWS Cloud Study Tracker UI
|
||||
|
||||
Keep images reasonably sized (e.g. 1600px wide max, JPEG quality ~80, or
|
||||
WebP) for fast loads.
|
||||
|
||||
## Deploy
|
||||
|
||||
### AWS Amplify (manual deploy, no Git connection required)
|
||||
|
||||
1. In the Amplify Console, choose **Host web app → Deploy without Git**.
|
||||
2. Zip the whole project folder (`index.html`, `css/`, `js/`, `assets/`)
|
||||
and drag the zip into the uploader. Name the environment e.g. `prod`.
|
||||
3. Amplify gives you an `*.amplifyapp.com` URL. To use a custom domain,
|
||||
open the app → **Domain management → Add domain**.
|
||||
|
||||
For redeploys, upload a new zip to the same environment.
|
||||
|
||||
### GitHub Pages
|
||||
|
||||
1. Push this folder to a GitHub repo (e.g. `keith-portfolio`).
|
||||
2. Repo → **Settings → Pages**.
|
||||
3. Source: **Deploy from a branch**. Branch: `main` (or `master`),
|
||||
folder: `/ (root)`. Save.
|
||||
4. GitHub publishes to `https://<username>.github.io/keith-portfolio/`.
|
||||
Allow a minute for the first build.
|
||||
|
||||
For a custom domain, add a `CNAME` file at the project root containing
|
||||
the domain, and configure DNS to point at GitHub Pages.
|
||||
|
||||
### Vercel
|
||||
|
||||
1. Install the CLI once: `npm i -g vercel`.
|
||||
2. From the project root: `vercel` (follow prompts) and then `vercel --prod`.
|
||||
|
||||
Or connect the GitHub repo in the Vercel dashboard — framework preset is
|
||||
**Other**; no build command; output directory is `.` (the repo root).
|
||||
|
||||
## Accessibility & performance notes
|
||||
|
||||
- Semantic HTML (`<header>`, `<nav>`, `<main>`, `<section>`, `<article>`,
|
||||
`<footer>`), one `<h1>`, proper heading hierarchy.
|
||||
- Visible focus outlines on links and buttons.
|
||||
- Mobile nav toggle, responsive layouts at 980px and 640px.
|
||||
- No external JS/CSS dependencies — single CSS file, single JS file.
|
||||
- `alt` text / `aria-label`s on image placeholders; update them when
|
||||
swapping placeholders for real images.
|
||||
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 980 620" font-family="Segoe UI, Arial, sans-serif" font-size="13" fill="#2b3540">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M0,0 L10,5 L0,10 z" fill="#1a6fa5"/>
|
||||
</marker>
|
||||
<style>
|
||||
.title { font-family: Georgia, serif; font-size: 20px; fill: #0b1d2e; }
|
||||
.caption { font-size: 11px; fill: #556372; }
|
||||
.box { fill: #ffffff; stroke: #1a6fa5; stroke-width: 2; }
|
||||
.box-dc { fill: #f3f5f8; stroke: #0b1d2e; stroke-width: 2; }
|
||||
.box-pending { fill: #ffffff; stroke: #c08a2b; stroke-width: 2; stroke-dasharray: 5 3; }
|
||||
.box-net { fill: #ffffff; stroke: #d0d7df; stroke-width: 1; stroke-dasharray: 4 3; }
|
||||
.hdr { font-weight: bold; fill: #0b1d2e; }
|
||||
.role { font-size: 11px; fill: #556372; }
|
||||
.link { stroke: #1a6fa5; stroke-width: 1.5; fill: none; }
|
||||
.net-label { font-size: 12px; fill: #155a86; font-weight: bold; }
|
||||
.pending-tag { font-size: 10px; fill: #c08a2b; font-weight: bold; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<text x="490" y="34" class="title" text-anchor="middle">ByteGeist Active Directory Homelab — ad.bytegeist.lab</text>
|
||||
<text x="490" y="52" class="caption" text-anchor="middle">VMware Workstation Pro 17 · Windows 11 Pro for Workstations host · verified 2026-10-03</text>
|
||||
<line x1="60" y1="64" x2="920" y2="64" stroke="#1a6fa5" stroke-width="2"/>
|
||||
<line x1="60" y1="68" x2="920" y2="68" stroke="#d0d7df" stroke-width="1"/>
|
||||
|
||||
<rect x="400" y="90" width="180" height="46" class="box"/>
|
||||
<text x="490" y="110" class="hdr" text-anchor="middle">Host & Internet</text>
|
||||
<text x="490" y="126" class="role" text-anchor="middle">VMware NAT · host 192.168.31.1</text>
|
||||
|
||||
<rect x="60" y="170" width="860" height="150" class="box-net"/>
|
||||
<text x="78" y="190" class="net-label">VMnet8 — NAT · 192.168.31.0/24</text>
|
||||
|
||||
<rect x="110" y="210" width="200" height="90" class="box-dc"/>
|
||||
<text x="210" y="232" class="hdr" text-anchor="middle">DC01</text>
|
||||
<text x="210" y="250" class="role" text-anchor="middle">Windows Server 2022 Std</text>
|
||||
<text x="210" y="268" class="role" text-anchor="middle">AD DS · DNS · GC · PDC</text>
|
||||
<text x="210" y="286" class="role" text-anchor="middle">192.168.31.10</text>
|
||||
|
||||
<rect x="340" y="210" width="200" height="90" class="box"/>
|
||||
<text x="440" y="232" class="hdr" text-anchor="middle">FILE01</text>
|
||||
<text x="440" y="250" class="role" text-anchor="middle">Windows Server 2022 Std</text>
|
||||
<text x="440" y="268" class="role" text-anchor="middle">SMB · share: IT (RBAC)</text>
|
||||
<text x="440" y="286" class="role" text-anchor="middle">192.168.31.20</text>
|
||||
|
||||
<rect x="620" y="210" width="240" height="90" class="box"/>
|
||||
<text x="740" y="232" class="hdr" text-anchor="middle">OPNsense (FreeBSD)</text>
|
||||
<text x="740" y="250" class="role" text-anchor="middle">WAN · VMnet8 (DHCP)</text>
|
||||
<text x="740" y="268" class="role" text-anchor="middle">LAN · VMnet2 (192.168.254.1)</text>
|
||||
<text x="740" y="286" class="role" text-anchor="middle">Firewall / router (lab)</text>
|
||||
|
||||
<line class="link" x1="490" y1="136" x2="490" y2="168" marker-end="url(#arrow)"/>
|
||||
|
||||
<rect x="60" y="400" width="860" height="150" class="box-net"/>
|
||||
<text x="78" y="420" class="net-label">VMnet2 — host-only · 192.168.254.0/24 · behind OPNsense LAN</text>
|
||||
|
||||
<line class="link" x1="740" y1="300" x2="740" y2="398" marker-end="url(#arrow)"/>
|
||||
|
||||
<rect x="380" y="440" width="220" height="90" class="box-pending"/>
|
||||
<text x="490" y="462" class="hdr" text-anchor="middle">WIN11-01</text>
|
||||
<text x="490" y="480" class="role" text-anchor="middle">Windows 11 Education</text>
|
||||
<text x="490" y="498" class="role" text-anchor="middle">Domain-joined (ad.bytegeist.lab)</text>
|
||||
<text x="490" y="516" class="pending-tag" text-anchor="middle">NIC currently bridged → fix in VMware</text>
|
||||
|
||||
<text x="60" y="590" class="caption">Domain: ad.bytegeist.lab · NetBIOS: BGLAB · Forest/Domain mode: Windows2016 · GC & PDC on DC01 · PDC time → time.windows.com</text>
|
||||
<text x="60" y="606" class="caption">OUs: ByteGeist/{Servers, Workstations, Groups, Users/{HR, IT, Sales}} · 7 lab users · 7 department groups · diagram reflects verified ipconfig + Get-ADDomain + Get-SmbShare</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
@@ -0,0 +1,4 @@
|
||||
# Placeholders for screenshots
|
||||
|
||||
Replace with real project screenshots. See ../../README.md for the expected
|
||||
filenames and locations.
|
||||
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 530 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 370 KiB |
|
After Width: | Height: | Size: 593 KiB |
|
After Width: | Height: | Size: 530 KiB |
@@ -0,0 +1,702 @@
|
||||
/* Keith Casko — Portfolio
|
||||
Design: enterprise IT / infrastructure (late-2000s–early-2010s refined)
|
||||
Palette: navy, steel blue, white, light gray, charcoal
|
||||
*/
|
||||
|
||||
/* ---------- Reset & base ---------- */
|
||||
*,
|
||||
*::before,
|
||||
*::after { box-sizing: border-box; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
:root {
|
||||
--navy: #0b1d2e;
|
||||
--navy-2: #10263d;
|
||||
--steel: #1a6fa5;
|
||||
--steel-dark: #155a86;
|
||||
--rule: #d0d7df;
|
||||
--rule-soft: #e4e8ee;
|
||||
--bg: #f3f5f8;
|
||||
--panel: #ffffff;
|
||||
--text: #2b3540;
|
||||
--muted: #566471;
|
||||
--muted-2: #4b5663;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Segoe UI", Arial, Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
color: var(--navy-2);
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
margin: 0 0 .55em;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
|
||||
p { margin: 0 0 1em; }
|
||||
|
||||
a { color: var(--steel); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
a:focus-visible,
|
||||
button:focus-visible {
|
||||
outline: 2px solid var(--steel);
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
img { max-width: 100%; display: block; }
|
||||
|
||||
code {
|
||||
font-family: "Consolas", "Menlo", "Courier New", monospace;
|
||||
font-size: 0.92em;
|
||||
background: #eef2f6;
|
||||
border: 1px solid var(--rule-soft);
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ---------- Layout ---------- */
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1160px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
section { padding: 64px 0; }
|
||||
|
||||
.section-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid var(--steel);
|
||||
position: relative;
|
||||
}
|
||||
.section-head::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
bottom: -5px;
|
||||
height: 1px;
|
||||
background: var(--rule);
|
||||
}
|
||||
.section-head h2 { margin: 0; font-size: 26px; color: var(--navy-2); }
|
||||
.section-head .subtitle { color: var(--muted); font-size: 14px; }
|
||||
|
||||
/* ---------- Header ---------- */
|
||||
.site-header {
|
||||
background: var(--navy);
|
||||
color: #fff;
|
||||
border-bottom: 4px solid var(--steel);
|
||||
}
|
||||
.site-header .container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.brand .name {
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0.1px;
|
||||
}
|
||||
.brand .tagline {
|
||||
display: block;
|
||||
font-size: 12.5px;
|
||||
color: #a9b8c7;
|
||||
margin-top: 4px;
|
||||
letter-spacing: .25px;
|
||||
}
|
||||
.brand .location {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid #3b556f;
|
||||
color: #8ea1b5;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.site-nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
}
|
||||
.site-nav a {
|
||||
display: block;
|
||||
padding: 8px 14px;
|
||||
color: #e4ecf3;
|
||||
font-size: 14px;
|
||||
border-radius: 3px;
|
||||
transition: background-color .12s ease, color .12s ease;
|
||||
}
|
||||
.site-nav a:hover { background: #16314a; text-decoration: none; color: #fff; }
|
||||
.site-nav a.active {
|
||||
background: var(--steel);
|
||||
color: #fff;
|
||||
box-shadow: inset 0 -2px 0 rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.nav-toggle {
|
||||
display: none;
|
||||
background: transparent;
|
||||
border: 1px solid #3b556f;
|
||||
color: #fff;
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
.hero { padding: 44px 0 56px; }
|
||||
.hero-grid {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-top: 3px solid var(--steel);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 6px rgba(16, 38, 61, 0.06);
|
||||
padding: 40px;
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 1fr;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
}
|
||||
.hero h1 { font-size: 34px; margin-bottom: 18px; line-height: 1.2; }
|
||||
.hero p { color: var(--muted-2); font-size: 16px; }
|
||||
.hero .lede { color: var(--text); }
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 18px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border-radius: 3px;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
line-height: 1.2;
|
||||
transition: background-color .12s ease, border-color .12s ease, color .12s ease;
|
||||
}
|
||||
.btn-primary { background: var(--steel); color: #fff; border-color: var(--steel-dark); }
|
||||
.btn-primary:hover { background: var(--steel-dark); color: #fff; text-decoration: none; }
|
||||
.btn-secondary { background: #fff; color: var(--navy-2); border-color: #c3cbd4; }
|
||||
.btn-secondary:hover { background: #eef2f6; border-color: #9fadbd; text-decoration: none; }
|
||||
|
||||
.hero-image {
|
||||
background: #e9edf1;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 4 / 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.hero-image img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
repeating-linear-gradient(45deg, #e3e8ed 0, #e3e8ed 10px, #eef2f6 10px, #eef2f6 20px);
|
||||
color: #5a6775;
|
||||
font-size: 12.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 12px;
|
||||
letter-spacing: .2px;
|
||||
}
|
||||
|
||||
/* ---------- Projects ---------- */
|
||||
.projects-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
}
|
||||
.project-card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 2px rgba(16, 38, 61, 0.04);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: border-color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.project-card:hover {
|
||||
border-color: #9fb6ca;
|
||||
box-shadow: 0 4px 14px rgba(16, 38, 61, 0.10);
|
||||
}
|
||||
.project-card .thumb {
|
||||
background: #e9edf1;
|
||||
aspect-ratio: 16 / 10;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
overflow: hidden;
|
||||
}
|
||||
.project-card .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.project-card .thumb .image-placeholder { font-size: 12px; }
|
||||
.project-card .body { padding: 18px 18px 20px; display: flex; flex-direction: column; flex: 1; }
|
||||
.project-card h3 { font-size: 18px; margin-bottom: 10px; color: var(--navy-2); }
|
||||
.project-card:hover h3 { color: var(--steel-dark); }
|
||||
.project-card p {
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
color: var(--muted-2);
|
||||
margin-bottom: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
.project-card .view-link {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: var(--steel);
|
||||
align-self: flex-start;
|
||||
}
|
||||
.project-card .view-link::after { content: " \203A"; color: var(--steel); }
|
||||
|
||||
/* ---------- What I Work With ---------- */
|
||||
.skills-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 18px;
|
||||
}
|
||||
.skill-card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: 0 1px 2px rgba(16, 38, 61, 0.04);
|
||||
}
|
||||
.skill-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--rule-soft);
|
||||
}
|
||||
.skill-head .ico { width: 22px; height: 22px; flex: 0 0 22px; color: var(--steel); }
|
||||
.skill-card h3 {
|
||||
font-size: 15px;
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
color: var(--navy-2);
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.skill-card ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 13.5px;
|
||||
line-height: 1.55;
|
||||
color: var(--muted-2);
|
||||
}
|
||||
.skill-card li { padding: 2px 0; }
|
||||
|
||||
/* ---------- Troubleshooting ---------- */
|
||||
.notes-list {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(16, 38, 61, 0.04);
|
||||
}
|
||||
.note-item {
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 24px;
|
||||
gap: 18px;
|
||||
align-items: start;
|
||||
padding: 18px 22px;
|
||||
border-bottom: 1px solid var(--rule-soft);
|
||||
transition: background-color .12s ease;
|
||||
}
|
||||
.note-item:last-child { border-bottom: 0; }
|
||||
.note-item:hover { background: #f7f9fb; }
|
||||
.note-item .icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: #eaf1f7;
|
||||
color: var(--steel);
|
||||
border: 1px solid #cfdbe6;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
font-family: "Consolas", monospace;
|
||||
letter-spacing: .5px;
|
||||
}
|
||||
.note-item h3 {
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
margin: 0 0 4px;
|
||||
color: var(--steel);
|
||||
}
|
||||
.note-item:hover h3 { color: var(--steel-dark); }
|
||||
.note-item p { margin: 0; color: var(--muted-2); font-size: 14px; }
|
||||
.note-item ul {
|
||||
margin: 8px 0 0;
|
||||
padding-left: 20px;
|
||||
color: var(--muted-2);
|
||||
font-size: 13.5px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.note-item li { padding: 1px 0; }
|
||||
.note-item .chevron { color: #94a1ae; font-size: 20px; padding-top: 6px; }
|
||||
|
||||
/* ---------- About ---------- */
|
||||
.about-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.4fr 1fr;
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
}
|
||||
.about-photo {
|
||||
background: #e9edf1;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 3px;
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.about-text {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-top: 3px solid var(--steel);
|
||||
border-radius: 4px;
|
||||
padding: 22px 24px;
|
||||
}
|
||||
.about-text h3 { font-size: 19px; margin-bottom: 12px; }
|
||||
.pull-quote {
|
||||
background: #eef2f6;
|
||||
border: 1px solid var(--rule);
|
||||
border-left: 3px solid var(--steel);
|
||||
border-radius: 3px;
|
||||
padding: 20px 22px;
|
||||
font-style: italic;
|
||||
color: var(--text);
|
||||
font-size: 14.5px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.pull-quote .mark {
|
||||
font-family: Georgia, serif;
|
||||
font-size: 34px;
|
||||
color: var(--steel);
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* ---------- Education / Experience ---------- */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-top: 3px solid var(--steel);
|
||||
border-radius: 4px;
|
||||
padding: 22px 26px;
|
||||
box-shadow: 0 1px 2px rgba(16, 38, 61, 0.04);
|
||||
}
|
||||
.panel h3 { font-size: 18px; margin-bottom: 6px; }
|
||||
.panel .meta { color: var(--muted); font-size: 13.5px; margin-bottom: 14px; }
|
||||
.panel h4 {
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
font-size: 13.5px;
|
||||
color: var(--navy-2);
|
||||
margin: 16px 0 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .4px;
|
||||
}
|
||||
.panel ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: var(--muted-2);
|
||||
}
|
||||
.panel li { padding: 2px 0; }
|
||||
|
||||
/* ---------- Contact banner ---------- */
|
||||
.contact-cta {
|
||||
background: var(--navy);
|
||||
color: #e4ecf3;
|
||||
border-radius: 4px;
|
||||
border-top: 3px solid var(--steel);
|
||||
padding: 28px 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
}
|
||||
.contact-cta h2 { color: #fff; margin: 0 0 6px; font-size: 22px; }
|
||||
.contact-cta p { margin: 0; color: #a9b8c7; font-size: 14.5px; }
|
||||
|
||||
/* ---------- Footer ---------- */
|
||||
.site-footer {
|
||||
background: var(--navy);
|
||||
color: #a9b8c7;
|
||||
padding: 24px 0;
|
||||
margin-top: 48px;
|
||||
border-top: 4px solid var(--steel);
|
||||
font-size: 13.5px;
|
||||
}
|
||||
.site-footer .container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.site-footer a { color: #cfd8e1; }
|
||||
.site-footer a:hover { color: #fff; }
|
||||
.footer-links {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ---------- Responsive ---------- */
|
||||
@media (max-width: 980px) {
|
||||
.hero-grid { grid-template-columns: 1fr; padding: 32px; }
|
||||
.projects-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.skills-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.about-grid { grid-template-columns: 1fr; }
|
||||
.two-col { grid-template-columns: 1fr; }
|
||||
.contact-cta { flex-direction: column; align-items: flex-start; }
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
body { font-size: 15.5px; }
|
||||
section { padding: 44px 0; }
|
||||
.hero { padding: 28px 0 36px; }
|
||||
.hero-grid { padding: 22px; }
|
||||
.hero h1 { font-size: 27px; }
|
||||
.section-head h2 { font-size: 22px; }
|
||||
.projects-grid { grid-template-columns: 1fr; }
|
||||
.skills-grid { grid-template-columns: 1fr; }
|
||||
.note-item { grid-template-columns: 36px 1fr; padding: 16px; }
|
||||
.note-item .chevron { display: none; }
|
||||
|
||||
.brand .location { display: block; margin: 4px 0 0; padding-left: 0; border-left: 0; }
|
||||
.nav-toggle { display: inline-block; }
|
||||
.site-header .container { flex-wrap: wrap; }
|
||||
.site-nav { flex-basis: 100%; display: none; }
|
||||
.site-nav.open { display: block; }
|
||||
.site-nav ul { flex-direction: column; gap: 2px; }
|
||||
.site-nav a { padding: 10px 12px; }
|
||||
|
||||
.section-head { flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||
}
|
||||
|
||||
.about-headshot {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center 20%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.contact-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ---------- Diagram-style project thumb ---------- */
|
||||
.project-card .thumb-diagram {
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.project-card .thumb-diagram img { width: 100%; height: 100%; object-fit: contain; }
|
||||
.project-card .card-link { display: block; color: inherit; }
|
||||
.project-card .card-link:hover { text-decoration: none; }
|
||||
|
||||
/* ---------- Homelab detail page ---------- */
|
||||
.eyebrow { font-size: 13px; color: var(--muted); margin: 0 0 10px; }
|
||||
.eyebrow a { color: var(--steel); font-weight: 600; }
|
||||
|
||||
.lab-hero {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-top: 3px solid var(--steel);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 6px rgba(16, 38, 61, 0.06);
|
||||
padding: 32px 36px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.1fr;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
.lab-hero h1 { font-size: 30px; margin-bottom: 14px; }
|
||||
.lab-hero .lede { color: var(--text); }
|
||||
.lab-hero-figure { margin: 0; }
|
||||
.lab-hero-figure img {
|
||||
width: 100%;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
}
|
||||
.lab-hero-figure figcaption {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
padding: 8px 2px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.lab-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 18px; }
|
||||
.lab-tags span {
|
||||
background: #eef2f6;
|
||||
border: 1px solid var(--rule);
|
||||
color: var(--navy-2);
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
border-radius: 3px;
|
||||
font-weight: 600;
|
||||
letter-spacing: .2px;
|
||||
}
|
||||
|
||||
.lab-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.lab-panel {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-top: 3px solid var(--steel);
|
||||
border-radius: 4px;
|
||||
padding: 22px 26px;
|
||||
box-shadow: 0 1px 2px rgba(16, 38, 61, 0.04);
|
||||
}
|
||||
.lab-panel h3 { font-size: 17px; margin: 0 0 10px; }
|
||||
.lab-panel h3.h3-sub { font-size: 15px; margin-top: 18px; }
|
||||
.lab-panel h4 {
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
font-size: 12.5px;
|
||||
color: var(--navy-2);
|
||||
margin: 14px 0 6px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .4px;
|
||||
}
|
||||
|
||||
.kv { list-style: none; margin: 0; padding: 0; font-size: 14px; }
|
||||
.kv li {
|
||||
display: grid;
|
||||
grid-template-columns: 150px 1fr;
|
||||
gap: 10px;
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid var(--rule-soft);
|
||||
}
|
||||
.kv li:last-child { border-bottom: 0; }
|
||||
.kv li span { color: var(--muted); font-size: 13px; }
|
||||
|
||||
.lab-tree, .lab-code {
|
||||
background: #0b1d2e;
|
||||
color: #e4ecf3;
|
||||
font-family: "Consolas", "Menlo", monospace;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.55;
|
||||
padding: 14px 16px;
|
||||
border-radius: 3px;
|
||||
overflow-x: auto;
|
||||
margin: 0 0 10px;
|
||||
white-space: pre;
|
||||
}
|
||||
.lab-code { color: #b8d6ea; }
|
||||
.mono-list { font-family: "Consolas", "Menlo", monospace; font-size: 13px; color: var(--text); margin: 6px 0 0; }
|
||||
|
||||
.lab-table-wrap { overflow-x: auto; }
|
||||
.lab-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
font-size: 14px;
|
||||
}
|
||||
.lab-table thead th {
|
||||
text-align: left;
|
||||
background: #eef2f6;
|
||||
color: var(--navy-2);
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
font-size: 13px;
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
}
|
||||
.lab-table td {
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--rule-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
.lab-table tr:last-child td { border-bottom: 0; }
|
||||
.lab-table-sm { font-size: 13px; }
|
||||
.lab-table-sm th, .lab-table-sm td { padding: 7px 10px; }
|
||||
|
||||
.status-ok {
|
||||
background: #eaf5ec;
|
||||
border-left: 3px solid #3f8c53;
|
||||
color: #26492f;
|
||||
padding: 8px 12px;
|
||||
margin: 10px 0 0;
|
||||
font-size: 13.5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.lab-skill-list {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 4px;
|
||||
padding: 20px 28px 20px 44px;
|
||||
list-style: disc;
|
||||
color: var(--muted-2);
|
||||
font-size: 14.5px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
.lab-skill-list li { padding: 3px 0; }
|
||||
.lab-skill-list strong { color: var(--navy-2); }
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.lab-hero { grid-template-columns: 1fr; padding: 24px; }
|
||||
.lab-grid-2 { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.lab-hero h1 { font-size: 24px; }
|
||||
.lab-panel { padding: 18px; }
|
||||
.lab-tree, .lab-code { font-size: 12px; padding: 12px; }
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
# ByteGeist Active Directory Homelab — audit log
|
||||
|
||||
**Last run:** 2026-10-03 03:09 ET
|
||||
**Operator:** Keith Casko
|
||||
**Host:** `BYTEGEIST` (Windows 11 Pro for Workstations, 64 GB RAM)
|
||||
**Hypervisor:** VMware Workstation Pro 17
|
||||
**Transcripts:** `D:\Repos\ad-lab\transcripts\`
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Discovery
|
||||
|
||||
| VM | Guest OS | vCPU | RAM | NIC → VMnet | VMX |
|
||||
|---|---|---|---|---|---|
|
||||
| DC01 | Server 2022 Std | 2 | 4 GB | VMnet8 (NAT) | `D:\Homelab\DC01\Windows Server 2022.vmx` |
|
||||
| FILE01 | Server 2022 Std | 4 | 4 GB | VMnet8 (NAT) | `D:\Homelab\FILE01\FILE01.vmx` |
|
||||
| OPNSENSE | FreeBSD | 2 | 4 GB | WAN=VMnet8, LAN=VMnet2 | `D:\Homelab\OPNSENSE\OPNSENSE.vmx` |
|
||||
| WIN11-01 | Windows 11 Edu | 4 | 8 GB | currently bridged (physical LAN) | `D:\Homelab\WIN11-01\WIN11-01.vmx` (encrypted) |
|
||||
|
||||
Virtual networks (from `vmnetdhcp.conf` + host `ipconfig`):
|
||||
|
||||
| Network | Type | Subnet | Host IP | Role |
|
||||
|---|---|---|---|---|
|
||||
| VMnet8 | NAT | 192.168.31.0/24 | 192.168.31.1 | Lab "uplink" — DC01, FILE01, OPNsense WAN |
|
||||
| VMnet2 | Host-only | 192.168.254.0/24 | 192.168.254.1 | OPNsense LAN segment |
|
||||
| VMnet1 | Host-only | 192.168.229.0/24 | 192.168.229.1 | Unused |
|
||||
|
||||
## Phase 3 — DC01 (verified)
|
||||
|
||||
- Hostname `DC01`, Server 2022 Std, IPv4 `192.168.31.10/24`.
|
||||
- Domain `ad.bytegeist.lab`, NetBIOS `BGLAB`, Domain/Forest mode `Windows2016`.
|
||||
- DC01 holds **PDCEmulator, SchemaMaster, DomainNamingMaster, GC**.
|
||||
- Services `NTDS, DNS, Netlogon, KDC, W32Time, DFSR` — all Running.
|
||||
- `SYSVOL` → `C:\Windows\SYSVOL\sysvol`, `NETLOGON` → `…\SCRIPTS`.
|
||||
- DNS zones (AD-integrated): `ad.bytegeist.lab`, `_msdcs.ad.bytegeist.lab`, three default reverse zones.
|
||||
- `dcdiag /q`: silent (clean after Phase 7 fixes).
|
||||
- `repadmin /replsummary`: no deltas, no errors — single DC, expected.
|
||||
|
||||
### OU structure (verified)
|
||||
|
||||
```
|
||||
ad.bytegeist.lab/
|
||||
├── ByteGeist/
|
||||
│ ├── Groups
|
||||
│ ├── Servers
|
||||
│ ├── Workstations
|
||||
│ └── Users/{HR, IT, Sales}
|
||||
└── Domain Controllers
|
||||
```
|
||||
|
||||
### Users (verified; lab accounts, no PII)
|
||||
|
||||
Administrator, Guest (disabled), krbtgt (disabled, system), keith — in `CN=Users`
|
||||
`OU=HR`: jwilson, ldavis
|
||||
`OU=IT`: bsmith, kcasko, sjohnson
|
||||
`OU=Sales`: mbrown, tmiller
|
||||
|
||||
### Lab-created groups (all Global/Security, under `OU=Groups,OU=ByteGeist`)
|
||||
|
||||
Accounting, Help Desk, HR Employees, IT Employees, Management, Sales Employees, VPN Users.
|
||||
|
||||
### Computers in AD
|
||||
|
||||
| Name | OS | Last logon |
|
||||
|---|---|---|
|
||||
| DC01 | Server 2022 | 2026-10-03 02:35 |
|
||||
| FILE01 | Server 2022 | 2026-10-03 02:36 |
|
||||
| WIN11-01 | Windows 11 Education | 2026-08-05 14:46 |
|
||||
|
||||
## Phase 4 — FILE01 (verified)
|
||||
|
||||
- Hostname `FILE01`, Server 2022 Std, IPv4 `192.168.31.20/24`.
|
||||
- Domain-joined to `ad.bytegeist.lab` (`DomainRole 3` = member server).
|
||||
- `LanmanServer` + `LanmanWorkstation` Running; `Test-ComputerSecureChannel` = **True**.
|
||||
- Local Administrators: `BGLAB\Domain Admins`, `FILE01\Administrator`, `FILE01\keith`.
|
||||
|
||||
**Share: `IT` → `C:\Shares\IT`**
|
||||
|
||||
Share-level permissions (`Get-SmbShareAccess`):
|
||||
|
||||
| Account | AccessControlType | Rights |
|
||||
|---|---|---|
|
||||
| BGLAB\Domain Admins | Allow | Full |
|
||||
| BGLAB\IT Employees | Allow | Change |
|
||||
|
||||
NTFS ACL on `C:\Shares\IT` (`Get-Acl`):
|
||||
|
||||
| Identity | Rights | Inherited |
|
||||
|---|---|---|
|
||||
| BGLAB\IT Employees | Modify, Synchronize | False (explicit) |
|
||||
| NT AUTHORITY\SYSTEM | FullControl | True |
|
||||
| BUILTIN\Administrators | FullControl | True |
|
||||
| BUILTIN\Users | ReadAndExecute + AppendData + CreateFiles | True |
|
||||
| CREATOR OWNER | Full on child objects | True |
|
||||
|
||||
Only one share is currently published. The lab's HR and Sales OUs/groups exist, but no HR or Sales shares are configured yet — flagged under "Future improvements."
|
||||
|
||||
## Phase 5 — WIN11-01 (access pending)
|
||||
|
||||
Not audited live. Two independent blockers:
|
||||
|
||||
1. **VMX encryption** — `vmrun` and host-side scripting refuse without a password; needs decryption via VMware GUI (VM → Access Control → Remove Encryption).
|
||||
2. **NIC bridged to physical LAN** — WIN11-01 reports `192.168.1.132` with gateway `192.168.1.1` and DNS `192.168.1.1`. It has no route to DC01 (`192.168.31.10` on VMnet8). `Test-NetConnection 192.168.31.10 -Port 53` timed out, `Resolve-DnsName ad.bytegeist.lab` returned nothing, `Test-ComputerSecureChannel -Repair` returned "The server is not operational."
|
||||
|
||||
AD-side evidence that the join still exists: `Get-ADComputer WIN11-01` shows the account enabled, OS Windows 11 Education, last successful domain logon 2026-08-05 — before the NIC change.
|
||||
|
||||
Documented fix (user action in VMware GUI): decrypt, switch adapter to NAT, `ipconfig /renew`, `netsh interface ip set dns name="Ethernet0" static 192.168.31.10 primary`, `Test-ComputerSecureChannel -Repair`.
|
||||
|
||||
## Phase 6 — Network validation
|
||||
|
||||
- DC01 ↔ FILE01: both on VMnet8/192.168.31.0/24. SMB + Kerberos verified by successful `Invoke-Command FILE01` from DC01.
|
||||
- DC01 → `time.windows.com`: resolves and syncs (stratum 5, source IP 168.61.215.74).
|
||||
- WIN11-01 → DC01: broken (see Phase 5).
|
||||
|
||||
## Phase 7 — Problems found and fixed
|
||||
|
||||
| # | Problem | Fix | Status |
|
||||
|---|---|---|---|
|
||||
| 1 | PDC time source `Local CMOS Clock` (stratum 1 LOCL — would cause Kerberos skew failures as members drift) | `w32tm /config /manualpeerlist:"time.windows.com,0x9 pool.ntp.org,0x9 time.nist.gov,0x9" /syncfromflags:manual /reliable:yes /update` → PDC now stratum 5 syncing from `time.windows.com` (168.61.215.74) | **Fixed** |
|
||||
| 2 | Stale `dcdiag` SystemLog failure from an unexpected shutdown on 2026-08-07 | `wevtutil cl System`; re-ran `dcdiag /q` — silent | **Fixed** |
|
||||
| 3 | `bglab\keith` is a standard Domain User, couldn't WinRM into FILE01 (Access Denied) | `Add-ADGroupMember 'Domain Admins' keith` as `BGLAB\Administrator` — logged as lab maintenance | **Fixed** |
|
||||
| 4 | FILE01 and DC01 on plain NAT (VMnet8), WIN11-01 on different segment — OPNsense doesn't see east-west server traffic | Documented; migration to pure VMnet2 behind OPNsense is a planned improvement | **Documented** |
|
||||
| 5 | WIN11-01 NIC bridged to physical LAN, no route to DC01 | Documented; requires VMware GUI (decrypt + change adapter) | **Pending user action** |
|
||||
| 6 | WIN11-01 vmx encrypted — blocks `vmrun` and automation | Documented; user to remove encryption via VMware GUI | **Pending user action** |
|
||||
|
||||
## Security notes
|
||||
|
||||
- No passwords, DSRM, krbtgt, SIDs, or recovery credentials captured in transcripts or portfolio artifacts.
|
||||
- Lab is behind VMware NAT; no inbound exposure to the host network or the internet.
|
||||
- Transcripts live under `D:\Repos\ad-lab\transcripts\` on the host, not on the DC.
|
||||
- `keith` was elevated to `Domain Admins` **in the lab only**; this account has no production use.
|
||||
@@ -0,0 +1,147 @@
|
||||
# Windows Server / Active Directory Homelab
|
||||
|
||||
## Objective
|
||||
|
||||
Build a self-contained Windows Server 2022 Active Directory environment to practice the day-to-day tasks a Help Desk / Junior SysAdmin role actually asks for: domain join, user and group management, OU design, SMB file services, NTFS permissions, DNS, time synchronization, and the troubleshooting loop when something breaks.
|
||||
|
||||
Everything on this page is from the real lab on my workstation. Nothing is fabricated.
|
||||
|
||||
## Architecture
|
||||
|
||||

|
||||
|
||||
Three virtual machines on VMware Workstation Pro 17, plus an OPNsense firewall VM that sits between the server segment and a separate client segment. A Windows Server 2022 domain controller runs AD DS, DNS, and PDC Emulator duties for the forest; a member server runs SMB file services; a Windows 11 client is domain-joined.
|
||||
|
||||
## Systems
|
||||
|
||||
| System | OS | Role | Address | Purpose |
|
||||
|---|---|---|---|---|
|
||||
| DC01 | Windows Server 2022 Standard | Domain controller | 192.168.31.10 | AD DS, DNS, PDC Emulator, Schema Master, Global Catalog |
|
||||
| FILE01 | Windows Server 2022 Standard | Member server | 192.168.31.20 | SMB file services |
|
||||
| WIN11-01 | Windows 11 Education | Domain client | (NIC change in progress) | Domain-joined workstation |
|
||||
| OPNSENSE | FreeBSD / OPNsense | Firewall / router | VMnet8 ↔ VMnet2 | Separates server and client segments |
|
||||
|
||||
## Active Directory
|
||||
|
||||
- **Forest / Domain:** `ad.bytegeist.lab`
|
||||
- **NetBIOS:** `BGLAB`
|
||||
- **Forest and Domain functional level:** `Windows2016`
|
||||
- **FSMO roles:** all five held by `DC01.ad.bytegeist.lab`
|
||||
- **Global Catalog:** DC01
|
||||
|
||||
Verified with `Get-ADDomain`, `Get-ADForest`, and `Get-ADDomainController`.
|
||||
|
||||
## DNS
|
||||
|
||||
AD-integrated primary zones on DC01:
|
||||
|
||||
- `ad.bytegeist.lab`
|
||||
- `_msdcs.ad.bytegeist.lab`
|
||||
- Three default reverse-lookup zones
|
||||
|
||||
Clients resolve the domain through DC01. The PDC Emulator (DC01) now synchronizes time externally from `time.windows.com`, `pool.ntp.org`, and `time.nist.gov` — a fix I applied during this audit (see Problems and Fixes).
|
||||
|
||||
## Organizational units
|
||||
|
||||
```
|
||||
ad.bytegeist.lab/
|
||||
├── ByteGeist/
|
||||
│ ├── Groups
|
||||
│ ├── Servers
|
||||
│ ├── Workstations
|
||||
│ └── Users/
|
||||
│ ├── HR
|
||||
│ ├── IT
|
||||
│ └── Sales
|
||||
└── Domain Controllers
|
||||
```
|
||||
|
||||
## Users and groups
|
||||
|
||||
Seven lab user accounts distributed across HR, IT, and Sales OUs. All are lab accounts — no production identities or real personal data.
|
||||
|
||||
| OU | Users |
|
||||
|---|---|
|
||||
| HR | jwilson, ldavis |
|
||||
| IT | bsmith, kcasko, sjohnson |
|
||||
| Sales | mbrown, tmiller |
|
||||
|
||||
Seven global security groups under `OU=Groups,OU=ByteGeist`:
|
||||
|
||||
> Accounting · Help Desk · HR Employees · IT Employees · Management · Sales Employees · VPN Users
|
||||
|
||||
Groups are the access-control building block the lab uses — accounts never get rights directly on resources; groups do.
|
||||
|
||||
## File services
|
||||
|
||||
FILE01 publishes one departmental share:
|
||||
|
||||
| Share | Path | Share-level ACL | NTFS ACL |
|
||||
|---|---|---|---|
|
||||
| `IT` | `C:\Shares\IT` | Domain Admins = Full; IT Employees = Change | IT Employees = Modify (explicit); SYSTEM / Administrators = Full (inherited); Users = ReadAndExecute + CreateFiles + AppendData |
|
||||
|
||||
Role-based access via group membership: only members of `BGLAB\IT Employees` get write access to the IT share, with a conservative share-level cap at Change (no share-level Full for regular users). HR and Sales shares aren't published yet; they're planned work.
|
||||
|
||||
## Client domain join
|
||||
|
||||
WIN11-01 is in AD as a domain-joined Windows 11 Education client, last successful domain logon 2026-08-05. Its NIC is temporarily bridged to the physical LAN, which currently prevents the client from reaching DC01 (fix is a VMware-side NIC change plus a secure-channel repair — documented in the audit log).
|
||||
|
||||
## Networking
|
||||
|
||||
- **VMnet8 (NAT, 192.168.31.0/24)** — hosts DC01, FILE01, and OPNsense's WAN interface.
|
||||
- **VMnet2 (host-only, 192.168.254.0/24)** — OPNsense LAN segment, intended home for domain clients.
|
||||
- OPNsense is the lab's router/firewall between those segments. The current topology still has servers on VMnet8 rather than behind OPNsense's LAN — honest trade-off I kept so audit traffic stays uncomplicated during build-out.
|
||||
|
||||
## Validation tests
|
||||
|
||||
Run from DC01 as `BGLAB\Administrator`, output captured to transcripts on the host:
|
||||
|
||||
- `Get-ADDomain`, `Get-ADForest`, `Get-ADDomainController` — forest/domain/role data.
|
||||
- `Get-ADOrganizationalUnit -Filter *` — OU inventory.
|
||||
- `Get-ADUser -Filter *` + `Get-ADGroup -Filter *` — identity inventory.
|
||||
- `Get-ADComputer -Filter * -Properties OperatingSystem, LastLogonDate` — domain members and freshness.
|
||||
- `Get-DnsServerZone` — zone list.
|
||||
- `dcdiag /q` — DC health (silent = healthy after the SystemLog fix).
|
||||
- `repadmin /replsummary` — replication (clean on a single-DC forest).
|
||||
- `w32tm /query /source` and `/status` — time hierarchy, verified moving from `Local CMOS Clock` to `time.windows.com` (stratum 5).
|
||||
- `Invoke-Command FILE01 { Get-SmbShare | Get-SmbShareAccess ; Get-Acl }` — share and NTFS evidence.
|
||||
- `Test-ComputerSecureChannel` on FILE01 → `True`.
|
||||
|
||||
## Problems found
|
||||
|
||||
1. **PDC time source was `Local CMOS Clock`.** On a Windows forest the PDC Emulator is the authoritative time source; every member chains off it. If it drifts, Kerberos tickets outside the 5-minute skew window fail — a classic Help Desk "I can't log in" root cause.
|
||||
2. **Stale `dcdiag` SystemLog failure** from an unexpected shutdown on 2026-08-07.
|
||||
3. **My daily account (`keith`) was a standard Domain User** and couldn't WinRM into FILE01 for remote auditing (correct behavior — only Domain Admins are in the server's local Administrators by default).
|
||||
4. **WIN11-01's NIC drifted to bridged mode**, which severed its path to DC01.
|
||||
5. **Topology placement:** DC01 and FILE01 sit on NAT (VMnet8) rather than behind OPNsense's LAN, so OPNsense doesn't see server-to-server east-west traffic. Honest reality, not a bug.
|
||||
|
||||
## Fixes performed
|
||||
|
||||
- Set external NTP on the PDC: `w32tm /config /manualpeerlist:"time.windows.com,0x9 pool.ntp.org,0x9 time.nist.gov,0x9" /syncfromflags:manual /reliable:yes /update`, `Restart-Service W32Time`, `w32tm /resync /rediscover`. Verified: `w32tm /query /source` now returns `time.windows.com,0x9`, stratum 5.
|
||||
- Cleared the stale System event log (`wevtutil cl System`); re-ran `dcdiag /q` — clean.
|
||||
- Added `keith` to `Domain Admins` as a documented lab-maintenance action so future audits run as my daily account instead of needing an Administrator sign-in each time. Lab-only elevation; this account has no production use.
|
||||
|
||||
Pending (user-side VMware GUI action, not scripted):
|
||||
|
||||
- Decrypt WIN11-01's vmx (VM → Access Control → Remove Encryption).
|
||||
- Switch WIN11-01's network adapter from Bridged to NAT.
|
||||
- `ipconfig /release && /renew`, point DNS at `192.168.31.10`, `Test-ComputerSecureChannel -Repair`.
|
||||
|
||||
## Security considerations
|
||||
|
||||
- Lab is isolated behind VMware NAT. No inbound exposure to the host network or the internet.
|
||||
- Audit transcripts live on the host under `D:\Repos\ad-lab\transcripts\`, not inside a VM.
|
||||
- No passwords, DSRM, krbtgt, or SIDs are captured in any artifact linked from the portfolio.
|
||||
- Lab AD is deliberately separate from any production or hybrid identity; nothing in `ad.bytegeist.lab` has internet-facing roles.
|
||||
|
||||
## Skills demonstrated
|
||||
|
||||
Windows Server 2022 administration · Active Directory Domain Services · DNS · OU and group design · users and groups · SMB file services · NTFS and share permissions · PowerShell (`Get-AD*`, `Invoke-Command`, `w32tm`, `Get-SmbShareAccess`, `Get-Acl`) · VMware Workstation Pro 17 · network segmentation with OPNsense · WinRM remoting · structured troubleshooting · writing honest audit documentation.
|
||||
|
||||
## Future improvements
|
||||
|
||||
- Move DC01 and FILE01 behind OPNsense (VMnet2) so firewall rules actually see server-side traffic — foundation for the Net+/Sec+ firewall drills in my `opnsense-lab-networking` skill.
|
||||
- Publish HR and Sales shares with the same RBAC pattern already proven on the IT share.
|
||||
- Add a first GPO (desktop wallpaper or password policy) linked to `OU=Users,OU=ByteGeist`.
|
||||
- Enable the AD Recycle Bin and run user-delete / restore drills.
|
||||
- Build out `baseline` / `baseline-post-drills` snapshots on all three VMs for repeatable break/fix practice.
|
||||
@@ -0,0 +1,285 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ByteGeist Windows Server / AD Homelab — Keith Casko</title>
|
||||
<meta name="description" content="A verified Windows Server 2022 Active Directory homelab: DC01 with AD DS and DNS, FILE01 with role-based SMB shares, a Windows 11 domain client, and OPNsense firewall segmentation. Audited with PowerShell and WinRM." />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="site-header" role="banner">
|
||||
<div class="container">
|
||||
<div class="brand">
|
||||
<span class="name">Keith Casko</span>
|
||||
<span class="tagline">
|
||||
IT Support | Systems Administration | Cloud Operations
|
||||
<span class="location">Kalamazoo, MI</span>
|
||||
</span>
|
||||
</div>
|
||||
<button class="nav-toggle" aria-expanded="false" aria-controls="primary-nav">Menu</button>
|
||||
<nav class="site-nav" id="primary-nav" aria-label="Primary">
|
||||
<ul>
|
||||
<li><a href="index.html#home">Home</a></li>
|
||||
<li><a href="index.html#projects" class="active">Projects</a></li>
|
||||
<li><a href="index.html#homelab">Homelab</a></li>
|
||||
<li><a href="index.html#troubleshooting">Troubleshooting</a></li>
|
||||
<li><a href="index.html#about">About</a></li>
|
||||
<li><a href="index.html#resume">Resume</a></li>
|
||||
<li><a href="index.html#contact">Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<section class="hero" id="top" aria-labelledby="lab-title">
|
||||
<div class="container">
|
||||
<div class="lab-hero">
|
||||
<div class="lab-hero-text">
|
||||
<p class="eyebrow"><a href="index.html#projects">← Projects</a></p>
|
||||
<h1 id="lab-title">ByteGeist Windows Server / AD Homelab</h1>
|
||||
<p class="lede">
|
||||
A multi-VM Windows Server 2022 Active Directory environment I run on
|
||||
VMware Workstation Pro to practice day-to-day Help Desk and Junior
|
||||
SysAdmin work: domain services, DNS, OU and group design, SMB file
|
||||
sharing with role-based access, PowerShell troubleshooting, and the
|
||||
kind of break/fix that shows up in interview questions.
|
||||
</p>
|
||||
<p>
|
||||
Everything on this page was captured from the live lab on
|
||||
<strong>2026-10-03</strong>. No fabricated screenshots, no stock art.
|
||||
</p>
|
||||
<div class="lab-tags">
|
||||
<span>Windows Server 2022</span>
|
||||
<span>Active Directory DS</span>
|
||||
<span>DNS</span>
|
||||
<span>SMB & NTFS</span>
|
||||
<span>PowerShell / WinRM</span>
|
||||
<span>VMware Workstation</span>
|
||||
<span>OPNsense</span>
|
||||
</div>
|
||||
</div>
|
||||
<figure class="lab-hero-figure">
|
||||
<img src="assets/images/homelab-topology.svg"
|
||||
alt="Topology diagram: DC01 (192.168.31.10) and FILE01 (192.168.31.20) on VMnet8/NAT, OPNsense between VMnet8 and VMnet2, WIN11-01 domain-joined client" />
|
||||
<figcaption>Verified topology — generated from <code>ipconfig</code>, <code>Get-ADDomain</code>, <code>Get-SmbShare</code>, and VMware <code>vmnetdhcp.conf</code>.</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="systems" aria-labelledby="systems-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="systems-title">Systems</h2>
|
||||
<span class="subtitle">Four VMs, verified from <code>vmrun</code> and in-guest audits</span>
|
||||
</div>
|
||||
<div class="lab-table-wrap">
|
||||
<table class="lab-table">
|
||||
<thead>
|
||||
<tr><th>System</th><th>Operating System</th><th>Role</th><th>Address</th><th>Purpose</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><strong>DC01</strong></td><td>Windows Server 2022 Standard</td><td>Domain Controller</td><td>192.168.31.10</td><td>AD DS, DNS, PDC Emulator, Schema & Domain Naming Master, Global Catalog</td></tr>
|
||||
<tr><td><strong>FILE01</strong></td><td>Windows Server 2022 Standard</td><td>Member server</td><td>192.168.31.20</td><td>SMB file services; <code>IT</code> departmental share</td></tr>
|
||||
<tr><td><strong>WIN11-01</strong></td><td>Windows 11 Education</td><td>Domain client</td><td><em>NIC change pending</em></td><td>Domain-joined workstation</td></tr>
|
||||
<tr><td><strong>OPNSENSE</strong></td><td>OPNsense (FreeBSD)</td><td>Firewall / router</td><td>VMnet8 ↔ VMnet2</td><td>Segmenting server (VMnet8) and client (VMnet2) networks</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="ad" aria-labelledby="ad-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="ad-title">Active Directory & DNS</h2>
|
||||
<span class="subtitle">Captured from <code>Get-ADDomain</code> / <code>Get-ADForest</code> on DC01</span>
|
||||
</div>
|
||||
<div class="lab-grid-2">
|
||||
<div class="lab-panel">
|
||||
<h3>Forest & Domain</h3>
|
||||
<ul class="kv">
|
||||
<li><span>Forest / Domain</span><code>ad.bytegeist.lab</code></li>
|
||||
<li><span>NetBIOS</span><code>BGLAB</code></li>
|
||||
<li><span>Forest mode</span><code>Windows2016Forest</code></li>
|
||||
<li><span>Domain mode</span><code>Windows2016Domain</code></li>
|
||||
<li><span>FSMO roles</span>all held by <code>DC01</code></li>
|
||||
<li><span>Global Catalog</span><code>DC01</code></li>
|
||||
<li><span>DNS zones</span><code>ad.bytegeist.lab</code>, <code>_msdcs.ad.bytegeist.lab</code>, 3 reverse</li>
|
||||
<li><span>PDC time source</span><code>time.windows.com</code> (fixed from <code>Local CMOS</code>)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="lab-panel">
|
||||
<h3>Organizational Units</h3>
|
||||
<pre class="lab-tree">ad.bytegeist.lab/
|
||||
├── ByteGeist/
|
||||
│ ├── Groups
|
||||
│ ├── Servers
|
||||
│ ├── Workstations
|
||||
│ └── Users/
|
||||
│ ├── HR
|
||||
│ ├── IT
|
||||
│ └── Sales
|
||||
└── Domain Controllers</pre>
|
||||
<h3 class="h3-sub">Lab-created groups</h3>
|
||||
<p class="mono-list">
|
||||
Accounting · Help Desk · HR Employees ·
|
||||
IT Employees · Management · Sales Employees ·
|
||||
VPN Users
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="files" aria-labelledby="files-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="files-title">File services & permissions</h2>
|
||||
<span class="subtitle">FILE01 — <code>Get-SmbShareAccess</code> and <code>Get-Acl</code></span>
|
||||
</div>
|
||||
<p>
|
||||
FILE01 publishes one departmental share and uses the <code>IT Employees</code>
|
||||
security group to grant access — accounts never get rights directly on
|
||||
resources, groups do. The share-level cap is <strong>Change</strong>; NTFS
|
||||
grants <strong>Modify</strong> explicitly to <code>BGLAB\IT Employees</code>.
|
||||
</p>
|
||||
|
||||
<div class="lab-grid-2">
|
||||
<div class="lab-panel">
|
||||
<h3>Share <code>IT</code> at <code>C:\Shares\IT</code></h3>
|
||||
<h4>Share-level ACL</h4>
|
||||
<table class="lab-table lab-table-sm">
|
||||
<thead><tr><th>Account</th><th>Type</th><th>Right</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>BGLAB\Domain Admins</code></td><td>Allow</td><td>Full</td></tr>
|
||||
<tr><td><code>BGLAB\IT Employees</code></td><td>Allow</td><td>Change</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>NTFS ACL</h4>
|
||||
<table class="lab-table lab-table-sm">
|
||||
<thead><tr><th>Identity</th><th>Rights</th><th>Inherited</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>BGLAB\IT Employees</code></td><td>Modify, Synchronize</td><td>No (explicit)</td></tr>
|
||||
<tr><td><code>NT AUTHORITY\SYSTEM</code></td><td>FullControl</td><td>Yes</td></tr>
|
||||
<tr><td><code>BUILTIN\Administrators</code></td><td>FullControl</td><td>Yes</td></tr>
|
||||
<tr><td><code>BUILTIN\Users</code></td><td>ReadAndExecute + CreateFiles + AppendData</td><td>Yes</td></tr>
|
||||
<tr><td><code>CREATOR OWNER</code></td><td>Full on child objects</td><td>Yes</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="lab-panel">
|
||||
<h3>How it was verified</h3>
|
||||
<pre class="lab-code">PS> Invoke-Command FILE01 {
|
||||
Get-SmbShare |
|
||||
Where-Object Name -NotIn 'ADMIN$','C$','IPC$' |
|
||||
Get-SmbShareAccess
|
||||
(Get-Acl C:\Shares\IT).Access
|
||||
}</pre>
|
||||
<p>
|
||||
Run from DC01 as a Domain Admin via WinRM, output captured to a transcript
|
||||
on the host (not inside the VM) so no sensitive data lives on a shared
|
||||
share.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="validation" aria-labelledby="validation-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="validation-title">Validation & fixes applied</h2>
|
||||
<span class="subtitle">Real problems found & resolved during the audit</span>
|
||||
</div>
|
||||
<div class="lab-grid-2">
|
||||
<div class="lab-panel">
|
||||
<h3>Problem: PDC was using the local CMOS clock</h3>
|
||||
<p>
|
||||
The PDC Emulator is the authoritative time source for a Windows forest.
|
||||
If it drifts, Kerberos tickets outside the 5-minute skew window fail —
|
||||
a classic "I can't log in" root cause. <code>w32tm /query /source</code>
|
||||
returned <code>Local CMOS Clock</code>.
|
||||
</p>
|
||||
<pre class="lab-code">w32tm /config /manualpeerlist:"time.windows.com,0x9 pool.ntp.org,0x9 time.nist.gov,0x9" `
|
||||
/syncfromflags:manual /reliable:yes /update
|
||||
Restart-Service W32Time
|
||||
w32tm /resync /rediscover</pre>
|
||||
<p class="status-ok">Verified: <code>Source: time.windows.com,0x9</code>, Stratum 5.</p>
|
||||
</div>
|
||||
<div class="lab-panel">
|
||||
<h3>Problem: stale <code>dcdiag</code> SystemLog failure</h3>
|
||||
<p>
|
||||
A past unexpected shutdown left an entry that <code>dcdiag</code> flagged on
|
||||
every run. Cleared the System log and re-ran <code>dcdiag /q</code>.
|
||||
</p>
|
||||
<pre class="lab-code">wevtutil cl System
|
||||
dcdiag /q</pre>
|
||||
<p class="status-ok">Verified: <code>dcdiag /q</code> now silent (clean).</p>
|
||||
<h3 class="h3-sub">Pending (user-side VMware GUI)</h3>
|
||||
<p>
|
||||
WIN11-01's vmx is encrypted and its NIC is bridged to the physical LAN
|
||||
(192.168.1.132), so it currently can't reach DC01 (192.168.31.10). Fix is a
|
||||
VMware-side NIC change plus a <code>Test-ComputerSecureChannel -Repair</code>.
|
||||
Documented honestly rather than papered over.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="skills" aria-labelledby="skills-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="skills-title">Skills demonstrated</h2>
|
||||
<span class="subtitle">Why this project matters for IT roles</span>
|
||||
</div>
|
||||
<ul class="lab-skill-list">
|
||||
<li><strong>Windows Server 2022</strong> — installed, promoted to a DC, operate day-to-day.</li>
|
||||
<li><strong>Active Directory</strong> — domain / forest design, OUs, users and groups, FSMO awareness, Global Catalog.</li>
|
||||
<li><strong>DNS</strong> — AD-integrated primary zones, reverse zones, understanding why the client's DNS must point at the DC.</li>
|
||||
<li><strong>SMB & NTFS</strong> — share creation, share-level vs. NTFS permissions, role-based access through groups.</li>
|
||||
<li><strong>PowerShell</strong> — <code>Get-AD*</code>, <code>Invoke-Command</code>, <code>Get-SmbShareAccess</code>, <code>Get-Acl</code>, <code>w32tm</code>, <code>Test-ComputerSecureChannel</code>.</li>
|
||||
<li><strong>Troubleshooting</strong> — recognizing <em>Local CMOS Clock</em> as a future Kerberos failure; recognizing an Access Denied on WinRM as "not in the server's local Administrators."</li>
|
||||
<li><strong>Virtualization & networking</strong> — VMware Workstation Pro, VMnet subnetting, OPNsense placement, bridged vs. NAT NIC implications.</li>
|
||||
<li><strong>Honest audit documentation</strong> — the <code>WIN11-01 pending</code> callout stays visible, with the exact fix listed.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="contact-cta">
|
||||
<div>
|
||||
<h2>Want to see the full audit log?</h2>
|
||||
<p>Phases, verified command output, problem-and-fix table, security notes.</p>
|
||||
</div>
|
||||
<div class="contact-actions">
|
||||
<a class="btn btn-primary" href="docs/windows-server-homelab.md">Project write-up</a>
|
||||
<a class="btn btn-secondary" href="docs/homelab-audit.md">Full audit log</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="site-footer" role="contentinfo">
|
||||
<div class="container">
|
||||
<div>© <span id="year">2026</span> Keith Casko. All rights reserved.</div>
|
||||
<ul class="footer-links">
|
||||
<li><a href="assets/resume/Keith_Casko_IT_Cloud_Resume.docx" download>Resume</a></li>
|
||||
<li><a href="https://github.com/kcasko" rel="noopener">GitHub</a></li>
|
||||
<li><a href="https://www.linkedin.com/in/keith-casko/" rel="noopener">LinkedIn</a></li>
|
||||
<li><a href="mailto:keith.casko@gmail.com">Email</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="js/main.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,513 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Keith Casko — IT Support, Systems Administration, Cloud Operations</title>
|
||||
<meta name="description" content="Keith Casko — IT portfolio: Windows Server, Active Directory, Linux, AWS, Docker, networking, and troubleshooting projects. Based in Kalamazoo, Michigan." />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ============ Header ============ -->
|
||||
<header class="site-header" role="banner">
|
||||
<div class="container">
|
||||
<div class="brand">
|
||||
<span class="name">Keith Casko</span>
|
||||
<span class="tagline">
|
||||
IT Support | Systems Administration | Cloud Operations
|
||||
<span class="location">Kalamazoo, MI</span>
|
||||
</span>
|
||||
</div>
|
||||
<button class="nav-toggle" aria-expanded="false" aria-controls="primary-nav">Menu</button>
|
||||
<nav class="site-nav" id="primary-nav" aria-label="Primary">
|
||||
<ul>
|
||||
<li><a href="#home" class="active">Home</a></li>
|
||||
<li><a href="#projects">Projects</a></li>
|
||||
<li><a href="#homelab">Homelab</a></li>
|
||||
<li><a href="#troubleshooting">Troubleshooting</a></li>
|
||||
<li><a href="#about">About</a></li>
|
||||
<li><a href="#resume">Resume</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- ============ Hero ============ -->
|
||||
<section class="hero" id="home" aria-labelledby="hero-title">
|
||||
<div class="container">
|
||||
<div class="hero-grid">
|
||||
<div>
|
||||
<h1 id="hero-title">Building Reliable Systems.<br />Solving Real Problems.</h1>
|
||||
<p class="lede">
|
||||
Hands-on experience with Windows Server, Active Directory, Linux, AWS,
|
||||
networking, Docker, monitoring, remote administration, and technical
|
||||
troubleshooting.
|
||||
</p>
|
||||
<p>
|
||||
Currently completing a B.S. in Software Engineering at Western Governors
|
||||
University while building practical infrastructure and support experience
|
||||
through a dedicated homelab, AWS projects, and ongoing study of systems
|
||||
administration and cloud operations.
|
||||
</p>
|
||||
<div class="hero-actions">
|
||||
<a class="btn btn-primary" href="#projects">View Projects</a>
|
||||
<a class="btn btn-secondary" href="assets/resume/Keith_Casko_IT_Cloud_Resume.docx" download>Download Resume</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-image">
|
||||
<img src="assets/images/hero-server-rack.png" alt="Home server rack and homelab workspace" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ Featured Projects ============ -->
|
||||
<section id="projects" aria-labelledby="projects-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="projects-title">Featured Projects</h2>
|
||||
<span class="subtitle">Hands-on infrastructure, support, and cloud work</span>
|
||||
</div>
|
||||
|
||||
<div class="projects-grid">
|
||||
|
||||
<article class="project-card">
|
||||
<a class="card-link" href="homelab.html" aria-label="View ByteGeist Windows Server Lab details">
|
||||
<div class="thumb thumb-diagram">
|
||||
<img src="assets/images/homelab-topology.svg"
|
||||
alt="ByteGeist AD homelab topology diagram — DC01, FILE01, OPNsense, WIN11-01" />
|
||||
</div>
|
||||
</a>
|
||||
<div class="body">
|
||||
<h3>ByteGeist Windows Server Lab</h3>
|
||||
<p>
|
||||
Windows Server 2022 Active Directory lab in VMware: domain
|
||||
controller with AD DS and DNS, member file server with
|
||||
role-based SMB shares, Windows 11 domain client, and an
|
||||
OPNsense firewall segmenting the networks. Audited and fixed
|
||||
real problems (PDC time source, stale event log) via
|
||||
PowerShell and WinRM.
|
||||
</p>
|
||||
<a class="view-link" href="homelab.html">View Lab Details</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="project-card">
|
||||
<div class="thumb">
|
||||
<img src="assets/screenshots/bytegeist-support-live.png"
|
||||
alt="ByteGeist Support Lab — live app screenshot" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>ByteGeist Support Lab</h3>
|
||||
<p>
|
||||
An interactive IT troubleshooting application on AWS (Amplify,
|
||||
Lambda, API Gateway, DynamoDB) with React. Realistic support
|
||||
incidents, command-line workflows, diagnosis, resolution, and
|
||||
scoring.
|
||||
</p>
|
||||
<a class="view-link" href="https://supportlab.casko.dev/" rel="noopener">View Live Site</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="project-card">
|
||||
<div class="thumb">
|
||||
<img src="assets/screenshots/bytegeist-infrastructure-live.png"
|
||||
alt="ByteGeist Infrastructure Lab live control plane showing host telemetry, container health, and service status"
|
||||
style="object-position: top center;" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>ByteGeist Infrastructure Lab</h3>
|
||||
<p>
|
||||
A self-hosted Hetzner Ubuntu environment running Docker-based
|
||||
services with Prometheus, node_exporter, cAdvisor, Grafana, Loki,
|
||||
Authentik, Gitea, Woodpecker CI, TLS routing, and a sanitized live
|
||||
status API. Built to practice deployment, monitoring, identity,
|
||||
security, recovery, and day-to-day infrastructure operations.
|
||||
</p>
|
||||
<a class="view-link" href="https://bytegeist.casko.dev/" rel="noopener">View Live Control Plane</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="project-card">
|
||||
<div class="thumb">
|
||||
<img src="assets/screenshots/aws-tracker.png"
|
||||
alt="AWS Cloud Study Tracker — live app screenshot" />
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>AWS Cloud Study Tracker</h3>
|
||||
<p>
|
||||
A serverless AWS CRUD application built with Amplify, API Gateway,
|
||||
Lambda, DynamoDB, and IAM. Used to practice AWS serverless
|
||||
architecture, permissions, and least-privilege access.
|
||||
</p>
|
||||
<a class="view-link" href="https://production.d2kf0c9e1fogq3.amplifyapp.com/" rel="noopener">View Live Site</a>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ What I Work With ============ -->
|
||||
<section id="homelab" aria-labelledby="skills-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="skills-title">What I Work With</h2>
|
||||
<span class="subtitle">Tools, platforms, and areas of practical experience</span>
|
||||
</div>
|
||||
|
||||
<div class="skills-grid">
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="M3 5.5 11 4v8H3V5.5zM13 3.75 21 2.5V12h-8V3.75zM3 13h8v7L3 18.5V13zm10 0h8v8.5L13 20v-7z"/>
|
||||
</svg>
|
||||
<h3>Windows & Active Directory</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Windows 10 / 11</li>
|
||||
<li>Windows Server 2022</li>
|
||||
<li>Active Directory</li>
|
||||
<li>Users & groups, OUs</li>
|
||||
<li>Domain joins</li>
|
||||
<li>File shares & permissions</li>
|
||||
<li>DNS</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="3" y="4" width="18" height="16" rx="1.5"/>
|
||||
<path d="m7 9 3 3-3 3M12 15h5"/>
|
||||
</svg>
|
||||
<h3>Linux</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Ubuntu</li>
|
||||
<li>WSL2</li>
|
||||
<li>Bash</li>
|
||||
<li>SSH</li>
|
||||
<li>Linux administration</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M7 18a4 4 0 0 1-.9-7.9A6 6 0 0 1 18 10.5a3.5 3.5 0 0 1-.5 7H7z"/>
|
||||
</svg>
|
||||
<h3>AWS</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>EC2, S3, IAM</li>
|
||||
<li>Lambda</li>
|
||||
<li>API Gateway</li>
|
||||
<li>DynamoDB</li>
|
||||
<li>Amplify</li>
|
||||
<li>Systems Manager</li>
|
||||
<li>KMS</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<circle cx="12" cy="4" r="2"/>
|
||||
<circle cx="4" cy="20" r="2"/>
|
||||
<circle cx="20" cy="20" r="2"/>
|
||||
<path d="M12 6v6M12 12l-7 6M12 12l7 6"/>
|
||||
</svg>
|
||||
<h3>Networking</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>TCP/IP, IPv4 / IPv6</li>
|
||||
<li>DNS, DHCP</li>
|
||||
<li>SSH, HTTP/HTTPS, TLS</li>
|
||||
<li>VPNs</li>
|
||||
<li>Reverse proxies</li>
|
||||
<li>Firewalls</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M4 14v-2a8 8 0 1 1 16 0v2"/>
|
||||
<rect x="2.5" y="14" width="4.5" height="6" rx="1"/>
|
||||
<rect x="17" y="14" width="4.5" height="6" rx="1"/>
|
||||
<path d="M17 20a3 3 0 0 1-3 3h-2"/>
|
||||
</svg>
|
||||
<h3>IT Support</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Troubleshooting</li>
|
||||
<li>Customer & remote support</li>
|
||||
<li>Technical communication</li>
|
||||
<li>Incident resolution</li>
|
||||
<li>Permissions & authentication</li>
|
||||
<li>Connectivity testing</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="3" y="4" width="18" height="5" rx="1"/>
|
||||
<rect x="3" y="11" width="18" height="5" rx="1"/>
|
||||
<rect x="3" y="18" width="18" height="3" rx="1"/>
|
||||
<path d="M6.5 6.5h.01M6.5 13.5h.01"/>
|
||||
</svg>
|
||||
<h3>Infrastructure</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>Docker, Docker Compose</li>
|
||||
<li>VirtualBox</li>
|
||||
<li>Portainer</li>
|
||||
<li>Grafana, Prometheus</li>
|
||||
<li>Loki</li>
|
||||
<li>Uptime Kuma</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<rect x="2.5" y="4" width="19" height="12" rx="1.5"/>
|
||||
<path d="M8 20h8M12 16v4"/>
|
||||
</svg>
|
||||
<h3>Remote Administration</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>RDP</li>
|
||||
<li>Parsec</li>
|
||||
<li>Tailscale</li>
|
||||
<li>RealVNC</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="skill-card">
|
||||
<div class="skill-head">
|
||||
<svg class="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="m8 7-5 5 5 5M16 7l5 5-5 5M14 5l-4 14"/>
|
||||
</svg>
|
||||
<h3>Scripting & Tools</h3>
|
||||
</div>
|
||||
<ul>
|
||||
<li>PowerShell</li>
|
||||
<li>Bash</li>
|
||||
<li>Git</li>
|
||||
<li>GitHub</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ Troubleshooting Notes ============ -->
|
||||
<section id="troubleshooting" aria-labelledby="notes-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="notes-title">Troubleshooting Notes</h2>
|
||||
<span class="subtitle">Short, practical case studies</span>
|
||||
</div>
|
||||
|
||||
<div class="notes-list">
|
||||
|
||||
<article class="note-item">
|
||||
<div class="icon" aria-hidden="true">DNS</div>
|
||||
<div>
|
||||
<h3>DNS Resolution Issue</h3>
|
||||
<p>Client cannot reach a service by name but can reach it by IP — isolate DNS from general network connectivity.</p>
|
||||
<ul>
|
||||
<li>Confirm TCP/IP configuration with <code>ipconfig /all</code></li>
|
||||
<li>Verify DNS server settings and search suffix</li>
|
||||
<li>Test reachability with <code>ping</code> against both hostname and IP</li>
|
||||
<li>Use <code>nslookup</code> against the primary and a known-good DNS server</li>
|
||||
<li>Flush client resolver cache; compare behavior from another host</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="chevron" aria-hidden="true">›</div>
|
||||
</article>
|
||||
|
||||
<article class="note-item">
|
||||
<div class="icon" aria-hidden="true">DKR</div>
|
||||
<div>
|
||||
<h3>Docker Networking Problem</h3>
|
||||
<p>A container responds locally but is unreachable from other hosts, or outbound requests fail.</p>
|
||||
<ul>
|
||||
<li>Check container state with <code>docker ps</code> and <code>docker logs</code></li>
|
||||
<li>Verify published ports and the attached Docker network</li>
|
||||
<li>Confirm DNS resolution from inside the container</li>
|
||||
<li>Check host firewall rules affecting the bridge or published ports</li>
|
||||
<li>Test HTTP endpoints with <code>curl -v</code> from host and container</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="chevron" aria-hidden="true">›</div>
|
||||
</article>
|
||||
|
||||
<article class="note-item">
|
||||
<div class="icon" aria-hidden="true">AD</div>
|
||||
<div>
|
||||
<h3>Active Directory Permissions Issue</h3>
|
||||
<p>A user authenticates successfully but cannot access a shared folder they should.</p>
|
||||
<ul>
|
||||
<li>Confirm the user's identity and group membership</li>
|
||||
<li>Review share permissions and NTFS permissions separately</li>
|
||||
<li>Check for deny ACEs that override group access</li>
|
||||
<li>Validate authentication path and session with <code>whoami /groups</code></li>
|
||||
<li>Test access from a known-good account to isolate user vs. share</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="chevron" aria-hidden="true">›</div>
|
||||
</article>
|
||||
|
||||
<article class="note-item">
|
||||
<div class="icon" aria-hidden="true">IAM</div>
|
||||
<div>
|
||||
<h3>AWS IAM Access Problem</h3>
|
||||
<p>A workload or user receives <em>AccessDenied</em> when calling an AWS service.</p>
|
||||
<ul>
|
||||
<li>Identify the exact action, resource, and principal from the error</li>
|
||||
<li>Review attached identity and resource policies against least privilege</li>
|
||||
<li>Check for explicit denies, SCPs, and permission boundaries</li>
|
||||
<li>Reproduce with the IAM Policy Simulator</li>
|
||||
<li>Grant only the specific action needed; verify, then narrow further</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="chevron" aria-hidden="true">›</div>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ About ============ -->
|
||||
<section id="about" aria-labelledby="about-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="about-title">About</h2>
|
||||
<span class="subtitle">Background and focus</span>
|
||||
</div>
|
||||
|
||||
<div class="about-grid">
|
||||
<div class="about-photo">
|
||||
<img src="assets/images/keith-casko-headshot.png"
|
||||
alt="Keith Casko"
|
||||
class="about-headshot" />
|
||||
</div>
|
||||
|
||||
<div class="about-text">
|
||||
<h3>Keith Casko — Kalamazoo, Michigan</h3>
|
||||
<p>
|
||||
I'm transitioning into IT, cloud, and systems administration while
|
||||
completing a B.S. in Software Engineering at Western Governors
|
||||
University. My focus is practical, hands-on infrastructure work:
|
||||
Windows Server and Active Directory, Linux, AWS, Docker, networking,
|
||||
and technical troubleshooting.
|
||||
</p>
|
||||
<p>
|
||||
My professional background is at Costco Wholesale as a Baker. The job
|
||||
is production-paced and team-based, and it's where I developed the
|
||||
habits I bring to IT work — customer service, teamwork, training
|
||||
coworkers, process discipline, prioritization, and real-world
|
||||
problem-solving under pressure.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<aside class="pull-quote" aria-label="Approach">
|
||||
<span class="mark">“</span>
|
||||
Reliable systems are built one careful change at a time. I'd rather
|
||||
understand a problem than paper over it — and I document what I
|
||||
learn so the next person has a shorter path.
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ Education & Experience ============ -->
|
||||
<section id="resume" aria-labelledby="resume-title">
|
||||
<div class="container">
|
||||
<div class="section-head">
|
||||
<h2 id="resume-title">Education & Experience</h2>
|
||||
<span class="subtitle">Current study and professional background</span>
|
||||
</div>
|
||||
|
||||
<div class="two-col">
|
||||
|
||||
<div class="panel">
|
||||
<h3>Western Governors University</h3>
|
||||
<div class="meta">Bachelor of Science, Software Engineering · In Progress</div>
|
||||
<h4>Relevant Study</h4>
|
||||
<ul>
|
||||
<li>Cloud Computing</li>
|
||||
<li>Networking</li>
|
||||
<li>IT Operations</li>
|
||||
<li>Software Development</li>
|
||||
<li>Java</li>
|
||||
<li>Web Development</li>
|
||||
<li>Security Fundamentals</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h3>Costco Wholesale — Baker</h3>
|
||||
<div class="meta">June 2015 – Present</div>
|
||||
<h4>Transferable Experience</h4>
|
||||
<ul>
|
||||
<li>High-volume production environment</li>
|
||||
<li>Accuracy and process discipline</li>
|
||||
<li>Prioritization under time pressure</li>
|
||||
<li>Day-to-day troubleshooting of equipment and workflows</li>
|
||||
<li>Coordinating with team members and supervisors</li>
|
||||
<li>Training coworkers on procedures</li>
|
||||
<li>Customer service and clear communication</li>
|
||||
<li>Problem solving and follow-through</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============ Contact ============ -->
|
||||
<section id="contact" aria-labelledby="contact-title">
|
||||
<div class="container">
|
||||
<div class="contact-cta">
|
||||
<div>
|
||||
<h2 id="contact-title">Open to IT Support, Help Desk, and Junior SysAdmin roles</h2>
|
||||
<p>Based in Kalamazoo, Michigan — available on-site, hybrid, or remote.</p>
|
||||
<p><a href="mailto:keith.casko@gmail.com">keith.casko@gmail.com</a></p>
|
||||
</div>
|
||||
<div class="contact-actions">
|
||||
<a class="btn btn-primary" href="mailto:keith.casko@gmail.com">Email Keith</a>
|
||||
<a class="btn btn-secondary" href="https://www.linkedin.com/in/keith-casko/" rel="noopener">LinkedIn</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ============ Footer ============ -->
|
||||
<footer class="site-footer" role="contentinfo">
|
||||
<div class="container">
|
||||
<div>© <span id="year">2026</span> Keith Casko. All rights reserved.</div>
|
||||
<ul class="footer-links">
|
||||
<li><a href="assets/resume/Keith_Casko_IT_Cloud_Resume.docx" download>Resume</a></li>
|
||||
<li><a href="https://github.com/kcasko" rel="noopener">GitHub</a></li>
|
||||
<li><a href="https://www.linkedin.com/in/keith-casko/" rel="noopener">LinkedIn</a></li>
|
||||
<li><a href="mailto:keith.casko@gmail.com">Email</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="js/main.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
// Minimal JS: mobile nav toggle + active section highlight.
|
||||
(function () {
|
||||
var toggle = document.querySelector('.nav-toggle');
|
||||
var nav = document.querySelector('.site-nav');
|
||||
if (toggle && nav) {
|
||||
toggle.addEventListener('click', function () {
|
||||
var open = nav.classList.toggle('open');
|
||||
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
});
|
||||
nav.addEventListener('click', function (e) {
|
||||
if (e.target.tagName === 'A' && window.innerWidth <= 640) {
|
||||
nav.classList.remove('open');
|
||||
toggle.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set current year in footer
|
||||
var y = document.getElementById('year');
|
||||
if (y) y.textContent = new Date().getFullYear();
|
||||
})();
|
||||