Initial commit

This commit is contained in:
2026-06-30 18:30:24 +00:00
commit 67e0a90b15
54 changed files with 6184 additions and 0 deletions
Binary file not shown.
+95
View File
@@ -0,0 +1,95 @@
from fastapi import FastAPI, UploadFile, File, HTTPException, Form
from fastapi.responses import JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
import os, json, uuid, shutil, re
from datetime import datetime
PDF_DIR = "/files"
META = "/files/.metadata.json"
app = FastAPI()
def load_meta():
if os.path.exists(META):
with open(META) as f:
return json.load(f)
return {}
def save_meta(m):
with open(META, "w") as f:
json.dump(m, f, indent=2)
def safe_name(name):
return re.sub(r'[^\w\-. ]', '_', name)[:200]
@app.get("/api/list")
def list_pdfs():
meta = load_meta()
items = []
for fid, info in meta.items():
path = os.path.join(PDF_DIR, info["filename"])
if os.path.exists(path):
items.append({
"id": fid,
"title": info.get("title", info["filename"]),
"filename": info["filename"],
"tag": info.get("tag", ""),
"size": os.path.getsize(path),
"uploaded": info.get("uploaded", ""),
})
items.sort(key=lambda x: x["uploaded"], reverse=True)
return items
@app.post("/api/upload")
async def upload(file: UploadFile = File(...), title: str = Form(""), tag: str = Form("")):
if not file.filename.lower().endswith(".pdf"):
raise HTTPException(400, "PDFs only")
fid = uuid.uuid4().hex[:12]
fname = safe_name(file.filename)
path = os.path.join(PDF_DIR, f"{fid}_{fname}")
with open(path, "wb") as f:
shutil.copyfileobj(file.file, f)
meta = load_meta()
meta[fid] = {
"filename": f"{fid}_{fname}",
"title": title or fname.rsplit(".", 1)[0],
"tag": tag,
"uploaded": datetime.utcnow().isoformat(),
}
save_meta(meta)
return {"id": fid}
@app.delete("/api/delete/{fid}")
def delete(fid: str):
meta = load_meta()
if fid not in meta:
raise HTTPException(404)
path = os.path.join(PDF_DIR, meta[fid]["filename"])
if os.path.exists(path):
os.remove(path)
del meta[fid]
save_meta(meta)
return {"ok": True}
@app.get("/view/{fid}")
def view(fid: str):
meta = load_meta()
if fid not in meta:
raise HTTPException(404)
path = os.path.join(PDF_DIR, meta[fid]["filename"])
return FileResponse(path, media_type="application/pdf")
@app.patch("/api/update/{fid}")
async def update(fid: str, tag: str = Form(None), title: str = Form(None)):
meta = load_meta()
if fid not in meta:
raise HTTPException(404)
if tag is not None:
meta[fid]["tag"] = tag
if title is not None and title.strip():
meta[fid]["title"] = title.strip()
save_meta(meta)
return {"ok": True}
app.mount("/", StaticFiles(directory="/static", html=True), name="static")
+205
View File
@@ -0,0 +1,205 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>PDF Library - The Dark Elite</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
:root { --bg:#060708; --panel:#0f1115; --border:rgba(80,90,100,0.2); --accent:#22d3ee; --text:#e5e7eb; --muted:#9ca3af; --danger:#dc2626; }
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font-family:system-ui,sans-serif;min-height:100vh}
.container{max-width:1200px;margin:0 auto;padding:2rem}
header{display:flex;justify-content:space-between;align-items:center;padding-bottom:1.5rem;border-bottom:1px solid var(--border);margin-bottom:2rem}
header h1{margin:0;font-size:1.5rem;letter-spacing:0.2em;text-transform:uppercase;color:var(--accent);text-shadow:0 0 12px rgba(34,211,238,0.4)}
header a{color:var(--muted);text-decoration:none;font-size:0.85rem}
header a:hover{color:var(--accent)}
.dropzone{border:2px dashed rgba(34,211,238,0.4);border-radius:8px;padding:2rem;text-align:center;background:linear-gradient(135deg,rgba(18,20,24,0.7),rgba(10,11,13,0.85));margin-bottom:2rem;cursor:pointer;transition:all 0.15s}
.dropzone:hover,.dropzone.drag{border-color:var(--accent);box-shadow:0 0 20px rgba(34,211,238,0.15)}
.dropzone p{margin:0.3rem 0;color:var(--muted);font-size:0.9rem}
.dropzone .big{color:var(--accent);font-size:1.1rem;letter-spacing:0.1em;text-transform:uppercase;font-family:ui-monospace,monospace}
.dropzone input[type=file]{display:none}
.fields{display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;margin-top:1rem}
.fields input{background:rgba(10,11,13,0.6);border:1px solid var(--border);color:var(--text);padding:0.5rem 0.8rem;border-radius:4px;font-size:0.85rem}
.fields input:focus{outline:none;border-color:var(--accent)}
.section-title{font-size:0.75rem;letter-spacing:0.25em;text-transform:uppercase;color:var(--accent);border-left:3px solid var(--accent);padding-left:0.75rem;margin-bottom:1rem;display:flex;justify-content:space-between;align-items:center}
.search{background:rgba(10,11,13,0.6);border:1px solid var(--border);color:var(--text);padding:0.4rem 0.7rem;border-radius:4px;font-size:0.8rem;font-family:ui-monospace,monospace}
.search:focus{outline:none;border-color:var(--accent)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:1rem}
.card{background:linear-gradient(135deg,rgba(18,20,24,0.9),rgba(10,11,13,0.95));border:1px solid var(--border);border-radius:6px;padding:1rem;transition:all 0.15s;position:relative;overflow:hidden}
.card:hover{border-color:rgba(34,211,238,0.5);transform:translateY(-2px);box-shadow:0 0 25px rgba(34,211,238,0.18)}
.card .title{font-size:0.95rem;color:var(--text);margin-bottom:0.3rem;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-weight:500}
.card .meta{font-size:0.7rem;color:var(--muted);font-family:ui-monospace,monospace;letter-spacing:0.05em}
.card .tag{display:inline-block;background:rgba(34,211,238,0.12);border:1px solid rgba(34,211,238,0.3);color:rgb(165,235,247);font-size:0.65rem;padding:0.15rem 0.5rem;border-radius:3px;margin-top:0.5rem;font-family:ui-monospace,monospace;text-transform:uppercase;letter-spacing:0.1em}
.card .actions{display:flex;gap:0.4rem;margin-top:0.7rem}
.card .actions a,.card .actions button{flex:1;background:rgba(10,11,13,0.6);border:1px solid var(--border);color:var(--text);padding:0.35rem;border-radius:3px;font-size:0.7rem;cursor:pointer;text-align:center;text-decoration:none;letter-spacing:0.1em;text-transform:uppercase}
.card .actions a:hover{border-color:var(--accent);color:var(--accent)}
.card .actions button:hover{border-color:var(--danger);color:var(--danger)}
.empty{color:var(--muted);text-align:center;padding:2rem;font-style:italic}
.toast{position:fixed;bottom:2rem;right:2rem;background:rgba(18,20,24,0.95);border:1px solid var(--accent);color:var(--accent);padding:0.8rem 1.2rem;border-radius:4px;font-family:ui-monospace,monospace;font-size:0.85rem;z-index:100;display:none}
.tag-row{margin-top:0.5rem;min-height:1.4rem}
.tag-edit{display:inline-block;background:rgba(34,211,238,0.12);border:1px solid rgba(34,211,238,0.3);color:rgb(165,235,247);font-size:0.65rem;padding:0.15rem 0.5rem;border-radius:3px;font-family:ui-monospace,monospace;text-transform:uppercase;letter-spacing:0.1em;cursor:pointer;transition:all 0.15s}
.tag-edit:empty::before,.tag-edit:hover{border-color:rgba(34,211,238,0.7);color:var(--accent)}
.tag-edit.empty{background:transparent;border-style:dashed;color:var(--muted)}
.tag-edit input{background:rgba(10,11,13,0.9);border:1px solid var(--accent);color:var(--accent);font:inherit;padding:0;width:8rem;outline:none;text-transform:uppercase}
</style>
</head>
<body>
<div class="container">
<header>
<div><h1>PDF Library</h1></div>
<a href="/">← Hub</a>
</header>
<div class="dropzone" id="dz">
<p class="big">Drop PDFs here or click to upload</p>
<p>Supports drag-drop, multiple files</p>
<input type="file" id="fileInput" accept=".pdf" multiple>
<div class="fields">
<input type="text" id="titleInput" placeholder="Title (optional - uses filename)">
<input type="text" id="tagInput" placeholder="Tag (medical, repair, etc.)">
</div>
</div>
<div class="section-title">
Library
<div style="display:flex;gap:0.5rem;align-items:center">
<select class="search" id="sortInput" style="font-family:ui-monospace,monospace">
<option value="date-desc">Newest First</option>
<option value="date-asc">Oldest First</option>
<option value="title-asc">Title A-Z</option>
<option value="title-desc">Title Z-A</option>
<option value="size-desc">Size (Largest)</option>
<option value="size-asc">Size (Smallest)</option>
<option value="tag-asc">Tag A-Z</option>
</select>
<input type="text" class="search" id="searchInput" placeholder="Filter...">
</div>
</div>
<div id="grid" class="grid"><div class="empty">Loading...</div></div>
</div>
<div id="toast" class="toast"></div>
<script>
let allItems = [];
function fmtSize(b){
if(b<1024) return b+" B";
if(b<1048576) return (b/1024).toFixed(1)+" KB";
if(b<1073741824) return (b/1048576).toFixed(1)+" MB";
return (b/1073741824).toFixed(2)+" GB";
}
function fmtDate(s){
if(!s) return "";
return new Date(s).toLocaleDateString();
}
function toast(msg){
const t=document.getElementById("toast");
t.textContent=msg;
t.style.display="block";
setTimeout(()=>t.style.display="none",2500);
}
async function loadList(){
const r=await fetch("api/list");
allItems=await r.json();
render();
}
function render(){
const q=document.getElementById("searchInput").value.toLowerCase();
const sort=document.getElementById("sortInput").value;
let items=allItems.filter(i=>!q || i.title.toLowerCase().includes(q) || (i.tag||"").toLowerCase().includes(q));
items.sort((a,b)=>{
switch(sort){
case "date-desc": return (b.uploaded||"").localeCompare(a.uploaded||"");
case "date-asc": return (a.uploaded||"").localeCompare(b.uploaded||"");
case "title-asc": return a.title.localeCompare(b.title);
case "title-desc":return b.title.localeCompare(a.title);
case "size-desc": return b.size-a.size;
case "size-asc": return a.size-b.size;
case "tag-asc": return (a.tag||"").localeCompare(b.tag||"") || a.title.localeCompare(b.title);
}
return 0;
});
const grid=document.getElementById("grid");
if(!items.length){
grid.innerHTML='<div class="empty">No PDFs yet. Upload some above.</div>';
return;
}
grid.innerHTML=items.map(i=>`
<div class="card">
<div class="title">${i.title}</div>
<div class="meta">${fmtSize(i.size)}${fmtDate(i.uploaded)}</div>
<div class="tag-row"><span class="tag-edit ${i.tag?'':'empty'}" onclick="editTag('${i.id}', this)">${i.tag||"+ add tag"}</span></div>
<div class="actions">
<a href="view/${i.id}" target="_blank">View</a>
<button onclick="del('${i.id}')">Delete</button>
</div>
</div>
`).join("");
}
async function del(fid){
if(!confirm("Delete this PDF?")) return;
await fetch("api/delete/"+fid,{method:"DELETE"});
toast("Deleted");
loadList();
}
async function upload(files){
for(const file of files){
if(!file.name.toLowerCase().endsWith(".pdf")){ toast("Skipped: "+file.name); continue; }
const fd=new FormData();
fd.append("file",file);
fd.append("title",document.getElementById("titleInput").value);
fd.append("tag",document.getElementById("tagInput").value);
toast("Uploading "+file.name+"...");
const r=await fetch("api/upload",{method:"POST",body:fd});
if(r.ok) toast("Uploaded "+file.name);
else toast("Failed: "+file.name);
}
document.getElementById("titleInput").value="";
document.getElementById("tagInput").value="";
loadList();
}
const dz=document.getElementById("dz");
const fi=document.getElementById("fileInput");
dz.addEventListener("click",e=>{ if(e.target.tagName!=="INPUT") fi.click(); });
fi.addEventListener("change",e=>upload(e.target.files));
dz.addEventListener("dragover",e=>{ e.preventDefault(); dz.classList.add("drag"); });
dz.addEventListener("dragleave",()=>dz.classList.remove("drag"));
dz.addEventListener("drop",e=>{ e.preventDefault(); dz.classList.remove("drag"); upload(e.dataTransfer.files); });
document.getElementById("searchInput").addEventListener("input",render);
document.getElementById("sortInput").addEventListener("change",render);
async function editTag(fid, el){
if(el.querySelector("input")) return;
const current = el.textContent === "+ add tag" ? "" : el.textContent;
el.innerHTML = '<input type="text" value="'+current+'" />';
const input = el.querySelector("input");
input.focus();
input.select();
const save = async () => {
const val = input.value.trim();
const fd = new FormData();
fd.append("tag", val);
await fetch("api/update/"+fid, {method:"PATCH", body: fd});
toast(val ? "Tag set: "+val : "Tag cleared");
loadList();
};
input.addEventListener("blur", save);
input.addEventListener("keydown", e => {
if(e.key === "Enter") input.blur();
if(e.key === "Escape"){ input.value = current; input.blur(); }
});
}
loadList();
</script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
{}
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import os, json, uuid, re
from datetime import datetime
PDF_DIR = "/opt/hub/pdfs/files"
META = os.path.join(PDF_DIR, ".metadata.json")
TAG_RULES = [
(r'fm[-_ ]?\d|atp[-_ ]?\d|tm[-_ ]?\d|tc[-_ ]?\d|army|military|combat|tactical|sniper|infantry|guerrilla|special.forces', 'defense'),
(r'medic|first.aid|surgery|trauma|nursing|hesperian|midwif|dentist|disease|emergency.med|tccc|where.there.is.no.doctor', 'medical'),
(r'survival|prepper|shtf|bug.out|wilderness|evasion', 'survival'),
(r'radio|ham|antenna|arrl|fcc|comms|aprs|signal', 'comms'),
(r'food|cook|cann|preserv|recipe|kitchen|baking|ferment', 'food'),
(r'farm|garden|crop|seed|livestock|chicken|poultry|cattle|sheep|goat|beekeep|honey|agriculture|permaculture', 'agriculture'),
(r'vet|animal|dog|cat|horse|pig|pet', 'veterinary'),
(r'navig|map|compass|gps|bowditch|topo', 'navigation'),
(r'solar|wind|battery|grid|power|nrel|energy|generator', 'energy'),
(r'electric|plumb|carpent|hvac|welding|machinist|trade|repair|audel', 'trades'),
(r'chem|biolog|physic|crc.handbook|merck.index', 'chemistry'),
(r'engineer|mechanic|machine|handbook', 'engineering'),
(r'law|legal|constitution|statute|code|court|right', 'law'),
(r'finance|tax|irs|money|invest|insurance', 'finance'),
(r'history|war|civil|world.war|geopolit', 'history'),
]
def infer_tag(filename):
name = filename.lower()
for pattern, tag in TAG_RULES:
if re.search(pattern, name):
return tag
return 'general'
def clean_title(filename):
name = filename.rsplit('.', 1)[0]
name = re.sub(r'[_-]+', ' ', name).strip()
return name[:200]
def main():
if os.path.exists(META):
with open(META) as f:
meta = json.load(f)
else:
meta = {}
registered = {info['filename'] for info in meta.values()}
added = 0
skipped = 0
for fname in sorted(os.listdir(PDF_DIR)):
if not fname.lower().endswith('.pdf'): continue
if fname.startswith('.'): continue
if fname in registered:
skipped += 1
continue
path = os.path.join(PDF_DIR, fname)
if os.path.getsize(path) < 50000: continue
fid = uuid.uuid4().hex[:12]
meta[fid] = {
'filename': fname,
'title': clean_title(fname),
'tag': infer_tag(fname),
'uploaded': datetime.utcnow().isoformat(),
}
added += 1
with open(META, 'w') as f:
json.dump(meta, f, indent=2)
print(f"Added: {added} Already registered: {skipped} Total in library: {len(meta)}")
if __name__ == '__main__':
main()
+109
View File
@@ -0,0 +1,109 @@
#!/bin/bash
# COMPREHENSIVE Reference PDF Library Seeder
# Covers Trades, Agriculture, Energy, Wilderness, Food, Chemistry,
# Mechanical, Comms, Medical, Military, Gov/Law, Navigation, Skills,
# Finance, Pet/Livestock
set -u
TMP=/tmp/pdf-seed-all
API=http://localhost/pdfs/api/upload
mkdir -p "$TMP"
PDFS=(
# ========== TRADES & REPAIR ==========
"https://archive.org/download/bwb_Y0-BMG-117/bwb_Y0-BMG-117.pdf|Audels Plumbers and Steam Fitters Guide #3 (1925)|trades"
"https://archive.org/download/cu31924004933945/cu31924004933945.pdf|Henleys Twentieth Century Book of Formulas (1909)|trades"
"https://archive.org/download/theboymechanicvo12655gut/theboymechanicvo12655gut.pdf|The Boy Mechanic Vol 1 (1913)|trades"
"https://archive.org/download/the-boy-mechanic-book-2-1000-things-for-unknown/The%20Boy%20Mechanic%2C%20Book%202%20%281915%29%20-%201%2C000%20Things%20for%20Boys%20to%20Do.pdf|The Boy Mechanic Vol 2 (1915)|trades"
"https://rexresearch1.com/AudelManuals/AudelPipefittersWeldersPocketManual.pdf|Audel Pipefitters and Welders Pocket Manual|trades"
# ========== AGRICULTURE / SELF-SUFFICIENCY ==========
# USDA Farmers Bulletins must be sourced individually; placing 1 sample
"https://naldc.nal.usda.gov/download/CAT74420061/PDF|USDA Farmers Bulletin No 1186 - Pork on the Farm|agriculture"
# ========== ENERGY / OFF-GRID ==========
"https://www.nrel.gov/docs/fy20osti/76011.pdf|NREL Off-Grid PV System Design Sizing Guide|energy"
# ========== WILDERNESS / OUTDOOR ==========
# Boy Scout Handbook 1911 is text-only on Gutenberg; sourcing alternate scan
"https://archive.org/download/boyscoutshandboo00boys/boyscoutshandboo00boys.pdf|Boy Scouts Handbook First Edition (1911)|wilderness"
# ========== FOOD / COOKING / PRESERVATION ==========
"https://archive.org/download/bostoncookingsc00farm/bostoncookingsc00farm.pdf|Boston Cooking School Cook Book - Fannie Farmer (1918)|food"
"https://nchfp.uga.edu/how/dry/food_drying_what_you_need_to_know.pdf|USDA NCHFP - Food Drying What You Need to Know|food"
# ========== CHEMISTRY / SCIENCE ==========
"https://archive.org/download/CRCHandbookOfChemistryAndPhysics97thEdition2016/CRC%20Handbook%20of%20Chemistry%20and%20Physics%2C%2097th%20Edition.pdf|CRC Handbook of Chemistry and Physics 97th Edition (2016)|chemistry"
# ========== MECHANICAL / ENGINEERING ==========
# Machinery's Handbook 6th already added in original batch
# ========== VEHICLE (manual sourcing recommended) ==========
# Ram 1500 / Cub Cadet FSMs typically need OEM login - skipped
# ========== COMMUNICATIONS ==========
# ARRL/Antenna books are paid; skipped
# ========== MEDICAL DEPTH ==========
"https://ia801609.us.archive.org/9/items/civilian-and-non-civilian-medical-guides/Ranger%20Medic%20Handbook%20%282007%29.pdf|Ranger Medic Handbook (2007)|medical"
"https://archive.org/download/SOFMH2001/SOFMH2001.pdf|Special Operations Forces Medical Handbook (2001)|medical"
"https://archive.org/download/where-there-is-no-vet/where-there-is-no-vet.pdf|Where There Is No Vet|veterinary"
# ========== MILITARY / TACTICAL ==========
"https://archive.org/download/Jungle_Operations_FM_90-5/Jungle_Operations_FM_90-5.pdf|FM 90-5 Jungle Operations (1982)|defense"
"https://ia801302.us.archive.org/16/items/FM31_21_1961/FM31_21_1961.pdf|FM 31-21 Guerrilla Warfare and Special Forces Operations (1961)|defense"
"https://archive.org/download/usa-tm-31-210-improvised-munitions-handbook/USA%20TM%2031%20210%20Improvised%20Munitions%20Handbook.pdf|TM 31-210 Improvised Munitions Handbook (1969)|defense"
# ========== GOVERNMENT / LAW ==========
"https://archive.org/download/TheFederalistPapers_201607/The%20Federalist%20Papers.pdf|The Federalist Papers|law"
"https://archive.org/download/roberts-rules-of-order/roberts-rules-of-order.pdf|Roberts Rules of Order (Parliamentary Procedure)|law"
"https://statutes.capitol.texas.gov/docs/sdocs/penalcode.pdf|Texas Penal Code|law"
"https://statutes.capitol.texas.gov/docs/sdocs/familycode.pdf|Texas Family Code|law"
"https://statutes.capitol.texas.gov/docs/sdocs/propertycode.pdf|Texas Property Code|law"
"https://statutes.capitol.texas.gov/docs/sdocs/cn.pdf|Texas Constitution|law"
# ========== NAVIGATION DEPTH ==========
# Bowditch V1+V2 already included; USGS topo symbols below
"https://pubs.usgs.gov/gip/TopographicMapSymbols/topomapsymbols.pdf|USGS Topographic Map Symbols|navigation"
# ========== SPECIFIC SKILLS ==========
# Welding/knots/blacksmithing - most under restricted access, manual sourcing
# ========== FINANCE / PRACTICAL LIFE ==========
"https://www.irs.gov/pub/irs-pdf/p17.pdf|IRS Publication 17 - Your Federal Income Tax|finance"
# ========== PETS / LIVESTOCK ==========
# Where There Is No Vet already in medical above
)
ok=0; fail=0; total=${#PDFS[@]}; i=0
for entry in "${PDFS[@]}"; do
i=$((i+1))
IFS='|' read -r url title tag <<< "$entry"
fname=$(echo "$title" | tr -c 'A-Za-z0-9._-' '_' | head -c 100).pdf
out="$TMP/$fname"
echo ""
echo "==> [$i/$total] [$title]"
if wget -q --show-progress -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" --tries=2 --timeout=120 -O "$out" "$url"; then
sz_bytes=$(stat -c%s "$out" 2>/dev/null || echo 0)
if [ "$sz_bytes" -gt 100000 ] && file "$out" | grep -qi "PDF"; then
sz=$(du -h "$out" | cut -f1)
curl -s -X POST "$API" -F "file=@$out;type=application/pdf" -F "title=$title" -F "tag=$tag" > /dev/null
echo " ✓ uploaded ($sz)"
ok=$((ok+1))
else
echo " ✗ skipped (not a valid PDF, $sz_bytes bytes)"
fail=$((fail+1))
fi
rm -f "$out"
else
echo " ✗ download failed"
fail=$((fail+1))
fi
done
echo ""
echo "================================"
echo "OK: $ok Failed: $fail Total: $total"
echo "================================"
rm -rf "$TMP"
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
set -u
TMP=/tmp/pdf-seed-fix
API=http://localhost/pdfs/api/upload
mkdir -p "$TMP"
PDFS=(
"https://ia801301.us.archive.org/5/items/WhereWomenHaveNoDoctor/18.HersperianFoundation-WhereWomenHaveNoDoctor.pdf|Where Women Have No Doctor (Hesperian)|medical"
"https://archive.org/download/WhereThereIsNoDoctor-English-DavidWerner/WhereThereIsNoDoctor-English-DavidWerner.pdf|Where There Is No Doctor (Hesperian)|medical"
"https://ia801500.us.archive.org/28/items/MManuals/Fm5-31Boobytraps.pdf|FM 5-31 Boobytraps (1965)|defense"
"https://archive.org/download/milmanual-fm-3-25.26-map-reading-and-land-navigation/fm_3-25.26_map_reading_and_land_navigation.pdf|FM 3-25.26 Map Reading and Land Navigation|navigation"
"https://archive.org/download/milmanual-fm-23-10-sniper-training/fm_23-10_sniper_training.pdf|FM 23-10 Sniper Training|defense"
"https://archive.org/download/milmanual-fm-21-150-combatives/fm_21-150_combatives.pdf|FM 21-150 Combatives|defense"
"https://archive.org/download/milmanual-fm-3-06.11-combined-arms-operations-in-urban-terrain/fm_3-06.11_combined_arms_operations_in_urban_terrain.pdf|FM 3-06.11 Combined Arms Urban Terrain|defense"
"https://armypubs.army.mil/epubs/DR_pubs/DR_a/ARN36119-TC_3-22.9-000-WEB-1.pdf|TC 3-22.9 Rifle and Carbine Marksmanship (v2)|defense"
)
ok=0; fail=0; total=${#PDFS[@]}; i=0
for entry in "${PDFS[@]}"; do
i=$((i+1))
IFS='|' read -r url title tag <<< "$entry"
fname=$(echo "$title" | tr -c 'A-Za-z0-9._-' '_' | head -c 100).pdf
out="$TMP/$fname"
echo ""
echo "==> [$i/$total] [$title]"
if wget -q --show-progress -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" --tries=2 --timeout=60 -O "$out" "$url"; then
sz_bytes=$(stat -c%s "$out" 2>/dev/null || echo 0)
if [ "$sz_bytes" -gt 100000 ] && file "$out" | grep -qi "PDF"; then
sz=$(du -h "$out" | cut -f1)
curl -s -X POST "$API" -F "file=@$out;type=application/pdf" -F "title=$title" -F "tag=$tag" > /dev/null
echo " ✓ uploaded ($sz)"
ok=$((ok+1))
else
echo " ✗ skipped (not a valid PDF, $sz_bytes bytes)"
fail=$((fail+1))
fi
rm -f "$out"
else
echo " ✗ download failed"
fail=$((fail+1))
fi
done
echo ""
echo "================================"
echo "OK: $ok Failed: $fail Total: $total"
echo "================================"
rm -rf "$TMP"
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
set -u
TMP=/tmp/pdf-seed-fix2
API=http://localhost/pdfs/api/upload
mkdir -p "$TMP"
PDFS=(
"https://archive.org/download/fm21-150-combatives/fm21-150-combatives.pdf|FM 21-150 Combatives (1992)|defense"
"https://archive.org/download/WhereThereIsNoDoctor-English-DavidWerner/WhereThereIsNoDoctor-English-DavidWerner.pdf|Where There Is No Doctor (Hesperian)|medical"
"https://www.globalsecurity.org/military/library/policy/army/fm/3-22-9/fm3-22-9_c1_2011.pdf|FM 3-22.9 Rifle Marksmanship M16/M4 (2011)|defense"
)
ok=0; fail=0; total=${#PDFS[@]}; i=0
for entry in "${PDFS[@]}"; do
i=$((i+1))
IFS='|' read -r url title tag <<< "$entry"
fname=$(echo "$title" | tr -c 'A-Za-z0-9._-' '_' | head -c 100).pdf
out="$TMP/$fname"
echo ""
echo "==> [$i/$total] [$title]"
if wget -q --show-progress -U "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" --tries=2 --timeout=120 -O "$out" "$url"; then
sz_bytes=$(stat -c%s "$out" 2>/dev/null || echo 0)
if [ "$sz_bytes" -gt 100000 ] && file "$out" | grep -qi "PDF"; then
sz=$(du -h "$out" | cut -f1)
curl -s -X POST "$API" -F "file=@$out;type=application/pdf" -F "title=$title" -F "tag=$tag" > /dev/null
echo " ✓ uploaded ($sz)"
ok=$((ok+1))
else
echo " ✗ skipped (not a valid PDF, $sz_bytes bytes)"
fail=$((fail+1))
fi
rm -f "$out"
else
echo " ✗ download failed"
fail=$((fail+1))
fi
done
echo ""
echo "================================"
echo "OK: $ok Failed: $fail Total: $total"
echo "================================"
rm -rf "$TMP"
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
# Reference PDF Library - Full SHTF/Prep Collection
set -u
TMP=/tmp/pdf-seed
API=http://localhost/pdfs/api/upload
mkdir -p "$TMP"
PDFS=(
# ========== MEDICAL - HESPERIAN (full books from archive.org mirror) ==========
"https://archive.org/download/strategic_intelligence_network/medical/hesperian_-_where_there_is_no_doctor_2017_full_book.pdf|Where There Is No Doctor (Hesperian 2017)|medical"
"https://archive.org/download/strategic_intelligence_network/medical/hesperian_-_where_there_is_no_dentist_2012_full_book.pdf|Where There Is No Dentist (Hesperian)|medical"
"https://archive.org/download/strategic_intelligence_network/medical/hesperian_-_a_book_for_the_midwives_2013_full_book.pdf|A Book for Midwives (Hesperian)|medical"
"https://archive.org/download/strategic_intelligence_network/medical/hesperian_-_disabled_village_children_2009_full_book.pdf|Disabled Village Children (Hesperian)|medical"
"https://archive.org/download/strategic_intelligence_network/medical/hesperian_-_helping_children_who_are_deaf_2004_full_book.pdf|Helping Children Who Are Deaf (Hesperian)|medical"
"https://archive.org/download/strategic_intelligence_network/medical/hesperian_-_where_women_have_no_doctor_full_book.pdf|Where Women Have No Doctor (Hesperian)|medical"
# ========== MEDICAL - MILITARY / TACTICAL ==========
"https://tccc.org.ua/files/downloads/clinical-guidelines-2024-en.pdf|TCCC Guidelines 2024 (Tactical Combat Casualty Care)|medical"
"https://archive.org/download/Fm4-25.11FirstAid/Fm4-25.11FirstAid.pdf|FM 4-25.11 First Aid|medical"
"https://archive.org/download/FM21-10_2000/FM21-10_2000.pdf|FM 21-10 Field Hygiene and Sanitation (2000)|medical"
# ========== SURVIVAL ==========
"https://trueprepper.com/wp-content/uploads/2022/11/FM-21-76-US-Army-Survival-Manual.pdf|FM 21-76 US Army Survival Manual (1992)|survival"
"https://archive.org/download/MManuals/Fm3-05.70Survival.pdf|FM 3-05.70 Survival (2002)|survival"
"https://www.globalsecurity.org/military/library/policy/army/fm/21-76-1/fm_21-76-1survival.pdf|FM 21-76-1 Survival Evasion Recovery|survival"
# ========== MILITARY / DEFENSE ==========
"https://armypubs.army.mil/epubs/DR_pubs/DR_a/pdf/web/ARN36119_TC%203-22x9%20FINAL%20WEB.pdf|TC 3-22.9 Rifle and Carbine Marksmanship|defense"
"https://armypubs.army.mil/epubs/DR_pubs/DR_a/pdf/web/ARN20697_FM%203-06%20FINAL%20WEB%201.pdf|FM 3-06 Urban Operations|defense"
"https://armypubs.army.mil/epubs/DR_pubs/DR_a/pdf/web/atp3_21x8.pdf|ATP 3-21.8 Infantry Platoon and Squad|defense"
"https://www.bits.de/NRANEU/others/amd-us-archive/fm5-31%2865%29.pdf|FM 5-31 Boobytraps|defense"
# ========== NAVIGATION ==========
"https://www.globalsecurity.org/military/library/policy/army/fm/3-25-26/fm3-25-26.pdf|FM 3-25.26 Map Reading and Land Navigation|navigation"
"https://navlist.net/imgx/Bowditch-2017-v1.pdf|Bowditch American Practical Navigator V1 (2017)|navigation"
"https://navlist.net/imgx/Bowditch-2017-v2.pdf|Bowditch American Practical Navigator V2 (2017)|navigation"
# ========== COMMUNICATIONS ==========
"https://www.cisa.gov/sites/default/files/2024-12/NIFOG%202.02_508%20FINAL%20VERSION%2012%2003%202024.pdf|NIFOG 2.02 - National Interop Field Ops Guide|comms"
"https://www.qsl.net/w/wb4bxw/books/ARRL_Ham_Radio_License_Manual.pdf|ARRL Ham Radio License Manual (Technician)|comms"
"https://www.govinfo.gov/content/pkg/CFR-2024-title47-vol5/pdf/CFR-2024-title47-vol5-part97.pdf|FCC Part 97 Amateur Radio Service Rules (2024)|comms"
# ========== PREPAREDNESS ==========
"https://www.ready.gov/sites/default/files/2021-11/are-you-ready-guide.pdf|FEMA Are You Ready Guide|preparedness"
# ========== FOOD PRESERVATION ==========
"https://archive.org/download/usda-complete-guide-to-home-canning-2015-revision/USDA-Complete-Guide-to-Home-Canning-2015-revision.pdf|USDA Complete Guide to Home Canning 2015|food"
# ========== TRADES / REPAIR (Audel, vintage public domain) ==========
"https://archive.org/download/audel.carpenter.no1.1923/audel.carpenter.no1.1923.pdf|Audels Carpenters and Builders Guide Vol 1 (1923)|repair"
# ========== ENGINEERING / SHOP ==========
"https://archive.org/download/machineryshandbo00indu/machineryshandbo00indu.pdf|Machinery's Handbook 6th Edition (1924)|engineering"
)
ok=0; fail=0; total=${#PDFS[@]}
i=0
for entry in "${PDFS[@]}"; do
i=$((i+1))
IFS='|' read -r url title tag <<< "$entry"
fname=$(echo "$title" | tr -c 'A-Za-z0-9._-' '_' | head -c 100).pdf
out="$TMP/$fname"
echo ""
echo "==> [$i/$total] [$title]"
echo " URL: $url"
if wget -q --show-progress -U "Mozilla/5.0" --tries=2 --timeout=60 -O "$out" "$url"; then
if [ -s "$out" ] && file "$out" | grep -qi "PDF"; then
sz=$(du -h "$out" | cut -f1)
curl -s -X POST "$API" -F "file=@$out;type=application/pdf" -F "title=$title" -F "tag=$tag" > /dev/null
echo " ✓ uploaded ($sz)"
ok=$((ok+1))
else
echo " ✗ skipped (not a valid PDF)"
fail=$((fail+1))
fi
rm -f "$out"
else
echo " ✗ download failed"
fail=$((fail+1))
fi
done
echo ""
echo "================================"
echo "OK: $ok Failed: $fail Total: $total"
echo "================================"
rm -rf "$TMP"