skel.py (11511B)
1 #!/usr/bin/env python3 2 """ 3 Prepara /etc/skel para novos usuários do runv.club (Debian). 4 Executar como root. Não cria usuários, não altera Apache nem SSH. 5 6 Versão 0.02 — runv.club 7 """ 8 9 from __future__ import annotations 10 11 import argparse 12 import os 13 import sys 14 from pathlib import Path 15 from typing import Final 16 17 _SCRIPT_DIR = Path(__file__).resolve().parent 18 if str(_SCRIPT_DIR) not in sys.path: 19 sys.path.insert(0, str(_SCRIPT_DIR)) 20 21 from admin_guard import ensure_admin_cli 22 23 # --------------------------------------------------------------------------- 24 # Constantes 25 # --------------------------------------------------------------------------- 26 27 SKEL_ROOT: Final[Path] = Path("/etc/skel") 28 PUBLIC_HTML_DIR: Final[Path] = SKEL_ROOT / "public_html" 29 INDEX_HTML: Final[Path] = PUBLIC_HTML_DIR / "index.html" 30 README_MD: Final[Path] = SKEL_ROOT / "README.md" 31 32 VERSION: Final[str] = "0.02" 33 34 EXIT_OK: Final[int] = 0 35 EXIT_ERROR: Final[int] = 1 36 EXIT_PRIVILEGE: Final[int] = 2 37 38 39 # --------------------------------------------------------------------------- 40 # Validação de privilégios 41 # --------------------------------------------------------------------------- 42 43 44 def validate_privileges() -> None: 45 """Exige UID 0 (root) para alterar /etc/skel.""" 46 if os.geteuid() != 0: 47 print( 48 "Erro: este script precisa ser executado como root (sudo).", 49 file=sys.stderr, 50 ) 51 raise SystemExit(EXIT_PRIVILEGE) 52 53 54 # --------------------------------------------------------------------------- 55 # Geração de conteúdo 56 # --------------------------------------------------------------------------- 57 58 59 def render_index_html() -> str: 60 """HTML estático com CSS embutido; visual simples e textual, sem dependências externas.""" 61 return """<!DOCTYPE html> 62 <html lang="pt-BR"> 63 <head> 64 <meta charset="utf-8"> 65 <meta name="viewport" content="width=device-width, initial-scale=1"> 66 <title>A sua página no runv.club</title> 67 <style> 68 :root { 69 --bg: #f4f0e8; 70 --fg: #1a1a12; 71 --muted: #5c5a52; 72 --accent: #2d6a4f; 73 --rule: #c4c0b8; 74 } 75 * { box-sizing: border-box; } 76 body { 77 margin: 0; 78 padding: 1.5rem 1rem 3rem; 79 font-family: "Georgia", "Times New Roman", serif; 80 font-size: 1rem; 81 line-height: 1.55; 82 color: var(--fg); 83 background: var(--bg); 84 max-width: 38rem; 85 margin-left: auto; 86 margin-right: auto; 87 } 88 h1 { 89 font-size: 1.35rem; 90 font-weight: normal; 91 letter-spacing: 0.02em; 92 border-bottom: 1px solid var(--rule); 93 padding-bottom: 0.5rem; 94 margin-top: 0; 95 } 96 .tagline { 97 font-style: italic; 98 color: var(--muted); 99 margin: 0.25rem 0 1.25rem; 100 font-size: 0.95rem; 101 } 102 pre, code { 103 font-family: ui-monospace, "Cascadia Mono", "Consolas", monospace; 104 font-size: 0.88rem; 105 } 106 pre { 107 background: #e8e4dc; 108 border: 1px solid var(--rule); 109 padding: 0.75rem 1rem; 110 overflow-x: auto; 111 margin: 0.75rem 0; 112 } 113 section { 114 margin: 1.5rem 0; 115 } 116 h2 { 117 font-size: 1.05rem; 118 font-weight: normal; 119 margin: 0 0 0.5rem; 120 color: var(--accent); 121 } 122 .url-box { 123 border-left: 3px solid var(--accent); 124 padding-left: 0.75rem; 125 margin: 0.75rem 0; 126 } 127 footer { 128 margin-top: 2rem; 129 padding-top: 0.75rem; 130 border-top: 1px solid var(--rule); 131 font-size: 0.85rem; 132 color: var(--muted); 133 } 134 </style> 135 </head> 136 <body> 137 <h1>Bem-vindo ao runv.club</h1> 138 <p class="tagline">Um cantinho na rede — pubnix runv.club.</p> 139 140 <p> 141 Esta página foi gerada automaticamente quando sua conta foi criada. 142 Você pode editá-la quando quiser: o arquivo fica em 143 <code>~/public_html/index.html</code>. 144 </p> 145 146 <section> 147 <h2>Próximos passos</h2> 148 <ol> 149 <li>Entrar no servidor por SSH.</li> 150 <li>Ir para a pasta do site pessoal.</li> 151 <li>Editar este HTML com um editor de texto.</li> 152 <li>Salvar e recarregar a página no navegador.</li> 153 </ol> 154 </section> 155 156 <section> 157 <h2>Comandos úteis</h2> 158 <pre>cd ~/public_html 159 nano index.html 160 ls -la</pre> 161 </section> 162 163 <section> 164 <h2>Sua URL</h2> 165 <p>Quando estiver no ar, seu site costuma aparecer em:</p> 166 <div class="url-box"> 167 <code>http://runv.club/~SEU_USUARIO/</code> 168 </div> 169 <p>Substitua <code>SEU_USUARIO</code> pelo seu nome de usuário Unix.</p> 170 </section> 171 172 <footer> 173 runv.club — servidor multiusuário. Edite esta página à vontade. 174 </footer> 175 </body> 176 </html> 177 """ 178 179 180 def render_readme_md() -> str: 181 """README em Markdown para a home inicial (copiado de /etc/skel).""" 182 return """# Bem-vindo ao runv.club 183 184 O **runv.club** é um servidor multiutilizador: cada pessoa tem uma conta Unix e um 185 site pessoal servido pelo Apache. 186 187 ## Onde fica o seu site 188 189 - **Pasta:** `~/public_html/` 190 - **Arquivo principal:** `~/public_html/index.html` — edite este primeiro. 191 192 ## URL pública 193 194 Depois de publicar, seu site costuma ficar em: 195 196 ```text 197 http://runv.club/~SEU_USUARIO/ 198 ``` 199 200 Troque `SEU_USUARIO` pelo seu nome de usuário Unix (o mesmo do login). 201 202 ## Permissões (referência) 203 204 Após a criação da conta, costuma ser assim para o site aparecer: 205 206 | Caminho | Permissão típica | 207 |---------|------------------| 208 | `~` (home) | `755` | 209 | `~/public_html` | `755` | 210 | `~/public_html/index.html` | `644` | 211 212 Se algo não carregar no navegador, peça ajuda a um admin e mencione estas pastas. 213 214 ## Comandos básicos 215 216 ```bash 217 cd ~/public_html 218 nano index.html 219 ls -la 220 ``` 221 222 ## Servidor multiusuário 223 224 - Muitas pessoas usam a mesma máquina. **Não guarde segredos** em arquivos dentro de 225 `public_html` ou em qualquer lugar que o site possa expor. 226 - O que está em `public_html` é pensado para ser **público na web**. 227 228 ## Dúvidas 229 230 Leia também a documentação do projeto ou fale com a equipe no canal indicado pelo runv.club. 231 232 — Equipe runv.club 233 """ 234 235 236 # --------------------------------------------------------------------------- 237 # Diretórios e ficheiros 238 # --------------------------------------------------------------------------- 239 240 241 def ensure_directories( 242 dry_run: bool, 243 verbose: bool, 244 ) -> tuple[list[Path], list[Path]]: 245 """ 246 Garante que os diretórios necessários existem. 247 Retorna (criados, já existentes). 248 """ 249 created: list[Path] = [] 250 existed: list[Path] = [] 251 for d in (SKEL_ROOT, PUBLIC_HTML_DIR): 252 if d.is_dir(): 253 existed.append(d) 254 if verbose: 255 print(f" [dir] já existe: {d}") 256 continue 257 if dry_run: 258 created.append(d) 259 print(f" [dry-run] criaria diretório: {d}") 260 continue 261 d.mkdir(parents=True, exist_ok=True) 262 created.append(d) 263 print(f" [dir] criado: {d}") 264 return created, existed 265 266 267 def apply_permissions(paths: list[Path], verbose: bool) -> None: 268 """Aplica modos 755 para diretórios e 644 para ficheiros.""" 269 for p in paths: 270 if not p.exists(): 271 continue 272 if p.is_dir(): 273 mode = 0o755 274 else: 275 mode = 0o644 276 if verbose: 277 print(f" [chmod] {oct(mode)} {p}") 278 try: 279 p.chmod(mode) 280 except OSError as e: 281 print(f"Erro ao definir permissões em {p}: {e}", file=sys.stderr) 282 raise SystemExit(EXIT_ERROR) from e 283 284 285 def write_file_safe( 286 path: Path, 287 content: str, 288 *, 289 force: bool, 290 dry_run: bool, 291 verbose: bool, 292 ) -> str: 293 """ 294 Escreve conteúdo se o ficheiro não existir ou se force=True. 295 Retorna: 'created' | 'updated' | 'preserved' 296 """ 297 existed_before = path.is_file() 298 299 if dry_run: 300 if existed_before and not force: 301 print(f" [dry-run] preservaria (sem alterar; use --force para regenerar): {path}") 302 return "preserved" 303 verb = "atualizaria" if existed_before else "criaria" 304 print(f" [dry-run] {verb} arquivo: {path}") 305 return "updated" if existed_before else "created" 306 307 if existed_before and not force: 308 hint = " (use --force para sobrescrever)" if verbose else "" 309 print(f" [file] preservado{hint}: {path}") 310 return "preserved" 311 312 path.parent.mkdir(parents=True, exist_ok=True) 313 path.write_text(content, encoding="utf-8") 314 label = "atualizado" if existed_before else "criado" 315 print(f" [file] {label}: {path}") 316 return "updated" if existed_before else "created" 317 318 319 def run_dry_run(verbose: bool) -> int: 320 """Mostra o plano sem escrever em disco (não exige root).""" 321 print("Modo dry-run — nenhuma alteração em disco.\n") 322 ensure_directories(dry_run=True, verbose=verbose) 323 write_file_safe( 324 INDEX_HTML, render_index_html(), force=False, dry_run=True, verbose=verbose 325 ) 326 write_file_safe( 327 README_MD, render_readme_md(), force=False, dry_run=True, verbose=verbose 328 ) 329 print("\nResumo: nada foi gravado. Execute sem --dry-run como root para aplicar.") 330 return EXIT_OK 331 332 333 def main() -> int: 334 parser = argparse.ArgumentParser( 335 description="Prepara /etc/skel para novos usuários do runv.club (Debian).", 336 ) 337 parser.add_argument( 338 "--dry-run", 339 action="store_true", 340 help="mostra o que seria feito sem alterar arquivos", 341 ) 342 parser.add_argument( 343 "--verbose", 344 action="store_true", 345 help="mais detalhes na saída", 346 ) 347 parser.add_argument( 348 "--force", 349 action="store_true", 350 help="sobrescreve index.html e README.md se já existirem", 351 ) 352 parser.add_argument( 353 "--version", 354 action="version", 355 version=f"%(prog)s {VERSION} — runv.club", 356 ) 357 args = parser.parse_args() 358 ensure_admin_cli( 359 script_name=Path(__file__).name, 360 dry_run=bool(args.dry_run), 361 ) 362 363 if args.dry_run: 364 return run_dry_run(args.verbose) 365 366 validate_privileges() 367 368 print("skel.py — preparando /etc/skel para runv.club\n") 369 370 dirs_created, _dirs_existed = ensure_directories(dry_run=False, verbose=args.verbose) 371 372 results: dict[str, str] = {} 373 for label, path, content in ( 374 ("index.html", INDEX_HTML, render_index_html()), 375 ("README.md", README_MD, render_readme_md()), 376 ): 377 results[label] = write_file_safe( 378 path, 379 content, 380 force=args.force, 381 dry_run=False, 382 verbose=args.verbose, 383 ) 384 385 # Permissões 386 to_chmod = [PUBLIC_HTML_DIR, INDEX_HTML, README_MD] 387 print("\nAplicando permissões...") 388 apply_permissions(to_chmod, verbose=args.verbose) 389 390 # Resumo 391 print("\n--- Resumo ---") 392 print(f" Diretórios criados agora: {len(dirs_created)}") 393 if dirs_created: 394 for d in dirs_created: 395 print(f" - {d}") 396 print(f" index.html: {results.get('index.html', '?')}") 397 print(f" README.md: {results.get('README.md', '?')}") 398 print(" Permissões: public_html → 755; index.html e README.md → 644") 399 400 print("\n--- Próximos passos sugeridos ---") 401 print(" 1. Crie um usuário de teste: sudo adduser --disabled-password testuser") 402 print(" 2. Verifique se a home copiou de /etc/skel:") 403 print(" ls -la ~/ (como esse usuário)") 404 print(" ls -la ~/public_html/") 405 print(" 3. Teste no navegador: http://runv.club/~testuser/ (ajuste DNS/host)") 406 407 return EXIT_OK 408 409 410 if __name__ == "__main__": 411 raise SystemExit(main())