Posti Forge is a post‑install automation designer. It lets you:
- ✅ Define profiles for different targets (e.g. Fedora, Ubuntu, kiosks, Windows etc).
- ✅ Compose ordered steps (commands with optional descriptions and confirmation gates).
- ✅ Preview the generated
posti.pyrunner script with a CRT‑style console UI. - ✅ Save each project in one canonical Python file with a ten-save undo history.
This repository contains a full web‑based designer and a backend builder wrapped in a single container.
- Profile management
- Multiple profiles with custom labels.
- Inline Add/Edit/Remove actions and safe delete confirmation.
- Step composer
- Create sub-steps command chain by
&& - Title, description, command, optional “Require confirmation” toggle.
- Multi‑selection of steps with Ctrl/Shift and bulk enable/disable actions.
- Clone, reorder via controls and enable/disable per step.
- Create sub-steps command chain by
- Posti script preview
- Live preview of the generated runner with Python syntax highlighting.
- “Generate preview” and “Copy to clipboard” actions.
- Project persistence
- The Profiles panel stays locked until a named project is created or loaded.
- Confirming a new project name immediately creates
workstation_posti.py. - “Save project” updates that file and retains the previous ten saved states.
- “Load project” lists the scripts stored under
data/projects; no local file picker is used.
- Binary builds
- “Download Python” exports the current editor state as
project-name_posti.py. - One‑click “Build Binary” invokes PyInstaller in the backend.
- Versioned binaries are stored under
data/generated_binaryand downloaded to the browser.
- “Download Python” exports the current editor state as
- PWA support
- Installable as a Progressive Web App with a manifest, icons and a service worker for offline shell.
Posti 2.1 runs as a non-root user and requires a numeric application PIN with at least four digits. Start by preparing the configuration and data directories:
cp .env.example .env
sed -i "s/^POSTI_UID=.*/POSTI_UID=$(id -u)/" .env
sed -i "s/^POSTI_GID=.*/POSTI_GID=$(id -g)/" .env
# Replace POSTI_APP_PIN in .env with your numeric PIN (at least four digits).
mkdir -p data/projects data/generated_binary
sudo chown -R "$(id -u):$(id -g)" dataThe supplied production Compose configuration is equivalent to:
services:
posti:
image: ghcr.io/pbuzdygan/posti:latest
container_name: posti
restart: unless-stopped
user: "${POSTI_UID:-1000}:${POSTI_GID:-1000}"
environment:
HOME: /tmp
POSTI_APP_PIN: "${POSTI_APP_PIN:?Set POSTI_APP_PIN in .env}"
ports:
- "127.0.0.1:${POSTI_PORT:-8012}:8000"
volumes:
- ./data:/app/data
read_only: true
tmpfs:
- /tmp:size=512m,mode=1777
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
pids_limit: 256
mem_limit: 2g
cpus: 2.0From the project root:
docker compose up -dThen open the UI in your browser:
- http://localhost:8012/ (or the host/port you configured)
The default docker-compose.yml maps:
./dataon the host →/app/datainside the container.
This folder is used for persistence (see below).
The application starts on a full-screen PIN login. A successful login creates an HTTP-only browser session; the designer and protected API remain unavailable before login.
The port is deliberately bound to localhost. If Posti must be reachable from another machine, put it behind an authenticated HTTPS reverse proxy or a VPN; do not publish the builder directly to an untrusted network.
Under the bind‑mounted data/ directory the backend expects:
data/projects– the project library and source of truth used by Save project and Load project, with names such asworkstation_posti.py. The hidden.historysubdirectory retains at most ten previous saves per project.data/generated_binary– built binaries (from Build Binary).
Create these subfolders on the host before first start and make them writable by
the configured POSTI_UID:POSTI_GID. Startup now fails clearly if persistence is
not writable instead of silently running with broken server-side saves.
Binary artifacts and saved scripts are marked executable (0755) where the filesystem/ACLs allow it.
Stop Posti and change the existing data tree once:
docker compose down
sudo chown -R "$(grep '^POSTI_UID=' .env | cut -d= -f2):$(grep '^POSTI_GID=' .env | cut -d= -f2)" data
docker compose up -dFor CIFS/NFS/NAS mounts, ownership may instead be controlled by share mount
options or ACLs. Align uid, gid, file_mode, and dir_mode with .env.
POSTI_APP_PIN— required numeric PIN containing at least four digits.POSTI_SESSION_TTL_SECONDS— login lifetime; default 12 hours.POSTI_COOKIE_SECURE— set totruewhen Posti is available over HTTPS.POSTI_MAX_SCRIPT_BYTES— maximum script size; default 1 MiB.POSTI_BUILD_TIMEOUT_SECONDS— PyInstaller timeout; default 180 seconds.POSTI_BUILD_QUEUE_TIMEOUT_SECONDS— second build wait time; default 2 seconds.POSTI_BUILD_TEMP_MAX_AGE_SECONDS— stale build cleanup age; default 24 hours.POSTI_CORS_ORIGINS— explicit comma-separated origins; disabled by default.
Only one binary build is admitted at a time. Compose also limits Posti to 2 CPUs, 2 GiB of RAM and 256 processes; these values can be adjusted for larger builds. Because a short numeric PIN has limited entropy, expose Posti only through HTTPS and a trusted LAN, VPN, or authenticated reverse proxy. Five failed attempts from one client temporarily block further login attempts.
- Create or load a project
- Enter a name above Active profile and click Create project. Posti immediately
creates
project-name_posti.py; unsafe filename characters are replaced automatically. - New project clears the current workspace and returns to this naming step.
- Alternatively, click Load project and choose a saved script from
data/projects. - Older
posti_vX.Y.pyscripts already present in that directory remain loadable. - Profile controls remain dimmed and unavailable until creation or loading succeeds.
- Enter a name above Active profile and click Create project. Posti immediately
creates
- Create a profile
- In the Profiles panel, click Add, name your profile and confirm.
- These controls become available after the project file has been created or loaded.
- Compose steps
- Use the Step composer to add steps with a title, description and command.
- Toggle Confirm if a step should require confirmation at runtime.
- Preview posti.py
- In the posti.py preview panel, click Generate preview.
- Review the script; use Copy to clipboard if you want to paste it elsewhere.
- Save project
- Click Save project in the Operations panel.
- The app updates
project-name_posti.pyand retains up to ten earlier saves. - Use Undo last save to restore those states one at a time.
- The server copy is the source of truth and is opened later through Load project.
- Download Python or build a binary
- Click Download Python to download the current editor state as
project-name_posti.pywithout changing the saved project or its undo history. - Click Build Binary to create a standalone executable from the current configuration.
- The
project-name_postibinary is stored indata/generated_binaryand downloaded to your browser.
- Click Download Python to download the current editor state as
Posti Forge is installable as a PWA:
- When conditions are met (served over HTTPS, supported browser), the app will show an Install banner.
- Alternatively, you can use your browser’s menu:
- Desktop Chromium: “Install app…”
- Android: “Add to Home screen”
Once installed, you get:
- Fullscreen, app‑like experience.
- Offline shell for the designer and
posti.pypreview (within the limits of cached resources).
For local development of the frontend only (outside the container):
-
Install Node.js (LTS) and pnpm/npm.
-
In
frontend/:npm install npm run dev
This starts Vite dev server (default http://localhost:5173/).
-
Ensure the backend is reachable. For cross-origin development, set
VITE_BUILDER_URLand add the frontend URL toPOSTI_CORS_ORIGINS. Production should retain the default same-origin/apiURL.
For most users, running via docker compose as described above is sufficient.
Container images are built and published only when a GitHub Release is published.
A normal push to main or dev runs tests and audits but never builds or
publishes a container image.
Select the intended branch as the release target and use its matching tag format:
- a release targeting
mainusesx.x.xand publisheslatestplus that version (for example,latestand2.1.0); - a release targeting
devusesdevx.x.xand publishesdev_latestplus that version (for example,dev_latestanddev2.1.0).
The publishing workflow rejects other release targets and version formats. It also verifies that the released commit belongs to the selected branch before uploading either image tag.


