- Python 82.7%
- Astro 7.3%
- Jinja 4.1%
- TypeScript 3.8%
- Just 1.8%
- Other 0.2%
Found during a real visual walkthrough (not just tests): toggling light/dark after already having a preview open left the iframe on whichever theme it happened to load with, since it only reads localStorage once, at load time. Since the iframe is same-origin, the toggle can just flip its data-theme attribute directly, instantly -- no reload or re-fetch needed. |
||
|---|---|---|
| .claude/skills/verify | ||
| .deepseek/state | ||
| docs | ||
| editor | ||
| meta | ||
| migration | ||
| scripts | ||
| site | ||
| .env.example | ||
| .gitignore | ||
| .mise.toml | ||
| .python-version | ||
| config.toml.example | ||
| justfile | ||
| README.md | ||
Restiamo Animali Migration
Offline tooling and planning for migrating the Restiamo Animali WordPress site to a static site.
Documents
docs/plan.md: actionable migration plan and phase gates.docs/instructions.md: original migration instructions and constraints.docs/content-contract.md: versioned static export contract.docs/chatgpt-handoff.md: shareable project status summary.
Generated reports live outside the repository:
/mnt/data/restiamoanimali/reports/audit.md: generated WordPress export audit.
Migration Tooling
The migration package lives in migration/ and is Python 3.14-only.
Tool Roles
miseinstalls and selects the outer command-line tools declared in.mise.toml: Python,uv, andjust.uvmanages the Python package environment inmigration/, usingmigration/pyproject.tomlandmigration/uv.lock.uv.lockpins Python development tools such asruff,mypy,pylint, andpytest.justis the task runner. The rootjustfilecontains the common project commands, runs them from the correct directory, and loads.envvia its built-indotenv-loadsetting.scripts/is a small Python 3.14 uv project for operational helpers. The FTP recipes call itsra-ftpcommand, which validates environment variables and runslftp.
After mise activate is set up in your shell (one-time), just, python,
and uv resolve to the mise-pinned versions automatically and just loads
.env itself. The mise exec -- prefix is only needed during bootstrap
before activation is in place. Do not call a globally installed ruff,
mypy, pylint, or pytest directly for this project.
lftp (>= 4.8.0) is required for FTP backup commands. Install it with your
system package manager. The ftp-check recipe verifies the version.
mariadb-dump or mysqldump is required for database export. On Arch-based
systems, install it with paru -S mariadb-clients or the equivalent package
manager command.
Bootstrap
From a fresh clone:
cd restiamoanimali
mise trust
mise install
cp .env.example .env
mise exec -- just sync
mise exec -- just doctor
mise exec -- just check
What these commands do:
mise trustallows mise to evaluate this repository's.mise.toml, including the local.envloader.mise installinstalls the pinned Python,uv, andjustversions if they are not already available.cp .env.example .envcreates the local credential file. Fill it only when you need FTP access or other machine-local secrets.mise exec -- just synccreates or updatesmigration/.venvfrommigration/uv.lockandscripts/.venvfromscripts/uv.lock.mise exec -- just doctorprints the resolved versions of Python,uv,just,ruff,mypy,pylint, andpytest.mise exec -- just checkruns formatting checks, linting, type checking, pylint, and tests.
Local credentials (.env)
Use .env for local credentials such as FTP_HOST, FTP_USER, and
FTP_PASSWORD. The justfile has dotenv-load enabled, so normal
just ... commands read .env directly. The [env] section in .mise.toml
also points to .env, so bootstrap commands that still use
mise exec -- just ... inherit the same variables.
The real .env is listed in .gitignore and must never be committed;
keep only .env.example in Git.
Tool configuration (config.toml)
config.toml is the local source of truth for non-secret project settings:
local data paths, the default WordPress export path, remote WordPress paths,
FTP transfer tuning, and optional audio upload destinations ([[ra_upload.ftp]],
[[ra_upload.scp]]; see config.toml.example). The real config.toml is
listed in .gitignore so it stays local and is never committed; keep only
config.toml.example in Git.
From a fresh clone:
cp config.toml.example config.toml
For the current hosting layout, wordpress.remote_site_root is
/www.restiamoanimali.it, while wordpress.remote_wordpress_root includes the
blog subdirectory: /www.restiamoanimali.it/blog.
If the FTP server presents a legacy certificate whose hostname does not match
the host in .env, set ftp.ssl_verify = false locally in config.toml.
If listings or transfers stall after login, plain FTP is usually the reliable
fallback for this legacy Aruba hosting; the example config already uses that.
Configuration is resolved in this order (highest priority wins):
--data-rootCLI flag--config path/to/config.tomlCLI flagRA_DATA_ROOTenvironment variableconfig.tomlat the repository root- Built-in default:
/mnt/data/restiamoanimali
When --config is used, repository config.toml discovery is skipped.
Run tests:
just test
Format code:
just format
Run the full maintenance suite:
just check
Generate the current audit:
just audit
Generate the first static export artifacts:
just export
This writes /mnt/data/restiamoanimali/export/ with versioned JSON sidecars,
Markdown content, copied WXR attachment and local audio files, and
reports/export-blockers.md. The strict recipe exits non-zero when blockers
are present. To deliberately write a snapshot while keeping the blockers for
inspection:
just export-allow-blockers
Verify the generated export before building a static site:
just verify-export
Generate a recovery map from original WordPress audio filenames to normalized export filenames:
just audio-map
This writes /mnt/data/restiamoanimali/reports/audio-filename-map.csv and
/mnt/data/restiamoanimali/reports/audio-filename-map.md.
Build the Astro static site from the verified export:
just site-build
site-build leaves /media/... as external references. This avoids copying
the multi-gigabyte media archive into site/dist/; deployment should publish
or alias /mnt/data/restiamoanimali/export/media at /media.
Run the local Astro development server:
just site-dev
site-dev creates a local site/public/media symlink to the exported media
tree so images and audio work while browsing the development server.
Run the same server on the local network for real phone/tablet testing:
just site-dev-lan
Open http://<computer-lan-ip>:4321/ on a device connected to the same
hotspot or Wi-Fi network. Find the computer address with ip addr and use the
address on the active Wi-Fi/hotspot interface, usually 192.168.x.x or
10.x.x.x. This exposes the development server only on the local network; use
the VPS only for shareable previews or deployment testing.
Check FTP credentials and remote paths:
just ftp-check
just ftp-ls
just ftp-ls-path /www.restiamoanimali.it/blog/wp-content/uploads/audio
Mirror WordPress files from FTP into the local backup area:
just mirror-wp-content
just mirror-wordpress-root
The mirror-wp-content recipe downloads
wordpress.remote_wp_content to paths.data_root/backup/wp-content.
The mirror-wordpress-root recipe downloads the WordPress installation root to
paths.data_root/backup/wordpress-root, excluding wp-content so the
large media/plugin/theme tree is mirrored by the dedicated resumable recipe.
These backup directories are outside Git because they are large, reproducible,
and may contain secrets such as wp-config.php.
The FTP orchestration lives in the Python scripts/ project, but transfers are
still delegated to lftp. That keeps recursive mirroring, resume support,
parallel transfers, retry behavior, timestamp handling, and old FTP server
compatibility in a mature external tool. Python's standard ftplib and curl
are useful for individual FTP operations, but they do not provide a complete
resumable directory mirror by themselves.
Dump the WordPress database:
just database-check
just database-dump
This reads database connection details from
paths.data_root/backup/wordpress-root/wp-config.php and writes
paths.data_root/backup/database.sql. The database password is passed through a
temporary MySQL defaults file, not through command-line arguments or Git.
Generate the shareable WordPress environment inventory:
just inventory-backup
This writes paths.data_root/backup/environment.md and
paths.data_root/backup/wp-config.redacted.php. The inventory records the
WordPress version, active theme, installed themes, and installed plugins from
the local backup. The redacted config removes database credentials and salts.
The PHP runtime version is not recoverable from the static backup and must be
recorded from the hosting panel or WordPress admin when needed.
Update backup metadata:
just metadata-backup
This writes paths.data_root/backup/metadata.md with the configured paths,
snapshot sizes, file counts, audio count, checksum target count, commands used,
and known missing backup items. It does not include FTP credentials.
Update deterministic backup checksums:
just checksum-backup
This writes paths.data_root/backup/SHA256SUMS from the configured backup root.
The checksum file itself is excluded from the generated list. Run
metadata-backup before checksum-backup when both are needed, so
metadata.md is included in the checksum snapshot.
Remove temporary FTP artifacts only when they are no longer needed:
just ftp-rm '/www.restiamoanimali.it/blog/wp-content.zip.*'
ftp-rm is destructive on the production server. It refuses patterns that
would match the whole WordPress root or wp-content, but it still passes the
given pattern to FTP after that guard.
Upload audio files to configured destinations:
just audio-upload-archive
just audio-upload-ftp
just audio-upload-scp
just audio-upload-all
These read export/data/audio.json and export/data/posts.json (run
just export first) and upload each MP3 via scripts/src/ra_scripts/ra_upload.
audio-upload-archive uploads to archive.org (rate-limited to ~2/minute,
resumable — already-uploaded items are skipped). audio-upload-ftp and
audio-upload-scp use destinations configured under [[ra_upload.ftp]] /
[[ra_upload.scp]] in config.toml; an unconfigured destination is skipped
with a log message, not an error. audio-upload-all runs archive + ftp + scp.
Dropbox upload exists but has no just recipe and needs DROPBOX_TOKEN in
.env; run it directly with uv run ra-upload dropbox from scripts/.
Background variants that log to /tmp and detach:
just audio-upload-archive-bg
just audio-upload-scp-bg
Push to the configured forge remote:
just push
The push recipe uses $GIT_SSH_COMMAND when it is already set. Otherwise it
defaults to ssh -F $HOME/.ssh/config, which keeps the SSH configuration
machine-local.
The default audit input is
/mnt/data/restiamoanimali/backup/restiamoanimali.WordPress.2026-07-02.xml.
The default generated report is /mnt/data/restiamoanimali/reports/audit.md.
Generated reports and backup data intentionally live outside the repository.