cert-study-app / cert_study_app /services /learning_lab_service.py
Kentlo's picture
Sync from GitHub b098d17dbed1a6386c212d28b4bfa6138ad59d75
88c7275 verified
Raw
History Blame Contribute Delete
48.7 kB
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
CONTENT_STATUSES = ["generated", "reviewed", "approved", "rejected"]
@dataclass(frozen=True)
class RoadmapStep:
title: str
description: str
level: str = "๊ธฐ์ดˆ"
@dataclass(frozen=True)
class LessonCard:
id: str
track: str
certification: str
title: str
summary: str
example: str
common_mistake: str
keywords: list[str]
source_id: str
status: str = "approved"
details: tuple[str, ...] = ()
level: str = "์ž…๋ฌธ"
related_practices: tuple[str, ...] = ()
@dataclass(frozen=True)
class LabQuiz:
id: str
lesson_id: str
track: str
question_type: str
question: str
options: list[str]
answer: str
explanation: str
difficulty: str = "easy"
source_id: str = "manual-linux-basic"
status: str = "approved"
@dataclass(frozen=True)
class PracticeTask:
id: str
track: str
title: str
task_description: str
expected_command: str
grading_conditions: list[str]
hint: str
explanation: str
difficulty: str = "easy"
status: str = "approved"
takeaway: str = ""
TRACKS = [
{
"id": "linux",
"name": "Linux",
"description": "ํ„ฐ๋ฏธ๋„, ํŒŒ์ผ, ๊ถŒํ•œ, ํ”„๋กœ์„ธ์Šค, ์„œ๋น„์Šค ๊ด€๋ฆฌ ๊ธฐ์ดˆ",
"priority": 1,
"is_active": True,
},
{
"id": "azure",
"name": "Azure",
"description": "Azure ๊ด€๋ฆฌ์ž ์—ญํ• ๊ณผ AZ-104 ์‹œํ—˜ ๋Œ€๋น„",
"priority": 2,
"is_active": True,
},
{
"id": "tool_docs",
"name": "Tool Docs",
"description": "Ollama, LangChain, Streamlit, Hugging Face ๊ณต์‹ ๋ฌธ์„œ ๊ธฐ๋ฐ˜ ํ•™์Šต",
"priority": 3,
"is_active": True,
},
{"id": "git", "name": "Git", "description": "๋ฒ„์ „ ๊ด€๋ฆฌ ๊ธฐ๋ณธ๊ธฐ", "priority": 4, "is_active": False},
{"id": "python", "name": "Python", "description": "๊ธฐ์ดˆ ๋ฌธ๋ฒ•๊ณผ ์ž๋™ํ™”", "priority": 5, "is_active": False},
{"id": "sql", "name": "SQL", "description": "์กฐํšŒ์™€ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๋ง ๊ธฐ์ดˆ", "priority": 6, "is_active": False},
{"id": "docker", "name": "Docker", "description": "์ปจํ…Œ์ด๋„ˆ ์ด๋ฏธ์ง€์™€ ์‹คํ–‰ ํ™˜๊ฒฝ", "priority": 7, "is_active": False},
{"id": "kubernetes", "name": "Kubernetes", "description": "์ปจํ…Œ์ด๋„ˆ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๊ธฐ์ดˆ", "priority": 8, "is_active": False},
]
CERTIFICATIONS = {
"azure": [
{
"id": "az-104",
"name": "AZ-104",
"description": "Microsoft Azure Administrator",
"readiness": "ready_with_questions",
"study_mode": "๋ฌธ์ œ์€ํ–‰ ๊ธฐ๋ฐ˜",
},
],
"linux": [
{
"id": "lfcs",
"name": "LFCS",
"description": "Linux Foundation Certified Systems Administrator",
"readiness": "practice_based",
"study_mode": "์‹ค์Šต ๊ณผ์ œ ๊ธฐ๋ฐ˜",
},
{
"id": "linux-master",
"name": "๋ฆฌ๋ˆ…์Šค๋งˆ์Šคํ„ฐ",
"description": "๋ฆฌ๋ˆ…์Šค ๋ช…๋ น์–ด์™€ ์šด์˜์ฒด์ œ ๊ฐœ๋… ์ค‘์‹ฌ ๊ตญ๋‚ด ์ž๊ฒฉ์ฆ",
"readiness": "concept_quiz_based",
"study_mode": "๊ฐœ๋…/ํ™•์ธ ํ€ด์ฆˆ ๊ธฐ๋ฐ˜",
},
],
"tool_docs": [
{
"id": "tool-docs",
"name": "Docs Study",
"description": "๊ณต์‹ ๋ฌธ์„œ ๊ธฐ๋ฐ˜ ๋„๊ตฌ ํ•™์Šต",
"readiness": "docs_based",
"study_mode": "๊ณต์‹ ๋ฌธ์„œ ๊ธฐ๋ฐ˜",
},
],
}
ROADMAPS = {
"linux": [
RoadmapStep("Essential Commands", "ํŒŒ์ผ ํƒ์ƒ‰, ๊ฒ€์ƒ‰, ์••์ถ•, ๋ฆฌ๋‹ค์ด๋ ‰์…˜, ๊ธฐ๋ณธ ํ…์ŠคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ์ตํž™๋‹ˆ๋‹ค.", "LFCS ํ•ต์‹ฌ"),
RoadmapStep("Users & Groups", "์‚ฌ์šฉ์ž, ๊ทธ๋ฃน, sudo, ๊ณ„์ • ์ •์ฑ…์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "LFCS ํ•ต์‹ฌ"),
RoadmapStep("Permissions", "rwx, chmod, chown, umask, ํŠน์ˆ˜ ๊ถŒํ•œ์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "LFCS ํ•ต์‹ฌ"),
RoadmapStep("Processes & Services", "ps, kill, systemctl, journalctl๋กœ ํ”„๋กœ์„ธ์Šค์™€ ์„œ๋น„์Šค๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.", "LFCS ์‹ค๋ฌด"),
RoadmapStep("Networking", "ip, ss, ping, curl, DNS, ๋ฐฉํ™”๋ฒฝ ๊ธฐ์ดˆ๋ฅผ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.", "LFCS ์‹ค๋ฌด"),
RoadmapStep("Storage", "ํŒŒํ‹ฐ์…˜, ํŒŒ์ผ์‹œ์Šคํ…œ, mount, swap, LVM ๊ฐœ๋…์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "LFCS ์‹ค๋ฌด"),
RoadmapStep("Package & Software", "apt, dnf/yum, repository, ์—…๋ฐ์ดํŠธ ํ๋ฆ„์„ ์ตํž™๋‹ˆ๋‹ค.", "LFCS ์‹ค๋ฌด"),
RoadmapStep("Logs & Troubleshooting", "๋กœ๊ทธ ์œ„์น˜์™€ ์žฅ์•  ํ™•์ธ ๋ฃจํ‹ด์„ ์—ฐ์Šตํ•ฉ๋‹ˆ๋‹ค.", "LFCS ์‹ค๋ฌด"),
RoadmapStep("Security Basics", "SSH, ๊ถŒํ•œ, ์„œ๋น„์Šค ๋…ธ์ถœ, ๊ธฐ๋ณธ ๋ณด์•ˆ ์ ๊ฒ€์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "LFCS ์‹ค๋ฌด"),
RoadmapStep("LFCS Style Labs", "๋ช…๋ น์–ด๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•ด ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.", "์‹ค์Šต"),
],
"azure": [
RoadmapStep("Identity & Governance", "Entra ID, RBAC, Policy, Resource Group, Management Group์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "AZ-104 20-25%"),
RoadmapStep("Storage", "Storage Account, Blob, Files, redundancy, lifecycle๋ฅผ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.", "AZ-104 15-20%"),
RoadmapStep("Compute", "VM, Scale Set, App Service, Container ๋ฆฌ์†Œ์Šค๋ฅผ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.", "AZ-104 20-25%"),
RoadmapStep("Networking", "VNet, Subnet, NSG, Peering, Load Balancer, DNS๋ฅผ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.", "AZ-104 15-20%"),
RoadmapStep("Monitor & Recovery", "Azure Monitor, Log Analytics, Backup, Site Recovery๋ฅผ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "AZ-104 10-15%"),
RoadmapStep("AZ-104 ๋ฌธ์ œํ’€์ด", "๊ธฐ์กด ๋ฌธ์ œ์€ํ–‰์œผ๋กœ ์‹œํ—˜ ์Šคํƒ€์ผ์„ ์—ฐ์Šตํ•ฉ๋‹ˆ๋‹ค.", "์‹œํ—˜ ๋Œ€๋น„"),
],
"tool_docs": [
RoadmapStep("Ollama", "๋กœ์ปฌ ๋ชจ๋ธ ์‹คํ–‰, ๋ชจ๋ธ ๊ด€๋ฆฌ, API ํ˜ธ์ถœ ํ๋ฆ„์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.", "๊ณต์‹ Docs"),
RoadmapStep("LangChain", "Chain, Retriever, Tool, Agent์˜ ๊ธฐ๋ณธ ์“ฐ์ž„์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "๊ณต์‹ Docs"),
RoadmapStep("Streamlit", "์ƒํƒœ ๊ด€๋ฆฌ, ์ž…๋ ฅ ์œ„์ ฏ, ๋ฐฐํฌ ํ๋ฆ„์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค.", "๊ณต์‹ Docs"),
RoadmapStep("Hugging Face", "Spaces, Models, Datasets, Secrets ๊ด€๋ฆฌ ํ๋ฆ„์„ ์ตํž™๋‹ˆ๋‹ค.", "๊ณต์‹ Docs"),
],
}
LESSONS = []
QUIZZES = [
LabQuiz(
"linux-q1",
"linux-pwd",
"linux",
"multiple_choice",
"ํ˜„์žฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ์œ„์น˜๋ฅผ ํ™•์ธํ•˜๋Š” ๋ช…๋ น์–ด๋Š”?",
["ls", "pwd", "cd", "mkdir"],
"pwd",
"`pwd`๋Š” print working directory์˜ ์•ฝ์ž๋กœ ํ˜„์žฌ ์ž‘์—… ์ค‘์ธ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ๊ฒฝ๋กœ๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.",
),
LabQuiz(
"linux-q2",
"linux-chmod",
"linux",
"multiple_choice",
"ํŒŒ์ผ ๊ถŒํ•œ์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ช…๋ น์–ด๋Š”?",
["chmod", "chown", "grep", "kill"],
"chmod",
"`chmod`๋Š” ํŒŒ์ผ์ด๋‚˜ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ๊ถŒํ•œ์„ ๋ณ€๊ฒฝํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.",
),
LabQuiz(
"linux-q3",
"linux-grep",
"linux",
"multiple_choice",
"ํ…์ŠคํŠธ์—์„œ ํŠน์ • ๋ฌธ์ž์—ด์„ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ช…๋ น์–ด๋Š”?",
["cat", "grep", "touch", "mv"],
"grep",
"`grep`์€ ํŒŒ์ผ์ด๋‚˜ ์ถœ๋ ฅ ๊ฒฐ๊ณผ์—์„œ ํŠน์ • ๋ฌธ์ž์—ด ํŒจํ„ด์„ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.",
source_id="linux-grep-manual",
),
LabQuiz(
"linux-q4",
"linux-mkdir",
"linux",
"command",
"`practice` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“œ๋Š” ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"mkdir practice",
"`mkdir practice`๋Š” ํ˜„์žฌ ์œ„์น˜์— practice ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.",
),
LabQuiz(
"linux-q5",
"linux-touch",
"linux",
"command",
"`hello.txt` ๋นˆ ํŒŒ์ผ์„ ๋งŒ๋“œ๋Š” ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"touch hello.txt",
"`touch hello.txt`๋Š” ๋นˆ ํŒŒ์ผ์„ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๊ธฐ์กด ํŒŒ์ผ์˜ ์ˆ˜์ • ์‹œ๊ฐ„์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.",
),
LabQuiz(
"linux-q6",
"linux-apt",
"linux",
"command",
"Ubuntu์—์„œ ํŒจํ‚ค์ง€ ๋ชฉ๋ก์„ ๊ฐฑ์‹ ํ•˜๋Š” ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"sudo apt update",
"`sudo apt update`๋Š” ์ €์žฅ์†Œ์˜ ํŒจํ‚ค์ง€ ๋ชฉ๋ก์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ํŒจํ‚ค์ง€ ์—…๊ทธ๋ ˆ์ด๋“œ๋Š” `sudo apt upgrade`์ž…๋‹ˆ๋‹ค.",
source_id="ubuntu-package-management",
),
LabQuiz(
"linux-q7",
"linux-ufw",
"linux",
"command",
"Ubuntu์—์„œ SSH ์ ‘์† ํฌํŠธ 22๋ฒˆ์„ ํ—ˆ์šฉํ•˜๋Š” ufw ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"sudo ufw allow 22",
"`sudo ufw allow 22`๋Š” 22๋ฒˆ ํฌํŠธ๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์›๊ฒฉ ์„œ๋ฒ„์—์„œ๋Š” ufw enable ์ „์— SSH ํฌํŠธ ํ—ˆ์šฉ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.",
source_id="ubuntu-firewall",
),
LabQuiz(
"azure-q1",
"azure-rg",
"azure",
"multiple_choice",
"AZ-104์—์„œ Resource Group์˜ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ํŠน์ง•์œผ๋กœ ๋งž๋Š” ๊ฒƒ์€?",
["๊ฐ€์ƒ ๋„คํŠธ์›Œํฌ ์ฃผ์†Œ ๊ณต๊ฐ„์„ ์ •์˜ํ•œ๋‹ค", "Azure ๋ฆฌ์†Œ์Šค๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ฌถ์–ด ๊ด€๋ฆฌํ•œ๋‹ค", "์‚ฌ์šฉ์ž ์•”ํ˜ธ๋ฅผ ์žฌ์„ค์ •ํ•œ๋‹ค", "VM ํฌ๊ธฐ๋ฅผ ์ž๋™์œผ๋กœ ์กฐ์ •ํ•œ๋‹ค"],
"Azure ๋ฆฌ์†Œ์Šค๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ฌถ์–ด ๊ด€๋ฆฌํ•œ๋‹ค",
"Resource Group์€ ์—ฌ๋Ÿฌ Azure ๋ฆฌ์†Œ์Šค๋ฅผ ๋…ผ๋ฆฌ์ ์œผ๋กœ ๋ฌถ๊ณ  ์ˆ˜๋ช…์ฃผ๊ธฐ์™€ ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ๋•๋Š” ๋‹จ์œ„์ž…๋‹ˆ๋‹ค.",
source_id="manual-az104-basic",
),
LabQuiz(
"azure-q2",
"azure-storage",
"azure",
"multiple_choice",
"Storage Account์—์„œ Blob Storage๊ฐ€ ์ฃผ๋กœ ์ €์žฅํ•˜๋Š” ๋ฐ์ดํ„ฐ ์œ ํ˜•์€?",
["๊ฐœ์ฒด ๋ฐ์ดํ„ฐ", "๊ฐ€์ƒ ๋„คํŠธ์›Œํฌ ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ”", "Entra ID ์‚ฌ์šฉ์ž", "Azure Policy ์ •์˜๋งŒ"],
"๊ฐœ์ฒด ๋ฐ์ดํ„ฐ",
"Blob Storage๋Š” ๋กœ๊ทธ, ์ด๋ฏธ์ง€, ๋ฐฑ์—… ํŒŒ์ผ ๊ฐ™์€ ๋น„์ •ํ˜• ๊ฐœ์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.",
source_id="azure-storage-account",
),
LabQuiz(
"azure-q3",
"azure-vnet",
"azure",
"multiple_choice",
"VNet์„ ์„ค๊ณ„ํ•  ๋•Œ ๋จผ์ € ํ™•์ธํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์€?",
["์ฃผ์†Œ ๊ณต๊ฐ„์ด ๊ฒน์น˜์ง€ ์•Š๋Š”์ง€", "๋ชจ๋“  VM์ด ๊ฐ™์€ ์ด๋ฆ„์ธ์ง€", "๋ชจ๋“  ๋ฆฌ์†Œ์Šค๊ฐ€ ๊ฐ™์€ ํƒœ๊ทธ์ธ์ง€", "Storage Account access tier๊ฐ€ Archive์ธ์ง€"],
"์ฃผ์†Œ ๊ณต๊ฐ„์ด ๊ฒน์น˜์ง€ ์•Š๋Š”์ง€",
"VNet ์ฃผ์†Œ ๊ณต๊ฐ„์ด ๋‹ค๋ฅธ ๋„คํŠธ์›Œํฌ์™€ ๊ฒน์น˜๋ฉด ํ”ผ์–ด๋ง์ด๋‚˜ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์—ฐ๊ฒฐ ์„ค๊ณ„์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
source_id="azure-vnet-overview",
),
LabQuiz(
"docs-q1",
"docs-streamlit-state",
"tool_docs",
"multiple_choice",
"Streamlit์—์„œ rerun ์ดํ›„์—๋„ ๊ฐ’์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ๊ฐ€์žฅ ์ ํ•ฉํ•œ ๊ฒƒ์€?",
["์ผ๋ฐ˜ ์ง€์—ญ ๋ณ€์ˆ˜", "st.session_state", "print", "requirements.txt"],
"st.session_state",
"Streamlit์€ ์ƒํ˜ธ์ž‘์šฉ ๋•Œ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋ฏ€๋กœ ์ง€์† ์ƒํƒœ๋Š” session_state์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์ด ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.",
source_id="official-docs-streamlit",
),
LabQuiz(
"docs-q3",
"docs-langchain-rag",
"tool_docs",
"multiple_choice",
"LangChain RAG์—์„œ ์งˆ๋ฌธ๊ณผ ๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ฐพ์•„ LLM์— ๋„˜๊ธฐ๋Š” ์—ญํ• ์— ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฒƒ์€?",
["Retriever", "Theme config", "Space Secret", "chmod"],
"Retriever",
"Retriever๋Š” ๋ฒกํ„ฐ ๊ฒ€์ƒ‰์ด๋‚˜ ํ‚ค์›Œ๋“œ ๊ฒ€์ƒ‰ ๋“ฑ์„ ํ†ตํ•ด ์งˆ๋ฌธ๊ณผ ๊ด€๋ จ ์žˆ๋Š” ๋ฌธ์„œ๋ฅผ ์ฐพ์•„ ์ฒด์ธ์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.",
source_id="official-docs-langchain",
),
LabQuiz(
"docs-q2",
"docs-hf-spaces",
"tool_docs",
"multiple_choice",
"Hugging Face Space์—์„œ API ํ‚ค ๊ฐ™์€ ๋น„๋ฐ€๊ฐ’์„ ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ์œ„์น˜๋Š”?",
["README ๋ณธ๋ฌธ", "Git ์ปค๋ฐ‹", "Space Secrets", "์•ฑ ํ™”๋ฉด ์บก์…˜"],
"Space Secrets",
"๋ฐฐํฌ ํ™˜๊ฒฝ์˜ ๋น„๋ฐ€๊ฐ’์€ Space Settings์˜ Secrets์— ์ €์žฅํ•˜๊ณ  ์ฝ”๋“œ์—๋Š” ํฌํ•จํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.",
source_id="official-docs-huggingface",
),
]
QUIZZES.extend(
[
LabQuiz(
"linux-q8",
"linux-chown",
"linux",
"multiple_choice",
"`chmod`์™€ ๋น„๊ตํ–ˆ์„ ๋•Œ `chown`์˜ ์ฃผ๋œ ์—ญํ• ์€?",
["ํŒŒ์ผ ๋‚ด์šฉ ์ถœ๋ ฅ", "์†Œ์œ ์ž/๊ทธ๋ฃน ๋ณ€๊ฒฝ", "ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ", "ํŒจํ‚ค์ง€ ์„ค์น˜"],
"์†Œ์œ ์ž/๊ทธ๋ฃน ๋ณ€๊ฒฝ",
"`chown`์€ owner/group์„ ๋ฐ”๊พธ๊ณ , `chmod`๋Š” ๊ถŒํ•œ ๋น„ํŠธ๋ฅผ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค.",
source_id="manual-linux-basic",
),
LabQuiz(
"linux-q9",
"linux-umask",
"linux",
"multiple_choice",
"`umask 022`์˜ ์˜๋ฏธ๋กœ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฒƒ์€?",
["๋ชจ๋“  ํŒŒ์ผ์„ 022 ๊ถŒํ•œ์œผ๋กœ ๋งŒ๋“ ๋‹ค", "๊ทธ๋ฃน/๊ธฐํƒ€ ์‚ฌ์šฉ์ž ์“ฐ๊ธฐ ๊ถŒํ•œ์„ ๊ธฐ๋ณธ ์ œ๊ฑฐํ•œ๋‹ค", "root๋งŒ ๋กœ๊ทธ์ธํ•˜๊ฒŒ ํ•œ๋‹ค", "๋””์Šคํฌ๋ฅผ ๋งˆ์šดํŠธํ•œ๋‹ค"],
"๊ทธ๋ฃน/๊ธฐํƒ€ ์‚ฌ์šฉ์ž ์“ฐ๊ธฐ ๊ถŒํ•œ์„ ๊ธฐ๋ณธ ์ œ๊ฑฐํ•œ๋‹ค",
"`umask`๋Š” ์ƒˆ ํŒŒ์ผ/๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์ œ๊ฑฐํ•  ๊ถŒํ•œ์„ ๋œปํ•ฉ๋‹ˆ๋‹ค.",
source_id="manual-linux-basic",
),
LabQuiz(
"linux-q10",
"linux-find",
"linux",
"command",
"`/var/log` ์•„๋ž˜์˜ `.log` ํŒŒ์ผ์„ ์ฐพ๋Š” find ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"find /var/log -type f -name '*.log'",
"`find ๊ฒฝ๋กœ -type f -name ํŒจํ„ด` ํ˜•ํƒœ๋กœ ํŒŒ์ผ ํƒ€์ž…๊ณผ ์ด๋ฆ„ ํŒจํ„ด์„ ํ•จ๊ป˜ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
source_id="manual-linux-basic",
),
LabQuiz(
"linux-q11",
"linux-pipe-redirect",
"linux",
"multiple_choice",
"`>>` ๋ฆฌ๋‹ค์ด๋ ‰์…˜์˜ ์˜๋ฏธ๋Š”?",
["ํŒŒ์ผ ๋ฎ์–ด์“ฐ๊ธฐ", "ํŒŒ์ผ ๋์— ์ด์–ด์“ฐ๊ธฐ", "ํ‘œ์ค€ ์—๋Ÿฌ ๋ฌด์‹œ", "๋ช…๋ น์„ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์‹คํ–‰"],
"ํŒŒ์ผ ๋์— ์ด์–ด์“ฐ๊ธฐ",
"`>`๋Š” ๋ฎ์–ด์“ฐ๊ธฐ, `>>`๋Š” ์ด์–ด์“ฐ๊ธฐ์ž…๋‹ˆ๋‹ค.",
source_id="manual-linux-basic",
),
LabQuiz(
"linux-q12",
"linux-tar",
"linux",
"command",
"`logs` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ gzip ์••์ถ• tar ํŒŒ์ผ `logs.tar.gz`๋กœ ๋งŒ๋“œ๋Š” ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"tar -czf logs.tar.gz logs",
"`-c`๋Š” ์ƒ์„ฑ, `-z`๋Š” gzip, `-f`๋Š” ํŒŒ์ผ๋ช…์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.",
source_id="manual-linux-basic",
),
LabQuiz(
"linux-q13",
"linux-systemctl",
"linux",
"multiple_choice",
"`systemctl enable nginx`์˜ ์˜๋ฏธ๋Š”?",
["์ง€๊ธˆ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰", "๋ถ€ํŒ… ์‹œ ์ž๋™ ์‹œ์ž‘ ๋“ฑ๋ก", "ํŒจํ‚ค์ง€ ์ œ๊ฑฐ", "๋กœ๊ทธ ์‚ญ์ œ"],
"๋ถ€ํŒ… ์‹œ ์ž๋™ ์‹œ์ž‘ ๋“ฑ๋ก",
"`start`๋Š” ์ฆ‰์‹œ ์‹คํ–‰, `enable`์€ ๋ถ€ํŒ… ์‹œ ์ž๋™ ์‹œ์ž‘ ์„ค์ •์ž…๋‹ˆ๋‹ค.",
source_id="linux-systemd-systemctl",
),
LabQuiz(
"linux-q14",
"linux-journalctl",
"linux",
"command",
"nginx ์„œ๋น„์Šค ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•˜๋Š” journalctl ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"journalctl -u nginx",
"`journalctl -u ์„œ๋น„์Šค๋ช…`์€ ํŠน์ • systemd unit ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.",
source_id="linux-systemd-systemctl",
),
LabQuiz(
"linux-q15",
"linux-ip-ss",
"linux",
"multiple_choice",
"ํ˜„์žฌ ์‹œ์Šคํ…œ์˜ listening ํฌํŠธ๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋ช…๋ น์€?",
["ss -tulpen", "touch port", "chmod 755", "tar -czf"],
"ss -tulpen",
"`ss -tulpen`์€ TCP/UDP listening ์†Œ์ผ“๊ณผ ํ”„๋กœ์„ธ์Šค ์ •๋ณด๋ฅผ ํ™•์ธํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.",
source_id="linux-man7-ip",
),
LabQuiz(
"linux-q16",
"linux-df-du-mount",
"linux",
"multiple_choice",
"`df -h`์™€ `du -sh /var/log`์˜ ์ฐจ์ด๋กœ ๋งž๋Š” ๊ฒƒ์€?",
["๋‘˜ ๋‹ค ํŒจํ‚ค์ง€ ์„ค์น˜ ๋ช…๋ น์ด๋‹ค", "`df`๋Š” ํŒŒ์ผ์‹œ์Šคํ…œ, `du`๋Š” ๊ฒฝ๋กœ๋ณ„ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ์— ๊ฐ€๊น๋‹ค", "`du`๋งŒ ๋„คํŠธ์›Œํฌ ์ƒํƒœ๋ฅผ ๋ณธ๋‹ค", "`df`๋Š” ์‚ฌ์šฉ์ž ์•”ํ˜ธ ๋ณ€๊ฒฝ ๋ช…๋ น์ด๋‹ค"],
"`df`๋Š” ํŒŒ์ผ์‹œ์Šคํ…œ, `du`๋Š” ๊ฒฝ๋กœ๋ณ„ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ์— ๊ฐ€๊น๋‹ค",
"`df`๋Š” ๋งˆ์šดํŠธ๋œ ํŒŒ์ผ์‹œ์Šคํ…œ ๋‹จ์œ„, `du`๋Š” ํŠน์ • ํŒŒ์ผ/๋””๋ ‰ํ„ฐ๋ฆฌ ์šฉ๋Ÿ‰ ํ™•์ธ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.",
source_id="ubuntu-storage",
),
LabQuiz(
"azure-q4",
"azure-rbac",
"azure",
"multiple_choice",
"Azure RBAC์—์„œ ๊ถŒํ•œ์ด ์ ์šฉ๋˜๋Š” ๋ฒ”์œ„๋ฅผ ๋œปํ•˜๋Š” ๋ง์€?",
["Scope", "Access tier", "Subnet mask", "Archive"],
"Scope",
"RBAC role assignment๋Š” management group, subscription, resource group, resource ๊ฐ™์€ scope์— ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.",
source_id="azure-rbac-overview",
),
LabQuiz(
"azure-q5",
"azure-policy",
"azure",
"multiple_choice",
"Azure Policy์˜ ์ฃผ๋œ ๋ชฉ์ ์€?",
["VM CPU๋ฅผ ์ฆ‰์‹œ ๋Š˜๋ฆฐ๋‹ค", "๋ฆฌ์†Œ์Šค ์ƒํƒœ๋ฅผ ์กฐ์ง ๊ทœ์น™์— ๋งž๊ฒŒ ํ‰๊ฐ€/์ œ์–ดํ•œ๋‹ค", "Blob์„ ๋‹ค์šด๋กœ๋“œํ•œ๋‹ค", "DNS ๋ ˆ์ฝ”๋“œ๋งŒ ์ƒ์„ฑํ•œ๋‹ค"],
"๋ฆฌ์†Œ์Šค ์ƒํƒœ๋ฅผ ์กฐ์ง ๊ทœ์น™์— ๋งž๊ฒŒ ํ‰๊ฐ€/์ œ์–ดํ•œ๋‹ค",
"Azure Policy๋Š” deny/audit ๊ฐ™์€ ํšจ๊ณผ๋กœ ๋ฆฌ์†Œ์Šค ๊ทœ์ • ์ค€์ˆ˜๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.",
source_id="azure-policy-overview",
),
LabQuiz(
"azure-q6",
"azure-vm",
"azure",
"multiple_choice",
"Azure VM์„ ์ดํ•ดํ•  ๋•Œ ํ•จ๊ป˜ ๋ด์•ผ ํ•  ๋ฆฌ์†Œ์Šค๋กœ ๊ฐ€์žฅ ์ž์—ฐ์Šค๋Ÿฌ์šด ์กฐํ•ฉ์€?",
["Disk, NIC, VNet, NSG", "Only DNS TXT record", "Only Policy initiative", "Only Blob tier"],
"Disk, NIC, VNet, NSG",
"VM์€ compute๋ฟ ์•„๋‹ˆ๋ผ ๋””์Šคํฌ์™€ ๋„คํŠธ์›Œํฌ ๋ฆฌ์†Œ์Šค๊ฐ€ ํ•จ๊ป˜ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.",
source_id="azure-vm-overview",
),
LabQuiz(
"azure-q7",
"azure-nsg",
"azure",
"multiple_choice",
"NSG ๊ทœ์น™ ํ‰๊ฐ€์—์„œ ๋จผ์ € ์ ์šฉ๋˜๋Š” ๊ทœ์น™์€?",
["์šฐ์„ ์ˆœ์œ„ ์ˆซ์ž๊ฐ€ ๋‚ฎ์€ ๊ทœ์น™", "์ด๋ฆ„์ด ๊ธด ๊ทœ์น™", "๋‚˜์ค‘์— ๋งŒ๋“  ๊ทœ์น™", "ํ•ญ์ƒ Deny ๊ทœ์น™"],
"์šฐ์„ ์ˆœ์œ„ ์ˆซ์ž๊ฐ€ ๋‚ฎ์€ ๊ทœ์น™",
"NSG๋Š” priority ์ˆซ์ž๊ฐ€ ๋‚ฎ์„์ˆ˜๋ก ๋จผ์ € ํ‰๊ฐ€๋ฉ๋‹ˆ๋‹ค.",
source_id="azure-nsg-overview",
),
LabQuiz(
"azure-q8",
"azure-app-gateway",
"azure",
"multiple_choice",
"Application Gateway๊ฐ€ Load Balancer์™€ ๋น„๊ตํ•ด ๋” ์ž˜ ๋‹ค๋ฃจ๋Š” ์˜์—ญ์€?",
["HTTP path ๊ธฐ๋ฐ˜ L7 ๋ผ์šฐํŒ…", "ํŒŒ์ผ ๊ถŒํ•œ ๋ณ€๊ฒฝ", "ํŒจํ‚ค์ง€ ๋ชฉ๋ก ๊ฐฑ์‹ ", "ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ"],
"HTTP path ๊ธฐ๋ฐ˜ L7 ๋ผ์šฐํŒ…",
"Application Gateway๋Š” HTTP/HTTPS L7 ๋ผ์šฐํŒ…๊ณผ WAF ๊ฐ™์€ ์›น ๊ณ„์ธต ๊ธฐ๋Šฅ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.",
source_id="azure-app-gateway-overview",
),
LabQuiz(
"azure-q9",
"azure-blob-tier",
"azure",
"multiple_choice",
"Archive tier์˜ ํŠน์ง•์œผ๋กœ ๊ฐ€์žฅ ๋งž๋Š” ๊ฒƒ์€?",
["ํ•ญ์ƒ ์ฆ‰์‹œ ์ฝ๊ธฐ ๊ฐ€๋Šฅ", "์˜คํ”„๋ผ์ธ ๊ณ„์ธต์ด๋ผ ์ฝ๊ธฐ ์ „์— rehydrate๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ", "VM ํฌ๊ธฐ ์„ค์ •", "RBAC ์—ญํ•  ์ด๋ฆ„"],
"์˜คํ”„๋ผ์ธ ๊ณ„์ธต์ด๋ผ ์ฝ๊ธฐ ์ „์— rehydrate๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Œ",
"Archive๋Š” ์žฅ๊ธฐ ๋ณด๊ด€ ๋น„์šฉ์„ ๋‚ฎ์ถ”๋Š” ๋Œ€์‹  ์ฆ‰์‹œ ์ ‘๊ทผ์„ฑ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
source_id="azure-blob-access-tiers",
),
LabQuiz(
"azure-q10",
"azure-log-analytics",
"azure",
"multiple_choice",
"Log Analytics์—์„œ ๋กœ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ฟผ๋ฆฌ ์–ธ์–ด๋Š”?",
["KQL", "Bash", "YAML only", "Markdown"],
"KQL",
"Log Analytics๋Š” KQL๋กœ ๋กœ๊ทธ๋ฅผ ํ•„ํ„ฐ๋งํ•˜๊ณ  ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.",
source_id="azure-log-analytics-overview",
),
LabQuiz(
"docs-q4",
"docs-lcel",
"tool_docs",
"multiple_choice",
"LCEL์—์„œ `prompt | llm | parser`์ฒ˜๋Ÿผ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์˜ ์žฅ์ ์€?",
["์ฒด์ธ ํ๋ฆ„์„ ๋ช…์‹œ์ ์œผ๋กœ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค", "์„œ๋ฒ„ ๋ฐฉํ™”๋ฒฝ์„ ์—ฐ๋‹ค", "ํŒŒ์ผ ๊ถŒํ•œ์„ ๋ฐ”๊พผ๋‹ค", "Blob tier๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค"],
"์ฒด์ธ ํ๋ฆ„์„ ๋ช…์‹œ์ ์œผ๋กœ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค",
"LCEL์€ Runnable์„ ํŒŒ์ดํ”„์ฒ˜๋Ÿผ ์—ฐ๊ฒฐํ•ด ์ž…๋ ฅ/์ถœ๋ ฅ ํ๋ฆ„์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.",
source_id="official-docs-langchain",
),
LabQuiz(
"docs-q5",
"docs-vectorstore",
"tool_docs",
"multiple_choice",
"VectorStore๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ์„ ๋ฐ”๊พธ๋ฉด ์ฃผ์˜ํ•  ์ ์€?",
["๊ธฐ์กด ๋ฒกํ„ฐ์™€ ์ฐจ์›์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค", "ํ•ญ์ƒ ๊ฐ™์€ ์ปฌ๋ ‰์…˜์— ์„ž์–ด์•ผ ํ•œ๋‹ค", "DB ์•”ํ˜ธ๊ฐ€ ์ž๋™ ๊ณต๊ฐœ๋œ๋‹ค", "UI ๋ฒ„ํŠผ์ด ์‚ฌ๋ผ์ง„๋‹ค"],
"๊ธฐ์กด ๋ฒกํ„ฐ์™€ ์ฐจ์›์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค",
"์ž„๋ฒ ๋”ฉ ๋ชจ๋ธ๋ณ„๋กœ ์ปฌ๋ ‰์…˜์„ ๋ถ„๋ฆฌํ•˜๋ฉด ์ฐจ์› ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
source_id="official-docs-langchain",
),
LabQuiz(
"docs-q6",
"docs-streamlit-cache",
"tool_docs",
"multiple_choice",
"Streamlit์—์„œ ๋ชจ๋ธ/DB ์—ฐ๊ฒฐ ๊ฐ™์€ ๋ฆฌ์†Œ์Šค ์ดˆ๊ธฐํ™”์— ๋” ์ ํ•ฉํ•œ ์บ์‹œ๋Š”?",
["st.cache_resource", "st.write", "st.button", "st.caption"],
"st.cache_resource",
"`st.cache_resource`๋Š” ๋ชจ๋ธ, DB connection ๊ฐ™์€ ๊ณต์œ  ๋ฆฌ์†Œ์Šค ์บ์‹œ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.",
source_id="official-docs-streamlit",
),
LabQuiz(
"docs-q7",
"docs-ollama-api",
"tool_docs",
"multiple_choice",
"Ollama API๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ํ•„์š”ํ•œ ์กฐ๊ฑด์œผ๋กœ ๊ฐ€์žฅ ๋งž๋Š” ๊ฒƒ์€?",
["Ollama ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘์ด๊ณ  ๋ชจ๋ธ์ด ์ค€๋น„๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค", "ufw๊ฐ€ ๋ฐ˜๋“œ์‹œ ๊บผ์ ธ ์žˆ์–ด์•ผ ํ•œ๋‹ค", "Azure VMSS๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค", "README์— API ํ‚ค๋ฅผ ์ ์–ด์•ผ ํ•œ๋‹ค"],
"Ollama ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰ ์ค‘์ด๊ณ  ๋ชจ๋ธ์ด ์ค€๋น„๋˜์–ด ์žˆ์–ด์•ผ ํ•œ๋‹ค",
"๋กœ์ปฌ Ollama ์„œ๋ฒ„์™€ ๋ชจ๋ธ์ด ์ค€๋น„๋˜์ง€ ์•Š์œผ๋ฉด generate/chat API ํ˜ธ์ถœ์ด ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.",
source_id="official-docs-ollama",
),
]
)
PRACTICE_TASKS = [
PracticeTask(
"linux-lab-1",
"linux",
"ํ˜„์žฌ ์œ„์น˜ ํ™•์ธ",
"ํ˜„์žฌ ์ž‘์—… ์ค‘์ธ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.",
"pwd",
["pwd"],
"ํ˜„์žฌ ์œ„์น˜๋Š” working directory๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.",
"`pwd`๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํ˜„์žฌ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-2",
"linux",
"๋””๋ ‰ํ„ฐ๋ฆฌ ์ƒ์„ฑ",
"`practice` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.",
"mkdir practice",
["mkdir", "practice"],
"๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“ค ๋•Œ๋Š” mkdir์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.",
"`mkdir practice`๋Š” practice๋ผ๋Š” ์ƒˆ ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-3",
"linux",
"ํŒŒ์ผ ์ƒ์„ฑ",
"`hello.txt` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”.",
"touch hello.txt",
["touch", "hello.txt"],
"๋นˆ ํŒŒ์ผ์„ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ค ๋•Œ touch๋ฅผ ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
"`touch hello.txt`๋Š” hello.txt ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-4",
"linux",
"ํŒŒ์ผ ๋‚ด์šฉ ์ถœ๋ ฅ",
"`hello.txt` ๋‚ด์šฉ์„ ์ถœ๋ ฅํ•˜์„ธ์š”.",
"cat hello.txt",
["cat", "hello.txt"],
"ํŒŒ์ผ ๋‚ด์šฉ์„ ํ‘œ์ค€ ์ถœ๋ ฅ์œผ๋กœ ๋ณผ ๋•Œ cat์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.",
"`cat hello.txt`๋Š” ํŒŒ์ผ ๋‚ด์šฉ์„ ํ™”๋ฉด์— ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-5",
"linux",
"์ž์„ธํ•œ ๋ชฉ๋ก ํ™•์ธ",
"ํ˜„์žฌ ๋””๋ ‰ํ„ฐ๋ฆฌ์˜ ํŒŒ์ผ ๋ชฉ๋ก์„ ์ž์„ธํžˆ ํ™•์ธํ•˜์„ธ์š”.",
"ls -al",
["ls", "-"],
"`-a`์™€ `-l`์„ ํ•จ๊ป˜ ์“ฐ๋ฉด ์ˆจ๊น€ ํŒŒ์ผ๊ณผ ์ž์„ธํ•œ ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
"`ls -al` ๋˜๋Š” `ls -la`๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
),
]
PRACTICE_TASKS.extend(
[
PracticeTask(
"linux-lab-6",
"linux",
"์†Œ์œ ์ž ๋ณ€๊ฒฝ",
"`app.log` ํŒŒ์ผ์˜ ์†Œ์œ ์ž๋ฅผ `app` ์‚ฌ์šฉ์ž์™€ `app` ๊ทธ๋ฃน์œผ๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”.",
"sudo chown app:app app.log",
["chown", "app:app", "app.log"],
"์†Œ์œ ์ž์™€ ๊ทธ๋ฃน์€ `user:group` ํ˜•ํƒœ๋กœ ํ•จ๊ป˜ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
"`sudo chown app:app app.log`๋Š” app.log์˜ ์†Œ์œ ์ž์™€ ๊ทธ๋ฃน์„ app์œผ๋กœ ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-7",
"linux",
"๋กœ๊ทธ ํŒŒ์ผ ๊ฒ€์ƒ‰",
"`/var/log` ์•„๋ž˜์—์„œ `.log` ํŒŒ์ผ์„ ๊ฒ€์ƒ‰ํ•˜์„ธ์š”.",
"find /var/log -type f -name '*.log'",
["find", "/var/log", "-type", "f", "-name"],
"ํŒŒ์ผ ์กฐ๊ฑด ๊ฒ€์ƒ‰์€ find๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.",
"`find /var/log -type f -name '*.log'`๋Š” /var/log ์•„๋ž˜์˜ .log ํŒŒ์ผ์„ ์ฐพ์Šต๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-8",
"linux",
"์ถœ๋ ฅ ์ด์–ด์“ฐ๊ธฐ",
"`hello` ๋ฌธ์ž์—ด์„ `note.txt` ํŒŒ์ผ ๋์— ์ด์–ด ์“ฐ์„ธ์š”.",
"echo hello >> note.txt",
["echo", "hello", ">>", "note.txt"],
"`>>`๋Š” ํŒŒ์ผ ๋์— ์ด์–ด ์”๋‹ˆ๋‹ค.",
"`echo hello >> note.txt`๋Š” note.txt ๋’ค์— hello ํ•œ ์ค„์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-9",
"linux",
"tar ์•„์นด์ด๋ธŒ ์ƒ์„ฑ",
"`logs` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ `logs.tar.gz` ํŒŒ์ผ๋กœ ์••์ถ•ํ•˜์„ธ์š”.",
"tar -czf logs.tar.gz logs",
["tar", "-czf", "logs.tar.gz", "logs"],
"`-c` ์ƒ์„ฑ, `-z` gzip, `-f` ํŒŒ์ผ๋ช… ์ง€์ •์ž…๋‹ˆ๋‹ค.",
"`tar -czf logs.tar.gz logs`๋Š” logs ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ gzip ์••์ถ• tar ํŒŒ์ผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-10",
"linux",
"์„œ๋น„์Šค ์ƒํƒœ ํ™•์ธ",
"`nginx` ์„œ๋น„์Šค ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.",
"systemctl status nginx",
["systemctl", "status", "nginx"],
"์„œ๋น„์Šค ์ƒํƒœ๋Š” systemctl status๋กœ ๋จผ์ € ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.",
"`systemctl status nginx`๋Š” nginx ์„œ๋น„์Šค์˜ ์‹คํ–‰ ์ƒํƒœ์™€ ์ตœ๊ทผ ๋กœ๊ทธ ํžŒํŠธ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-11",
"linux",
"์„œ๋น„์Šค ๋กœ๊ทธ ํ™•์ธ",
"`nginx` ์„œ๋น„์Šค ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.",
"journalctl -u nginx",
["journalctl", "-u", "nginx"],
"ํŠน์ • unit ๋กœ๊ทธ๋Š” `journalctl -u`๋กœ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.",
"`journalctl -u nginx`๋Š” nginx systemd unit ๋กœ๊ทธ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-12",
"linux",
"ํŒจํ‚ค์ง€ ๋ชฉ๋ก ๊ฐฑ์‹ ",
"Ubuntu ํŒจํ‚ค์ง€ ๋ชฉ๋ก์„ ๊ฐฑ์‹ ํ•˜์„ธ์š”.",
"sudo apt update",
["apt", "update"],
"`update`์™€ `upgrade`๋ฅผ ๊ตฌ๋ถ„ํ•˜์„ธ์š”.",
"`sudo apt update`๋Š” ์ €์žฅ์†Œ ํŒจํ‚ค์ง€ ๋ชฉ๋ก์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.",
),
PracticeTask(
"linux-lab-13",
"linux",
"SSH ํฌํŠธ ํ—ˆ์šฉ",
"ufw์—์„œ SSH 22๋ฒˆ ํฌํŠธ๋ฅผ ํ—ˆ์šฉํ•˜์„ธ์š”.",
"sudo ufw allow 22",
["ufw", "allow", "22"],
"์›๊ฒฉ ์„œ๋ฒ„์—์„œ๋Š” ๋ฐฉํ™”๋ฒฝ ํ™œ์„ฑํ™” ์ „์— SSH ํ—ˆ์šฉ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜์„ธ์š”.",
"`sudo ufw allow 22`๋Š” 22๋ฒˆ ํฌํŠธ๋ฅผ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค.",
),
]
)
QUIZZES.extend(
[
LabQuiz(
"linux-q17",
"linux-useradd",
"linux",
"command",
"`devuser` ์‚ฌ์šฉ์ž๋ฅผ ํ™ˆ ๋””๋ ‰ํ„ฐ๋ฆฌ์™€ ํ•จ๊ป˜ ์ƒ์„ฑํ•˜๋Š” ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.",
[],
"sudo useradd -m devuser",
"`useradd -m`์€ ํ™ˆ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ƒ์„ฑ์„ ํ•จ๊ป˜ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.",
source_id="ubuntu-user-management",
),
LabQuiz("linux-q18", "linux-usermod", "linux", "command", "`devuser`๋ฅผ sudo ๊ทธ๋ฃน์— ์ถ”๊ฐ€ํ•˜๋Š” ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", [], "sudo usermod -aG sudo devuser", "`-aG`๋Š” ๊ธฐ์กด ๋ณด์กฐ ๊ทธ๋ฃน์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ƒˆ ๊ทธ๋ฃน์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.", source_id="ubuntu-user-management"),
LabQuiz("linux-q19", "linux-sudoers", "linux", "multiple_choice", "sudoers ํŒŒ์ผ์„ ์•ˆ์ „ํ•˜๊ฒŒ ํŽธ์ง‘ํ•˜๋Š” ๋ช…๋ น์€?", ["visudo", "cat", "touch", "tar"], "visudo", "`visudo`๋Š” sudoers ๋ฌธ๋ฒ• ๊ฒ€์‚ฌ๋ฅผ ๋„์™€ sudo ์„ค์ • ์˜ค๋ฅ˜๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค.", source_id="ubuntu-user-management"),
LabQuiz("linux-q20", "linux-special-perms", "linux", "multiple_choice", "๊ณต์šฉ ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ํƒ€์ธ ํŒŒ์ผ ์‚ญ์ œ๋ฅผ ์ œํ•œํ•˜๋Š” ํŠน์ˆ˜ ๊ถŒํ•œ์€?", ["sticky bit", "setuid", "umask", "swap"], "sticky bit", "sticky bit๋Š” /tmp ๊ฐ™์€ ๊ณต์šฉ ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์ž์ฃผ ์“ฐ์ž…๋‹ˆ๋‹ค.", source_id="manual-linux-basic"),
LabQuiz("linux-q21", "linux-ssh-key", "linux", "multiple_choice", "SSH ํ‚ค ์ธ์ฆ์—์„œ ์„œ๋ฒ„์˜ authorized_keys์— ๋“ค์–ด๊ฐ€๋Š” ๊ฒƒ์€?", ["๊ณต๊ฐœํ‚ค", "๊ฐœ์ธํ‚ค", "root ์•”ํ˜ธ", "swap ํŒŒ์ผ"], "๊ณต๊ฐœํ‚ค", "๊ฐœ์ธํ‚ค๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๊ด€ํ•˜๊ณ  ๊ณต๊ฐœํ‚ค๋ฅผ ์„œ๋ฒ„์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.", source_id="ubuntu-server-docs"),
LabQuiz("linux-q22", "linux-dns-resolve", "linux", "multiple_choice", "IP ํ†ต์‹ ์€ ๋˜๋Š”๋ฐ ๋„๋ฉ”์ธ ์ ‘์†๋งŒ ์‹คํŒจํ•  ๋•Œ ๋จผ์ € ์˜์‹ฌํ•  ์˜์—ญ์€?", ["DNS", "chmod", "tar", "umask"], "DNS", "๋„๋ฉ”์ธ ์ด๋ฆ„ ํ•ด์„ ์‹คํŒจ๋Š” DNS ์„ค์ • ๋ฌธ์ œ์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", source_id="ubuntu-networking"),
LabQuiz("linux-q23", "linux-route", "linux", "command", "๊ธฐ๋ณธ ๋ผ์šฐํŒ… ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋Š” ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.", [], "ip route", "`ip route`๋Š” ๊ธฐ๋ณธ ๊ฒŒ์ดํŠธ์›จ์ด์™€ ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ”์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.", source_id="linux-man7-ip"),
LabQuiz("linux-q24", "linux-cron", "linux", "multiple_choice", "์‚ฌ์šฉ์ž๋ณ„ ๋ฐ˜๋ณต ์ž‘์—…์„ ํŽธ์ง‘ํ•˜๋Š” ๋ช…๋ น์€?", ["crontab -e", "journalctl -u", "lsblk", "swapon"], "crontab -e", "`crontab -e`๋Š” ์‚ฌ์šฉ์ž cron ์ž‘์—…์„ ํŽธ์ง‘ํ•ฉ๋‹ˆ๋‹ค.", source_id="ubuntu-server-docs"),
LabQuiz("linux-q25", "linux-lsblk", "linux", "multiple_choice", "๋ธ”๋ก ์žฅ์น˜์™€ ํŒŒํ‹ฐ์…˜ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•˜๋Š” ๋ช…๋ น์€?", ["lsblk", "grep", "passwd", "curl"], "lsblk", "`lsblk`๋Š” ๋””์Šคํฌ/ํŒŒํ‹ฐ์…˜/๋งˆ์šดํŠธ ๊ด€๊ณ„๋ฅผ ๋ณด๊ธฐ ์ข‹์Šต๋‹ˆ๋‹ค.", source_id="ubuntu-storage"),
LabQuiz("linux-q26", "linux-fstab", "linux", "multiple_choice", "๋ถ€ํŒ… ์‹œ ์ž๋™ ๋งˆ์šดํŠธ ์„ค์ •์„ ๋‹ด๋Š” ํŒŒ์ผ์€?", ["/etc/fstab", "/etc/hosts", "/etc/passwd", "/var/log/auth.log"], "/etc/fstab", "`/etc/fstab`์€ ํŒŒ์ผ์‹œ์Šคํ…œ ์ž๋™ ๋งˆ์šดํŠธ ์„ค์ •์„ ๋‹ด์Šต๋‹ˆ๋‹ค.", source_id="ubuntu-storage"),
LabQuiz("linux-q27", "linux-exit-code", "linux", "multiple_choice", "์ง์ „ ๋ช…๋ น์˜ exit code๋ฅผ ํ™•์ธํ•˜๋Š” ํ‘œํ˜„์€?", ["echo $?", "echo $PATH", "pwd", "id"], "echo $?", "`$?`๋Š” ์ง์ „ ๋ช…๋ น์˜ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ๋‹ด์Šต๋‹ˆ๋‹ค.", source_id="linux-bash-cd"),
LabQuiz("linux-q28", "linux-remove-purge", "linux", "multiple_choice", "`apt purge`๊ฐ€ `apt remove`์™€ ๋น„๊ตํ•ด ๋” ์ •๋ฆฌํ•˜๋Š” ๊ฒƒ์€?", ["์„ค์ • ํŒŒ์ผ", "CPU core", "VNet", "DNS zone"], "์„ค์ • ํŒŒ์ผ", "`purge`๋Š” ํŒจํ‚ค์ง€ ์„ค์ • ํŒŒ์ผ๊นŒ์ง€ ์ œ๊ฑฐํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.", source_id="ubuntu-package-management"),
LabQuiz("azure-q11", "azure-entra-id", "azure", "multiple_choice", "์ธ์ฆ๊ณผ ์‚ฌ์šฉ์ž/๊ทธ๋ฃน ID ๊ด€๋ฆฌ์˜ ์ค‘์‹ฌ ์„œ๋น„์Šค๋Š”?", ["Microsoft Entra ID", "Azure Blob Tier", "Load Balancer Probe", "Recovery Point"], "Microsoft Entra ID", "Entra ID๋Š” ID์™€ ์ธ์ฆ์˜ ์ค‘์‹ฌ์ด๊ณ  RBAC๋Š” Azure ๋ฆฌ์†Œ์Šค ๊ถŒํ•œ ์ œ์–ด์— ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค.", source_id="manual-az104-basic"),
LabQuiz("azure-q12", "azure-management-group", "azure", "multiple_choice", "์—ฌ๋Ÿฌ subscription์— ์ •์ฑ…์„ ๊ณ„์ธต์ ์œผ๋กœ ์ ์šฉํ•˜๊ธฐ ์ข‹์€ ๋ฒ”์œ„๋Š”?", ["Management Group", "NIC", "Blob", "VM disk"], "Management Group", "Management Group์€ subscription์„ ๋ฌถ์–ด ๊ฑฐ๋ฒ„๋„Œ์Šค๋ฅผ ์ ์šฉํ•˜๋Š” ์ƒ์œ„ ๋ฒ”์œ„์ž…๋‹ˆ๋‹ค.", source_id="manual-az104-basic"),
LabQuiz("azure-q13", "azure-resource-lock", "azure", "multiple_choice", "์ค‘์š” ๋ฆฌ์†Œ์Šค์˜ ์‹ค์ˆ˜ ์‚ญ์ œ๋ฅผ ๋ง‰๋Š” ๋ฐ ์ ํ•ฉํ•œ ๊ธฐ๋Šฅ์€?", ["Resource Lock", "Access tier", "NAT rule", "KQL"], "Resource Lock", "CanNotDelete lock์€ ์‚ญ์ œ ๋ฐฉ์ง€์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.", source_id="manual-az104-basic"),
LabQuiz("azure-q14", "azure-sas", "azure", "multiple_choice", "Storage์— ์ œํ•œ๋œ ๊ธฐ๊ฐ„/๊ถŒํ•œ์œผ๋กœ ์ ‘๊ทผํ•˜๊ฒŒ ํ•˜๋Š” URL ํ† ํฐ์€?", ["SAS", "NSG", "VMSS", "KQL"], "SAS", "Shared Access Signature๋Š” Storage ์ ‘๊ทผ ๊ถŒํ•œ์„ ์ œํ•œ์ ์œผ๋กœ ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-storage-account"),
LabQuiz("azure-q15", "azure-storage-redundancy", "azure", "multiple_choice", "์ง€์—ญ ๊ฐ„ ๋ณต์ œ๋ฅผ ๊ณ ๋ คํ•˜๋Š” Storage redundancy ์˜ต์…˜์œผ๋กœ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฒƒ์€?", ["GRS", "LRS only", "NSG", "UFW"], "GRS", "GRS๋Š” geo-redundant storage๋กœ ์ง€์—ญ ๊ฐ„ ๋ณต์ œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-storage-account"),
LabQuiz("azure-q16", "azure-availability-set", "azure", "multiple_choice", "Availability Set์—์„œ VM ์žฅ์•  ๋ถ„์‚ฐ์— ์“ฐ์ด๋Š” ๊ฐœ๋…์€?", ["Fault domain", "Blob container", "SAS token", "Private DNS record"], "Fault domain", "Fault domain/update domain์€ Availability Set์˜ ํ•ต์‹ฌ ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.", source_id="azure-vm-overview"),
LabQuiz("azure-q17", "azure-vnet-peering", "azure", "multiple_choice", "VNet Peering์ด ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋Š” ๋Œ€ํ‘œ ์›์ธ์€?", ["์ฃผ์†Œ ๊ณต๊ฐ„ ๊ฒน์นจ", "ํŒŒ์ผ ๊ถŒํ•œ 755", "cron ๋ฏธ์„ค์ •", "Archive tier ์‚ฌ์šฉ"], "์ฃผ์†Œ ๊ณต๊ฐ„ ๊ฒน์นจ", "VNet ์ฃผ์†Œ ๊ณต๊ฐ„์ด ๊ฒน์น˜๋ฉด peeringํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", source_id="azure-vnet-overview"),
LabQuiz("azure-q18", "azure-load-balancer-probe", "azure", "multiple_choice", "Backend VM์ด ํŠธ๋ž˜ํ”ฝ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” Load Balancer ๊ตฌ์„ฑ์€?", ["Health probe", "Tag", "SAS", "Key Vault secret"], "Health probe", "Probe๊ฐ€ ์‹คํŒจํ•˜๋ฉด backend pool VM์€ ํŠธ๋ž˜ํ”ฝ ๋Œ€์ƒ์—์„œ ์ œ์™ธ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", source_id="azure-load-balancer"),
LabQuiz("azure-q19", "azure-private-dns", "azure", "multiple_choice", "Private Endpoint ์ ‘์† ๋ฌธ์ œ์—์„œ ํ•จ๊ป˜ ํ™•์ธํ•ด์•ผ ํ•  ๋Œ€ํ‘œ ์˜์—ญ์€?", ["Private DNS", "umask", "tar", "cron"], "Private DNS", "Private Endpoint๋Š” DNS๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ private IP๋กœ ํ•ด์„๋˜๋Š”์ง€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-dns-overview"),
LabQuiz("azure-q20", "azure-alert", "azure", "multiple_choice", "Azure Monitor Alert์—์„œ ์‹ค์ œ ์•Œ๋ฆผ ๋Œ€์ƒ์„ ์ •์˜ํ•˜๋Š” ๊ตฌ์„ฑ์€?", ["Action Group", "Availability Set", "Storage tier", "Subnet mask"], "Action Group", "Alert rule์€ ์กฐ๊ฑด, action group์€ ์•Œ๋ฆผ/์กฐ์น˜ ๋Œ€์ƒ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-monitor-overview"),
LabQuiz("azure-q21", "azure-backup-policy", "azure", "multiple_choice", "๋ฐฑ์—… ๋นˆ๋„์™€ ๋ณด์กด ๊ธฐ๊ฐ„์„ ์ •์˜ํ•˜๋Š” ๊ฒƒ์€?", ["Backup Policy", "NSG Rule", "VNet Peering", "Access Tier"], "Backup Policy", "Backup Policy๋Š” ๋ฐฑ์—… ์ผ์ •๊ณผ retention์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-recovery-services-vault"),
LabQuiz("azure-q22", "azure-key-vault", "azure", "multiple_choice", "๋น„๋ฐ€๊ฐ’๊ณผ ์ธ์ฆ์„œ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•˜๋Š” Azure ์„œ๋น„์Šค๋Š”?", ["Key Vault", "Load Balancer", "VMSS", "Activity Log"], "Key Vault", "Key Vault๋Š” secret/key/certificate ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค.", source_id="manual-az104-basic"),
LabQuiz("azure-q23", "azure-bastion", "azure", "multiple_choice", "๊ณต์ธ IP ์—†์ด VM์— ์•ˆ์ „ํ•˜๊ฒŒ RDP/SSH ์ ‘์†ํ•  ๋•Œ ์ ํ•ฉํ•œ ์„œ๋น„์Šค๋Š”?", ["Azure Bastion", "Blob lifecycle", "UFW", "Cron"], "Azure Bastion", "Azure Bastion์€ VM public IP ์—†์ด ํฌํ„ธ/๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ์ ‘์†์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-bastion-overview"),
LabQuiz("azure-q24", "azure-nat-gateway", "azure", "multiple_choice", "Private subnet์˜ ์•„์›ƒ๋ฐ”์šด๋“œ ์ธํ„ฐ๋„ท ์—ฐ๊ฒฐ์„ ๊ณ ์ • ๊ณต์ธ IP๋กœ ์ œ๊ณตํ•˜๋Š” ์„œ๋น„์Šค๋Š”?", ["NAT Gateway", "Private DNS", "Recovery Vault", "Policy Initiative"], "NAT Gateway", "NAT Gateway๋Š” subnet outbound ์—ฐ๊ฒฐ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-nat-gateway"),
LabQuiz("azure-q25", "azure-deployment-slot", "azure", "multiple_choice", "App Service์—์„œ staging ๊ฒ€์ฆ ํ›„ production์œผ๋กœ ์ „ํ™˜ํ•˜๋Š” ๊ธฐ๋Šฅ์€?", ["Deployment slot swap", "VNet peering", "SAS rotation", "Fault domain"], "Deployment slot swap", "Deployment Slot์€ App Service ๋ฐฐํฌ ์•ˆ์ •ํ™”์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.", source_id="azure-app-service-overview"),
LabQuiz("azure-q26", "azure-managed-identity", "azure", "multiple_choice", "์ฝ”๋“œ์— ๋น„๋ฐ€๊ฐ’ ์—†์ด Azure ๋ฆฌ์†Œ์Šค๊ฐ€ Key Vault์— ์ ‘๊ทผํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹์€?", ["Managed Identity", "Access tier", "Sticky bit", "Archive restore"], "Managed Identity", "Managed Identity๋Š” Azure ๋ฆฌ์†Œ์Šค์— Entra ID ๊ธฐ๋ฐ˜ ID๋ฅผ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.", source_id="azure-key-vault-overview"),
LabQuiz("azure-q27", "azure-template-deployment", "azure", "multiple_choice", "Bicep/ARM ๋ฐฐํฌ์—์„œ resource group/subscription ๊ฐ™์€ ๋ฐฐํฌ ๋ฒ”์œ„๋ฅผ ๋œปํ•˜๋Š” ๊ฐœ๋…์€?", ["target scope", "health probe", "authorized_keys", "umask"], "target scope", "๋ฐฐํฌ scope์— ๋”ฐ๋ผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜์™€ ๋ฆฌ์†Œ์Šค ๋ฒ”์œ„๊ฐ€ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", source_id="manual-az104-basic"),
]
)
PRACTICE_TASKS.extend(
[
PracticeTask("linux-lab-14", "linux", "์‚ฌ์šฉ์ž ์ƒ์„ฑ", "`devuser` ์‚ฌ์šฉ์ž๋ฅผ ํ™ˆ ๋””๋ ‰ํ„ฐ๋ฆฌ์™€ ํ•จ๊ป˜ ์ƒ์„ฑํ•˜์„ธ์š”.", "sudo useradd -m devuser", ["useradd", "-m", "devuser"], "ํ™ˆ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ƒ์„ฑ ์˜ต์…˜์„ ํ•จ๊ป˜ ์จ์•ผ ํ•ฉ๋‹ˆ๋‹ค.", "`sudo useradd -m devuser`๊ฐ€ ๋Œ€ํ‘œ ๋ช…๋ น์ž…๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-15", "linux", "sudo ๊ทธ๋ฃน ์ถ”๊ฐ€", "`devuser`๋ฅผ sudo ๊ทธ๋ฃน์— ์ถ”๊ฐ€ํ•˜์„ธ์š”.", "sudo usermod -aG sudo devuser", ["usermod", "-aG", "sudo", "devuser"], "`-aG`๋ฅผ ํ•จ๊ป˜ ์จ ๊ธฐ์กด ๊ทธ๋ฃน์„ ์œ ์ง€ํ•˜์„ธ์š”.", "`sudo usermod -aG sudo devuser`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-16", "linux", "๊ทธ๋ฃน ํ™•์ธ", "`devuser`์˜ UID/GID์™€ ๊ทธ๋ฃน์„ ํ™•์ธํ•˜์„ธ์š”.", "id devuser", ["id", "devuser"], "`id`๋Š” UID, GID, groups๋ฅผ ํ•จ๊ป˜ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.", "`id devuser`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-17", "linux", "๋ผ์šฐํŒ… ํ™•์ธ", "ํ˜„์žฌ ๋ผ์šฐํŒ… ํ…Œ์ด๋ธ”์„ ํ™•์ธํ•˜์„ธ์š”.", "ip route", ["ip", "route"], "์™ธ๋ถ€ ํ†ต์‹  ์žฅ์•  ๋•Œ default route๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", "`ip route`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-18", "linux", "ํฌํŠธ ํ™•์ธ", "ํ˜„์žฌ listening ํฌํŠธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", "ss -tulpen", ["ss", "-"], "listening TCP/UDP ์†Œ์ผ“์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.", "`ss -tulpen`์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-19", "linux", "cron ํŽธ์ง‘", "ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ cron ์ž‘์—…์„ ํŽธ์ง‘ํ•˜์„ธ์š”.", "crontab -e", ["crontab", "-e"], "์‚ฌ์šฉ์ž๋ณ„ ๋ฐ˜๋ณต ์ž‘์—…์€ crontab์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.", "`crontab -e`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-20", "linux", "๋””์Šคํฌ ๊ตฌ์กฐ ํ™•์ธ", "๋ธ”๋ก ์žฅ์น˜์™€ ํŒŒํ‹ฐ์…˜ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", "lsblk", ["lsblk"], "๋””์Šคํฌ/ํŒŒํ‹ฐ์…˜/๋งˆ์šดํŠธ ๊ตฌ์กฐ๋ฅผ ๋ด…๋‹ˆ๋‹ค.", "`lsblk`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-21", "linux", "swap ํ™•์ธ", "ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ swap์„ ํ™•์ธํ•˜์„ธ์š”.", "swapon --show", ["swapon", "--show"], "swap ์‚ฌ์šฉ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.", "`swapon --show`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-22", "linux", "์ง์ „ ๋ช…๋ น ๊ฒฐ๊ณผ ํ™•์ธ", "์ง์ „ ๋ช…๋ น์˜ exit code๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", "echo $?", ["echo", "$?"], "`$?`๋Š” ์ง์ „ ๋ช…๋ น์˜ ์ข…๋ฃŒ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.", "`echo $?`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-23", "linux", "ํŒจํ‚ค์ง€ ํ›„๋ณด ํ™•์ธ", "`nginx` ํŒจํ‚ค์ง€ ํ›„๋ณด ๋ฒ„์ „์„ ํ™•์ธํ•˜์„ธ์š”.", "apt-cache policy nginx", ["apt-cache", "policy", "nginx"], "repository ๋ฌธ์ œ๋ฅผ ๋ณผ ๋•Œ ํŒจํ‚ค์ง€ ํ›„๋ณด๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.", "`apt-cache policy nginx`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-24", "linux", "SSH ํ‚ค ์ƒ์„ฑ", "ed25519 ๋ฐฉ์‹ SSH ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.", "ssh-keygen -t ed25519", ["ssh-keygen", "-t", "ed25519"], "ํ‚ค ๊ธฐ๋ฐ˜ ์ธ์ฆ์€ ๊ณต๊ฐœํ‚ค/๊ฐœ์ธํ‚ค ์Œ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.", "`ssh-keygen -t ed25519`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-25", "linux", "DNS ์ƒํƒœ ํ™•์ธ", "ํ˜„์žฌ DNS resolver ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.", "resolvectl status", ["resolvectl", "status"], "DNS ๋ฌธ์ œ๋Š” ๋„คํŠธ์›Œํฌ ์žฅ์• ์™€ ๊ตฌ๋ถ„ํ•ด์„œ ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", "`resolvectl status`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-26", "linux", "HTTP ํ—ค๋” ํ™•์ธ", "example.com์˜ HTTP ํ—ค๋”๋งŒ ํ™•์ธํ•˜์„ธ์š”.", "curl -I https://example.com", ["curl", "-I", "example.com"], "์„œ๋น„์Šค ์‘๋‹ต ํ™•์ธ์€ curl์ด ํŽธํ•ฉ๋‹ˆ๋‹ค.", "`curl -I https://example.com`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-27", "linux", "์Šคํ† ๋ฆฌ์ง€ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ", "`/var/log` ๋””๋ ‰ํ„ฐ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์š”์•ฝํ•ด์„œ ํ™•์ธํ•˜์„ธ์š”.", "du -sh /var/log", ["du", "-sh", "/var/log"], "๋””๋ ‰ํ„ฐ๋ฆฌ๋ณ„ ์‚ฌ์šฉ๋Ÿ‰์€ du๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.", "`du -sh /var/log`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-28", "linux", "fstab ํ™•์ธ", "์ž๋™ ๋งˆ์šดํŠธ ์„ค์ • ํŒŒ์ผ์„ ์ถœ๋ ฅํ•˜์„ธ์š”.", "cat /etc/fstab", ["cat", "/etc/fstab"], "๋ถ€ํŒ… ์‹œ ๋งˆ์šดํŠธ ๋ฌธ์ œ๋Š” fstab์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.", "`cat /etc/fstab`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-29", "linux", "ํ™˜๊ฒฝ ๋ณ€์ˆ˜ PATH ํ™•์ธ", "ํ˜„์žฌ PATH ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ถœ๋ ฅํ•˜์„ธ์š”.", "echo $PATH", ["echo", "$PATH"], "๋ช…๋ น ๊ฒ€์ƒ‰ ๊ฒฝ๋กœ ํ™•์ธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", "`echo $PATH`๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
PracticeTask("linux-lab-30", "linux", "์ปค๋„ ๋กœ๊ทธ ํ™•์ธ", "์ปค๋„ ๋กœ๊ทธ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ํ™•์ธํ•˜์„ธ์š”.", "dmesg | tail", ["dmesg", "tail"], "๋ถ€ํŒ…/ํ•˜๋“œ์›จ์–ด ํžŒํŠธ๋Š” dmesg์—์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.", "`dmesg | tail`์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."),
]
)
def active_tracks() -> list[dict[str, Any]]:
return [track for track in TRACKS if track["is_active"]]
def normalize_track_id(track_id: str) -> str:
if track_id == "azure-az104":
return "azure"
return track_id or "linux"
def track_by_id(track_id: str) -> dict[str, Any]:
normalized = normalize_track_id(track_id)
return next((track for track in TRACKS if track["id"] == normalized), TRACKS[0])
def certifications_for_track(track_id: str) -> list[dict[str, str]]:
return CERTIFICATIONS.get(normalize_track_id(track_id), [])
def certification_by_id(certification_id: str) -> dict[str, str]:
for certifications in CERTIFICATIONS.values():
for certification in certifications:
if certification["id"] == certification_id:
return certification
return {"id": "", "name": "๋ฏธ์ •", "description": "", "readiness": "unknown", "study_mode": "๋ฏธ์ •"}
def certification_for_track(track_id: str) -> dict[str, str]:
certifications = certifications_for_track(track_id)
return certifications[0] if certifications else {"id": "", "name": "๋ฏธ์ •", "description": "", "readiness": "unknown", "study_mode": "๋ฏธ์ •"}
def lessons_for_track(track_id: str) -> list[LessonCard]:
track_id = normalize_track_id(track_id)
return [lesson for lesson in LESSONS if lesson.track == track_id and lesson.status == "approved"]
def quizzes_for_track(track_id: str) -> list[LabQuiz]:
track_id = normalize_track_id(track_id)
return [quiz for quiz in QUIZZES if quiz.track == track_id and quiz.status == "approved"]
def roadmap_for_track(track_id: str) -> list[RoadmapStep]:
return ROADMAPS.get(normalize_track_id(track_id), [])
def evaluate_lab_quiz(quiz: LabQuiz, answer: str) -> bool:
normalized_answer = _normalize(answer)
expected = _normalize(quiz.answer)
if quiz.question_type == "command":
return all(part in normalized_answer for part in expected.split())
return normalized_answer == expected
def evaluate_lab_quiz_detail(quiz: LabQuiz, answer: str) -> tuple[bool, list[tuple[str, bool]]]:
"""(์ „์ฒด_์ •๋‹ต, [(ํ† ํฐ, ์ผ์น˜์—ฌ๋ถ€), ...]) โ€” command ํƒ€์ž…๋งŒ ํ† ํฐ ๋ถ„ํ•ด, ๋‚˜๋จธ์ง€๋Š” ๋‹จ์ผ ํ•ญ๋ชฉ."""
normalized_answer = _normalize(answer)
expected = _normalize(quiz.answer)
if quiz.question_type == "command":
tokens = expected.split()
results = [(tok, tok in normalized_answer) for tok in tokens]
return all(ok for _, ok in results), results
matched = normalized_answer == expected
return matched, [(quiz.answer, matched)]
def evaluate_practice(task: PracticeTask, command: str) -> bool:
normalized = _normalize(command)
return all(_normalize(condition) in normalized for condition in task.grading_conditions)
def evaluate_practice_detail(task: PracticeTask, command: str) -> tuple[bool, list[tuple[str, bool]]]:
"""(์ „์ฒด_์ •๋‹ต, [(์กฐ๊ฑด๋ฌธ์ž์—ด, ์ถฉ์กฑ์—ฌ๋ถ€), ...])."""
normalized = _normalize(command)
results = [(_normalize(c), _normalize(c) in normalized) for c in task.grading_conditions]
return all(ok for _, ok in results), results
def track_progress(track_id: str, completed_lessons: set[str], completed_quizzes: set[str], completed_practices: set[str]) -> dict[str, int]:
lessons = lessons_for_track(track_id)
quizzes = quizzes_for_track(track_id)
practices = [task for task in PRACTICE_TASKS if task.track == track_id and task.status == "approved"]
total = len(lessons) + len(quizzes) + len(practices)
completed = (
len({lesson.id for lesson in lessons} & completed_lessons)
+ len({quiz.id for quiz in quizzes} & completed_quizzes)
+ len({task.id for task in practices} & completed_practices)
)
return {"completed": completed, "total": total, "percent": int((completed / total) * 100) if total else 0}
def _normalize(value: str) -> str:
return " ".join(str(value or "").strip().lower().split())
# โ”€โ”€ JSON content files (cert_study_app/content/{track}/*.json) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def _lessons_from_content(track_id: str) -> list[LessonCard]:
try:
from cert_study_app.content.loader import load_lesson_dicts
return [
LessonCard(
d["id"], track_id,
d.get("certification", ""),
d["title"], d["summary"],
d.get("example", ""), d.get("common_mistake", ""),
d.get("keywords", []), d.get("source_id", "manual"),
details=tuple(d.get("details", [])),
level=d.get("level", "์ž…๋ฌธ"),
related_practices=tuple(d.get("related_practices", [])),
)
for d in load_lesson_dicts(track_id)
]
except Exception:
return []
def _quizzes_from_content(track_id: str) -> list[LabQuiz]:
try:
from cert_study_app.content.loader import load_quiz_dicts
return [
LabQuiz(
d["id"], d.get("lesson_id", ""), track_id,
d.get("question_type", "multiple_choice"),
d["question"], d.get("options", []),
d["answer"], d.get("explanation", ""),
difficulty=d.get("difficulty", "easy"),
source_id=d.get("source_id", "manual"),
)
for d in load_quiz_dicts(track_id)
]
except Exception:
return []
def _practices_from_content(track_id: str) -> list[PracticeTask]:
try:
from cert_study_app.content.loader import load_practice_dicts
return [
PracticeTask(
d["id"], track_id, d["title"],
d["task_description"], d["expected_command"],
d.get("grading_conditions", []),
d.get("hint", ""), d.get("explanation", ""),
difficulty=d.get("difficulty", "easy"),
takeaway=d.get("takeaway", ""),
)
for d in load_practice_dicts(track_id)
]
except Exception:
return []
for _tid in ("linux", "azure", "tool_docs"):
LESSONS.extend(_lessons_from_content(_tid))
QUIZZES.extend(_quizzes_from_content(_tid))
PRACTICE_TASKS.extend(_practices_from_content(_tid))