Add footer with CHNS.tech credit and Buy Me a Coffee button

Adds a green footer (matching nav colour) to all authenticated pages
with a "Created by: CHNS.tech" link and a styled BMC button. CSP updated
to allow buymeacoffee CDN domains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-23 22:50:30 -07:00
parent d2afdc4ea3
commit 4172b63dc2
9 changed files with 105 additions and 8 deletions

View File

@@ -44,17 +44,24 @@ A self-hosted, multi-user web app for backyard chicken keepers to track egg prod
cp .env.example .env cp .env.example .env
``` ```
Key variables: Required variables:
| Variable | Description | | Variable | Description |
|----------|-------------| |----------|-------------|
| `MYSQL_ROOT_PASSWORD` | MySQL root password — generate with `openssl rand -hex 16` | | `MYSQL_ROOT_PASSWORD` | MySQL root password — generate with `openssl rand -hex 16` |
| `MYSQL_DATABASE` | Database name (default: `eggtracker`) |
| `MYSQL_USER` | MySQL app username |
| `MYSQL_PASSWORD` | MySQL app user password | | `MYSQL_PASSWORD` | MySQL app user password |
| `ADMIN_USERNAME` | Username for the built-in admin account | | `ADMIN_USERNAME` | Username for the built-in admin account |
| `ADMIN_PASSWORD` | Password for the built-in admin account | | `ADMIN_PASSWORD` | Password for the built-in admin account |
| `JWT_SECRET` | JWT signing secret — generate with `openssl rand -hex 32` | | `JWT_SECRET` | JWT signing secret — generate with `openssl rand -hex 32` |
| `SECURE_COOKIES` | Set to `true` (default) when behind HTTPS; `false` for local HTTP testing only |
| `ALLOWED_ORIGINS` | Comma-separated list of external origins allowed to call the API. Leave empty if accessed only through the bundled nginx frontend. | Optional variables (with defaults):
| Variable | Default | Description |
|----------|---------|-------------|
| `SECURE_COOKIES` | `true` | Set to `false` for local HTTP testing only; leave `true` when behind HTTPS |
| `ALLOWED_ORIGINS` | *(empty)* | Comma-separated list of external origins allowed to call the API. Leave empty if accessed only through the bundled nginx frontend. |
3. Start the stack: 3. Start the stack:
```bash ```bash
@@ -109,6 +116,10 @@ The gear icon (⚙) in the top-right nav opens the Settings panel:
| SQL injection | SQLAlchemy ORM with parameterized queries throughout | | SQL injection | SQLAlchemy ORM with parameterized queries throughout |
| Container security | API runs as non-root user; all volume mounts read-only except database | | Container security | API runs as non-root user; all volume mounts read-only except database |
## Footer
All authenticated pages include a footer with a **Created by: [CHNS.tech](https://chns.tech)** credit and a Buy Me a Coffee button linking to [buymeacoffee.com/CHNS](https://www.buymeacoffee.com/CHNS).
## Migrating an Existing Install (pre-multi-user) ## Migrating an Existing Install (pre-multi-user)
If you have an existing single-user install, run the migration script before rebuilding: If you have an existing single-user install, run the migration script before rebuilding:
@@ -161,17 +172,26 @@ yolkbook/
│ └── Dockerfile │ └── Dockerfile
├── nginx/ ├── nginx/
│ ├── html/ # Frontend (HTML, CSS, JS) │ ├── html/ # Frontend (HTML, CSS, JS)
│ │ ├── login.html
│ │ ├── admin.html
│ │ ├── index.html # Dashboard │ │ ├── index.html # Dashboard
│ │ ├── log.html # Log Eggs + full collection history │ │ ├── log.html # Log Eggs + full collection history
│ │ ├── flock.html # Flock management
│ │ ├── budget.html # Budget / cost analysis
│ │ ├── summary.html # Monthly summary
│ │ ├── admin.html # Admin panel
│ │ ├── login.html
│ │ ├── 404.html
│ │ ├── 50x.html
│ │ ├── js/ │ │ ├── js/
│ │ │ ├── api.js # Shared fetch helpers │ │ │ ├── api.js # Shared fetch helpers
│ │ │ ├── auth.js # Auth utilities, nav, settings modal │ │ │ ├── auth.js # Auth utilities, nav, settings modal
│ │ │ ├── login.js # Login/register page logic │ │ │ ├── login.js # Login/register page logic
│ │ │ ├── admin.js # Admin panel │ │ │ ├── dashboard.js
│ │ │ ├── log.js # Egg logging interface
│ │ │ ├── history.js # Collection history table (used on log page) │ │ │ ├── history.js # Collection history table (used on log page)
│ │ │ ── dashboard.js │ │ │ ── flock.js
│ │ │ ├── budget.js
│ │ │ ├── summary.js
│ │ │ └── admin.js # Admin panel
│ │ └── css/style.css │ │ └── css/style.css
│ └── nginx.conf │ └── nginx.conf
├── mysql/ ├── mysql/

View File

@@ -77,6 +77,11 @@
</div> </div>
</main> </main>
<footer class="site-footer">
Created by: <a href="https://chns.tech" target="_blank" rel="noopener noreferrer">CHNS.tech</a>
<a href="https://www.buymeacoffee.com/CHNS" target="_blank" rel="noopener noreferrer" class="bmc-btn">🎉 Buy me an energy drink</a>
</footer>
<script src="/js/api.js?v=4"></script> <script src="/js/api.js?v=4"></script>
<script src="/js/auth.js?v=4"></script> <script src="/js/auth.js?v=4"></script>
<script src="/js/admin.js?v=4"></script> <script src="/js/admin.js?v=4"></script>

View File

@@ -122,6 +122,11 @@
</div> </div>
</main> </main>
<footer class="site-footer">
Created by: <a href="https://chns.tech" target="_blank" rel="noopener noreferrer">CHNS.tech</a>
<a href="https://www.buymeacoffee.com/CHNS" target="_blank" rel="noopener noreferrer" class="bmc-btn">🎉 Buy me an energy drink</a>
</footer>
<script src="/js/api.js?v=4"></script> <script src="/js/api.js?v=4"></script>
<script src="/js/auth.js?v=4"></script> <script src="/js/auth.js?v=4"></script>
<script src="/js/budget.js?v=4"></script> <script src="/js/budget.js?v=4"></script>

View File

@@ -23,6 +23,9 @@ body {
background: var(--bg); background: var(--bg);
color: var(--text); color: var(--text);
line-height: 1.5; line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
} }
a { color: var(--green); text-decoration: none; } a { color: var(--green); text-decoration: none; }
@@ -75,6 +78,8 @@ a:hover { text-decoration: underline; }
max-width: 980px; max-width: 980px;
margin: 0 auto; margin: 0 auto;
padding: 2rem 1.5rem; padding: 2rem 1.5rem;
flex: 1;
width: 100%;
} }
h1 { font-size: 1.6rem; margin-bottom: 1.5rem; } h1 { font-size: 1.6rem; margin-bottom: 1.5rem; }
@@ -414,3 +419,45 @@ td input[type="date"] {
padding: 0 0.25rem; padding: 0 0.25rem;
line-height: 1; line-height: 1;
} }
/* ── Footer ──────────────────────────────────────────────────────────────── */
.site-footer {
background: var(--green);
color: rgba(255,255,255,0.85);
padding: 1rem 1.5rem;
display: flex;
align-items: center;
justify-content: center;
gap: 1.25rem;
flex-wrap: wrap;
font-size: 0.9rem;
margin-top: auto;
}
.site-footer a {
color: #fff;
font-weight: 600;
text-decoration: none;
}
.site-footer a:hover {
text-decoration: underline;
}
.bmc-btn {
display: inline-block;
background: #d4850a;
color: #000 !important;
font-family: Arial, sans-serif;
font-size: 0.8rem;
font-weight: 700;
padding: 0.3rem 0.75rem;
border-radius: 6px;
border: 1px solid rgba(0,0,0,0.3);
white-space: nowrap;
text-decoration: none !important;
}
.bmc-btn:hover {
background: #c07609;
text-decoration: none !important;
}

View File

@@ -79,6 +79,11 @@
</div> </div>
</main> </main>
<footer class="site-footer">
Created by: <a href="https://chns.tech" target="_blank" rel="noopener noreferrer">CHNS.tech</a>
<a href="https://www.buymeacoffee.com/CHNS" target="_blank" rel="noopener noreferrer" class="bmc-btn">🎉 Buy me an energy drink</a>
</footer>
<script src="/js/api.js?v=4"></script> <script src="/js/api.js?v=4"></script>
<script src="/js/auth.js?v=4"></script> <script src="/js/auth.js?v=4"></script>
<script src="/js/flock.js?v=4"></script> <script src="/js/flock.js?v=4"></script>

View File

@@ -70,6 +70,11 @@
</main> </main>
<footer class="site-footer">
Created by: <a href="https://chns.tech" target="_blank" rel="noopener noreferrer">CHNS.tech</a>
<a href="https://www.buymeacoffee.com/CHNS" target="_blank" rel="noopener noreferrer" class="bmc-btn">🎉 Buy me an energy drink</a>
</footer>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" integrity="sha384-e6nUZLBkQ86NJ6TVVKAeSaK8jWa3NhkYWZFomE39AvDbQWeie9PlQqM3pmYW5d1g" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js" integrity="sha384-e6nUZLBkQ86NJ6TVVKAeSaK8jWa3NhkYWZFomE39AvDbQWeie9PlQqM3pmYW5d1g" crossorigin="anonymous"></script>
<script src="/js/api.js?v=4"></script> <script src="/js/api.js?v=4"></script>
<script src="/js/auth.js?v=4"></script> <script src="/js/auth.js?v=4"></script>

View File

@@ -83,6 +83,11 @@
</div> </div>
</main> </main>
<footer class="site-footer">
Created by: <a href="https://chns.tech" target="_blank" rel="noopener noreferrer">CHNS.tech</a>
<a href="https://www.buymeacoffee.com/CHNS" target="_blank" rel="noopener noreferrer" class="bmc-btn">🎉 Buy me an energy drink</a>
</footer>
<script src="/js/api.js?v=4"></script> <script src="/js/api.js?v=4"></script>
<script src="/js/auth.js?v=4"></script> <script src="/js/auth.js?v=4"></script>
<script src="/js/log.js?v=4"></script> <script src="/js/log.js?v=4"></script>

View File

@@ -66,6 +66,11 @@
</div> </div>
</main> </main>
<footer class="site-footer">
Created by: <a href="https://chns.tech" target="_blank" rel="noopener noreferrer">CHNS.tech</a>
<a href="https://www.buymeacoffee.com/CHNS" target="_blank" rel="noopener noreferrer" class="bmc-btn">🎉 Buy me an energy drink</a>
</footer>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="/js/api.js?v=4"></script> <script src="/js/api.js?v=4"></script>
<script src="/js/auth.js?v=4"></script> <script src="/js/auth.js?v=4"></script>

View File

@@ -33,7 +33,7 @@ http {
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always; add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self'; frame-ancestors 'none'" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.buymeacoffee.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.buymeacoffee.com https://img.buymeacoffee.com; connect-src 'self'; font-src 'self'; frame-ancestors 'none'" always;
# ── Static files ────────────────────────────────────────────────────── # ── Static files ──────────────────────────────────────────────────────
location / { location / {