feat: ADD 'new' function to generate new instance to avoid git clone #1
This commit is contained in:
parent
8e91571121
commit
0cbd56b689
2 changed files with 119 additions and 13 deletions
17
Dockerfile
17
Dockerfile
|
|
@ -15,6 +15,16 @@ RUN python3 -m pip install --no-cache-dir -r /tmp/requirements.txt --break-syste
|
|||
# Copy Python script to /usr/local/bin so it survives volume mounts
|
||||
COPY picopaper.py /usr/local/bin/picopaper.py
|
||||
|
||||
# Bake the default blog content into the image. The `new` command copies these
|
||||
# into the working directory to scaffold a fresh blog without cloning the repo.
|
||||
# Only user content is included — not picopaper.py/requirements.txt/.gitignore.
|
||||
ENV PICOPAPER_SKELETON=/usr/local/share/picopaper/skeleton
|
||||
COPY config.py /usr/local/share/picopaper/skeleton/
|
||||
COPY theme/ /usr/local/share/picopaper/skeleton/theme/
|
||||
COPY items/ /usr/local/share/picopaper/skeleton/items/
|
||||
COPY images/ /usr/local/share/picopaper/skeleton/images/
|
||||
COPY static/ /usr/local/share/picopaper/skeleton/static/
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
|
|
@ -24,5 +34,8 @@ ENV PYTHONPATH=/app
|
|||
# Run as root to allow writing to mounted volumes
|
||||
# (The mounted volume will have host user permissions)
|
||||
|
||||
# Generate the site on container start
|
||||
CMD ["python3", "/usr/local/bin/picopaper.py"]
|
||||
# With no command the site is generated; pass `new` to scaffold a blog instead:
|
||||
# podman run ... picopaper:latest -> build the site (default)
|
||||
# podman run ... picopaper:latest new -> create a new blog in the current dir
|
||||
ENTRYPOINT ["python3", "/usr/local/bin/picopaper.py"]
|
||||
CMD []
|
||||
|
|
|
|||
115
picopaper.py
115
picopaper.py
|
|
@ -2,22 +2,32 @@
|
|||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import markdown
|
||||
from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_SECTIONS_FROM_MAIN,
|
||||
NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH,
|
||||
ENABLE_RSS_FEED, RSS_FEED_PATH,
|
||||
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS,
|
||||
BLOGROLL_PATH, ROOT_PAGE)
|
||||
# Blog configuration lives in config.py in the working directory. It is absent
|
||||
# when scaffolding a new blog into an empty directory (the `new` command), so a
|
||||
# missing config.py must not stop this module from loading — the build command
|
||||
# checks CONFIG_AVAILABLE below and reports it clearly.
|
||||
try:
|
||||
from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_SECTIONS_FROM_MAIN,
|
||||
NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH,
|
||||
ENABLE_RSS_FEED, RSS_FEED_PATH,
|
||||
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS,
|
||||
BLOGROLL_PATH, ROOT_PAGE)
|
||||
CONFIG_AVAILABLE = True
|
||||
except ImportError:
|
||||
CONFIG_AVAILABLE = False
|
||||
|
||||
# STRIP_IMAGE_EXIF is a newer setting; older config.py files may not define it.
|
||||
# Default to enabled so the privacy feature works without a config update.
|
||||
try:
|
||||
from config import STRIP_IMAGE_EXIF
|
||||
except ImportError:
|
||||
STRIP_IMAGE_EXIF = True
|
||||
if CONFIG_AVAILABLE:
|
||||
try:
|
||||
from config import STRIP_IMAGE_EXIF
|
||||
except ImportError:
|
||||
STRIP_IMAGE_EXIF = True
|
||||
|
||||
# Raster image formats whose metadata (EXIF/GPS/XMP/text chunks) is stripped on copy
|
||||
STRIP_IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.tiff', '.tif', '.bmp'}
|
||||
|
|
@ -543,9 +553,92 @@ class SSGGGenerator:
|
|||
|
||||
print(f"\n✓ Site generated successfully in {self.output_dir}/")
|
||||
|
||||
# Location of the default project files baked into the container image.
|
||||
DEFAULT_SKELETON_DIR = '/usr/local/share/picopaper/skeleton'
|
||||
|
||||
# The blog content that `new` scaffolds. Only these are copied — the CLI itself
|
||||
# (picopaper.py), requirements.txt and .gitignore belong to the tool/image, not
|
||||
# the user's blog, so they are intentionally excluded.
|
||||
SCAFFOLD_ITEMS = ('config.py', 'theme', 'items', 'images', 'static')
|
||||
|
||||
|
||||
def scaffold_new(target_dir='.'):
|
||||
"""Create a new picopaper blog in target_dir by copying the default files.
|
||||
|
||||
The source ("skeleton") defaults to the path baked into the container image
|
||||
and can be overridden with the PICOPAPER_SKELETON environment variable.
|
||||
Existing files in the target are never overwritten.
|
||||
"""
|
||||
import shutil
|
||||
|
||||
skeleton = Path(os.environ.get('PICOPAPER_SKELETON', DEFAULT_SKELETON_DIR))
|
||||
target = Path(target_dir)
|
||||
|
||||
if not skeleton.is_dir():
|
||||
print(f"Error: default project files not found at {skeleton}")
|
||||
print("Run this inside the picopaper container, or point PICOPAPER_SKELETON "
|
||||
"at a picopaper source directory.")
|
||||
return 1
|
||||
|
||||
created, skipped = [], []
|
||||
for name in SCAFFOLD_ITEMS:
|
||||
source = skeleton / name
|
||||
if not source.exists():
|
||||
continue
|
||||
dest = target / name
|
||||
if dest.exists():
|
||||
# Never overwrite the user's files — leave anything already there.
|
||||
skipped.append(name)
|
||||
continue
|
||||
if source.is_dir():
|
||||
shutil.copytree(source, dest)
|
||||
else:
|
||||
shutil.copy2(source, dest)
|
||||
created.append(name)
|
||||
|
||||
if not created:
|
||||
print("Nothing to create — this directory already contains a picopaper blog.")
|
||||
return 0
|
||||
|
||||
print("✓ Created a new picopaper blog in the current directory:")
|
||||
for name in created:
|
||||
print(f" {name}")
|
||||
if skipped:
|
||||
print("\nLeft untouched (already present):")
|
||||
for name in skipped:
|
||||
print(f" {name}")
|
||||
|
||||
print("\nNext steps:")
|
||||
print(" 1. Edit config.py (blog title, description, navbar).")
|
||||
print(" 2. Add or edit Markdown posts in items/.")
|
||||
print(" 3. Build the site by running the container with no command:")
|
||||
print(" podman run --rm --userns=keep-id -v $(pwd):/app git.uphillsecurity.com/cf7/picopaper:latest")
|
||||
print("\nYour generated site will appear in output/.")
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
generator = SSGGGenerator()
|
||||
generator.generate()
|
||||
args = sys.argv[1:]
|
||||
command = args[0] if args else 'build'
|
||||
|
||||
if command in ('build', 'generate'):
|
||||
if not CONFIG_AVAILABLE:
|
||||
print("Error: no config.py found in the current directory.")
|
||||
print("Run 'picopaper.py new' to scaffold a blog first, then edit config.py.")
|
||||
sys.exit(1)
|
||||
generator = SSGGGenerator()
|
||||
generator.generate()
|
||||
elif command == 'new':
|
||||
sys.exit(scaffold_new())
|
||||
elif command in ('help', '-h', '--help'):
|
||||
print("Usage: picopaper.py [command]\n")
|
||||
print("Commands:")
|
||||
print(" build Generate the site from the current directory (default)")
|
||||
print(" new Scaffold a new picopaper blog into the current directory")
|
||||
else:
|
||||
print(f"Unknown command: {command!r}")
|
||||
print("Usage: picopaper.py [build|new] (run 'picopaper.py help' for details)")
|
||||
sys.exit(2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue