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 Python script to /usr/local/bin so it survives volume mounts
|
||||||
COPY picopaper.py /usr/local/bin/picopaper.py
|
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
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
@ -24,5 +34,8 @@ ENV PYTHONPATH=/app
|
||||||
# Run as root to allow writing to mounted volumes
|
# Run as root to allow writing to mounted volumes
|
||||||
# (The mounted volume will have host user permissions)
|
# (The mounted volume will have host user permissions)
|
||||||
|
|
||||||
# Generate the site on container start
|
# With no command the site is generated; pass `new` to scaffold a blog instead:
|
||||||
CMD ["python3", "/usr/local/bin/picopaper.py"]
|
# 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 os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
import markdown
|
import markdown
|
||||||
from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_SECTIONS_FROM_MAIN,
|
# Blog configuration lives in config.py in the working directory. It is absent
|
||||||
NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH,
|
# when scaffolding a new blog into an empty directory (the `new` command), so a
|
||||||
ENABLE_RSS_FEED, RSS_FEED_PATH,
|
# missing config.py must not stop this module from loading — the build command
|
||||||
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS,
|
# checks CONFIG_AVAILABLE below and reports it clearly.
|
||||||
BLOGROLL_PATH, ROOT_PAGE)
|
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.
|
# 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.
|
# Default to enabled so the privacy feature works without a config update.
|
||||||
try:
|
if CONFIG_AVAILABLE:
|
||||||
from config import STRIP_IMAGE_EXIF
|
try:
|
||||||
except ImportError:
|
from config import STRIP_IMAGE_EXIF
|
||||||
STRIP_IMAGE_EXIF = True
|
except ImportError:
|
||||||
|
STRIP_IMAGE_EXIF = True
|
||||||
|
|
||||||
# Raster image formats whose metadata (EXIF/GPS/XMP/text chunks) is stripped on copy
|
# 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'}
|
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}/")
|
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():
|
def main():
|
||||||
generator = SSGGGenerator()
|
args = sys.argv[1:]
|
||||||
generator.generate()
|
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__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue