feat(anisette): self-hosted Apple anisette server for SideStore (infra #40)
Some checks failed
ci/woodpecker/push/default Pipeline failed
Some checks failed
ci/woodpecker/push/default Pipeline failed
Deploy a small stateless anisette-data server so the TripIt iOS Shell can be sideloaded with SideStore using a free Apple ID, without brokering the Apple-ID auth dance through a public third-party anisette server (which would see every login). SideStore points at a stable internal endpoint we control. - Image: Dadoum/anisette-v3-server, the de-facto standard anisette-v3 server for SideStore/AltStore. Upstream ships only a mutable :latest (no GitHub releases / semver / sha tags), so pinned by manifest digest instead of a tag per the "never :latest" rule. Pulled from DockerHub via the registry-VM pull-through cache like echo/cyberchef. Diun watches :latest (notify-only) so a new upstream build prompts a digest re-pin. - Stateless: emptyDir backs the provisioning-library cache dir (regenerable download; upstream issue #23 means it doesn't preserve client auth across restarts anyway) — no PVC, no Vault secret. - Internal-only endpoint http://anisette.viktorbarzin.lan (auth=none, allow_local_access_only, ssl_redirect off) — SideStore is a native client that can't do the Authentik cookie dance, same reasoning as android-emulator's adb. The .lan CNAME is auto-created by technitium-ingress-dns-sync; never publicly exposed. Mirrors the echo/networking-toolbox/android-emulator stack pattern. Service catalog updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fe1f8d62e7
commit
0bfa6f0774
4 changed files with 181 additions and 0 deletions
|
|
@ -42,6 +42,7 @@
|
|||
| webhook_handler | Webhook processing | webhook_handler |
|
||||
| tuya-bridge | Smart home bridge | tuya-bridge |
|
||||
| android-emulator | Shared Android 16 test emulator (adb 10.0.20.200:5555, noVNC android-emulator.viktorbarzin.lan) | android-emulator |
|
||||
| anisette | Self-hosted Apple anisette-data server (Dadoum/anisette-v3-server, digest-pinned) for sideloading the TripIt iOS Shell via SideStore; internal-only http://anisette.viktorbarzin.lan, auth=none, LAN-only, stateless | anisette |
|
||||
| dawarich | Location history | dawarich |
|
||||
| owntracks | Location tracking | owntracks |
|
||||
| nextcloud | File sync/share | nextcloud |
|
||||
|
|
|
|||
171
stacks/anisette/main.tf
Normal file
171
stacks/anisette/main.tf
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
# anisette — self-hosted Apple anisette-data server for SideStore/AltStore.
|
||||
#
|
||||
# Purpose (infra issue #40): the TripIt iOS Shell is sideloaded with SideStore
|
||||
# using a free Apple ID. SideStore needs an "anisette" server to broker the
|
||||
# Apple-ID auth dance; the public community anisette servers see every login,
|
||||
# so we run our own. Stateless HTTP service on a stable INTERNAL endpoint
|
||||
# (anisette.viktorbarzin.lan) that SideStore points at.
|
||||
#
|
||||
# Image: Dadoum/anisette-v3-server — the de-facto standard anisette-v3 server
|
||||
# for SideStore/AltStore (the same project SideStore's own docs point at).
|
||||
# Upstream publishes ONLY a mutable :latest tag (no GitHub releases, no semver,
|
||||
# no date/sha tags — verified 2026-06-14), so we pin by MANIFEST DIGEST instead
|
||||
# (immutable, honours the "never :latest" rule). DockerHub is pulled
|
||||
# transparently via the registry-VM pull-through cache, same as echo/cyberchef.
|
||||
# To bump: `docker buildx imagetools inspect dadoum/anisette-v3-server:latest`,
|
||||
# then replace the digest below.
|
||||
#
|
||||
# Stateless: the container caches Apple provisioning libraries under
|
||||
# /home/Alcoholic/.config/anisette-v3/lib (a regenerable download — re-fetched
|
||||
# if absent — and per upstream issue #23 it does NOT preserve client auth across
|
||||
# restarts anyway). So an emptyDir is the honest fit: keeps that path writable
|
||||
# without taking on a backup-pipeline obligation. No PVC, no Vault secret.
|
||||
|
||||
variable "tls_secret_name" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
resource "kubernetes_namespace" "anisette" {
|
||||
metadata {
|
||||
name = "anisette"
|
||||
labels = {
|
||||
"istio-injection" : "disabled"
|
||||
tier = local.tiers.aux
|
||||
}
|
||||
}
|
||||
lifecycle {
|
||||
# KYVERNO_LIFECYCLE_V1: goldilocks-vpa-auto-mode ClusterPolicy stamps this label on every namespace
|
||||
ignore_changes = [metadata[0].labels["goldilocks.fairwinds.com/vpa-update-mode"]]
|
||||
}
|
||||
}
|
||||
|
||||
module "tls_secret" {
|
||||
source = "../../modules/kubernetes/setup_tls_secret"
|
||||
namespace = kubernetes_namespace.anisette.metadata[0].name
|
||||
tls_secret_name = var.tls_secret_name
|
||||
}
|
||||
|
||||
resource "kubernetes_deployment" "anisette" {
|
||||
metadata {
|
||||
name = "anisette"
|
||||
namespace = kubernetes_namespace.anisette.metadata[0].name
|
||||
labels = {
|
||||
app = "anisette"
|
||||
tier = local.tiers.aux
|
||||
}
|
||||
}
|
||||
spec {
|
||||
replicas = 1
|
||||
selector {
|
||||
match_labels = {
|
||||
app = "anisette"
|
||||
}
|
||||
}
|
||||
template {
|
||||
metadata {
|
||||
labels = {
|
||||
app = "anisette"
|
||||
}
|
||||
annotations = {
|
||||
# Diun notify-only watch. Upstream tags only :latest, so watch the
|
||||
# digest of :latest rather than a semver pattern.
|
||||
"diun.enable" = "true"
|
||||
"diun.watch_repo" = "false"
|
||||
"diun.include_tags" = "^latest$"
|
||||
}
|
||||
}
|
||||
spec {
|
||||
container {
|
||||
# Pinned by digest — upstream ships only a mutable :latest (no tags).
|
||||
image = "dadoum/anisette-v3-server@sha256:1e20384985d3c49965f444bef39d627768dacc39ea0dca91f2a535edb7591ba3"
|
||||
name = "anisette"
|
||||
port {
|
||||
name = "http"
|
||||
container_port = 6969
|
||||
}
|
||||
# The image runs as the non-root user "Alcoholic" and writes its
|
||||
# provisioning-library cache here; back it with an emptyDir so the
|
||||
# path is writable (stateless — wiped on restart, re-downloaded).
|
||||
volume_mount {
|
||||
name = "provisioning-cache"
|
||||
mount_path = "/home/Alcoholic/.config/anisette-v3/lib"
|
||||
}
|
||||
resources {
|
||||
requests = {
|
||||
cpu = "10m"
|
||||
memory = "128Mi"
|
||||
}
|
||||
limits = {
|
||||
memory = "128Mi"
|
||||
}
|
||||
}
|
||||
readiness_probe {
|
||||
http_get {
|
||||
path = "/"
|
||||
port = 6969
|
||||
}
|
||||
period_seconds = 15
|
||||
initial_delay_seconds = 5
|
||||
}
|
||||
liveness_probe {
|
||||
http_get {
|
||||
path = "/"
|
||||
port = 6969
|
||||
}
|
||||
period_seconds = 30
|
||||
failure_threshold = 6
|
||||
}
|
||||
}
|
||||
volume {
|
||||
name = "provisioning-cache"
|
||||
empty_dir {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
spec[0].template[0].spec[0].dns_config, # KYVERNO_LIFECYCLE_V1
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_service" "anisette" {
|
||||
metadata {
|
||||
name = "anisette"
|
||||
namespace = kubernetes_namespace.anisette.metadata[0].name
|
||||
labels = {
|
||||
"app" = "anisette"
|
||||
}
|
||||
}
|
||||
spec {
|
||||
selector = {
|
||||
app = "anisette"
|
||||
}
|
||||
port {
|
||||
name = "http"
|
||||
port = "80"
|
||||
target_port = "6969"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "ingress" {
|
||||
source = "../../modules/kubernetes/ingress_factory"
|
||||
# auth = "none": SideStore is a native iOS client — it can't replay the
|
||||
# Authentik forward-auth cookie dance, so Authentik would break it (same
|
||||
# reasoning as android-emulator's adb). Internal-only: anisette.viktorbarzin.lan,
|
||||
# allow_local_access_only locks it to the LAN, and it brokers no user data of
|
||||
# ours (it just relays Apple-ID anisette data). Never publicly exposed.
|
||||
auth = "none"
|
||||
namespace = kubernetes_namespace.anisette.metadata[0].name
|
||||
name = "anisette"
|
||||
root_domain = "viktorbarzin.lan"
|
||||
tls_secret_name = var.tls_secret_name
|
||||
allow_local_access_only = true
|
||||
ssl_redirect = false
|
||||
extra_annotations = {
|
||||
"gethomepage.dev/enabled" = "false"
|
||||
}
|
||||
}
|
||||
1
stacks/anisette/secrets
Symbolic link
1
stacks/anisette/secrets
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../secrets
|
||||
8
stacks/anisette/terragrunt.hcl
Normal file
8
stacks/anisette/terragrunt.hcl
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
include "root" {
|
||||
path = find_in_parent_folders()
|
||||
}
|
||||
|
||||
dependency "platform" {
|
||||
config_path = "../platform"
|
||||
skip_outputs = true
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue