Initial commit: railwayka.ru landing page as dcape app

- Dcape app structure with Makefile, docker-compose.yml
- Woodpecker CI/CD pipeline configuration
- Custom Dockerfile with nginx alpine
- Static website with dark theme and animations
- Service cards, tech stack, GitOps workflow visualization
- Konami code easter egg
This commit is contained in:
rail 2025-10-25 21:04:29 +03:00
commit 90314a5851
9 changed files with 692 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.env
.env.bak

20
.woodpecker.yml Normal file
View File

@ -0,0 +1,20 @@
# Woodpecker CI pipeline for railwayka landing page
variables:
- &dcape_img 'dcape-compose'
clone:
git:
image: woodpeckerci/plugin-git
settings:
lfs: false
tags: false
steps:
deploy:
image: *dcape_img
commands:
- make .default-deploy
volumes:
- /var/run/docker.sock:/var/run/docker.sock

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
ARG IMAGE=nginx
ARG IMAGE_VER=1.27-alpine
FROM --platform=$BUILDPLATFORM ${IMAGE}:${IMAGE_VER}
COPY html /usr/share/nginx/html

46
Makefile Normal file
View File

@ -0,0 +1,46 @@
## dcape-app-railwayka-landing Makefile
## This file extends Makefile.app from dcape
#:
SHELL = /bin/bash
CFG ?= .env
CFG_BAK ?= $(CFG).bak
#- Docker repo & image name without version
IMAGE ?= nginx
#- ver
IMAGE_VER ?= 1.27-alpine
# ------------------------------------------------------------------------------
# app custom config
# Overwrite for setup
APP_SITE ?= railwayka.ru
#- domain (no www for this site)
APP_ACME_DOMAIN ?= $(APP_SITE)
PERSIST_FILES =
# ------------------------------------------------------------------------------
# if exists - load old values
-include $(CFG_BAK)
export
-include $(CFG)
export
# ------------------------------------------------------------------------------
# Find and include DCAPE_ROOT/Makefile
DCAPE_COMPOSE ?= dcape-compose
DCAPE_ROOT ?= $(shell docker inspect -f "{{.Config.Labels.dcape_root}}" $(DCAPE_COMPOSE))
ifeq ($(shell test -e $(DCAPE_ROOT)/Makefile.app && echo -n yes),yes)
include $(DCAPE_ROOT)/Makefile.app
else
include /opt/dcape/Makefile.app
endif
.default-deploy: docker-build

75
README.md Normal file
View File

@ -0,0 +1,75 @@
# railwayka.ru Landing Page
Business card website for railwayka.ru infrastructure, deployed via [dcape](https://github.com/dopos/dcape) GitOps platform.
## Features
- Modern dark theme with animated stars background
- Service cards showcasing current and planned services
- Technology stack descriptions
- GitOps workflow visualization
- Konami code easter egg
- Responsive design
## Technology Stack
- **Nginx**: Alpine-based web server
- **Docker**: Containerized deployment
- **Traefik**: Automatic HTTPS via Let's Encrypt
- **Dcape**: GitOps-based deployment
## Deployment
This application uses GitOps workflow:
1. Push code to Git repository
2. Woodpecker CI automatically triggers
3. Application builds and deploys
4. Service goes live at https://railwayka.ru
### Manual Deployment
```bash
# Clone repository
git clone https://git.dc.railwayka.ru/howl/railwayka-landing.git
cd railwayka-landing
# Configure environment
make config
# Edit .env.sample and rename to .env
mv .env.sample .env
# Build and deploy
make docker-build
make up
```
## Local Development
To run locally:
```bash
# Serve files with any web server
cd html
python3 -m http.server 8000
# Visit http://localhost:8000
```
## Structure
```
.
├── Makefile # Dcape app Makefile
├── docker-compose.yml # Service definition
├── Dockerfile # Custom nginx image
├── .woodpecker.yml # CI/CD pipeline
├── html/ # Static website files
│ ├── index.html
│ ├── style.css
│ └── script.js
└── README.md
```
## License
MIT

13
docker-compose.yml Normal file
View File

@ -0,0 +1,13 @@
# Railwayka Landing Page - dcape app config
services:
app:
labels:
- traefik.http.routers.${APP_TAG}.rule=Host(`${APP_SITE:?Must be set}`)
# TLS support
- traefik.http.routers.${APP_TAG}.tls.domains[0].main=${APP_SITE}
build:
context: .
args:
IMAGE: "${IMAGE}"
IMAGE_VER: "${IMAGE_VER}"

131
html/index.html Normal file
View File

@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>railwayka.ru - Infrastructure & Services</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="stars"></div>
<div class="container">
<header>
<h1 class="glitch" data-text="railwayka.ru">railwayka.ru</h1>
<p class="tagline">Self-Hosted Infrastructure & Services</p>
</header>
<section class="intro">
<p>Welcome to my personal infrastructure platform, powered by modern DevOps technologies and GitOps workflows.</p>
</section>
<section class="services">
<h2>Available Services</h2>
<div class="service-grid">
<div class="service-card">
<div class="service-icon">🔧</div>
<h3>Git Repository</h3>
<p>Self-hosted Git server with web interface</p>
<a href="https://git.dc.railwayka.ru" class="service-link">Visit Gitea →</a>
</div>
<div class="service-card coming-soon">
<div class="service-icon">💬</div>
<h3>Matrix Server</h3>
<p>Decentralized, secure messaging</p>
<span class="badge">Coming Soon</span>
</div>
<div class="service-card coming-soon">
<div class="service-icon">📝</div>
<h3>Blog</h3>
<p>Technical writing and documentation</p>
<span class="badge">Coming Soon</span>
</div>
<div class="service-card coming-soon">
<div class="service-icon">🔐</div>
<h3>Password Manager</h3>
<p>Self-hosted credential storage</p>
<span class="badge">Coming Soon</span>
</div>
<div class="service-card coming-soon">
<div class="service-icon">☁️</div>
<h3>Cloud Storage</h3>
<p>Private file storage and sync</p>
<span class="badge">Coming Soon</span>
</div>
<div class="service-card coming-soon">
<div class="service-icon">📊</div>
<h3>Monitoring</h3>
<p>System metrics and dashboards</p>
<span class="badge">Coming Soon</span>
</div>
</div>
</section>
<section class="tech-stack">
<h2>Technology Stack</h2>
<div class="tech-info">
<div class="tech-item">
<h3>🚀 Dcape</h3>
<p>GitOps-based infrastructure management platform. Deploy applications with simple <code>git push</code> commands.</p>
</div>
<div class="tech-item">
<h3>🐳 Docker</h3>
<p>All services run in isolated containers with automatic orchestration and health monitoring.</p>
</div>
<div class="tech-item">
<h3>🔒 Let's Encrypt</h3>
<p>Automatic SSL/TLS certificates with wildcard domain support for secure HTTPS connections.</p>
</div>
<div class="tech-item">
<h3>🌐 Traefik</h3>
<p>Modern reverse proxy with automatic service discovery and HTTPS routing.</p>
</div>
<div class="tech-item">
<h3>🗄️ PostgreSQL</h3>
<p>Shared production-grade database for application data persistence.</p>
</div>
<div class="tech-item">
<h3>📡 PowerDNS</h3>
<p>Self-hosted authoritative DNS server with API-driven zone management.</p>
</div>
</div>
</section>
<section class="about-gitops">
<h2>What is GitOps?</h2>
<p>GitOps is a modern infrastructure management approach where Git serves as the single source of truth. All infrastructure changes and application deployments are made through Git commits and pull requests.</p>
<div class="gitops-flow">
<div class="flow-step">
<span class="step-number">1</span>
<p>Push code to Git repository</p>
</div>
<div class="flow-arrow"></div>
<div class="flow-step">
<span class="step-number">2</span>
<p>CI/CD pipeline builds</p>
</div>
<div class="flow-arrow"></div>
<div class="flow-step">
<span class="step-number">3</span>
<p>Automatic deployment</p>
</div>
<div class="flow-arrow"></div>
<div class="flow-step">
<span class="step-number">4</span>
<p>Service goes live</p>
</div>
</div>
</section>
<footer>
<p>Powered by open-source technologies and self-hosted infrastructure</p>
<p class="footer-note">This server runs on <strong>Ubuntu 24.04</strong> with hardened SSH security (key-only authentication)</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>

75
html/script.js Normal file
View File

@ -0,0 +1,75 @@
// Smooth scroll for any future internal links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// Add fade-in animation to cards on scroll
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '0';
entry.target.style.transform = 'translateY(20px)';
entry.target.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
setTimeout(() => {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}, 100);
observer.unobserve(entry.target);
}
});
}, observerOptions);
// Observe all service cards and tech items
document.querySelectorAll('.service-card, .tech-item, .flow-step').forEach(el => {
observer.observe(el);
});
// Easter egg: Konami code detector
let konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
let konamiIndex = 0;
document.addEventListener('keydown', (e) => {
if (e.key === konamiCode[konamiIndex]) {
konamiIndex++;
if (konamiIndex === konamiCode.length) {
document.body.style.animation = 'rainbow 2s ease infinite';
setTimeout(() => {
document.body.style.animation = '';
konamiIndex = 0;
}, 5000);
}
} else {
konamiIndex = 0;
}
});
// Add CSS for rainbow animation
const style = document.createElement('style');
style.textContent = `
@keyframes rainbow {
0% { filter: hue-rotate(0deg); }
100% { filter: hue-rotate(360deg); }
}
`;
document.head.appendChild(style);
// Console message for developers
console.log('%c👋 Hello, developer!', 'font-size: 20px; color: #6366f1; font-weight: bold;');
console.log('%cInterested in the infrastructure? Check out https://git.dc.railwayka.ru', 'font-size: 14px; color: #a1a1aa;');
console.log('%cPowered by Dcape - GitOps infrastructure management', 'font-size: 12px; color: #6366f1;');

324
html/style.css Normal file
View File

@ -0,0 +1,324 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #0a0e27;
--bg-secondary: #141b3d;
--text-primary: #e4e4e7;
--text-secondary: #a1a1aa;
--accent: #6366f1;
--accent-hover: #4f46e5;
--card-bg: rgba(20, 27, 61, 0.6);
--border: rgba(99, 102, 241, 0.2);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
overflow-x: hidden;
}
.stars {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background-image:
radial-gradient(2px 2px at 20px 30px, white, transparent),
radial-gradient(2px 2px at 60px 70px, white, transparent),
radial-gradient(1px 1px at 50px 50px, white, transparent),
radial-gradient(1px 1px at 130px 80px, white, transparent),
radial-gradient(2px 2px at 90px 10px, white, transparent);
background-size: 200px 200px;
animation: twinkle 5s ease-in-out infinite;
opacity: 0.4;
}
@keyframes twinkle {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.7; }
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
position: relative;
z-index: 1;
}
header {
text-align: center;
padding: 4rem 0 2rem;
}
h1 {
font-size: 4rem;
font-weight: 800;
background: linear-gradient(135deg, var(--accent) 0%, #ec4899 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 0.5rem;
position: relative;
}
.glitch {
animation: glitch 3s infinite;
}
@keyframes glitch {
0%, 90%, 100% {
text-shadow: none;
}
92% {
text-shadow: 2px 2px #ff00de, -2px -2px #00fff9;
}
94% {
text-shadow: -2px 2px #ff00de, 2px -2px #00fff9;
}
}
.tagline {
font-size: 1.25rem;
color: var(--text-secondary);
font-weight: 300;
}
section {
margin: 4rem 0;
}
.intro {
text-align: center;
font-size: 1.1rem;
color: var(--text-secondary);
max-width: 600px;
margin: 2rem auto;
}
h2 {
font-size: 2rem;
margin-bottom: 2rem;
text-align: center;
color: var(--text-primary);
}
.service-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.service-card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 12px;
padding: 2rem;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
position: relative;
overflow: hidden;
}
.service-card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.1), transparent);
transition: left 0.5s ease;
}
.service-card:hover::before {
left: 100%;
}
.service-card:hover {
transform: translateY(-5px);
border-color: var(--accent);
box-shadow: 0 10px 30px rgba(99, 102, 241, 0.3);
}
.service-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.service-card h3 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.service-card p {
color: var(--text-secondary);
margin-bottom: 1rem;
}
.service-link {
display: inline-block;
color: var(--accent);
text-decoration: none;
font-weight: 500;
transition: color 0.3s ease;
}
.service-link:hover {
color: var(--accent-hover);
text-decoration: underline;
}
.service-card.coming-soon {
opacity: 0.7;
}
.badge {
display: inline-block;
padding: 0.25rem 0.75rem;
background: rgba(99, 102, 241, 0.2);
border: 1px solid var(--accent);
border-radius: 20px;
font-size: 0.875rem;
color: var(--accent);
}
.tech-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.tech-item {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
backdrop-filter: blur(10px);
}
.tech-item h3 {
font-size: 1.25rem;
margin-bottom: 0.75rem;
color: var(--text-primary);
}
.tech-item p {
color: var(--text-secondary);
font-size: 0.95rem;
}
code {
background: rgba(99, 102, 241, 0.2);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Courier New', monospace;
color: var(--accent);
}
.about-gitops {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 12px;
padding: 3rem;
backdrop-filter: blur(10px);
}
.about-gitops p {
text-align: center;
max-width: 800px;
margin: 0 auto 2rem;
color: var(--text-secondary);
}
.gitops-flow {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
margin-top: 2rem;
}
.flow-step {
background: rgba(99, 102, 241, 0.1);
border: 2px solid var(--accent);
border-radius: 12px;
padding: 1.5rem;
text-align: center;
min-width: 150px;
transition: all 0.3s ease;
}
.flow-step:hover {
transform: scale(1.05);
background: rgba(99, 102, 241, 0.2);
}
.step-number {
display: block;
width: 40px;
height: 40px;
margin: 0 auto 0.5rem;
background: var(--accent);
color: white;
border-radius: 50%;
line-height: 40px;
font-weight: bold;
}
.flow-arrow {
color: var(--accent);
font-size: 2rem;
font-weight: bold;
}
footer {
text-align: center;
padding: 3rem 0 2rem;
color: var(--text-secondary);
border-top: 1px solid var(--border);
margin-top: 4rem;
}
.footer-note {
margin-top: 0.5rem;
font-size: 0.9rem;
}
footer strong {
color: var(--accent);
}
@media (max-width: 768px) {
h1 {
font-size: 2.5rem;
}
.service-grid {
grid-template-columns: 1fr;
}
.gitops-flow {
flex-direction: column;
}
.flow-arrow {
transform: rotate(90deg);
}
.container {
padding: 1rem;
}
}